Clean up surfaces when app is resumed without being stopped.

When quickly toggling between two apps, app could be resumed while
it's stopping but not yet stopped. Upon resuming, it could have
surfaces that's marked mDestroying and waiting for the stopped
to be destroyed.

We need to dispose these surfaces properly. If the window is already
removed, we destroy them. Otherwise, clear mDestroying flag so that
the window is ready to be used again. Leaving mDestroying=true makes
the window ineligible for certain things such as receiving wallpaper.

bug: 30255354
Change-Id: Id881653550595ab8e702d6950949bf202ac5a0d9
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9bc0bb4..0601219 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -173,7 +173,8 @@
             in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
             int icon, int logo, int windowFlags, IBinder transferFrom, boolean createIfNeeded);
     void setAppVisibility(IBinder token, boolean visible);
-    void notifyAppStopped(IBinder token, boolean stopped);
+    void notifyAppResumed(IBinder token, boolean wasStopped);
+    void notifyAppStopped(IBinder token);
     void startAppFreezingScreen(IBinder token, int configChanges);
     void stopAppFreezingScreen(IBinder token, boolean force);
     void removeAppToken(IBinder token);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index eb02dc3..752dbd9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1249,7 +1249,7 @@
             r.stopped = true;
             r.state = ActivityState.STOPPED;
 
-            mWindowManager.notifyAppStopped(r.appToken, true);
+            mWindowManager.notifyAppStopped(r.appToken);
 
             if (getVisibleBehindActivity() == r) {
                 mStackSupervisor.requestVisibleBehindLocked(r, false);
@@ -2490,7 +2490,7 @@
 
                 // Well the app will no longer be stopped.
                 // Clear app token stopped state in window manager if needed.
-                mWindowManager.notifyAppStopped(next.appToken, false);
+                mWindowManager.notifyAppResumed(next.appToken, next.stopped);
 
                 EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
                         System.identityHashCode(next), next.task.taskId, next.shortComponentName);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 66e9fd86..eac72b0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -337,21 +337,41 @@
         }
     }
 
-    // Here we destroy surfaces which have been marked as eligible by the animator, taking care
-    // to ensure the client has finished with them. If the client could still be using them
-    // we will skip destruction and try again when the client has stopped.
     void destroySurfaces() {
+        destroySurfaces(false /*cleanupOnResume*/);
+    }
+
+    /**
+     * Destroy surfaces which have been marked as eligible by the animator, taking care to ensure
+     * the client has finished with them.
+     *
+     * @param cleanupOnResume whether this is done when app is resumed without fully stopped. If
+     * set to true, destroy only surfaces of removed windows, and clear relevant flags of the
+     * others so that they are ready to be reused. If set to false (common case), destroy all
+     * surfaces that's eligible, if the app is already stopped.
+     */
+
+    private void destroySurfaces(boolean cleanupOnResume) {
         final ArrayList<WindowState> allWindows = (ArrayList<WindowState>) allAppWindows.clone();
         final DisplayContentList displayList = new DisplayContentList();
         for (int i = allWindows.size() - 1; i >= 0; i--) {
             final WindowState win = allWindows.get(i);
 
-            if (!(mAppStopped || win.mWindowRemovalAllowed)) {
+            if (!(mAppStopped || win.mWindowRemovalAllowed || cleanupOnResume)) {
                 continue;
             }
 
             win.mWinAnimator.destroyPreservedSurfaceLocked();
 
+            if (cleanupOnResume) {
+                // If the window has an unfinished exit animation, consider that animation
+                // done and mark the window destroying so that it goes through the cleanup.
+                if (win.mAnimatingExit) {
+                    win.mDestroying = true;
+                    win.mAnimatingExit = false;
+                }
+            }
+
             if (!win.mDestroying) {
                 continue;
             }
@@ -361,7 +381,9 @@
                     + " win.mWindowRemovalAllowed=" + win.mWindowRemovalAllowed
                     + " win.mRemoveOnExit=" + win.mRemoveOnExit);
 
-            win.destroyOrSaveSurface();
+            if (!cleanupOnResume || win.mRemoveOnExit) {
+                win.destroyOrSaveSurface();
+            }
             if (win.mRemoveOnExit) {
                 service.removeWindowInnerLocked(win);
             }
@@ -379,21 +401,30 @@
     }
 
     /**
-     * If the application has stopped it is okay to destroy any surfaces which were keeping alive
-     * in case they were still being used.
+     * Notify that the app is now resumed, and it was not stopped before, perform a clean
+     * up of the surfaces
      */
-    void notifyAppStopped(boolean stopped) {
-        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: stopped=" + stopped + " " + this);
-        mAppStopped = stopped;
-
-        if (stopped) {
-            destroySurfaces();
-            // Remove any starting window that was added for this app if they are still around.
-            mTask.mService.scheduleRemoveStartingWindowLocked(this);
+    void notifyAppResumed(boolean wasStopped) {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppResumed: wasStopped=" + wasStopped + " " + this);
+        mAppStopped = false;
+        if (!wasStopped) {
+            destroySurfaces(true /*cleanupOnResume*/);
         }
     }
 
     /**
+     * Notify that the app has stopped, and it is okay to destroy any surfaces which were
+     * keeping alive in case they were still being used.
+     */
+    void notifyAppStopped() {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
+        mAppStopped = true;
+        destroySurfaces();
+        // Remove any starting window that was added for this app if they are still around.
+        mTask.mService.scheduleRemoveStartingWindowLocked(this);
+    }
+
+    /**
      * Checks whether we should save surfaces for this app.
      *
      * @return true if the surfaces should be saved, false otherwise.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1b89327..0d687d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4476,7 +4476,25 @@
     }
 
     @Override
-    public void notifyAppStopped(IBinder token, boolean stopped) {
+    public void notifyAppResumed(IBinder token, boolean wasStopped) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "notifyAppResumed()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            final AppWindowToken wtoken;
+            wtoken = findAppWindowToken(token);
+            if (wtoken == null) {
+                Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: " + token);
+                return;
+            }
+            wtoken.notifyAppResumed(wasStopped);
+        }
+    }
+
+    @Override
+    public void notifyAppStopped(IBinder token) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "notifyAppStopped()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -4486,10 +4504,10 @@
             final AppWindowToken wtoken;
             wtoken = findAppWindowToken(token);
             if (wtoken == null) {
-                Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
+                Slog.w(TAG_WM, "Attempted to notify stopped of non-existing app token: " + token);
                 return;
             }
-            wtoken.notifyAppStopped(stopped);
+            wtoken.notifyAppStopped();
         }
     }
 
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 49ab9f9..5a9860d 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -351,7 +351,12 @@
     }
 
     @Override
-    public void notifyAppStopped(IBinder token, boolean stopped) throws RemoteException {
+    public void notifyAppResumed(IBinder token, boolean wasStopped) throws RemoteException {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public void notifyAppStopped(IBinder token) throws RemoteException {
         // TODO Auto-generated method stub
     }