From d16620e8287da61d7fa0b7e157fd2f30f9253b55 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Mon, 6 Apr 2020 20:48:06 +0800 Subject: [PATCH] Apply rotation animation to restore rotated activity Consider the steps with fixed rotation enabled: 1. Sensor reports landscape and the foreground is a fixed portrait activity, so the display is portrait. 2. Launch a rotatable activity so the activity is created in landscape. Before the remote rotation completes, the display hasn't applied the rotation. 3. Sensor reports portrait and then the remote rotation is done. Display is still portrait but the rotatable activity has shown as rotated. To avoid flickering when updating the configuration of the activity from landscape to portrait, a rotation animation with customized original rotation is still applied to cover the change. Fixes: 151597653 Test: ActivityRecordTests#testActivityOnCancelFixedRotationTransform Change-Id: Ie897885782aaa1411113bb7fdbac6c4f33fdc0ef --- .../com/android/server/wm/ActivityRecord.java | 65 +++++++++++++++---- .../com/android/server/wm/DisplayContent.java | 6 +- .../android/server/wm/DisplayRotation.java | 23 ++++++- .../server/wm/ScreenRotationAnimation.java | 40 +++++++----- .../server/wm/WindowManagerService.java | 29 +++++---- .../com/android/server/wm/WindowToken.java | 10 +++ .../server/wm/ActivityRecordTests.java | 43 ++++++++++++ 7 files changed, 170 insertions(+), 46 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ed9b019166575..3efa8eff48e7f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5112,26 +5112,48 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void startFreezingScreen() { + startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */); + } + + void startFreezingScreen(int overrideOriginalDisplayRotation) { ProtoLog.i(WM_DEBUG_ORIENTATION, "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s", appToken, isVisible(), mFreezingScreen, mVisibleRequested, new RuntimeException().fillInStackTrace()); - if (mVisibleRequested) { - if (!mFreezingScreen) { - mFreezingScreen = true; - mWmService.registerAppFreezeListener(this); - mWmService.mAppsFreezingScreen++; - if (mWmService.mAppsFreezingScreen == 1) { - mWmService.startFreezingDisplayLocked(0, 0, getDisplayContent()); - mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT); - mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000); + if (!mVisibleRequested) { + return; + } + + // If the override is given, the rotation of display doesn't change but we still want to + // cover the activity whose configuration is changing by freezing the display and running + // the rotation animation. + final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED; + if (!mFreezingScreen) { + mFreezingScreen = true; + mWmService.registerAppFreezeListener(this); + mWmService.mAppsFreezingScreen++; + if (mWmService.mAppsFreezingScreen == 1) { + if (forceRotation) { + // Make sure normal rotation animation will be applied. + mDisplayContent.getDisplayRotation().cancelSeamlessRotation(); } + mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, + mDisplayContent, overrideOriginalDisplayRotation); + mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT); + mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000); } - final int count = mChildren.size(); - for (int i = 0; i < count; i++) { - final WindowState w = mChildren.get(i); - w.onStartFreezingScreen(); - } + } + if (forceRotation) { + // The rotation of the real display won't change, so in order to unfreeze the screen + // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call + // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update + // the drawn state. + return; + } + final int count = mChildren.size(); + for (int i = 0; i < count; i++) { + final WindowState w = mChildren.get(i); + w.onStartFreezingScreen(); } } @@ -6145,6 +6167,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); } + @Override + void onCancelFixedRotationTransform(int originalDisplayRotation) { + if (this != mDisplayContent.getLastOrientationSource() + || getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED) { + // Only need to handle the activity that should be rotated with display. + return; + } + + // Perform rotation animation according to the rotation of this activity. + startFreezingScreen(originalDisplayRotation); + // This activity may relaunch or perform configuration change so once it has reported drawn, + // the screen can be unfrozen. + ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS); + } + void setRequestedOrientation(int requestedOrientation) { setOrientation(requestedOrientation, mayFreezeScreenLocked()); mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 80a1a4592ff3d..42025c8a16a09 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -494,6 +494,7 @@ class DisplayContent extends WindowContainer { + if (w.mSeamlesslyRotated) { + w.finishSeamlessRotation(false /* timeout */); + w.mSeamlesslyRotated = false; + } + }, true /* traverseTopToBottom */); + mSeamlessRotationCount = 0; + mRotatingSeamlessly = false; } private void prepareSeamlessRotation() { diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index b92ead1a05315..5f33ea170923d 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -41,7 +41,6 @@ import android.graphics.Rect; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import android.view.Surface.OutOfResourcesException; @@ -117,8 +116,9 @@ class ScreenRotationAnimation { private BlackFrame mEnteringBlackFrame; private int mWidth, mHeight; - private int mOriginalRotation; - private int mOriginalWidth, mOriginalHeight; + private final int mOriginalRotation; + private final int mOriginalWidth; + private final int mOriginalHeight; private int mCurRotation; private Rect mOriginalDisplayRect = new Rect(); @@ -140,20 +140,18 @@ class ScreenRotationAnimation { /** Intensity of light/whiteness of the layout after rotation occurs. */ private float mEndLuma; - public ScreenRotationAnimation(Context context, DisplayContent displayContent, - boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) { - mService = service; - mContext = context; + ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) { + mService = displayContent.mWmService; + mContext = mService.mContext; mDisplayContent = displayContent; displayContent.getBounds(mOriginalDisplayRect); // Screenshot does NOT include rotation! - final Display display = displayContent.getDisplay(); - int originalRotation = display.getRotation(); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int realOriginalRotation = displayInfo.rotation; final int originalWidth; final int originalHeight; - DisplayInfo displayInfo = displayContent.getDisplayInfo(); - if (fixedToUserRotation) { + if (displayContent.getDisplayRotation().isFixedToUserRotation()) { // Emulated orientation. mForceDefaultOrientation = true; originalWidth = displayContent.mBaseDisplayWidth; @@ -163,8 +161,8 @@ class ScreenRotationAnimation { originalWidth = displayInfo.logicalWidth; originalHeight = displayInfo.logicalHeight; } - if (originalRotation == Surface.ROTATION_90 - || originalRotation == Surface.ROTATION_270) { + if (realOriginalRotation == Surface.ROTATION_90 + || realOriginalRotation == Surface.ROTATION_270) { mWidth = originalHeight; mHeight = originalWidth; } else { @@ -173,10 +171,18 @@ class ScreenRotationAnimation { } mOriginalRotation = originalRotation; - mOriginalWidth = originalWidth; - mOriginalHeight = originalHeight; + // If the delta is not zero, the rotation of display may not change, but we still want to + // apply rotation animation because there should be a top app shown as rotated. So the + // specified original rotation customizes the direction of animation to have better look + // when restoring the rotated app to the same rotation as current display. + final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation); + final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270; + mOriginalWidth = flipped ? originalHeight : originalWidth; + mOriginalHeight = flipped ? originalWidth : originalHeight; mSurfaceRotationAnimationController = new SurfaceRotationAnimationController(); + // Check whether the current screen contains any secure content. + final boolean isSecure = displayContent.hasSecureWindowOnScreen(); final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { mBackColorSurface = displayContent.makeChildSurface(null) @@ -202,7 +208,7 @@ class ScreenRotationAnimation { t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. - final int displayId = display.getDisplayId(); + final int displayId = displayContent.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); surface.copyFrom(mScreenshotLayer); SurfaceControl.ScreenshotGraphicBuffer gb = @@ -242,7 +248,7 @@ class ScreenRotationAnimation { ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: CREATE", mScreenshotLayer); - setRotation(t, originalRotation); + setRotation(t, realOriginalRotation); t.apply(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9d879765f8df8..5c7d37baef371 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -29,6 +29,7 @@ import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.content.pm.PackageManager.FEATURE_PC; @@ -2941,7 +2942,7 @@ public class WindowManagerService extends IWindowManager.Stub mClientFreezingScreen = true; final long origId = Binder.clearCallingIdentity(); try { - startFreezingDisplayLocked(exitAnim, enterAnim); + startFreezingDisplay(exitAnim, enterAnim); mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000); } finally { @@ -5479,13 +5480,17 @@ public class WindowManagerService extends IWindowManager.Stub return changed; } - void startFreezingDisplayLocked(int exitAnim, int enterAnim) { - startFreezingDisplayLocked(exitAnim, enterAnim, - getDefaultDisplayContentLocked()); + void startFreezingDisplay(int exitAnim, int enterAnim) { + startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked()); } - void startFreezingDisplayLocked(int exitAnim, int enterAnim, - DisplayContent displayContent) { + void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) { + startFreezingDisplay(exitAnim, enterAnim, displayContent, + ROTATION_UNDEFINED /* overrideOriginalRotation */); + } + + void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent, + int overrideOriginalRotation) { if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) { return; } @@ -5529,14 +5534,12 @@ public class WindowManagerService extends IWindowManager.Stub screenRotationAnimation.kill(); } - // Check whether the current screen contains any secure content. - boolean isSecure = displayContent.hasSecureWindowOnScreen(); - displayContent.updateDisplayInfo(); - screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent, - displayContent.getDisplayRotation().isFixedToUserRotation(), isSecure, - this); - displayContent.setRotationAnimation(screenRotationAnimation); + final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED + ? overrideOriginalRotation + : displayContent.getDisplayInfo().rotation; + displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent, + originalRotation)); } void stopFreezingDisplayLocked() { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e34b81654c720..2d5c4c1c3b7c5 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -595,7 +595,17 @@ class WindowToken extends WindowContainer { // The window may be detached or detaching. return; } + final int originalRotation = getWindowConfiguration().getRotation(); onConfigurationChanged(parent.getConfiguration()); + onCancelFixedRotationTransform(originalRotation); + } + + /** + * It is called when the window is using fixed rotation transform, and before display applies + * the same rotation, the rotation change for display is canceled, e.g. the orientation from + * sensor is updated to previous direction. + */ + void onCancelFixedRotationTransform(int originalDisplayRotation) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 08f6409cb9022..3c111e21f7113 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -63,6 +63,7 @@ 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.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; @@ -1272,6 +1273,48 @@ public class ActivityRecordTests extends ActivityTestsBase { .diff(wpc.getRequestedOverrideConfiguration())); } + @Test + public void testActivityOnCancelFixedRotationTransform() { + mService.mWindowManager.mIsFixedRotationTransformEnabled = true; + final DisplayRotation displayRotation = mActivity.mDisplayContent.getDisplayRotation(); + spyOn(displayRotation); + + final DisplayContent display = mActivity.mDisplayContent; + final int originalRotation = display.getRotation(); + + // Make {@link DisplayContent#sendNewConfiguration} not apply rotation immediately. + doReturn(true).when(displayRotation).isWaitingForRemoteRotation(); + doReturn((originalRotation + 1) % 4).when(displayRotation).rotationForOrientation( + anyInt() /* orientation */, anyInt() /* lastRotation */); + // Set to visible so the activity can freeze the screen. + mActivity.setVisibility(true); + + display.rotateInDifferentOrientationIfNeeded(mActivity); + display.mFixedRotationLaunchingApp = mActivity; + displayRotation.updateRotationUnchecked(false /* forceUpdate */); + + assertTrue(displayRotation.isRotatingSeamlessly()); + + // Simulate the rotation has been updated to previous one, e.g. sensor updates before the + // remote rotation is completed. + doReturn(originalRotation).when(displayRotation).rotationForOrientation( + anyInt() /* orientation */, anyInt() /* lastRotation */); + display.updateOrientation(); + + final DisplayInfo rotatedInfo = mActivity.getFixedRotationTransformDisplayInfo(); + mActivity.finishFixedRotationTransform(); + final ScreenRotationAnimation rotationAnim = display.getRotationAnimation(); + rotationAnim.setRotation(display.getPendingTransaction(), originalRotation); + + // Because the display doesn't rotate, the rotated activity needs to cancel the fixed + // rotation. There should be a rotation animation to cover the change of activity. + verify(mActivity).onCancelFixedRotationTransform(rotatedInfo.rotation); + assertTrue(mActivity.isFreezingScreen()); + assertFalse(displayRotation.isRotatingSeamlessly()); + assertNotNull(rotationAnim); + assertTrue(rotationAnim.isRotating()); + } + @Test public void testActivityOnDifferentDisplayUpdatesProcessOverride() { final ActivityRecord secondaryDisplayActivity =