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);