From 6a930e0643fc65676bd63321f8c9ae1bf1c34c1e Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 17 Jun 2021 21:22:27 -0700 Subject: [PATCH] Handle rotation while recents animation is running - It's not actually possible for Launcher to cleanly handle display configuration changes since all of its signals are after the rotation has happened which affects the app in live tile mode. Instead, we can preemptively cancel the recents animation in these cases, providing Launcher the snapshot to update the running task view with. Bug: 189843542 Test: atest RecentsAnimationControllerTest Test: With live-tile enabled, swipe up to overview and rotate or swipe up, and then change a display property (size/density/etc) Change-Id: I41278e78b491c4b956e49b8b1fea40b470252aab --- .../com/android/server/wm/DisplayContent.java | 8 +++ .../android/server/wm/DisplayRotation.java | 10 ++++ .../server/wm/RecentsAnimationController.java | 51 +++++++++++++++---- .../wm/RecentsAnimationControllerTest.java | 30 +++++++++++ 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c7cf4b05564f9..3dbc517af45c3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5492,6 +5492,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } boolean updateDisplayOverrideConfigurationLocked() { + // Preemptively cancel the running recents animation -- SysUI can't currently handle this + // case properly since the signals it receives all happen post-change + final RecentsAnimationController recentsAnimationController = + mWmService.getRecentsAnimationController(); + if (recentsAnimationController != null) { + recentsAnimationController.cancelAnimationForDisplayChange(); + } + Configuration values = new Configuration(); computeScreenConfiguration(values); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index c8f2777cc172a..73d6cecd9155b 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -480,6 +480,16 @@ public class DisplayRotation { return false; } + // Preemptively cancel the running recents animation -- SysUI can't currently handle this + // case properly since the signals it receives all happen post-change. We do this earlier + // in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems + // to happen too late. + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + if (recentsAnimationController != null) { + recentsAnimationController.cancelAnimationForDisplayChange(); + } + final Transition t = (useShellTransitions && !mService.mAtmService.getTransitionController().isCollecting()) ? mService.mAtmService.getTransitionController().createTransition(TRANSIT_CHANGE) diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 5362771ed2862..a7e633527b541 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -221,7 +221,7 @@ public class RecentsAnimationController implements DeathRecipient { final ArraySet tasks = Sets.newArraySet(task); snapshotController.snapshotTasks(tasks); snapshotController.addSkipClosingAppSnapshotTasks(tasks); - return snapshotController.getSnapshot(taskId, 0 /* userId */, + return snapshotController.getSnapshot(taskId, task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */); } } @@ -353,18 +353,23 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void cleanupScreenshot() { - synchronized (mService.mGlobalLock) { - if (mRecentScreenshotAnimator != null) { - mRecentScreenshotAnimator.cancelAnimation(); - mRecentScreenshotAnimator = null; + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.mGlobalLock) { + if (mRecentScreenshotAnimator != null) { + mRecentScreenshotAnimator.cancelAnimation(); + mRecentScreenshotAnimator = null; + } } + } finally { + Binder.restoreCallingIdentity(token); } } @Override public void setWillFinishToHome(boolean willFinishToHome) { synchronized (mService.getWindowManagerLock()) { - mWillFinishToHome = willFinishToHome; + RecentsAnimationController.this.setWillFinishToHome(willFinishToHome); } } @@ -513,7 +518,6 @@ public class RecentsAnimationController implements DeathRecipient { || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; } - @VisibleForTesting AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { return addAnimation(task, isRecentTaskInvisible, false /* hidden */, @@ -816,6 +820,18 @@ public class RecentsAnimationController implements DeathRecipient { cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "rootTaskOrderChanged"); } + /** + * If there is a recents animation running, we need to cancel the animation and snapshot the + * tasks before the change (to ensure they are captured at the right configuration) + */ + public void cancelAnimationForDisplayChange() { + if (mCanceled) { + return; + } + cancelAnimation(mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION, + true /* screenshot */, "cancelAnimationForDisplayChange"); + } + private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "cancelAnimation(): reason=%s", reason); synchronized (mService.getWindowManagerLock()) { @@ -826,7 +842,7 @@ public class RecentsAnimationController implements DeathRecipient { mService.mH.removeCallbacks(mFailsafeRunnable); mCanceled = true; - if (screenshot) { + if (screenshot && !mPendingAnimations.isEmpty()) { // Screen shot previous task when next task starts transition and notify the runner. // We will actually finish the animation once the runner calls cleanUpScreenshot(). final Task task = mPendingAnimations.get(0).mTask; @@ -853,6 +869,11 @@ public class RecentsAnimationController implements DeathRecipient { } } + @VisibleForTesting + void setWillFinishToHome(boolean willFinishToHome) { + mWillFinishToHome = willFinishToHome; + } + /** * Cancel recents animation when the next app transition starts. *

@@ -905,7 +926,8 @@ public class RecentsAnimationController implements DeathRecipient { return null; } - final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(mService.mSurfaceControlFactory, task, + final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable( + mService.mSurfaceControlFactory, task, new SurfaceControl.ScreenshotHardwareBuffer(taskSnapshot.getHardwareBuffer(), taskSnapshot.getColorSpace(), false /* containsSecureLayers */)); mRecentScreenshotAnimator = new SurfaceAnimator( @@ -1006,7 +1028,16 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void binderDied() { - cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied"); + if (!mCanceled) { + cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied"); + } else { + synchronized (mService.getWindowManagerLock()) { + if (mRecentScreenshotAnimator != null) { + mRecentScreenshotAnimator.cancelAnimation(); + mRecentScreenshotAnimator = null; + } + } + } synchronized (mService.getWindowManagerLock()) { // Clear associated input consumers on runner death 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 39a59c9e50646..afd0ecf0fdee5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -655,6 +655,36 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { assertFalse(win1.mHasSurface); } + @Test + public void testCancelForRotation_ReorderToTop() throws Exception { + mWm.setRecentsAnimationController(mController); + final ActivityRecord activity = createActivityRecord(mDefaultDisplay); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); + activity.addWindow(win1); + + mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); + mController.setWillFinishToHome(true); + mController.cancelAnimationForDisplayChange(); + + verify(mMockRunner).onAnimationCanceled(any()); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); + } + + @Test + public void testCancelForRotation_ReorderToOriginalPosition() throws Exception { + mWm.setRecentsAnimationController(mController); + final ActivityRecord activity = createActivityRecord(mDefaultDisplay); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); + activity.addWindow(win1); + + mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); + mController.setWillFinishToHome(false); + mController.cancelAnimationForDisplayChange(); + + verify(mMockRunner).onAnimationCanceled(any()); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false); + } + private ActivityRecord createHomeActivity() { final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) .setParentTask(mRootHomeTask)