Added WindowContainer.removeImmediately to support immediate removal

There are 2 types of window conatiner removals. The ones that happen
immediately and the ones that are deferred until the system is in
a good state to handle them. WindowContainer and its child classes
now support both through WC.removeImmediately() and WC.removeIfPossible()
methods.
The method names create a consistency in the codebase and also makes is
obvious what the methods will do.

Bug: 30060889
Change-Id: I459c2eef17e20cefc42e9cc542c9a3c69fc9b898
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 518b9b5..47b5f3d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -75,6 +75,7 @@
     final boolean voiceInteraction;
 
     Task mTask;
+    // TODO: Have a fillParent variable in WindowContainer to this?
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
     boolean layoutConfigChanges;
@@ -100,7 +101,7 @@
     // These are to track the app's real drawing status if there were no saved surfaces.
     boolean allDrawnExcludingSaved;
     int numInterestingWindowsExcludingSaved;
-    int numDrawnWindowsExclusingSaved;
+    int numDrawnWindowsExcludingSaved;
 
     // Is this window's surface needed?  This is almost like hidden, except
     // it will sometimes be true a little earlier: when the token has
@@ -133,6 +134,7 @@
     // Input application handle used by the input dispatcher.
     final InputApplicationHandle mInputApplicationHandle;
 
+    // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
     boolean mIsExiting;
 
     boolean mLaunchTaskBehind;
@@ -356,18 +358,12 @@
         return StackId.canReceiveKeys(mTask.mStack.mStackId) || mAlwaysFocusable;
     }
 
-    void removeAppFromTaskLocked() {
+    @Override
+    void removeIfPossible() {
         mIsExiting = false;
         removeAllWindows();
-
-        // Use local variable because removeAppToken will null out mTask.
-        final Task task = mTask;
-        if (task != null) {
-            if (!task.removeAppToken(this)) {
-                Slog.e(TAG, "removeAppFromTaskLocked: token=" + this
-                        + " not found.");
-            }
-            task.mStack.mExitingAppTokens.remove(this);
+        if (mTask != null) {
+            mTask.detachChild(this);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 990405a..fb455fe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -514,7 +514,7 @@
                     for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
                         AppWindowToken wtoken = tokens.get(tokenNdx);
                         if (wtoken.mIsExiting) {
-                            wtoken.removeAppFromTaskLocked();
+                            wtoken.removeIfPossible();
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4241b32..754a9a6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -179,6 +179,14 @@
         }
     }
 
+    // TODO: Don't forget to switch to WC.detachChild
+    void detachChild(AppWindowToken wtoken) {
+        if (!removeAppToken(wtoken)) {
+            Slog.e(TAG, "detachChild: token=" + this + " not found.");
+        }
+        mStack.mExitingAppTokens.remove(wtoken);
+    }
+
     boolean removeAppToken(AppWindowToken wtoken) {
         boolean removed = mAppTokens.remove(wtoken);
         if (mAppTokens.size() == 0) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd6994c..d37d0e1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -67,17 +67,50 @@
         mChildren.add(child);
     }
 
-    /** Removes this window container and its children */
+    /**
+     * Removes this window container and its children with no regard for what else might be going on
+     * in the system. For example, the container will be removed during animation if this method is
+     * called which isn't desirable. For most cases you want to call {@link #removeIfPossible()}
+     * which allows the system to defer removal until a suitable time.
+     */
     @CallSuper
-    void remove() {
+    void removeImmediately() {
         while (!mChildren.isEmpty()) {
-            final WindowContainer child = mChildren.removeLast();
-            child.remove();
+            final WindowContainer child = mChildren.peekLast();
+            child.removeImmediately();
+            // Need to do this after calling remove on the child because the child might try to
+            // remove/detach itself from its parent which will cause an exception if we remove
+            // it before calling remove on the child.
+            mChildren.remove(child);
         }
 
         if (mParent != null) {
-            mParent.mChildren.remove(this);
-            mParent = null;
+            mParent.detachChild(this);
+        }
+    }
+
+    /**
+     * Removes this window container and its children taking care not to remove them during a
+     * critical stage in the system. For example, some containers will not be removed during
+     * animation if this method is called.
+     */
+    // TODO: figure-out implementation that works best for this.
+    // E.g. when do we remove from parent list? maybe not...
+    void removeIfPossible() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.removeIfPossible();
+        }
+    }
+
+    /** Detaches the input child container from this container which is its parent. */
+    @CallSuper
+    void detachChild(WindowContainer child) {
+        if (mChildren.remove(child)) {
+            child.mParent = null;
+        } else {
+            throw new IllegalArgumentException("detachChild: container=" + child
+                    + " is not a child of container=" + this);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 856e101..0aa1143 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -247,7 +247,6 @@
 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.WindowStateAnimator.DRAW_PENDING;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
@@ -1896,7 +1895,10 @@
 
     /**
      * Performs some centralized bookkeeping clean-up on the window that is being removed.
-     * NOTE: Should only be called from {@link WindowState#remove()}
+     * NOTE: Should only be called from {@link WindowState#removeImmediately()}
+     * TODO: Maybe better handled with a method {@link WindowContainer#detachChild} if we can
+     * figure-out a good way to have all parents of a WindowState doing the same thing without
+     * forgetting to add the wiring when a new parent of WindowState is added.
      */
     void postWindowRemoveCleanupLocked(WindowState win) {
         if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "postWindowRemoveCleanupLocked: " + win);
@@ -3756,7 +3758,7 @@
                     // soon as their animations are complete
                     wtoken.mAppAnimator.clearAnimation();
                     wtoken.mAppAnimator.animating = false;
-                    wtoken.removeAppFromTaskLocked();
+                    wtoken.removeIfPossible();
                 }
 
                 wtoken.removed = true;
@@ -10449,7 +10451,7 @@
         public void removeWindowToken(IBinder token, boolean removeWindows) {
             synchronized(mWindowMap) {
                 if (removeWindows) {
-                    WindowToken wtoken = mTokenMap.remove(token);
+                    final WindowToken wtoken = mTokenMap.remove(token);
                     if (wtoken != null) {
                         wtoken.removeAllWindows();
                     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2c359fc..4fe29cb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -187,7 +187,17 @@
     boolean mEnforceSizeCompat;
     int mViewVisibility;
     int mSystemUiVisibility;
+    /**
+     * The visibility of the window based on policy like {@link WindowManagerPolicy}.
+     * Normally set by calling {@link #showLw} and {@link #hideLw}.
+     */
     boolean mPolicyVisibility = true;
+    /**
+     * What {@link #mPolicyVisibility} should be set to after a transition animation.
+     * For example, {@link #mPolicyVisibility} might true during an exit animation to hide it and
+     * then set to the value of {@link #mPolicyVisibilityAfterAnim} which is false after the exit
+     * animation is done.
+     */
     boolean mPolicyVisibilityAfterAnim = true;
     boolean mAppOpVisibility = true;
     boolean mAppFreezing;
@@ -1404,8 +1414,7 @@
     @Override
     public boolean isDrawnLw() {
         return mHasSurface && !mDestroying &&
-                (mWinAnimator.mDrawState == READY_TO_SHOW
-                || mWinAnimator.mDrawState == HAS_DRAWN);
+                (mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
     }
 
     /**
@@ -1594,12 +1603,12 @@
     void onWindowReplacementTimeout() {
         if (mWillReplaceWindow) {
             // Since the window already timed out, remove it immediately now.
-            // Use WindowState#remove() instead of WindowState#removeIfPossible(), as the latter
+            // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as the latter
             // delays removal on certain conditions, which will leave the stale window in the
             // stack and marked mWillReplaceWindow=false, so the window will never be removed.
             //
             // Also removes child windows.
-            remove();
+            removeImmediately();
         } else {
             for (int i = mChildren.size() - 1; i >= 0; --i) {
                 final WindowState c = (WindowState) mChildren.get(i);
@@ -1618,12 +1627,13 @@
     }
 
     @Override
-    void remove() {
-        super.remove();
+    void removeImmediately() {
+        super.removeImmediately();
 
         if (mRemoved) {
             // Nothing to do.
-            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "WS.remove: " + this + " Already removed...");
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                    "WS.removeImmediately: " + this + " Already removed...");
             return;
         }
 
@@ -1660,15 +1670,13 @@
         mService.postWindowRemoveCleanupLocked(this);
     }
 
+    @Override
     void removeIfPossible() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = (WindowState) mChildren.get(i);
-            c.removeIfPossible(false /*keepVisibleDeadWindow*/);
-        }
+        super.removeIfPossible();
         removeIfPossible(false /*keepVisibleDeadWindow*/);
     }
 
-    void removeIfPossible(boolean keepVisibleDeadWindow) {
+    private void removeIfPossible(boolean keepVisibleDeadWindow) {
         mWindowRemovalAllowed = true;
         if (DEBUG_ADD_REMOVE) Slog.v(TAG,
                 "removeIfPossible: " + this + " callers=" + Debug.getCallers(5));
@@ -1793,7 +1801,7 @@
             }
         }
 
-        remove();
+        removeImmediately();
         // Removing a visible window will effect the computed orientation
         // So just update orientation if needed.
         if (wasVisible && mService.updateOrientationFromAppTokensLocked(false)) {
@@ -2010,7 +2018,7 @@
         mReplacingRemoveRequested = false;
         mReplacementWindow = null;
         if (mAnimatingExit || !mAnimateReplacingWindow) {
-            remove();
+            removeImmediately();
         }
     }
 
@@ -2567,7 +2575,7 @@
                     destroyOrSaveSurface();
                 }
                 if (mRemoveOnExit) {
-                    remove();
+                    removeImmediately();
                 }
                 if (cleanupOnResume) {
                     requestUpdateWallpaperIfNeeded();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d0c73d3..317bb35 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -215,7 +215,7 @@
             while (!mService.mForceRemoves.isEmpty()) {
                 final WindowState ws = mService.mForceRemoves.remove(0);
                 Slog.i(TAG, "Force removing: " + ws);
-                ws.remove();
+                ws.removeImmediately();
             }
             Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
             Object tmp = new Object();
@@ -461,7 +461,7 @@
                     token.mAppAnimator.animating = false;
                     if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
                             "performLayout: App token exiting now removed" + token);
-                    token.removeAppFromTaskLocked();
+                    token.removeIfPossible();
                 }
             }
         }
@@ -545,7 +545,7 @@
             DisplayContentList displayList = new DisplayContentList();
             for (i = 0; i < N; i++) {
                 final WindowState w = mService.mPendingRemoveTmp[i];
-                w.remove();
+                w.removeImmediately();
                 final DisplayContent displayContent = w.getDisplayContent();
                 if (displayContent != null && !displayList.contains(displayContent)) {
                     displayList.add(displayContent);
@@ -797,7 +797,7 @@
                         atoken.lastTransactionSequence = mService.mTransactionSequence;
                         atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
                         atoken.numInterestingWindowsExcludingSaved = 0;
-                        atoken.numDrawnWindowsExclusingSaved = 0;
+                        atoken.numDrawnWindowsExcludingSaved = 0;
                         atoken.startingDisplayed = false;
                     }
                     if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
@@ -840,7 +840,7 @@
                         if (w != atoken.startingWindow && w.isInteresting()) {
                             atoken.numInterestingWindowsExcludingSaved++;
                             if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
-                                atoken.numDrawnWindowsExclusingSaved++;
+                                atoken.numDrawnWindowsExcludingSaved++;
                                 if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
                                     Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken
                                             + " w=" + w + " numInteresting="
@@ -1550,11 +1550,11 @@
                     if (!wtoken.allDrawnExcludingSaved) {
                         int numInteresting = wtoken.numInterestingWindowsExcludingSaved;
                         if (numInteresting > 0
-                                && wtoken.numDrawnWindowsExclusingSaved >= numInteresting) {
+                                && wtoken.numDrawnWindowsExcludingSaved >= numInteresting) {
                             if (DEBUG_VISIBILITY)
                                 Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken
                                     + " interesting=" + numInteresting
-                                    + " drawn=" + wtoken.numDrawnWindowsExclusingSaved);
+                                    + " drawn=" + wtoken.numDrawnWindowsExcludingSaved);
                             wtoken.allDrawnExcludingSaved = true;
                             displayContent.layoutNeeded = true;
                             if (wtoken.isAnimatingInvisibleWithSavedSurface()
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index ea8b7bb..24d797b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -98,7 +98,7 @@
         assertFalse(child2.hasChild(child12));
    }
 
-    public void testRemove() throws Exception {
+    public void testRemoveImmediately() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
 
@@ -108,13 +108,16 @@
         final TestWindowContainer child12 = child1.addChildWindow();
         final TestWindowContainer child21 = child2.addChildWindow();
 
-        child12.remove();
+        assertNotNull(child12.getParentWindow());
+        child12.removeImmediately();
         assertNull(child12.getParentWindow());
         assertEquals(1, child1.getChildrenCount());
         assertFalse(child1.hasChild(child12));
         assertFalse(root.hasChild(child12));
 
-        child2.remove();
+        assertTrue(root.hasChild(child2));
+        assertNotNull(child2.getParentWindow());
+        child2.removeImmediately();
         assertNull(child2.getParentWindow());
         assertNull(child21.getParentWindow());
         assertEquals(0, child2.getChildrenCount());
@@ -125,7 +128,7 @@
         assertTrue(root.hasChild(child1));
         assertTrue(root.hasChild(child11));
 
-        root.remove();
+        root.removeImmediately();
         assertEquals(0, root.getChildrenCount());
     }
 
@@ -183,6 +186,32 @@
         assertFalse(child21.isVisible());
     }
 
+    public void testDetachChild() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+
+        assertTrue(root.hasChild(child2));
+        assertTrue(root.hasChild(child21));
+        root.detachChild(child2);
+        assertFalse(root.hasChild(child2));
+        assertFalse(root.hasChild(child21));
+        assertNull(child2.getParentWindow());
+
+        boolean gotException = false;
+        assertTrue(root.hasChild(child11));
+        try {
+            // Can only detach our direct children.
+            root.detachChild(child11);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private class TestWindowContainer extends WindowContainer {
         private final int mLayer;
@@ -190,6 +219,8 @@
         private final boolean mCanDetach;
         private boolean mIsAnimating;
         private boolean mIsVisible;
+        private int mRemoveIfPossibleCount;
+        private int mRemoveImmediatelyCount;
 
         /**
          * Compares 2 window layers and returns -1 if the first is lesser than the second in terms
@@ -252,6 +283,18 @@
         boolean isVisible() {
             return mIsVisible || super.isVisible();
         }
+
+        @Override
+        void removeImmediately() {
+            super.removeImmediately();
+            mRemoveImmediatelyCount++;
+        }
+
+        @Override
+        void removeIfPossible() {
+            super.removeIfPossible();
+            mRemoveIfPossibleCount++;
+        }
     }
 
     private class TestWindowContainerBuilder {