From 7c6f6b809b2a90c5d0d0499987b75fbf2aef8744 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Fri, 18 Feb 2022 18:57:19 +0800 Subject: [PATCH] Defer rotation update during recent transition This migrates the changes [1] and [2] to shell transition. [1] I3f2a46ddfe5e76ad3200ea27ad272ae36b6bcc8d [2] I5e7487b08e139b251d160bd0627326b83351640e With shell transition+fixed rotation, even if the home is moved to top for showing recents overview, a rotation change may still happen if the display is in non-natural rotation (different than NOSENSOR). So the legacy condition is still needed to defer the rotation change. Eliminate the post message for updating rotation by setting the orientation of recents mLastOrientation. So when finishing the transition, the change can be seen: Legacy: handleAppTransitionReady -> FINISH_LAYOUT_REDO_CONFIG Shell: onAppTransitionFinishedLocked -> continueUpdateOrientationForDiffOrienLaunchingApp Bug: 216169338 Test: atest TransitionTests#testDeferRotationForTransientLaunch RecentsAnimationControllerTest#testCheckRotationAfterCleanup Change-Id: I2580c6d7b46a641abd4d8f963e915122af215ac7 --- .../com/android/server/wm/DisplayContent.java | 27 ++++++++++--- .../android/server/wm/DisplayRotation.java | 20 ++++------ .../server/wm/RecentsAnimationController.java | 23 ----------- .../wm/RecentsAnimationControllerTest.java | 23 ++++++++--- .../android/server/wm/TransitionTests.java | 40 +++++++++++++++++++ 5 files changed, 86 insertions(+), 47 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 71a1b3e6c5d27..1c0687a351e48 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6368,13 +6368,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** - * Return {@code true} if there is an ongoing animation to the "Recents" activity and this - * activity as a fixed orientation so shouldn't be rotated. + * Returns the fixed orientation requested by a transient launch (e.g. recents animation). + * If it doesn't return SCREEN_ORIENTATION_UNSET, the rotation change should be deferred. */ - boolean isTopFixedOrientationRecentsAnimating() { - return mAnimatingRecents != null - && mAnimatingRecents.getRequestedConfigurationOrientation(true /* forDisplay */) - != ORIENTATION_UNDEFINED && !hasTopFixedRotationLaunchingApp(); + @ActivityInfo.ScreenOrientation int getTransientFixedOrientation() { + ActivityRecord source = null; + if (mTransitionController.isShellTransitionsEnabled()) { + final ActivityRecord r = mFixedRotationLaunchingApp; + if (r != null && mTransitionController.isTransientLaunch(r)) { + source = r; + } + } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) { + source = mAnimatingRecents; + } + if (source == null || source.getRequestedConfigurationOrientation( + true /* forDisplay */) == ORIENTATION_UNDEFINED) { + return SCREEN_ORIENTATION_UNSET; + } + if (!mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) { + // If screen is off or the device is going to sleep, then still allow to update. + return SCREEN_ORIENTATION_UNSET; + } + return source.mOrientation; } @Override diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 6438d79e57610..f116fffa80759 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -419,11 +419,8 @@ public class DisplayRotation { * THE SCREEN. */ boolean updateRotationUnchecked(boolean forceUpdate) { - final boolean useShellTransitions = - mDisplayContent.mTransitionController.isShellTransitionsEnabled(); - final int displayId = mDisplayContent.getDisplayId(); - if (!forceUpdate && !useShellTransitions) { + if (!forceUpdate) { if (mDeferredRotationPauseCount > 0) { // Rotation updates have been paused temporarily. Defer the update until updates // have been resumed. @@ -449,17 +446,16 @@ public class DisplayRotation { return false; } - final RecentsAnimationController recentsAnimController = - mService.getRecentsAnimationController(); - if (recentsAnimController != null && mDisplayContent.mFixedRotationTransitionListener - .isTopFixedOrientationRecentsAnimating() - // If screen is off or the device is going to sleep, then still allow to update. - && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) { + final int transientFixedOrientation = + mDisplayContent.mFixedRotationTransitionListener.getTransientFixedOrientation(); + if (transientFixedOrientation != SCREEN_ORIENTATION_UNSET) { + // Makes sure that after the transition is finished, updateOrientation() can see + // the difference from the latest orientation source. + mLastOrientation = transientFixedOrientation; // During the recents animation, the closing app might still be considered on top. // In order to ignore its requested orientation to avoid a sensor led rotation (e.g // user rotating the device while the recents animation is running), we ignore // rotation update while the animation is running. - recentsAnimController.setCheckRotationAfterCleanup(); return false; } } @@ -513,7 +509,7 @@ public class DisplayRotation { mDisplayContent.setLayoutNeeded(); - if (useShellTransitions) { + if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting(); final TransitionRequestInfo.DisplayChange change = wasCollecting ? null : new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(), diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 30906e5b3db8c..93714e881441b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -122,7 +122,6 @@ public class RecentsAnimationController implements DeathRecipient { private final int mDisplayId; private boolean mWillFinishToHome = false; private final Runnable mFailsafeRunnable = this::onFailsafe; - private Runnable mCheckRotationAfterCleanup; // The recents component app token that is shown behind the visibile tasks private ActivityRecord mTargetActivityRecord; @@ -919,24 +918,6 @@ public class RecentsAnimationController implements DeathRecipient { mCancelDeferredWithScreenshot = screenshot; } - /** - * If the display rotation change is ignored while recents animation is running, make sure that - * the pending rotation change will be applied after the animation finishes. - */ - void setCheckRotationAfterCleanup() { - if (mCheckRotationAfterCleanup != null) return; - mCheckRotationAfterCleanup = () -> { - synchronized (mService.mGlobalLock) { - if (mDisplayContent.getDisplayRotation() - .updateRotationAndSendNewConfigIfChanged()) { - if (mTargetActivityRecord != null) { - mTargetActivityRecord.finishFixedRotationTransform(); - } - } - } - }; - } - /** * @return Whether we should defer the cancel from a root task order change until the next app * transition. @@ -1037,10 +1018,6 @@ public class RecentsAnimationController implements DeathRecipient { if (mStatusBar != null) { mStatusBar.onRecentsAnimationStateChanged(false /* running */); } - if (mCheckRotationAfterCleanup != null) { - mService.mH.post(mCheckRotationAfterCleanup); - mCheckRotationAfterCleanup = null; - } } void scheduleFailsafe() { 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 e0911909f47e6..3c3351c0b2079 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -440,16 +441,26 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { public void testCheckRotationAfterCleanup() { mWm.setRecentsAnimationController(mController); spyOn(mDisplayContent.mFixedRotationTransitionListener); - doReturn(true).when(mDisplayContent.mFixedRotationTransitionListener) - .isTopFixedOrientationRecentsAnimating(); + final ActivityRecord recents = mock(ActivityRecord.class); + recents.mOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + doReturn(ORIENTATION_PORTRAIT).when(recents) + .getRequestedConfigurationOrientation(anyBoolean()); + mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents); + // Rotation update is skipped while the recents animation is running. - assertFalse(mDisplayContent.getDisplayRotation().updateOrientation(DisplayContentTests - .getRotatedOrientation(mDefaultDisplay), false /* forceUpdate */)); + final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); + final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay); + assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */)); + assertEquals(recents.mOrientation, displayRotation.getLastOrientation()); final int prevRotation = mDisplayContent.getRotation(); mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); - waitHandlerIdle(mWm.mH); + + // In real case, it is called from RecentsAnimation#finishAnimation -> continueWindowLayout + // -> handleAppTransitionReady -> add FINISH_LAYOUT_REDO_CONFIG, and DisplayContent# + // applySurfaceChangesTransaction will call updateOrientation for FINISH_LAYOUT_REDO_CONFIG. + assertTrue(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */)); // The display should be updated to the changed orientation after the animation is finished. - assertNotEquals(mDisplayContent.getRotation(), prevRotation); + assertNotEquals(displayRotation.getRotation(), prevRotation); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 32af672c8693b..501f0c4ac1b3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; @@ -33,13 +34,17 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.isIndependent; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -659,6 +664,41 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getAsyncRotationController()); } + @Test + public void testDeferRotationForTransientLaunch() { + final TestTransitionPlayer player = registerTestTransitionPlayer(); + assumeFalse(mDisplayContent.mTransitionController.useShellTransitionsRotation()); + final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityRecord home = new ActivityBuilder(mAtm) + .setTask(mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask()) + .setScreenOrientation(SCREEN_ORIENTATION_NOSENSOR).setVisible(false).build(); + final Transition transition = home.mTransitionController.createTransition(TRANSIT_OPEN); + final int prevRotation = mDisplayContent.getRotation(); + transition.setTransientLaunch(home, null /* restoreBelow */); + home.mTransitionController.requestStartTransition(transition, home.getTask(), + null /* remoteTransition */, null /* displayChange */); + transition.collectExistenceChange(home); + home.mVisibleRequested = true; + mDisplayContent.setFixedRotationLaunchingAppUnchecked(home); + doReturn(true).when(home).hasFixedRotationTransform(any()); + player.startTransition(); + player.onTransactionReady(mDisplayContent.getSyncTransaction()); + + final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); + spyOn(displayRotation); + doReturn(true).when(displayRotation).isWaitingForRemoteRotation(); + doReturn(prevRotation + 1).when(displayRotation).rotationForOrientation( + anyInt() /* orientation */, anyInt() /* lastRotation */); + // Rotation update is skipped while the recents animation is running. + assertFalse(mDisplayContent.updateRotationUnchecked()); + assertEquals(SCREEN_ORIENTATION_NOSENSOR, displayRotation.getLastOrientation()); + // Return to the app without fixed orientation from recents. + app.moveFocusableActivityToTop("test"); + player.finish(); + // The display should be updated to the changed orientation after the animation is finish. + assertNotEquals(mDisplayContent.getRotation(), prevRotation); + } + @Test public void testIntermediateVisibility() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);