From d49b5d442dd8e473f4f980a41ea83dcbeec27345 Mon Sep 17 00:00:00 2001 From: Issei Suzuki Date: Wed, 27 Apr 2022 15:37:03 +0000 Subject: [PATCH] Suppress app transition while recents is running This fixes conflict between app transition and recents animation in the following scenario. 1) App transition animation finishes after app closing animation, which is controlled by recents, finishes. During the app closing animation, recents makes the closing app surface invisible, but app transition animation overrides it to visible again. This causes a flicker. 2) App transition starts during recents animation. This can happen when a user launches an activity, and immediately after that, swipes up the screen to close the app. While recents is running, we assume animation on tasks is controlled by recents, and visibility is commited without animation after recents animation finishes. However starting app transition during recents breaks this assumption, which ends up with playing one more unexpected closing animation (so users see closing animation twice). Bug: 223499269 Bug: 231669960 Test: atest AppTransitionTest + manual tests Test 1 1. Launch Gmail app 2. Click icon on the bottom tab (e.g. Chat) 3. Swipe up from the bottom (immediately after step 2) 4. Verify closing animation only plays once Test 2 1. Launch "Google TV" 2. Play a trailer 3. Full screen and PIP mode switch twice 4. Verify PIP window is shown Test 3 1. Change phone to portlait mode 2. Launch Photo app 3. Swipe up from the bottom 4. Verify no rotation animation on the launcher Change-Id: Ic18d00812308903db08d3564136f33f1eccf408c --- data/etc/services.core.protolog.json | 6 +++ .../com/android/server/wm/ActivityRecord.java | 1 + .../server/wm/AppTransitionController.java | 7 +++- .../com/android/server/wm/DisplayContent.java | 2 +- .../android/server/wm/WindowContainer.java | 8 ++++ .../server/wm/WindowManagerService.java | 14 ++++++- .../android/server/wm/AppTransitionTests.java | 41 ++++++++++++++++++- 7 files changed, 74 insertions(+), 5 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 60da2e8cba278..12d3d642a862d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2485,6 +2485,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "323235828": { + "message": "Delaying app transition for recents animation to finish", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "327461496": { "message": "Complete pause: %s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 791d193f36ab9..9e5892a1772f9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9338,6 +9338,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A sb.append(mUserId); sb.append(' '); sb.append(intent.getComponent().flattenToShortString()); + sb.append("}"); stringName = sb.toString(); return stringName; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 701fc9441acb8..cb6559715c558 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -68,6 +68,7 @@ import static com.android.server.wm.AppTransition.isNormalTransit; import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp; import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -1142,13 +1143,17 @@ public class AppTransitionController { if (activity == null) { continue; } + if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Delaying app transition for recents animation to finish"); + return false; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + "startingMoved=%b isRelaunching()=%b startingWindow=%s", activity, activity.allDrawn, activity.startingDisplayed, activity.startingMoved, activity.isRelaunching(), activity.mStartingWindow); - final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { return false; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d0baa238f7326..c12f7f33b069c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5265,7 +5265,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void prepareAppTransition(@WindowManager.TransitionType int transit, @WindowManager.TransitionFlags int flags) { final boolean prepared = mAppTransition.prepareAppTransition(transit, flags); - if (prepared && okToAnimate()) { + if (prepared && okToAnimate() && transit != TRANSIT_NONE) { mSkipAppTransitionAnimation = false; } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2f00bc821678b..a480c37fbcf37 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1584,6 +1584,14 @@ class WindowContainer extends ConfigurationContainer< return true; } + void forAllWindowContainers(Consumer callback) { + callback.accept(this); + final int count = mChildren.size(); + for (int i = 0; i < count; i++) { + mChildren.get(i).forAllWindowContainers(callback); + } + } + /** * For all windows at or below this container call the callback. * @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8f1f7ece897bf..9872f55ad3836 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -118,6 +118,7 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; @@ -3052,13 +3053,22 @@ public class WindowManagerService extends IWindowManager.Stub } } + void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { if (mRecentsAnimationController != null) { final RecentsAnimationController controller = mRecentsAnimationController; mRecentsAnimationController = null; controller.cleanupAnimation(reorderMode); - // TODO(mult-display): currently only default display support recents animation. - getDefaultDisplayContentLocked().mAppTransition.updateBooster(); + // TODO(multi-display): currently only default display support recents animation. + final DisplayContent dc = getDefaultDisplayContentLocked(); + if (dc.mAppTransition.isTransitionSet()) { + dc.mSkipAppTransitionAnimation = true; + } + dc.forAllWindowContainers((wc) -> { + if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) { + wc.cancelAnimation(); + } + }); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index eb6395b461201..35921585d56fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -39,8 +39,10 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; @@ -48,7 +50,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import android.graphics.Rect; import android.os.Binder; @@ -376,7 +380,7 @@ public class AppTransitionTests extends WindowTestsBase { doReturn(false).when(dc).onDescendantOrientationChanged(any()); final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "exiting app"); - final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord; + final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord; // Wait until everything in animation handler get executed to prevent the exiting window // from being removed during WindowSurfacePlacer Traversal. waitUntilHandlersIdle(); @@ -404,6 +408,41 @@ public class AppTransitionTests extends WindowTestsBase { assertTrue(runner.mCancelled); } + @Test + public void testDelayWhileRecents() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + doReturn(false).when(dc).onDescendantOrientationChanged(any()); + final Task task = createTask(dc); + + // Simulate activity1 launches activity2. + final ActivityRecord activity1 = createActivityRecord(task); + activity1.setVisible(true); + activity1.mVisibleRequested = false; + activity1.allDrawn = true; + final ActivityRecord activity2 = createActivityRecord(task); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + activity2.allDrawn = true; + + dc.mClosingApps.add(activity1); + dc.mOpeningApps.add(activity2); + dc.prepareAppTransition(TRANSIT_OPEN); + assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN)); + + // Wait until everything in animation handler get executed to prevent the exiting window + // from being removed during WindowSurfacePlacer Traversal. + waitUntilHandlersIdle(); + + // Start recents + doReturn(true).when(task) + .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS)); + + dc.mAppTransitionController.handleAppTransitionReady(); + + verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); + verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); + } + @Test public void testGetAnimationStyleResId() { // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without