summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Riddle Hsu <riddlehsu@google.com> 2020-04-06 20:48:06 +0800
committer Riddle Hsu <riddlehsu@google.com> 2020-04-13 18:08:36 +0800
commitd16620e8287da61d7fa0b7e157fd2f30f9253b55 (patch)
treeea3c4207b78ce76e6db79c253ba478a3365ddea5
parent13d870c8cd929958a647866c0140b1ea5a23c634 (diff)
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
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java65
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java23
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java40
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java43
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 ed9b01916657..3efa8eff48e7 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 80a1a4592ff3..42025c8a16a0 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<DisplayContent.DisplayChildWindowCo
* The launching activity which is using fixed rotation transformation.
*
* @see #handleTopActivityLaunchingInDifferentOrientation
+ * @see DisplayRotation#shouldRotateSeamlessly
*/
ActivityRecord mFixedRotationLaunchingApp;
@@ -1237,7 +1238,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();
}
@@ -1475,6 +1476,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 */);