From 45e6d2dc8b8b7bd4e588368179d8d4b05fc6810c Mon Sep 17 00:00:00 2001 From: Chong Zhang Date: Wed, 20 Jul 2016 18:33:56 -0700 Subject: [PATCH] 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 --- core/java/android/view/IWindowManager.aidl | 3 +- .../java/com/android/server/am/ActivityStack.java | 4 +- .../java/com/android/server/wm/AppWindowToken.java | 61 ++++++++++++++++------ .../android/server/wm/WindowManagerService.java | 24 +++++++-- .../src/android/view/IWindowManagerImpl.java | 7 ++- 5 files changed, 77 insertions(+), 22 deletions(-) diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 9bc0bb4b38f7..06012198333c 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -173,7 +173,8 @@ interface IWindowManager 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 eb02dc34e1a2..752dbd95c4c2 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { // 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 66e9fd8659d5..eac72b01c8f9 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 @@ class AppWindowToken extends WindowToken { } } - // 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 allWindows = (ArrayList) 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 @@ class AppWindowToken extends WindowToken { + " 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 @@ class AppWindowToken extends WindowToken { } /** - * 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 1b893270b62b..0d687d8c77db 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 @@ public class WindowManagerService extends IWindowManager.Stub } @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 @@ public class WindowManagerService extends IWindowManager.Stub 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 49ab9f911833..5a9860d5c80a 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -351,7 +351,12 @@ public class IWindowManagerImpl implements IWindowManager { } @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 } -- 2.11.0