Prevent orphaning of windows when token is removed

When the owner of a window token requests window manager to remove the
token, we were removing it from it's parent which orphaned the token
and all it's windows and prevented them from being accessed from calls
like forAllWindows(). This problem wasn't visible before as the token
windows would still be in the window list, so they can still be accessed
for the exit animation transition which eventually removes them.
We no longer remove tokens from their parent we when are asked to remove
them from the system. We just set WindowToken.setExiting() so the token
can be removed once all its windows are removed at the end of the exit
animation.

Also,
- In AppWindowToken.removeIfPossible() and Task.removeIfPossible(), call
removeImmediately() instead of parent.removeChild() to make sure the
token and its windows are properly clean-up and avoid orphaning if they
aren't.
- Added DisplayContent.reParentWindowToken() for changing the display a
window token is on which is different from DisplayContent.removeWindowToken
which prepares the token and it's windows to be removed from the system.
- Renamed WindowToken.explicit to WindowToken.mPersistOnEmpty which is
what it does.

Fixes: 33098800
Test: bit FrameworksServicesTests:com.android.server.wm.WindowTokenTests
Test: Make sure toast windows don't persist on screen.
Change-Id: I40e0e8832141514b614e79d8e95cd27f24e52366
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5838a37..71816fe 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -42,7 +42,6 @@
 import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN;
 import static com.android.server.wm.WindowManagerService.H.NOTIFY_STARTING_WINDOW_DRAWN;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.logWithStack;
 
@@ -62,12 +61,10 @@
 import android.view.IApplicationToken;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.animation.Animation;
 
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.function.Function;
 
 class AppTokenList extends ArrayList<AppWindowToken> {
 }
@@ -390,10 +387,10 @@
     @Override
     void removeIfPossible() {
         mIsExiting = false;
-        removeAllWindows();
+        removeAllWindowsIfPossible();
         if (mTask != null) {
             mTask.mStack.mExitingAppTokens.remove(this);
-            mTask.removeChild(this);
+            removeImmediately();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 79d58a3..cbb1843 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -300,7 +300,7 @@
         return token.asAppWindowToken();
     }
 
-    void setWindowToken(IBinder binder, WindowToken token) {
+    void addWindowToken(IBinder binder, WindowToken token) {
         final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
         if (dc != null) {
             // We currently don't support adding a window token to the display if the display
@@ -333,20 +333,33 @@
     WindowToken removeWindowToken(IBinder binder) {
         final WindowToken token = mTokenMap.remove(binder);
         if (token != null && token.asAppWindowToken() == null) {
+            token.setExiting();
+        }
+        return token;
+    }
+
+    /** Changes the display the input window token is housed on to this one. */
+    void reParentWindowToken(WindowToken token) {
+        final DisplayContent prevDc = token.getDisplayContent();
+        if (prevDc == this) {
+            return;
+        }
+        if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null) {
             switch (token.windowType) {
                 case TYPE_WALLPAPER:
-                    mBelowAppWindowsContainers.removeChild(token);
+                    prevDc.mBelowAppWindowsContainers.removeChild(token);
                     break;
                 case TYPE_INPUT_METHOD:
                 case TYPE_INPUT_METHOD_DIALOG:
-                    mImeWindowsContainers.removeChild(token);
+                    prevDc.mImeWindowsContainers.removeChild(token);
                     break;
                 default:
-                    mAboveAppWindowsContainers.removeChild(token);
+                    prevDc.mAboveAppWindowsContainers.removeChild(token);
                     break;
             }
         }
-        return token;
+
+        addWindowToken(token.token, token);
     }
 
     void removeAppToken(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d33ae48..fe0160a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -146,11 +146,11 @@
         if (content != null) {
             content.mDimLayerController.removeDimLayerUser(this);
         }
-        getParent().removeChild(this);
+        removeImmediately();
         mService.mTaskIdToTask.delete(mTaskId);
     }
 
-    // Change to use reparenting in WC when TaskStack is switched to use WC.
+    // Change to use re-parenting in WC when TaskStack is switched to use WC.
     void moveTaskToStack(TaskStack stack, boolean toTop) {
         if (stack == mStack) {
             return;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 51e8a56..2b33f09 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1654,7 +1654,7 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Removing " + win + " from " + token);
         // Window will already be removed from token before this post clean-up method is called.
         if (token.isEmpty()) {
-            if (!token.explicit) {
+            if (!token.mPersistOnEmpty) {
                 token.removeImmediately();
             } else if (atoken != null) {
                 // TODO: Should this be moved into AppWindowToken.removeWindow? Might go away after
@@ -2443,8 +2443,6 @@
                     return;
                 }
 
-                token.setExiting();
-
                 mInputMonitor.updateInputWindowsLw(true /*force*/);
             }
         } finally {
@@ -8924,7 +8922,7 @@
                         return;
                     }
 
-                    token.removeAllWindows();
+                    token.removeAllWindowsIfPossible();
                 }
                 WindowManagerService.this.removeWindowToken(binder, displayId);
             }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index a2eebc3..ebf110b 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -18,25 +18,18 @@
 
 import java.util.Comparator;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
-import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.animation.Animation;
 
 import java.io.PrintWriter;
 
@@ -58,8 +51,8 @@
     final int windowType;
 
     // Set if this token was explicitly added by a client, so should
-    // not be removed when all windows are removed.
-    final boolean explicit;
+    // persist (not be removed) when all windows are removed.
+    boolean mPersistOnEmpty;
 
     // For printing.
     String stringName;
@@ -104,27 +97,28 @@
         return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
     };
 
-    WindowToken(WindowManagerService service, IBinder _token, int type, boolean _explicit,
+    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
             DisplayContent dc) {
         mService = service;
         token = _token;
         windowType = type;
-        explicit = _explicit;
+        mPersistOnEmpty = persistOnEmpty;
         onDisplayChanged(dc);
     }
 
-    void removeAllWindows() {
+    void removeAllWindowsIfPossible() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState win = mChildren.get(i);
-            if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
+            if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM,
+                    "removeAllWindowsIfPossible: removing win=" + win);
             win.removeIfPossible();
-            if (mChildren.contains(win)) {
-                removeChild(win);
-            }
         }
     }
 
     void setExiting() {
+        // This token is exiting, so allow it to be removed when it no longer contains any windows.
+        mPersistOnEmpty = false;
+
         if (hidden) {
             return;
         }
@@ -297,16 +291,8 @@
     }
 
     void onDisplayChanged(DisplayContent dc) {
-        if (mDisplayContent == dc) {
-            return;
-        }
-
-        if (mDisplayContent != null) {
-            mDisplayContent.removeWindowToken(token);
-        }
+        dc.reParentWindowToken(this);
         mDisplayContent = dc;
-        mDisplayContent.setWindowToken(token, this);
-
         super.onDisplayChanged(dc);
     }