Loading services/core/java/com/android/server/wm/DisplayContent.java +136 −35 Original line number Diff line number Diff line Loading @@ -494,10 +494,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * The launching activity which is using fixed rotation transformation. * * @see #handleTopActivityLaunchingInDifferentOrientation * @see #setFixedRotationLaunchingApp * @see DisplayRotation#shouldRotateSeamlessly */ ActivityRecord mFixedRotationLaunchingApp; final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>(); Loading Loading @@ -928,6 +932,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); Loading Loading @@ -1266,6 +1271,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (configUpdated) { return; } // The display configuration doesn't change. If there is a launching transformed app, that // means its request to change display configuration has been discarded, then it should // respect to the current configuration of display. clearFixedRotationLaunchingApp(); // Something changed (E.g. device rotation), but no configuration update is needed. // E.g. changing device rotation by 180 degrees. Go ahead and perform surface placement to // unfreeze the display since we froze it when the rotation was updated in Loading Loading @@ -1383,7 +1394,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo orientationSource != null ? orientationSource.asActivityRecord() : null; // Currently there is no use case from non-activity. if (r != null && handleTopActivityLaunchingInDifferentOrientation(r)) { mFixedRotationLaunchingApp = r; // Display orientation should be deferred until the top fixed rotation is finished. return false; } Loading Loading @@ -1448,47 +1458,66 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return false; } startFixedRotationTransform(r, rotation); mAppTransition.registerListenerLocked(new WindowManagerInternal.AppTransitionListener() { void done() { r.finishFixedRotationTransform(); mAppTransition.unregisterListener(this); setFixedRotationLaunchingApp(r, rotation); return true; } @Override public void onAppTransitionFinishedLocked(IBinder token) { if (token == r.token) { done(); } /** * Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed * rotation transform to it and indicate that the display may be rotated after it is launched. */ void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Surface.Rotation int rotation) { final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp; if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation // It is animating so we can expect there will have a transition callback. && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) { // It may be the case that multiple activities launch consecutively. Because their // rotation are the same, the transformed state can be shared to avoid duplicating // the heavy operations. This also benefits that the states of multiple activities // are handled together. r.linkFixedRotationTransform(prevRotatedLaunchingApp); return; } @Override public void onAppTransitionCancelledLocked(int transit) { done(); if (!r.hasFixedRotationTransform()) { startFixedRotationTransform(r, rotation); } @Override public void onAppTransitionTimeoutLocked() { done(); mFixedRotationLaunchingApp = r; if (prevRotatedLaunchingApp != null) { prevRotatedLaunchingApp.finishFixedRotationTransform(); } }); return true; } /** @return {@code true} if the display orientation will be changed. */ boolean continueUpdateOrientationForDiffOrienLaunchingApp(WindowToken token) { if (token != mFixedRotationLaunchingApp) { return false; /** * Continue updating the orientation change of display if it was deferred by a top activity * launched in a different orientation. */ void continueUpdateOrientationForDiffOrienLaunchingApp() { if (mFixedRotationLaunchingApp == null) { return; } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { sendNewConfiguration(); return true; return; } // The orientation of display is not changed. clearFixedRotationLaunchingApp(); } /** * Clears the {@link mFixedRotationLaunchingApp} without applying rotation to display. It is * used when the display won't rotate (e.g. the orientation from sensor has updated again before * applying rotation to display) but the launching app has been transformed. So the record need * to be cleared and restored to stop using seamless rotation and rotated configuration. */ private void clearFixedRotationLaunchingApp() { if (mFixedRotationLaunchingApp == null) { return; } // The display won't rotate (e.g. the orientation from sensor has updated again before // applying rotation to display), so clear it to stop using seamless rotation. mFixedRotationLaunchingApp.finishFixedRotationTransform(); mFixedRotationLaunchingApp = null; return false; } private void startFixedRotationTransform(WindowToken token, int rotation) { Loading Loading @@ -2858,6 +2887,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.print(" mLastStatusBarVisibility=0x"); pw.println(Integer.toHexString(mLastStatusBarVisibility)); } if (mFixedRotationLaunchingApp != null) { pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp); } pw.println(); mWallpaperController.dump(pw, " "); Loading Loading @@ -5184,7 +5216,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int currRotation = currOverrideConfig.windowConfiguration.getRotation(); final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation(); if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) { applyRotationAndClearFixedRotation(currRotation, overrideRotation); applyRotationAndFinishFixedRotation(currRotation, overrideRotation); } mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration); super.onRequestedOverrideConfigurationChanged(overrideConfiguration); Loading @@ -5200,7 +5232,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * fixed rotation transform also needs to be cleared to make sure the rotated activity fits * the display naturally. */ private void applyRotationAndClearFixedRotation(int oldRotation, int newRotation) { private void applyRotationAndFinishFixedRotation(int oldRotation, int newRotation) { if (mFixedRotationLaunchingApp == null) { applyRotation(oldRotation, newRotation); return; Loading @@ -5219,7 +5251,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } }); mFixedRotationLaunchingApp.clearFixedRotationTransform( mFixedRotationLaunchingApp.finishFixedRotationTransform( () -> applyRotation(oldRotation, newRotation)); mFixedRotationLaunchingApp = null; } Loading Loading @@ -5494,6 +5526,75 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo }); } /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */ class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener { /** * The animating activity which shows the recents task list. It is set between * {@link RecentsAnimationController#initialize} and * {@link RecentsAnimationController#cancelAnimation}. */ private ActivityRecord mAnimatingRecents; /** * If the recents activity has a fixed orientation which is different from the current top * activity, it will be rotated before being shown so we avoid a screen rotation animation * when showing the Recents view. */ void onStartRecentsAnimation(@NonNull ActivityRecord r) { mAnimatingRecents = r; rotateInDifferentOrientationIfNeeded(r); if (r.hasFixedRotationTransform()) { // Set the record so we can recognize it to continue to update display orientation // if the recents activity becomes the top later. setFixedRotationLaunchingApp(r, r.getWindowConfiguration().getRotation()); } } /** * If {@link #mAnimatingRecents} still has fixed rotation, it should be moved to top so we * don't clear {@link #mFixedRotationLaunchingApp} that will be handled by transition. */ void onFinishRecentsAnimation() { final ActivityRecord animatingRecents = mAnimatingRecents; mAnimatingRecents = null; if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp && !animatingRecents.hasFixedRotationTransform()) { // The recents activity won't be the top, such as giving up the swipe up gesture // and return to the original top. mFixedRotationLaunchingApp = null; } } @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = getActivityRecord(token); // Ignore the animating recents so the fixed rotation transform won't be switched twice // by finishing the recents animation and moving it to top. That also avoids flickering // due to wait for previous activity to be paused if it supports PiP that ignores the // effect of resume-while-pausing. if (r == null || r == mAnimatingRecents) { return; } if (mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) { continueUpdateOrientationForDiffOrienLaunchingApp(); } else { r.finishFixedRotationTransform(); } } @Override public void onAppTransitionCancelledLocked(int transit) { continueUpdateOrientationForDiffOrienLaunchingApp(); } @Override public void onAppTransitionTimeoutLocked() { continueUpdateOrientationForDiffOrienLaunchingApp(); } } class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; Loading services/core/java/com/android/server/wm/RecentsAnimationController.java +5 −6 Original line number Diff line number Diff line Loading @@ -411,10 +411,7 @@ public class RecentsAnimationController implements DeathRecipient { mService.mWindowPlacerLocked.performSurfacePlacement(); // If the target activity has a fixed orientation which is different from the current top // activity, it will be rotated before being shown so we avoid a screen rotation // animation when showing the Recents view. mDisplayContent.rotateInDifferentOrientationIfNeeded(mTargetActivityRecord); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); // Notify that the animation has started if (mStatusBar != null) { Loading Loading @@ -736,11 +733,13 @@ public class RecentsAnimationController implements DeathRecipient { if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) { mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked( mTargetActivityRecord.token); } if (mTargetActivityRecord.hasFixedRotationTransform()) { } else { // The target activity will be moved to original position (non-top). Since it won't // affect display orientation, just finish the transform. mTargetActivityRecord.finishFixedRotationTransform(); } } mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); // Notify that the animation has ended if (mStatusBar != null) { Loading services/core/java/com/android/server/wm/WindowToken.java +14 −22 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ class WindowToken extends WindowContainer<WindowState> { * rotated by the given rotated display info, frames and insets. */ private static class FixedRotationTransformState { final WindowToken mOwner; final DisplayInfo mDisplayInfo; final DisplayFrames mDisplayFrames; final InsetsState mInsetsState; Loading @@ -133,10 +132,9 @@ class WindowToken extends WindowContainer<WindowState> { final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3); boolean mIsTransforming = true; FixedRotationTransformState(WindowToken owner, DisplayInfo rotatedDisplayInfo, FixedRotationTransformState(DisplayInfo rotatedDisplayInfo, DisplayFrames rotatedDisplayFrames, InsetsState rotatedInsetsState, Configuration rotatedConfig, int currentRotation) { mOwner = owner; mDisplayInfo = rotatedDisplayInfo; mDisplayFrames = rotatedDisplayFrames; mInsetsState = rotatedInsetsState; Loading Loading @@ -482,6 +480,14 @@ class WindowToken extends WindowContainer<WindowState> { return mFixedRotationTransformState != null; } /** Returns {@code true} if the given token shares the same transform. */ boolean hasFixedRotationTransform(WindowToken token) { if (mFixedRotationTransformState == null || token == null) { return false; } return this == token || mFixedRotationTransformState == token.mFixedRotationTransformState; } boolean isFinishingFixedRotationTransform() { return mFixedRotationTransformState != null && !mFixedRotationTransformState.mIsTransforming; Loading Loading @@ -520,15 +526,14 @@ class WindowToken extends WindowContainer<WindowState> { final InsetsState insetsState = new InsetsState(); mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, insetsState, mDisplayContent.getConfiguration().uiMode); mFixedRotationTransformState = new FixedRotationTransformState(this, info, displayFrames, mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, insetsState, new Configuration(config), mDisplayContent.getRotation()); onConfigurationChanged(getParent().getConfiguration()); } /** * Reuses the {@link FixedRotationTransformState} (if any) from the other WindowToken to this * one. This takes the same effect as {@link #applyFixedRotationTransform}, but the linked state * can only be cleared by the state owner. * one. This takes the same effect as {@link #applyFixedRotationTransform}. */ void linkFixedRotationTransform(WindowToken other) { if (mFixedRotationTransformState != null) { Loading @@ -543,28 +548,15 @@ class WindowToken extends WindowContainer<WindowState> { onConfigurationChanged(getParent().getConfiguration()); } /** * Finishes the transform and continue updating the orientation change of display. Only the * state owner can finish the transform state. */ void finishFixedRotationTransform() { if (mFixedRotationTransformState == null || mFixedRotationTransformState.mOwner != this) { return; } final boolean changed = mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(this); // If it is not the launching app or the display is not rotated, make sure the transform is // cleared and the configuration is restored from parent. if (!changed) { clearFixedRotationTransform(null /* applyDisplayRotation */); } finishFixedRotationTransform(null /* applyDisplayRotation */); } /** * Clears the transform and apply display rotation if the action is given. If the display will * Finishes the transform and apply display rotation if the action is given. If the display will * not rotate, the transformed containers are restored to their original states. */ void clearFixedRotationTransform(Runnable applyDisplayRotation) { void finishFixedRotationTransform(Runnable applyDisplayRotation) { final FixedRotationTransformState state = mFixedRotationTransformState; if (state == null) { return; Loading services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +13 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityOptions; Loading Loading @@ -1402,6 +1403,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo(); mActivity.finishFixedRotationTransform(); final ScreenRotationAnimation rotationAnim = display.getRotationAnimation(); assertNotNull(rotationAnim); rotationAnim.setRotation(display.getPendingTransaction(), originalRotation); // Because the display doesn't rotate, the rotated activity needs to cancel the fixed Loading @@ -1409,8 +1411,18 @@ public class ActivityRecordTests extends ActivityTestsBase { verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation); assertTrue(mActivity.isFreezingScreen()); assertFalse(displayRotation.isRotatingSeamlessly()); assertNotNull(rotationAnim); assertTrue(rotationAnim.isRotating()); // Simulate the remote rotation has completed and the configuration doesn't change, then // the rotated activity should also be restored by clearing the transform. displayRotation.updateRotationUnchecked(true /* forceUpdate */); doReturn(false).when(displayRotation).isWaitingForRemoteRotation(); clearInvocations(mActivity); display.mFixedRotationLaunchingApp = mActivity; display.sendNewConfiguration(); assertNull(display.mFixedRotationLaunchingApp); assertFalse(mActivity.hasFixedRotationTransform()); } @Test Loading services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +24 −8 Original line number Diff line number Diff line Loading @@ -790,9 +790,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; final int newOrientation = getRotatedOrientation(dc); final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer) Loading @@ -812,9 +810,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; final int newOrientation = getRotatedOrientation(dc); final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer) Loading Loading @@ -1083,7 +1079,8 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mDisplayContent.mOpeningApps.add(app); app.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); final int newOrientation = getRotatedOrientation(mDisplayContent); app.setRequestedOrientation(newOrientation); assertTrue(app.isFixedRotationTransforming()); assertTrue(mDisplayContent.getDisplayRotation().shouldRotateSeamlessly( Loading Loading @@ -1124,12 +1121,25 @@ public class DisplayContentTests extends WindowTestsBase { mWallpaperWindow.mToken.onAnimationLeashCreated(t, null /* leash */); verify(t, never()).setPosition(any(), eq(0), eq(0)); // Launch another activity before the transition is finished. final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot) .setDisplay(mDisplayContent).build().getTopMostActivity(); mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mDisplayContent.mOpeningApps.add(app2); app2.setRequestedOrientation(newOrientation); // The activity should share the same transform state as the existing one. assertTrue(app.hasFixedRotationTransform(app2)); // The display should be rotated after the launch is finished. mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); // The animation in old rotation should be cancelled. assertFalse(closingApp.isAnimating()); // The display should be rotated after the launch is finished. // The fixed rotation should be cleared and the new rotation is applied to display. assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); } Loading Loading @@ -1292,6 +1302,12 @@ public class DisplayContentTests extends WindowTestsBase { assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); } private static int getRotatedOrientation(DisplayContent dc) { return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; } private static List<WindowState> reverseList(List<WindowState> list) { final ArrayList<WindowState> result = new ArrayList<>(list); Collections.reverse(result); Loading Loading
services/core/java/com/android/server/wm/DisplayContent.java +136 −35 Original line number Diff line number Diff line Loading @@ -494,10 +494,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * The launching activity which is using fixed rotation transformation. * * @see #handleTopActivityLaunchingInDifferentOrientation * @see #setFixedRotationLaunchingApp * @see DisplayRotation#shouldRotateSeamlessly */ ActivityRecord mFixedRotationLaunchingApp; final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>(); Loading Loading @@ -928,6 +932,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); Loading Loading @@ -1266,6 +1271,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (configUpdated) { return; } // The display configuration doesn't change. If there is a launching transformed app, that // means its request to change display configuration has been discarded, then it should // respect to the current configuration of display. clearFixedRotationLaunchingApp(); // Something changed (E.g. device rotation), but no configuration update is needed. // E.g. changing device rotation by 180 degrees. Go ahead and perform surface placement to // unfreeze the display since we froze it when the rotation was updated in Loading Loading @@ -1383,7 +1394,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo orientationSource != null ? orientationSource.asActivityRecord() : null; // Currently there is no use case from non-activity. if (r != null && handleTopActivityLaunchingInDifferentOrientation(r)) { mFixedRotationLaunchingApp = r; // Display orientation should be deferred until the top fixed rotation is finished. return false; } Loading Loading @@ -1448,47 +1458,66 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return false; } startFixedRotationTransform(r, rotation); mAppTransition.registerListenerLocked(new WindowManagerInternal.AppTransitionListener() { void done() { r.finishFixedRotationTransform(); mAppTransition.unregisterListener(this); setFixedRotationLaunchingApp(r, rotation); return true; } @Override public void onAppTransitionFinishedLocked(IBinder token) { if (token == r.token) { done(); } /** * Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed * rotation transform to it and indicate that the display may be rotated after it is launched. */ void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Surface.Rotation int rotation) { final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp; if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation // It is animating so we can expect there will have a transition callback. && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) { // It may be the case that multiple activities launch consecutively. Because their // rotation are the same, the transformed state can be shared to avoid duplicating // the heavy operations. This also benefits that the states of multiple activities // are handled together. r.linkFixedRotationTransform(prevRotatedLaunchingApp); return; } @Override public void onAppTransitionCancelledLocked(int transit) { done(); if (!r.hasFixedRotationTransform()) { startFixedRotationTransform(r, rotation); } @Override public void onAppTransitionTimeoutLocked() { done(); mFixedRotationLaunchingApp = r; if (prevRotatedLaunchingApp != null) { prevRotatedLaunchingApp.finishFixedRotationTransform(); } }); return true; } /** @return {@code true} if the display orientation will be changed. */ boolean continueUpdateOrientationForDiffOrienLaunchingApp(WindowToken token) { if (token != mFixedRotationLaunchingApp) { return false; /** * Continue updating the orientation change of display if it was deferred by a top activity * launched in a different orientation. */ void continueUpdateOrientationForDiffOrienLaunchingApp() { if (mFixedRotationLaunchingApp == null) { return; } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { sendNewConfiguration(); return true; return; } // The orientation of display is not changed. clearFixedRotationLaunchingApp(); } /** * Clears the {@link mFixedRotationLaunchingApp} without applying rotation to display. It is * used when the display won't rotate (e.g. the orientation from sensor has updated again before * applying rotation to display) but the launching app has been transformed. So the record need * to be cleared and restored to stop using seamless rotation and rotated configuration. */ private void clearFixedRotationLaunchingApp() { if (mFixedRotationLaunchingApp == null) { return; } // The display won't rotate (e.g. the orientation from sensor has updated again before // applying rotation to display), so clear it to stop using seamless rotation. mFixedRotationLaunchingApp.finishFixedRotationTransform(); mFixedRotationLaunchingApp = null; return false; } private void startFixedRotationTransform(WindowToken token, int rotation) { Loading Loading @@ -2858,6 +2887,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.print(" mLastStatusBarVisibility=0x"); pw.println(Integer.toHexString(mLastStatusBarVisibility)); } if (mFixedRotationLaunchingApp != null) { pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp); } pw.println(); mWallpaperController.dump(pw, " "); Loading Loading @@ -5184,7 +5216,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int currRotation = currOverrideConfig.windowConfiguration.getRotation(); final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation(); if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) { applyRotationAndClearFixedRotation(currRotation, overrideRotation); applyRotationAndFinishFixedRotation(currRotation, overrideRotation); } mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration); super.onRequestedOverrideConfigurationChanged(overrideConfiguration); Loading @@ -5200,7 +5232,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * fixed rotation transform also needs to be cleared to make sure the rotated activity fits * the display naturally. */ private void applyRotationAndClearFixedRotation(int oldRotation, int newRotation) { private void applyRotationAndFinishFixedRotation(int oldRotation, int newRotation) { if (mFixedRotationLaunchingApp == null) { applyRotation(oldRotation, newRotation); return; Loading @@ -5219,7 +5251,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } }); mFixedRotationLaunchingApp.clearFixedRotationTransform( mFixedRotationLaunchingApp.finishFixedRotationTransform( () -> applyRotation(oldRotation, newRotation)); mFixedRotationLaunchingApp = null; } Loading Loading @@ -5494,6 +5526,75 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo }); } /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */ class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener { /** * The animating activity which shows the recents task list. It is set between * {@link RecentsAnimationController#initialize} and * {@link RecentsAnimationController#cancelAnimation}. */ private ActivityRecord mAnimatingRecents; /** * If the recents activity has a fixed orientation which is different from the current top * activity, it will be rotated before being shown so we avoid a screen rotation animation * when showing the Recents view. */ void onStartRecentsAnimation(@NonNull ActivityRecord r) { mAnimatingRecents = r; rotateInDifferentOrientationIfNeeded(r); if (r.hasFixedRotationTransform()) { // Set the record so we can recognize it to continue to update display orientation // if the recents activity becomes the top later. setFixedRotationLaunchingApp(r, r.getWindowConfiguration().getRotation()); } } /** * If {@link #mAnimatingRecents} still has fixed rotation, it should be moved to top so we * don't clear {@link #mFixedRotationLaunchingApp} that will be handled by transition. */ void onFinishRecentsAnimation() { final ActivityRecord animatingRecents = mAnimatingRecents; mAnimatingRecents = null; if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp && !animatingRecents.hasFixedRotationTransform()) { // The recents activity won't be the top, such as giving up the swipe up gesture // and return to the original top. mFixedRotationLaunchingApp = null; } } @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = getActivityRecord(token); // Ignore the animating recents so the fixed rotation transform won't be switched twice // by finishing the recents animation and moving it to top. That also avoids flickering // due to wait for previous activity to be paused if it supports PiP that ignores the // effect of resume-while-pausing. if (r == null || r == mAnimatingRecents) { return; } if (mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) { continueUpdateOrientationForDiffOrienLaunchingApp(); } else { r.finishFixedRotationTransform(); } } @Override public void onAppTransitionCancelledLocked(int transit) { continueUpdateOrientationForDiffOrienLaunchingApp(); } @Override public void onAppTransitionTimeoutLocked() { continueUpdateOrientationForDiffOrienLaunchingApp(); } } class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; Loading
services/core/java/com/android/server/wm/RecentsAnimationController.java +5 −6 Original line number Diff line number Diff line Loading @@ -411,10 +411,7 @@ public class RecentsAnimationController implements DeathRecipient { mService.mWindowPlacerLocked.performSurfacePlacement(); // If the target activity has a fixed orientation which is different from the current top // activity, it will be rotated before being shown so we avoid a screen rotation // animation when showing the Recents view. mDisplayContent.rotateInDifferentOrientationIfNeeded(mTargetActivityRecord); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); // Notify that the animation has started if (mStatusBar != null) { Loading Loading @@ -736,11 +733,13 @@ public class RecentsAnimationController implements DeathRecipient { if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) { mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked( mTargetActivityRecord.token); } if (mTargetActivityRecord.hasFixedRotationTransform()) { } else { // The target activity will be moved to original position (non-top). Since it won't // affect display orientation, just finish the transform. mTargetActivityRecord.finishFixedRotationTransform(); } } mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); // Notify that the animation has ended if (mStatusBar != null) { Loading
services/core/java/com/android/server/wm/WindowToken.java +14 −22 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ class WindowToken extends WindowContainer<WindowState> { * rotated by the given rotated display info, frames and insets. */ private static class FixedRotationTransformState { final WindowToken mOwner; final DisplayInfo mDisplayInfo; final DisplayFrames mDisplayFrames; final InsetsState mInsetsState; Loading @@ -133,10 +132,9 @@ class WindowToken extends WindowContainer<WindowState> { final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3); boolean mIsTransforming = true; FixedRotationTransformState(WindowToken owner, DisplayInfo rotatedDisplayInfo, FixedRotationTransformState(DisplayInfo rotatedDisplayInfo, DisplayFrames rotatedDisplayFrames, InsetsState rotatedInsetsState, Configuration rotatedConfig, int currentRotation) { mOwner = owner; mDisplayInfo = rotatedDisplayInfo; mDisplayFrames = rotatedDisplayFrames; mInsetsState = rotatedInsetsState; Loading Loading @@ -482,6 +480,14 @@ class WindowToken extends WindowContainer<WindowState> { return mFixedRotationTransformState != null; } /** Returns {@code true} if the given token shares the same transform. */ boolean hasFixedRotationTransform(WindowToken token) { if (mFixedRotationTransformState == null || token == null) { return false; } return this == token || mFixedRotationTransformState == token.mFixedRotationTransformState; } boolean isFinishingFixedRotationTransform() { return mFixedRotationTransformState != null && !mFixedRotationTransformState.mIsTransforming; Loading Loading @@ -520,15 +526,14 @@ class WindowToken extends WindowContainer<WindowState> { final InsetsState insetsState = new InsetsState(); mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, insetsState, mDisplayContent.getConfiguration().uiMode); mFixedRotationTransformState = new FixedRotationTransformState(this, info, displayFrames, mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, insetsState, new Configuration(config), mDisplayContent.getRotation()); onConfigurationChanged(getParent().getConfiguration()); } /** * Reuses the {@link FixedRotationTransformState} (if any) from the other WindowToken to this * one. This takes the same effect as {@link #applyFixedRotationTransform}, but the linked state * can only be cleared by the state owner. * one. This takes the same effect as {@link #applyFixedRotationTransform}. */ void linkFixedRotationTransform(WindowToken other) { if (mFixedRotationTransformState != null) { Loading @@ -543,28 +548,15 @@ class WindowToken extends WindowContainer<WindowState> { onConfigurationChanged(getParent().getConfiguration()); } /** * Finishes the transform and continue updating the orientation change of display. Only the * state owner can finish the transform state. */ void finishFixedRotationTransform() { if (mFixedRotationTransformState == null || mFixedRotationTransformState.mOwner != this) { return; } final boolean changed = mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(this); // If it is not the launching app or the display is not rotated, make sure the transform is // cleared and the configuration is restored from parent. if (!changed) { clearFixedRotationTransform(null /* applyDisplayRotation */); } finishFixedRotationTransform(null /* applyDisplayRotation */); } /** * Clears the transform and apply display rotation if the action is given. If the display will * Finishes the transform and apply display rotation if the action is given. If the display will * not rotate, the transformed containers are restored to their original states. */ void clearFixedRotationTransform(Runnable applyDisplayRotation) { void finishFixedRotationTransform(Runnable applyDisplayRotation) { final FixedRotationTransformState state = mFixedRotationTransformState; if (state == null) { return; Loading
services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +13 −1 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityOptions; Loading Loading @@ -1402,6 +1403,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo(); mActivity.finishFixedRotationTransform(); final ScreenRotationAnimation rotationAnim = display.getRotationAnimation(); assertNotNull(rotationAnim); rotationAnim.setRotation(display.getPendingTransaction(), originalRotation); // Because the display doesn't rotate, the rotated activity needs to cancel the fixed Loading @@ -1409,8 +1411,18 @@ public class ActivityRecordTests extends ActivityTestsBase { verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation); assertTrue(mActivity.isFreezingScreen()); assertFalse(displayRotation.isRotatingSeamlessly()); assertNotNull(rotationAnim); assertTrue(rotationAnim.isRotating()); // Simulate the remote rotation has completed and the configuration doesn't change, then // the rotated activity should also be restored by clearing the transform. displayRotation.updateRotationUnchecked(true /* forceUpdate */); doReturn(false).when(displayRotation).isWaitingForRemoteRotation(); clearInvocations(mActivity); display.mFixedRotationLaunchingApp = mActivity; display.sendNewConfiguration(); assertNull(display.mFixedRotationLaunchingApp); assertFalse(mActivity.hasFixedRotationTransform()); } @Test Loading
services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +24 −8 Original line number Diff line number Diff line Loading @@ -790,9 +790,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; final int newOrientation = getRotatedOrientation(dc); final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer) Loading @@ -812,9 +810,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; final int newOrientation = getRotatedOrientation(dc); final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer) Loading Loading @@ -1083,7 +1079,8 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mDisplayContent.mOpeningApps.add(app); app.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); final int newOrientation = getRotatedOrientation(mDisplayContent); app.setRequestedOrientation(newOrientation); assertTrue(app.isFixedRotationTransforming()); assertTrue(mDisplayContent.getDisplayRotation().shouldRotateSeamlessly( Loading Loading @@ -1124,12 +1121,25 @@ public class DisplayContentTests extends WindowTestsBase { mWallpaperWindow.mToken.onAnimationLeashCreated(t, null /* leash */); verify(t, never()).setPosition(any(), eq(0), eq(0)); // Launch another activity before the transition is finished. final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot) .setDisplay(mDisplayContent).build().getTopMostActivity(); mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mDisplayContent.mOpeningApps.add(app2); app2.setRequestedOrientation(newOrientation); // The activity should share the same transform state as the existing one. assertTrue(app.hasFixedRotationTransform(app2)); // The display should be rotated after the launch is finished. mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); // The animation in old rotation should be cancelled. assertFalse(closingApp.isAnimating()); // The display should be rotated after the launch is finished. // The fixed rotation should be cleared and the new rotation is applied to display. assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); } Loading Loading @@ -1292,6 +1302,12 @@ public class DisplayContentTests extends WindowTestsBase { assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); } private static int getRotatedOrientation(DisplayContent dc) { return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; } private static List<WindowState> reverseList(List<WindowState> list) { final ArrayList<WindowState> result = new ArrayList<>(list); Collections.reverse(result); Loading