From 50dfb959248466147c2564596405890d30c15314 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Sun, 31 Jan 2021 23:06:23 +0800 Subject: [PATCH] Fix app afterimage flashing while device unlocking The problem is because in WMS#tryStartExitingAnimation set win.mAnimatingExit as ture but not destroy surface when pressing power button, so let `WindowState#mHasSurface` keeps true when screen off. (Normally the surface should be destroyed when screen off) This results when unlocking the screen will can see both the app window afterimage and launcher are animating. Check with DC#okToAnimate in tryStartExitingAnimation to ensure the surface can be destroyed when the display is not ready to animate (e.g. screen off). Also, in order to cleanly clean up surfaces while cancelling recents animation when the removal is allowed, make sure to invoke onAnimationFinished for task's windows to finish the process of the surface removal. Fix: 173379232 Test: atest WmTests CtsWindowManagerDeviceTestCases Test: atest RecentsAnimationControllerTests#\ testCleanupAnimation_expectExitAnimationDone Test: manul as below steps: 1. in gesture nav mode, launch any apps (e.g. Settings). 2. swipe up app task to launcher 3. before swipe up animation finish, quickly pressing power button. 4. turn on screen and unlock, see if the app window flashing then back to launcher. Change-Id: Ib2778c1b2677752039be12a16a42623a25773fa2 --- .../server/wm/RecentsAnimationController.java | 5 ++- .../server/wm/WindowManagerService.java | 5 +-- .../wm/RecentsAnimationControllerTest.java | 31 ++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e1fdefd026cc2..e02cce4b946a0 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -421,7 +421,10 @@ public class RecentsAnimationController implements DeathRecipient { if (skipAnimation(task)) { continue; } - addAnimation(task, !recentTaskIds.get(task.mTaskId)); + addAnimation(task, !recentTaskIds.get(task.mTaskId), false /* hidden */, + (type, anim) -> task.forAllWindows(win -> { + win.onAnimationFinished(type, anim); + }, true /* traverseTopToBottom */)); } // Skip the animation if there is nothing to animate diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b6fabee33d602..b2e32d3673f0c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2563,11 +2563,12 @@ public class WindowManagerService extends IWindowManager.Stub } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { focusMayChange = true; win.mAnimatingExit = true; - } else if (win.isAnimating(TRANSITION | PARENTS)) { + } else if (win.mDisplayContent.okToAnimate() && win.isAnimating(TRANSITION | PARENTS)) { // Currently in a hide animation... turn this into // an exit. win.mAnimatingExit = true; - } else if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) { + } else if (win.mDisplayContent.okToAnimate() + && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) { // If the wallpaper is currently behind this // window, we need to change both of them inside // of a transaction to avoid artifacts. diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 409bad416cf89..c6be987802b5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -493,7 +493,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { initializeRecentsAnimationController(mController, homeActivity); // Verify RecentsAnimationController will animate visible leaf task by default. - verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), eq(null)); + verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any()); assertTrue(leafTask.isAnimatingByRecents()); // Make sure isAnimatingByRecents will also return true when it called by the parent task. @@ -543,6 +543,35 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { verify(navBarFadeAnimationController, never()).fadeWindowToken(anyBoolean()); } + @Test + public void testCleanupAnimation_expectExitAnimationDone() { + mWm.setRecentsAnimationController(mController); + final ActivityRecord homeActivity = createHomeActivity(); + final ActivityRecord activity = createActivityRecord(mDefaultDisplay); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); + activity.addWindow(win1); + + initializeRecentsAnimationController(mController, homeActivity); + mController.startAnimation(); + + spyOn(win1); + spyOn(win1.mWinAnimator); + // Simulate when the window is exiting and cleanupAnimation invoked + // (e.g. screen off during RecentsAnimation animating), will expect the window receives + // onExitAnimationDone to destroy the surface when the removal is allowed. + win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); + win1.mHasSurface = true; + win1.mAnimatingExit = true; + win1.mRemoveOnExit = true; + win1.mWindowRemovalAllowed = true; + mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); + verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any()); + verify(win1).onExitAnimationDone(); + verify(win1).destroySurface(eq(false), eq(false)); + assertFalse(win1.mAnimatingExit); + assertFalse(win1.mHasSurface); + } + private ActivityRecord createHomeActivity() { final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) .setParentTask(mRootHomeTask)