diff options
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 9c56788ad28f..93074f1d0ad7 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5125,26 +5125,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(); } } @@ -6159,6 +6181,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 8d46a6769bc8..36d558312c2f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -495,6 +495,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * The launching activity which is using fixed rotation transformation. * * @see #handleTopActivityLaunchingInDifferentOrientation + * @see DisplayRotation#shouldRotateSeamlessly */ ActivityRecord mFixedRotationLaunchingApp; @@ -1238,7 +1239,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (configChanged) { mWaitingForConfig = true; - mWmService.startFreezingDisplayLocked(0 /* exitAnim */, 0 /* enterAnim */, this); + mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this); sendNewConfiguration(); } @@ -1476,6 +1477,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo sendNewConfiguration(); return true; } + // 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 = null; return false; } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index bef80f0a230a..ebfe70c0c371 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -537,8 +537,29 @@ public class DisplayRotation { } void prepareNormalRotationAnimation() { + cancelSeamlessRotation(); final RotationAnimationPair anim = selectRotationAnimation(); - mService.startFreezingDisplayLocked(anim.mExit, anim.mEnter, mDisplayContent); + mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent); + } + + /** + * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was + * set by previous {@link #updateRotationUnchecked}, but another orientation change happens + * before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished) + * and it doesn't choose seamless rotation. + */ + void cancelSeamlessRotation() { + if (!mRotatingSeamlessly) { + return; + } + mDisplayContent.forAllWindows(w -> { + 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 b92ead1a0531..5f33ea170923 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 9d879765f8df..5c7d37baef37 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 e34b81654c72..2d5c4c1c3b7c 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<WindowState> { // 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 08f6409cb902..3c111e21f711 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; @@ -1273,6 +1274,48 @@ public class ActivityRecordTests extends ActivityTestsBase { } @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 = createActivityOnDisplay(false /* defaultDisplay */, null /* process */); |