From 8e4bda9e0f7411a3bfad0c625177f67248ff8a12 Mon Sep 17 00:00:00 2001 From: Chong Zhang Date: Wed, 4 May 2016 15:08:18 -0700 Subject: [PATCH] Fix a flicker when window is removed during entering animation When animation is started with saved surfaces, app may decide to remove one of the child windows during early animation and replace it with a new window. This causes the app below (usually Recents) to show through for one or more frames. If we started animation early with a window, delay removal of that window until the app is allDrawn in the "traditional sense" (i.e. allDrawn without using any saved surfaces). bug: 28742353 Change-Id: I4ef663a947b737aae96c3ccfe13a9f4c1d0567f0 --- .../java/com/android/server/wm/AppWindowToken.java | 39 ++++++++++++++++++ .../java/com/android/server/wm/WindowAnimator.java | 1 + .../android/server/wm/WindowManagerService.java | 33 +++++++++++++++ .../java/com/android/server/wm/WindowState.java | 36 ++++++++++++++++- .../com/android/server/wm/WindowSurfacePlacer.java | 47 +++++++++++++++++++--- 5 files changed, 149 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index e490a4030644..abbb5f48c606 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -88,6 +88,11 @@ class AppWindowToken extends WindowToken { // case do not clear allDrawn until the animation completes. boolean deferClearAllDrawn; + // These are to track the app's real drawing status if there were no saved surfaces. + boolean allDrawnExcludingSaved; + int numInterestingWindowsExcludingSaved; + int numDrawnWindowsExclusingSaved; + // Is this window's surface needed? This is almost like hidden, except // it will sometimes be true a little earlier: when the token has // been shown, but is still waiting for its app transition to execute @@ -411,6 +416,39 @@ class AppWindowToken extends WindowToken { } } + /** + * Whether the app has some window that is invisible in layout, but + * animating with saved surface. + */ + boolean isAnimatingInvisibleWithSavedSurface() { + for (int i = allAppWindows.size() - 1; i >= 0; i--) { + final WindowState w = allAppWindows.get(i); + if (w.isAnimatingInvisibleWithSavedSurface()) { + return true; + } + } + return false; + } + + /** + * Hide all window surfaces that's still invisible in layout but animating + * with a saved surface, and mark them destroying. + */ + void stopUsingSavedSurfaceLocked() { + for (int i = allAppWindows.size() - 1; i >= 0; i--) { + final WindowState w = allAppWindows.get(i); + if (w.isAnimatingInvisibleWithSavedSurface()) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.d(TAG, + "stopUsingSavedSurfaceLocked: " + w); + w.clearAnimatingWithSavedSurface(); + w.mDestroying = true; + w.mWinAnimator.hide("stopUsingSavedSurfaceLocked"); + w.mWinAnimator.mWallpaperControllerLocked.hideWallpapers(w); + } + } + destroySurfaces(); + } + void restoreSavedSurfaces() { if (!canRestoreSurfaces()) { clearVisibleBeforeClientHidden(); @@ -456,6 +494,7 @@ class AppWindowToken extends WindowToken { void clearAllDrawn() { allDrawn = false; deferClearAllDrawn = false; + allDrawnExcludingSaved = false; } @Override diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index eae783881c9f..be060d243404 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -788,6 +788,7 @@ public class WindowAnimator { removeReplacedWindowsLocked(); } + mService.stopUsingSavedSurfaceLocked(); mService.destroyPreservedSurfaceLocked(); mService.mWindowPlacerLocked.destroyPendingSurfaces(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index defd513703a4..d8170360727c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -415,6 +415,13 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList mFinishedStarting = new ArrayList<>(); /** + * List of window tokens that have finished drawing their own windows and + * no longer need to show any saved surfaces. Windows that's still showing + * saved surfaces will be cleaned up after next animation pass. + */ + final ArrayList mFinishedEarlyAnim = new ArrayList<>(); + + /** * List of app window tokens that are waiting for replacing windows. If the * replacement doesn't come in time the stale windows needs to be disposed of. */ @@ -2330,6 +2337,23 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); return; } + + if (win.isAnimatingWithSavedSurface() && !appToken.allDrawnExcludingSaved) { + // We started enter animation early with a saved surface, now the app asks to remove + // this window. If we remove it now and the app is not yet drawn, we'll show a + // flicker. Delay the removal now until it's really drawn. + if (DEBUG_ADD_REMOVE) { + Slog.d(TAG_WM, "removeWindowLocked: delay removal of " + win + + " due to early animation"); + } + // Do not set mAnimatingExit to true here, it will cause the surface to be hidden + // immediately after the enter animation is done. If the app is not yet drawn then + // it will show up as a flicker. + win.mRemoveOnExit = true; + win.mWindowRemovalAllowed = true; + Binder.restoreCallingIdentity(origId); + return; + } // If we are not currently running the exit animation, we need to see about starting one wasVisible = win.isWinVisibleLw(); @@ -8557,6 +8581,15 @@ public class WindowManagerService extends IWindowManager.Stub } mDestroyPreservedSurface.clear(); } + + void stopUsingSavedSurfaceLocked() { + for (int i = mFinishedEarlyAnim.size() - 1; i >= 0 ; i--) { + final AppWindowToken wtoken = mFinishedEarlyAnim.get(i); + wtoken.stopUsingSavedSurfaceLocked(); + } + mFinishedEarlyAnim.clear(); + } + // ------------------------------------------------------------- // IWindowManager API // ------------------------------------------------------------- diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b66de89f6a2d..c9d945aa73ac 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -437,7 +437,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { // used to start an entering animation earlier. private boolean mSurfaceSaved = false; - // Whether we're performing an entering animation with a saved surface. + // Whether we're performing an entering animation with a saved surface. This flag is + // true during the time we're showing a window with a previously saved surface. It's + // cleared when surface is destroyed, saved, or re-drawn by the app. private boolean mAnimatingWithSavedSurface; // Whether the window was visible when we set the app to invisible last time. WM uses @@ -1256,6 +1258,32 @@ final class WindowState implements WindowManagerPolicy.WindowState { } /** + * Whether this window's drawn state might affect the drawn states of the app token. + * + * @param visibleOnly Whether we should consider only the windows that's currently + * visible in layout. If true, windows that has not relayout to VISIBLE + * would always return false. + * + * @return true if the window should be considered while evaluating allDrawn flags. + */ + boolean mightAffectAllDrawn(boolean visibleOnly) { + final boolean isViewVisible = (mViewVisibility == View.VISIBLE) + && (mAppToken == null || !mAppToken.clientHidden); + return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible) + || mWinAnimator.mAttrType == TYPE_BASE_APPLICATION) + && !mAnimatingExit && !mDestroying; + } + + /** + * Whether this window is "interesting" when evaluating allDrawn. If it's interesting, + * it must be drawn before allDrawn can become true. + */ + boolean isInteresting() { + return mAppToken != null && !mAppDied + && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing); + } + + /** * Like isOnScreen(), but we don't return true if the window is part * of a transition that has not yet been started. */ @@ -1960,6 +1988,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAnimatingWithSavedSurface; } + boolean isAnimatingInvisibleWithSavedSurface() { + return mAnimatingWithSavedSurface + && (mViewVisibility != View.VISIBLE || mWindowRemovalAllowed); + } + public void setVisibleBeforeClientHidden() { mWasVisibleBeforeClientHidden |= (mViewVisibility == View.VISIBLE || mAnimatingWithSavedSurface); @@ -2042,6 +2075,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (mWinAnimator.mSurfaceController != null) { mWinAnimator.mSurfaceController.disconnectInTransaction(); } + mAnimatingWithSavedSurface = false; } else { mWinAnimator.destroySurfaceLocked(); } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index e20e245aa574..7e9993db4cb4 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -792,15 +792,16 @@ class WindowSurfacePlacer { + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn + " freezingScreen=" + atoken.mAppAnimator.freezingScreen); } - if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { + if (atoken != null && (!atoken.allDrawn || !atoken.allDrawnExcludingSaved + || atoken.mAppAnimator.freezingScreen)) { if (atoken.lastTransactionSequence != mService.mTransactionSequence) { atoken.lastTransactionSequence = mService.mTransactionSequence; atoken.numInterestingWindows = atoken.numDrawnWindows = 0; + atoken.numInterestingWindowsExcludingSaved = 0; + atoken.numDrawnWindowsExclusingSaved = 0; atoken.startingDisplayed = false; } - if ((w.isOnScreenIgnoringKeyguard() - || winAnimator.mAttrType == TYPE_BASE_APPLICATION) - && !w.mAnimatingExit && !w.mDestroying) { + if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) { if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() @@ -816,13 +817,14 @@ class WindowSurfacePlacer { } } if (w != atoken.startingWindow) { - if (!w.mAppDied && - (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing)) { + if (w.isInteresting()) { atoken.numInterestingWindows++; if (w.isDrawnLw()) { atoken.numDrawnWindows++; if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: " + atoken + + " w=" + w + " numInteresting=" + + atoken.numInterestingWindows + " freezingScreen=" + atoken.mAppAnimator.freezingScreen + " mAppFreezing=" + w.mAppFreezing); @@ -834,6 +836,23 @@ class WindowSurfacePlacer { atoken.startingDisplayed = true; } } + if (!atoken.allDrawnExcludingSaved + && w.mightAffectAllDrawn(true /* visibleOnly */)) { + if (w != atoken.startingWindow && w.isInteresting()) { + atoken.numInterestingWindowsExcludingSaved++; + if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) { + atoken.numDrawnWindowsExclusingSaved++; + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) + Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken + + " w=" + w + " numInteresting=" + + atoken.numInterestingWindowsExcludingSaved + + " freezingScreen=" + + atoken.mAppAnimator.freezingScreen + + " mAppFreezing=" + w.mAppFreezing); + updateAllDrawn = true; + } + } + } } if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus @@ -1539,6 +1558,22 @@ class WindowSurfacePlacer { wtoken.token).sendToTarget(); } } + if (!wtoken.allDrawnExcludingSaved) { + int numInteresting = wtoken.numInterestingWindowsExcludingSaved; + if (numInteresting > 0 + && wtoken.numDrawnWindowsExclusingSaved >= numInteresting) { + if (DEBUG_VISIBILITY) + Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindowsExclusingSaved); + wtoken.allDrawnExcludingSaved = true; + displayContent.layoutNeeded = true; + if (wtoken.isAnimatingInvisibleWithSavedSurface() + && !mService.mFinishedEarlyAnim.contains(wtoken)) { + mService.mFinishedEarlyAnim.add(wtoken); + } + } + } } } } -- 2.11.0