Centralize rotation control to DisplayRotation
- Move seamless rotation logic. The scattered conditions
(mayRotateSeamlessly) are also combined into shouldRotateSeamlessly.
- Move DisplayContent#updateRotationUnchecked and the related fields.
- Consolidate DisplayContent#updateOrientationFromAppTokens and
DisplayRotation#setCurrentOrientation to DR#updateOrientation.
Bug: 117593656
Test: go/wm-smoke
Test: atest DisplayContentTests DisplayPolicyTests DisplayRotationTests
Change-Id: Ifd978a20a2773a39000a90edf683e6459adf0d2d
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index d4e95cf..6b2f9da 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -462,7 +462,7 @@
// new rotation animation after the old one finally finishes. It's better to defer the
// app transition.
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
- mService.getDefaultDisplayContentLocked().rotationNeedsUpdate()) {
+ mDisplayContent.getDisplayRotation().needsUpdate()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e851a06..71a0126 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -130,9 +130,7 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowManagerService.logSurface;
import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
@@ -148,6 +146,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -323,7 +322,7 @@
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final DisplayPolicy mDisplayPolicy;
- private DisplayRotation mDisplayRotation;
+ private final DisplayRotation mDisplayRotation;
DisplayFrames mDisplayFrames;
private final RemoteCallbackList<ISystemGestureExclusionListener>
@@ -352,22 +351,6 @@
float mCompatibleScreenScale;
/**
- * Current rotation of the display.
- * Constants as per {@link android.view.Surface.Rotation}.
- *
- * @see #updateRotationUnchecked()
- */
- private int mRotation = 0;
-
- /**
- * Last applied orientation of the display.
- * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}.
- *
- * @see #updateOrientationFromAppTokens()
- */
- private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
- /**
* Orientation forced by some window. If there is no visible window that specifies orientation
* it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
*
@@ -407,7 +390,6 @@
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
- int mDeferredRotationPauseCount;
/**
* Used to gate application window layout until we have sent the complete configuration.
@@ -1138,25 +1120,17 @@
return mInsetsStateController;
}
- @VisibleForTesting
- void setDisplayRotation(DisplayRotation displayRotation) {
- mDisplayRotation = displayRotation;
- }
-
+ @Surface.Rotation
int getRotation() {
- return mRotation;
+ return mDisplayRotation.getRotation();
}
- @VisibleForTesting
- void setRotation(int newRotation) {
- mRotation = newRotation;
- mDisplayRotation.setRotation(newRotation);
- }
-
+ @ScreenOrientation
int getLastOrientation() {
- return mLastOrientation;
+ return mDisplayRotation.getLastOrientation();
}
+ @ScreenOrientation
int getLastWindowForcedOrientation() {
return mLastWindowForcedOrientation;
}
@@ -1166,48 +1140,6 @@
}
/**
- * Temporarily pauses rotation changes until resumed.
- *
- * This can be used to prevent rotation changes from occurring while the user is
- * performing certain operations, such as drag and drop.
- *
- * This call nests and must be matched by an equal number of calls to
- * {@link #resumeRotationLocked}.
- */
- void pauseRotationLocked() {
- mDeferredRotationPauseCount++;
- }
-
- /**
- * Resumes normal rotation changes after being paused.
- */
- void resumeRotationLocked() {
- if (mDeferredRotationPauseCount <= 0) {
- return;
- }
-
- mDeferredRotationPauseCount--;
- if (mDeferredRotationPauseCount == 0) {
- updateRotationAndSendNewConfigIfNeeded();
- }
- }
-
- /**
- * If this is true we have updated our desired orientation, but not yet changed the real
- * orientation our applied our screen rotation animation. For example, because a previous
- * screen rotation was in progress.
- *
- * @return {@code true} if the there is an ongoing rotation change.
- */
- boolean rotationNeedsUpdate() {
- final int lastOrientation = getLastOrientation();
- final int oldRotation = getRotation();
-
- final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);
- return oldRotation != rotation;
- }
-
- /**
* The display content may have configuration set from {@link #DisplayWindowSettings}. This
* callback let the owner of container know there is existing configuration to prevent the
* values from being replaced by the initializing {@link #ActivityDisplay}.
@@ -1225,7 +1157,7 @@
configureDisplayPolicy();
setLayoutNeeded();
- boolean configChanged = updateOrientationFromAppTokens();
+ boolean configChanged = updateOrientation();
final Configuration currentDisplayConfig = getConfiguration();
mTmpConfiguration.setTo(currentDisplayConfig);
computeScreenConfiguration(mTmpConfiguration);
@@ -1263,8 +1195,8 @@
@Override
boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
ConfigurationContainer requestingContainer) {
- final Configuration config = updateOrientationFromAppTokens(
- getRequestedOverrideConfiguration(), freezeDisplayToken, false);
+ final Configuration config = updateOrientation(
+ getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */);
// If display rotation class tells us that it doesn't consider app requested orientation,
// this display won't rotate just because of an app changes its requested orientation. Thus
// it indicates that this display chooses not to handle this request.
@@ -1298,31 +1230,35 @@
/**
* Determine the new desired orientation of this display.
*
- * The orientation is computed from non-application windows first. If none of the
- * non-application windows specify orientation, the orientation is computed from application
- * tokens.
- *
- * @return {@code true} if the orientation is changed.
+ * @see #getOrientation()
+ * @return {@code true} if the orientation is changed and the caller should call
+ * {@link #sendNewConfiguration} if the method returns {@code true}.
*/
- boolean updateOrientationFromAppTokens() {
- return updateOrientationFromAppTokens(false /* forceUpdate */);
+ boolean updateOrientation() {
+ return mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */);
}
/**
- * Update orientation of the target display, returning a non-null new Configuration if it has
+ * Update orientation of the display, returning a non-null new Configuration if it has
* changed from the current orientation. If a non-null configuration is returned, someone must
* call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
* DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
- * be done by calling {@link WindowManagerService#sendNewConfiguration(int)}.
+ * be done by calling {@link #sendNewConfiguration}.
+ *
+ * @param currentConfig The current requested override configuration (it is usually set from
+ * the last {@link #sendNewConfiguration}) of the display. It is used to
+ * check if the configuration container has the latest state.
+ * @param freezeDisplayToken Freeze the app window token if the orientation is changed.
+ * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
*/
- Configuration updateOrientationFromAppTokens(Configuration currentConfig,
- IBinder freezeDisplayToken, boolean forceUpdate) {
+ Configuration updateOrientation(Configuration currentConfig, IBinder freezeDisplayToken,
+ boolean forceUpdate) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
- if (updateOrientationFromAppTokens(forceUpdate)) {
+ if (mDisplayRotation.updateOrientation(getOrientation(), forceUpdate)) {
// If we changed the orientation but mOrientationChangeComplete is already true,
// we used seamless rotation, and we don't need to freeze the screen.
if (freezeDisplayToken != null && !mWmService.mRoot.mOrientationChangeComplete) {
@@ -1345,10 +1281,7 @@
if (currentConfig.diff(mTmpConfiguration) != 0) {
mWaitingForConfig = true;
setLayoutNeeded();
- int[] anim = new int[2];
- getDisplayPolicy().selectRotationAnimationLw(anim);
-
- mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);
+ mDisplayRotation.prepareNormalRotationAnimation();
config = new Configuration(mTmpConfiguration);
}
}
@@ -1356,163 +1289,14 @@
return config;
}
-
- private boolean updateOrientationFromAppTokens(boolean forceUpdate) {
- final int req = getOrientation();
- if (req != mLastOrientation || forceUpdate) {
- mLastOrientation = req;
- mDisplayRotation.setCurrentOrientation(req);
- return updateRotationUnchecked(forceUpdate);
- }
- return false;
- }
-
- /**
- * Update rotation of the display and send configuration if the rotation is changed.
- *
- * @return {@code true} if the rotation has been changed and the new config is sent.
- */
- boolean updateRotationAndSendNewConfigIfNeeded() {
- final boolean changed = updateRotationUnchecked(false /* forceUpdate */);
- if (changed) {
- sendNewConfiguration();
- }
- return changed;
- }
-
/**
* Update rotation of the display.
*
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
- * {@link WindowManagerService#sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
+ * {@link #sendNewConfiguration} TO UNFREEZE THE SCREEN.
*/
boolean updateRotationUnchecked() {
- return updateRotationUnchecked(false /* forceUpdate */);
- }
-
- /**
- * Update rotation of the DisplayContent with an option to force the update. This updates
- * the container's perception of rotation and, depending on the top activities, will freeze
- * the screen or start seamless rotation. The display itself gets rotated in
- * {@link #applyRotationLocked} during {@link DisplayContent#sendNewConfiguration}.
- *
- * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
- * orientation because we're waiting for some rotation to finish or display
- * to unfreeze, which results in configuration of the previously visible
- * activity being applied to a newly visible one. Forcing the rotation
- * update allows to workaround this issue.
- * @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
- * {@link WindowManagerService#sendNewConfiguration(int)} TO COMPLETE THE ROTATION AND
- * UNFREEZE THE SCREEN.
- */
- boolean updateRotationUnchecked(boolean forceUpdate) {
- ScreenRotationAnimation screenRotationAnimation;
- if (!forceUpdate) {
- if (mDeferredRotationPauseCount > 0) {
- // Rotation updates have been paused temporarily. Defer the update until
- // updates have been resumed.
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
- return false;
- }
-
- screenRotationAnimation =
- mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
- // Rotation updates cannot be performed while the previous rotation change
- // animation is still in progress. Skip this update. We will try updating
- // again after the animation is finished and the display is unfrozen.
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
- return false;
- }
- if (mWmService.mDisplayFrozen) {
- // Even if the screen rotation animation has finished (e.g. isAnimating
- // returns false), there is still some time where we haven't yet unfrozen
- // the display. We also need to abort rotation here.
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Deferring rotation, still finishing previous rotation");
- return false;
- }
- }
-
- if (!mWmService.mDisplayEnabled) {
- // No point choosing a rotation if the display is not enabled.
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
- return false;
- }
-
- final int oldRotation = mRotation;
- final int lastOrientation = mLastOrientation;
- final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id="
- + mDisplayId + " based on lastOrientation=" + lastOrientation
- + " and oldRotation=" + oldRotation);
- boolean mayRotateSeamlessly = mDisplayPolicy.shouldRotateSeamlessly(mDisplayRotation,
- oldRotation, rotation);
-
- if (mayRotateSeamlessly) {
- final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
- if (seamlessRotated != null && !forceUpdate) {
- // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
- // to complete (that is, waiting for windows to redraw). It's tempting to check
- // w.mSeamlessRotationCount but that could be incorrect in the case of
- // window-removal.
- return false;
- }
-
- // In the presence of the PINNED stack or System Alert
- // windows we unfortunately can not seamlessly rotate.
- if (hasPinnedStack()) {
- mayRotateSeamlessly = false;
- }
- for (int i = 0; i < mWmService.mSessions.size(); i++) {
- if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
- mayRotateSeamlessly = false;
- break;
- }
- }
- }
- final boolean rotateSeamlessly = mayRotateSeamlessly;
-
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
- + " selected orientation " + lastOrientation
- + ", got rotation " + rotation);
-
- if (oldRotation == rotation) {
- // No change.
- return false;
- }
-
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
- + " rotation changed to " + rotation
- + " from " + oldRotation
- + ", lastOrientation=" + lastOrientation);
-
- if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
- mWaitingForConfig = true;
- }
-
- mRotation = rotation;
-
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
- this, WINDOW_FREEZE_TIMEOUT_DURATION);
-
- setLayoutNeeded();
- final int[] anim = new int[2];
- mDisplayPolicy.selectRotationAnimationLw(anim);
-
- if (!rotateSeamlessly) {
- mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);
- // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
- } else {
- // The screen rotation animation uses a screenshot to freeze the screen
- // while windows resize underneath.
- // When we are rotating seamlessly, we allow the elements to transition
- // to their rotated state independently and without a freeze required.
- mWmService.startSeamlessRotation();
- }
-
- return true;
+ return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
}
/**
@@ -1523,10 +1307,11 @@
* @param rotation the rotation to apply.
*/
void applyRotationLocked(final int oldRotation, final int rotation) {
- mDisplayRotation.setRotation(rotation);
- final boolean rotateSeamlessly = mWmService.isRotatingSeamlessly();
- ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
+ mDisplayRotation.applyCurrentRotation(rotation);
+ final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
+ final ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
? null : mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+ final Transaction transaction = getPendingTransaction();
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -1537,15 +1322,14 @@
// NOTE: We disable the rotation in the emulator because
// it doesn't support hardware OpenGL emulation yet.
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- screenRotationAnimation.setRotation(getPendingTransaction(), rotation);
+ screenRotationAnimation.setRotation(transaction, rotation);
}
forAllWindows(w -> {
- w.seamlesslyRotateIfAllowed(getPendingTransaction(), oldRotation, rotation,
- rotateSeamlessly);
+ w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */);
- mWmService.mDisplayManagerInternal.performTraversal(getPendingTransaction());
+ mWmService.mDisplayManagerInternal.performTraversal(transaction);
scheduleAnimation();
forAllWindows(w -> {
@@ -1627,19 +1411,20 @@
*/
private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
// Use the effective "visual" dimensions based on current rotation
- final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270);
+ final int rotation = getRotation();
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
// Update application display metrics.
- final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(mRotation);
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
- final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
+ final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
- final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
+ final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
displayCutout);
- mDisplayInfo.rotation = mRotation;
+ mDisplayInfo.rotation = rotation;
mDisplayInfo.logicalWidth = dw;
mDisplayInfo.logicalHeight = dh;
mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
@@ -2044,7 +1829,7 @@
return mTaskStackContainers.getPinnedStack();
}
- private boolean hasPinnedStack() {
+ boolean hasPinnedStack() {
return mTaskStackContainers.getPinnedStack() != null;
}
@@ -2198,6 +1983,11 @@
return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
}
+ /**
+ * In the general case, the orientation is computed from the above app windows first. If none of
+ * the above app windows specify orientation, the orientation is computed from the child window
+ * container, e.g. {@link AppWindowToken#getOrientation(int)}.
+ */
@Override
int getOrientation() {
final WindowManagerPolicy policy = mWmService.mPolicy;
@@ -2222,8 +2012,8 @@
// things aren't stable while the display is frozen, for example the window could be
// momentarily unavailable due to activity relaunch.
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
- + " is frozen while keyguard locked, return " + mLastOrientation);
- return mLastOrientation;
+ + " is frozen while keyguard locked, return " + getLastOrientation());
+ return getLastOrientation();
}
} else {
final int orientation = mAboveAppWindowsContainers.getOrientation();
@@ -2835,7 +2625,7 @@
}
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
- proto.write(ROTATION, mRotation);
+ proto.write(ROTATION, getRotation());
final ScreenRotationAnimation screenRotationAnimation =
mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
if (screenRotationAnimation != null) {
@@ -2892,8 +2682,6 @@
pw.println();
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
- pw.print(prefix);
- pw.print("mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
if (mLastFocus != mCurrentFocus) {
@@ -3264,6 +3052,15 @@
return mTmpWindow != null;
}
+ boolean hasAlertWindowSurfaces() {
+ for (int i = mWmService.mSessions.size() - 1; i >= 0; --i) {
+ if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces(this)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Set input method window for the display.
* @param win Set when window added or Null when destroyed.
@@ -3747,7 +3544,7 @@
if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
- if (updateOrientationFromAppTokens()) {
+ if (updateOrientation()) {
setLayoutNeeded();
sendNewConfiguration();
}
@@ -3887,7 +3684,7 @@
calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
// TODO: Not sure if we really need to set the rotation here since we are updating from
// the display info above...
- mDisplayFrames.mRotation = mRotation;
+ mDisplayFrames.mRotation = getRotation();
mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
int seq = mLayoutSeq + 1;
@@ -4002,25 +3799,6 @@
}
}
- void onSeamlessRotationTimeout() {
- // Used to indicate the layout is needed.
- mTmpWindow = null;
-
- forAllWindows(w -> {
- if (!w.mSeamlesslyRotated) {
- return;
- }
- mTmpWindow = w;
- w.setDisplayLayoutNeeded();
- w.finishSeamlessRotation(true /* timeout */);
- mWmService.markForSeamlessRotation(w, false);
- }, true /* traverseTopToBottom */);
-
- if (mTmpWindow != null) {
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
- }
-
void setExitingTokensHasVisible(boolean hasVisible) {
for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
mExitingTokens.get(i).hasVisible = hasVisible;
@@ -4502,11 +4280,11 @@
}
if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "No app is requesting an orientation, return " + mLastOrientation
+ "No app is requesting an orientation, return " + getLastOrientation()
+ " for display id=" + mDisplayId);
// The next app has not been requested to be visible, so we keep the current orientation
// to prevent freezing/unfreezing the display too early.
- return mLastOrientation;
+ return getLastOrientation();
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e5962ae..8328770 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,10 +57,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -257,7 +253,6 @@
private volatile boolean mNavigationBarCanMove;
private volatile boolean mNavigationBarLetsThroughTaps;
private volatile boolean mNavigationBarAlwaysShowOnSideGesture;
- private volatile boolean mAllowSeamlessRotationDespiteNavBarMoving;
// Written by vr manager thread, only read in this class.
private volatile boolean mPersistentVrModeEnabled;
@@ -1151,81 +1146,6 @@
}
/**
- * Determine the animation to run for a rotation transition based on the
- * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
- * and whether it is currently fullscreen and frontmost.
- *
- * @param anim The exiting animation resource id is stored in anim[0], the
- * entering animation resource id is stored in anim[1].
- */
- public void selectRotationAnimationLw(int anim[]) {
- // If the screen is off or non-interactive, force a jumpcut.
- final boolean forceJumpcut = !mScreenOnFully || !mService.mPolicy.okToAnimate();
- if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation mTopFullscreen="
- + mTopFullscreenOpaqueWindowState + " rotationAnimation="
- + (mTopFullscreenOpaqueWindowState == null
- ? "0" : mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation)
- + " forceJumpcut=" + forceJumpcut);
- if (forceJumpcut) {
- anim[0] = R.anim.rotation_animation_jump_exit;
- anim[1] = R.anim.rotation_animation_enter;
- return;
- }
- if (mTopFullscreenOpaqueWindowState != null) {
- int animationHint = mTopFullscreenOpaqueWindowState.getRotationAnimationHint();
- if (animationHint < 0 && mTopIsFullscreen) {
- animationHint = mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation;
- }
- switch (animationHint) {
- case ROTATION_ANIMATION_CROSSFADE:
- case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
- anim[0] = R.anim.rotation_animation_xfade_exit;
- anim[1] = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_JUMPCUT:
- anim[0] = R.anim.rotation_animation_jump_exit;
- anim[1] = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_ROTATE:
- default:
- anim[0] = anim[1] = 0;
- break;
- }
- } else {
- anim[0] = anim[1] = 0;
- }
- }
-
- /**
- * Validate whether the current top fullscreen has specified the same
- * {@link WindowManager.LayoutParams#rotationAnimation} value as that
- * being passed in from the previous top fullscreen window.
- *
- * @param exitAnimId exiting resource id from the previous window.
- * @param enterAnimId entering resource id from the previous window.
- * @param forceDefault For rotation animations only, if true ignore the
- * animation values and just return false.
- * @return true if the previous values are still valid, false if they
- * should be replaced with the default.
- */
- public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
- boolean forceDefault) {
- switch (exitAnimId) {
- case R.anim.rotation_animation_xfade_exit:
- case R.anim.rotation_animation_jump_exit:
- // These are the only cases that matter.
- if (forceDefault) {
- return false;
- }
- int anim[] = new int[2];
- selectRotationAnimationLw(anim);
- return (exitAnimId == anim[0] && enterAnimId == anim[1]);
- default:
- return true;
- }
- }
-
- /**
* Called when a new system UI visibility is being reported, allowing
* the policy to adjust what is actually reported.
* @param visibility The raw visibility reported by the status bar.
@@ -2388,6 +2308,14 @@
displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
}
+ WindowState getTopFullscreenOpaqueWindow() {
+ return mTopFullscreenOpaqueWindowState;
+ }
+
+ boolean isTopLayoutFullscreen() {
+ return mTopIsFullscreen;
+ }
+
/**
* Called following layout of all windows before each window has policy applied.
*/
@@ -2773,8 +2701,7 @@
mNavigationBarCanMove =
mDisplayContent.mBaseDisplayWidth != mDisplayContent.mBaseDisplayHeight
&& res.getBoolean(R.bool.config_navBarCanMove);
- mAllowSeamlessRotationDespiteNavBarMoving =
- res.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+ mDisplayContent.getDisplayRotation().updateUserDependentConfiguration(res);
}
/**
@@ -3562,45 +3489,6 @@
return (systemUiFlags & disableNavigationBar) == disableNavigationBar;
}
- boolean shouldRotateSeamlessly(DisplayRotation displayRotation, int oldRotation,
- int newRotation) {
- // For the upside down rotation we don't rotate seamlessly as the navigation
- // bar moves position.
- // Note most apps (using orientation:sensor or user as opposed to fullSensor)
- // will not enter the reverse portrait orientation, so actually the
- // orientation won't change at all.
- if (oldRotation == displayRotation.getUpsideDownRotation()
- || newRotation == displayRotation.getUpsideDownRotation()) {
- return false;
- }
- // If the navigation bar can't change sides, then it will
- // jump when we change orientations and we don't rotate
- // seamlessly - unless that is allowed, eg. with gesture
- // navigation where the navbar is low-profile enough that this isn't very noticeable.
- if (!navigationBarCanMove() && !mAllowSeamlessRotationDespiteNavBarMoving) {
- return false;
- }
-
- final WindowState w = mTopFullscreenOpaqueWindowState;
- if (w == null || w != mFocusedWindow) {
- return false;
- }
- // If the bounds of activity window is different from its parent, then reject to be seamless
- // because the window position may change after rotation that will look like a sudden jump.
- if (w.mAppToken != null && !w.mAppToken.matchParentBounds()) {
- return false;
- }
-
- // We only enable seamless rotation if the top window has requested
- // it and is in the fullscreen opaque state. Seamless rotation
- // requires freezing various Surface states and won't work well
- // with animations, so we disable it in the animation case for now.
- if (!w.isAnimatingLw() && w.getAttrs().rotationAnimation == ROTATION_ANIMATION_SEAMLESS) {
- return true;
- }
- return false;
- }
-
private final Runnable mHiddenNavPanic = new Runnable() {
@Override
public void run() {
@@ -3665,7 +3553,7 @@
}
void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("DisplayPolicy");
+ pw.print(prefix); pw.println("DisplayPolicy");
prefix += " ";
pw.print(prefix);
pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
@@ -3724,12 +3612,12 @@
pw.print(prefix); pw.print("mForcingShowNavBarLayer=");
pw.println(mForcingShowNavBarLayer);
}
- pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
+ pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
pw.print(" mForceStatusBarFromKeyguard="); pw.println(mForceStatusBarFromKeyguard);
- pw.print(" mForceShowSystemBarsFromExternal=");
- pw.println(mForceShowSystemBarsFromExternal);
- pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
+ pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal=");
+ pw.print(mForceShowSystemBarsFromExternal);
+ pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
mStatusBarController.dump(pw, prefix);
mNavigationBarController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 410cc94..4d188f4 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,11 +16,21 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
+import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
+import android.annotation.AnimRes;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -28,6 +38,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -41,6 +52,7 @@
import android.util.SparseArray;
import android.view.Surface;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.UiThread;
@@ -59,6 +71,13 @@
public class DisplayRotation {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
+ private static class RotationAnimationPair {
+ @AnimRes
+ int mEnter;
+ @AnimRes
+ int mExit;
+ }
+
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mDisplayPolicy;
@@ -72,12 +91,30 @@
private final int mCarDockRotation;
private final int mDeskDockRotation;
private final int mUndockedHdmiRotation;
+ private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
private OrientationListener mOrientationListener;
private StatusBarManagerInternal mStatusBarManagerInternal;
private SettingsObserver mSettingsObserver;
- private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ @ScreenOrientation
+ private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /**
+ * Last applied orientation of the display.
+ *
+ * @see #updateOrientationFromApp
+ */
+ @ScreenOrientation
+ private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /**
+ * Current rotation of the display.
+ *
+ * @see #updateRotationUnchecked
+ */
+ @Surface.Rotation
+ private int mRotation;
@VisibleForTesting
int mLandscapeRotation; // default landscape
@@ -88,11 +125,52 @@
@VisibleForTesting
int mUpsideDownRotation; // "other" portrait
- // Behavior of rotation suggestions. (See Settings.Secure.SHOW_ROTATION_SUGGESTION)
+ private boolean mAllowSeamlessRotationDespiteNavBarMoving;
+
+ private int mDeferredRotationPauseCount;
+
+ /**
+ * A count of the windows which are 'seamlessly rotated', e.g. a surface at an old orientation
+ * is being transformed. We freeze orientation updates while any windows are seamlessly rotated,
+ * so we need to track when this hits zero so we can apply deferred orientation updates.
+ */
+ private int mSeamlessRotationCount;
+
+ /**
+ * True in the interval from starting seamless rotation until the last rotated window draws in
+ * the new orientation.
+ */
+ private boolean mRotatingSeamlessly;
+
+ /**
+ * Behavior of rotation suggestions.
+ *
+ * @see Settings.Secure#SHOW_ROTATION_SUGGESTIONS
+ */
private int mShowRotationSuggestions;
- private int mAllowAllRotations = -1;
+ private static final int ALLOW_ALL_ROTATIONS_UNDEFINED = -1;
+ private static final int ALLOW_ALL_ROTATIONS_DISABLED = 0;
+ private static final int ALLOW_ALL_ROTATIONS_ENABLED = 1;
+
+ @IntDef({ ALLOW_ALL_ROTATIONS_UNDEFINED, ALLOW_ALL_ROTATIONS_DISABLED,
+ ALLOW_ALL_ROTATIONS_ENABLED })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface AllowAllRotations {}
+
+ /**
+ * Whether to allow the screen to rotate to all rotations (including 180 degree) according to
+ * the sensor even when the current orientation is not
+ * {@link ActivityInfo#SCREEN_ORIENTATION_FULL_SENSOR} or
+ * {@link ActivityInfo#SCREEN_ORIENTATION_FULL_USER}.
+ */
+ @AllowAllRotations
+ private int mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
+
+ @WindowManagerPolicy.UserRotationMode
private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+
+ @Surface.Rotation
private int mUserRotation = Surface.ROTATION_0;
/**
@@ -125,6 +203,7 @@
* regardless of all other states (including app requrested orientation). {@code true} the
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
*/
+ @FixedToUserRotation
private int mFixedToUserRotation = FIXED_TO_USER_ROTATION_DEFAULT;
private int mDemoHdmiRotation;
@@ -149,21 +228,17 @@
mLock = lock;
isDefaultDisplay = displayContent.isDefaultDisplay;
- mSupportAutoRotation = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_supportAutoRotation);
- mLidOpenRotation = readRotation(
- com.android.internal.R.integer.config_lidOpenRotation);
- mCarDockRotation = readRotation(
- com.android.internal.R.integer.config_carDockRotation);
- mDeskDockRotation = readRotation(
- com.android.internal.R.integer.config_deskDockRotation);
- mUndockedHdmiRotation = readRotation(
- com.android.internal.R.integer.config_undockedHdmiRotation);
+ mSupportAutoRotation =
+ mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
+ mLidOpenRotation = readRotation(R.integer.config_lidOpenRotation);
+ mCarDockRotation = readRotation(R.integer.config_carDockRotation);
+ mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
+ mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener = new OrientationListener(mContext, uiHandler);
- mOrientationListener.setCurrentRotation(displayContent.getRotation());
+ mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
}
@@ -188,12 +263,21 @@
return -1;
}
+ /**
+ * Updates the configuration which may have different values depending on current user, e.g.
+ * runtime resource overlay.
+ */
+ void updateUserDependentConfiguration(Resources currentUserRes) {
+ mAllowSeamlessRotationDespiteNavBarMoving =
+ currentUserRes.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+ }
+
void configure(int width, int height, int shortSizeDp, int longSizeDp) {
final Resources res = mContext.getResources();
if (width > height) {
mLandscapeRotation = Surface.ROTATION_0;
mSeascapeRotation = Surface.ROTATION_180;
- if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ if (res.getBoolean(R.bool.config_reverseDefaultRotation)) {
mPortraitRotation = Surface.ROTATION_90;
mUpsideDownRotation = Surface.ROTATION_270;
} else {
@@ -203,7 +287,7 @@
} else {
mPortraitRotation = Surface.ROTATION_0;
mUpsideDownRotation = Surface.ROTATION_180;
- if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ if (res.getBoolean(R.bool.config_reverseDefaultRotation)) {
mLandscapeRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_90;
} else {
@@ -246,19 +330,325 @@
&& !"true".equals(SystemProperties.get("config.override_forced_orient"));
}
- void setRotation(int rotation) {
+ void applyCurrentRotation(@Surface.Rotation int rotation) {
if (mOrientationListener != null) {
mOrientationListener.setCurrentRotation(rotation);
}
}
- void setCurrentOrientation(int newOrientation) {
+ @VisibleForTesting
+ void setRotation(@Surface.Rotation int rotation) {
+ mRotation = rotation;
+ }
+
+ @Surface.Rotation
+ int getRotation() {
+ return mRotation;
+ }
+
+ @ScreenOrientation
+ int getLastOrientation() {
+ return mLastOrientation;
+ }
+
+ boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {
+ if (newOrientation == mLastOrientation && !forceUpdate) {
+ return false;
+ }
+ mLastOrientation = newOrientation;
if (newOrientation != mCurrentAppOrientation) {
mCurrentAppOrientation = newOrientation;
if (isDefaultDisplay) {
updateOrientationListenerLw();
}
}
+ return updateRotationUnchecked(forceUpdate);
+ }
+
+ /**
+ * Update rotation of the display and send configuration if the rotation is changed.
+ *
+ * @return {@code true} if the rotation has been changed and the new config is sent.
+ */
+ boolean updateRotationAndSendNewConfigIfChanged() {
+ final boolean changed = updateRotationUnchecked(false /* forceUpdate */);
+ if (changed) {
+ mDisplayContent.sendNewConfiguration();
+ }
+ return changed;
+ }
+
+ /**
+ * Update rotation with an option to force the update. This updates the container's perception
+ * of rotation and, depending on the top activities, will freeze the screen or start seamless
+ * rotation. The display itself gets rotated in {@link DisplayContent#applyRotationLocked}
+ * during {@link DisplayContent#sendNewConfiguration}.
+ *
+ * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
+ * orientation because we're waiting for some rotation to finish or display
+ * to unfreeze, which results in configuration of the previously visible
+ * activity being applied to a newly visible one. Forcing the rotation
+ * update allows to workaround this issue.
+ * @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
+ * {@link DisplayContent#sendNewConfiguration} TO COMPLETE THE ROTATION AND UNFREEZE
+ * THE SCREEN.
+ */
+ boolean updateRotationUnchecked(boolean forceUpdate) {
+ final int displayId = mDisplayContent.getDisplayId();
+ if (!forceUpdate) {
+ if (mDeferredRotationPauseCount > 0) {
+ // Rotation updates have been paused temporarily. Defer the update until updates
+ // have been resumed.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
+ return false;
+ }
+
+ final ScreenRotationAnimation screenRotationAnimation =
+ mService.mAnimator.getScreenRotationAnimationLocked(displayId);
+ if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+ // Rotation updates cannot be performed while the previous rotation change animation
+ // is still in progress. Skip this update. We will try updating again after the
+ // animation is finished and the display is unfrozen.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
+ return false;
+ }
+ if (mService.mDisplayFrozen) {
+ // Even if the screen rotation animation has finished (e.g. isAnimating returns
+ // false), there is still some time where we haven't yet unfrozen the display. We
+ // also need to abort rotation here.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "Deferring rotation, still finishing previous rotation");
+ return false;
+ }
+ }
+
+ if (!mService.mDisplayEnabled) {
+ // No point choosing a rotation if the display is not enabled.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
+ return false;
+ }
+
+ final int oldRotation = mRotation;
+ final int lastOrientation = mLastOrientation;
+ final int rotation = rotationForOrientation(lastOrientation, oldRotation);
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id="
+ + displayId + " based on lastOrientation=" + lastOrientation
+ + " and oldRotation=" + oldRotation);
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + displayId
+ + " selected orientation " + lastOrientation
+ + ", got rotation " + rotation);
+
+ if (oldRotation == rotation) {
+ // No change.
+ return false;
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + displayId
+ + " rotation changed to " + rotation
+ + " from " + oldRotation
+ + ", lastOrientation=" + lastOrientation);
+
+ if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
+ mDisplayContent.mWaitingForConfig = true;
+ }
+
+ mRotation = rotation;
+
+ mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
+ mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
+ mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
+
+ mDisplayContent.setLayoutNeeded();
+
+ if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
+ // The screen rotation animation uses a screenshot to freeze the screen while windows
+ // resize underneath. When we are rotating seamlessly, we allow the elements to
+ // transition to their rotated state independently and without a freeze required.
+ prepareSeamlessRotation();
+ } else {
+ prepareNormalRotationAnimation();
+ }
+
+ return true;
+ }
+
+ void prepareNormalRotationAnimation() {
+ final RotationAnimationPair anim = selectRotationAnimation();
+ mService.startFreezingDisplayLocked(anim.mExit, anim.mEnter, mDisplayContent);
+ }
+
+ private void prepareSeamlessRotation() {
+ // We are careful to reset this in case a window was removed before it finished
+ // seamless rotation.
+ mSeamlessRotationCount = 0;
+ mRotatingSeamlessly = true;
+ }
+
+ boolean isRotatingSeamlessly() {
+ return mRotatingSeamlessly;
+ }
+
+ @VisibleForTesting
+ boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
+ final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
+ if (w == null || w != mDisplayContent.mCurrentFocus) {
+ return false;
+ }
+ // We only enable seamless rotation if the top window has requested it and is in the
+ // fullscreen opaque state. Seamless rotation requires freezing various Surface states and
+ // won't work well with animations, so we disable it in the animation case for now.
+ if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
+ return false;
+ }
+
+ // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
+ // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
+ // will not enter the reverse portrait orientation, so actually the orientation won't change
+ // at all.
+ if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
+ return false;
+ }
+
+ // If the navigation bar can't change sides, then it will jump when we change orientations
+ // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation
+ // where the navbar is low-profile enough that this isn't very noticeable.
+ if (!mAllowSeamlessRotationDespiteNavBarMoving && !mDisplayPolicy.navigationBarCanMove()) {
+ return false;
+ }
+
+ // If the bounds of activity window is different from its parent, then reject to be seamless
+ // because the window position may change after rotation that will look like a sudden jump.
+ if (w.mAppToken != null && !w.mAppToken.matchParentBounds()) {
+ return false;
+ }
+
+ // In the presence of the PINNED stack or System Alert windows we unfortunately can not
+ // seamlessly rotate.
+ if (mDisplayContent.hasPinnedStack() || mDisplayContent.hasAlertWindowSurfaces()) {
+ return false;
+ }
+
+ // We can't rotate (seamlessly or not) while waiting for the last seamless rotation to
+ // complete (that is, waiting for windows to redraw). It's tempting to check
+ // mSeamlessRotationCount but that could be incorrect in the case of window-removal.
+ if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
+ if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
+ return;
+ }
+
+ w.mSeamlesslyRotated = seamlesslyRotated;
+ if (seamlesslyRotated) {
+ mSeamlessRotationCount++;
+ } else {
+ mSeamlessRotationCount--;
+ }
+ if (mSeamlessRotationCount == 0) {
+ if (DEBUG_ORIENTATION) {
+ Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
+ }
+ // Finish seamless rotation.
+ mRotatingSeamlessly = false;
+
+ updateRotationAndSendNewConfigIfChanged();
+ }
+ }
+
+ void onSeamlessRotationTimeout() {
+ final boolean[] isLayoutNeeded = { false };
+
+ mDisplayContent.forAllWindows(w -> {
+ if (!w.mSeamlesslyRotated) {
+ return;
+ }
+ isLayoutNeeded[0] = true;
+ w.setDisplayLayoutNeeded();
+ w.finishSeamlessRotation(true /* timeout */);
+ markForSeamlessRotation(w, false /* seamlesslyRotated */);
+ }, true /* traverseTopToBottom */);
+
+ if (isLayoutNeeded[0]) {
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ }
+
+ /**
+ * Returns the animation to run for a rotation transition based on the top fullscreen windows
+ * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
+ * fullscreen and frontmost.
+ */
+ private RotationAnimationPair selectRotationAnimation() {
+ // If the screen is off or non-interactive, force a jumpcut.
+ final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
+ || !mService.mPolicy.okToAnimate();
+ final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
+ if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation topFullscreen="
+ + topFullscreen + " rotationAnimation="
+ + (topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation)
+ + " forceJumpcut=" + forceJumpcut);
+ if (forceJumpcut) {
+ mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
+ mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+ return mTmpRotationAnim;
+ }
+ if (topFullscreen != null) {
+ int animationHint = topFullscreen.getRotationAnimationHint();
+ if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
+ animationHint = topFullscreen.getAttrs().rotationAnimation;
+ }
+ switch (animationHint) {
+ case ROTATION_ANIMATION_CROSSFADE:
+ case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
+ mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
+ mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+ break;
+ case ROTATION_ANIMATION_JUMPCUT:
+ mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
+ mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+ break;
+ case ROTATION_ANIMATION_ROTATE:
+ default:
+ mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
+ break;
+ }
+ } else {
+ mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
+ }
+ return mTmpRotationAnim;
+ }
+
+ /**
+ * Validate whether the current top fullscreen has specified the same
+ * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
+ * in from the previous top fullscreen window.
+ *
+ * @param exitAnimId exiting resource id from the previous window.
+ * @param enterAnimId entering resource id from the previous window.
+ * @param forceDefault For rotation animations only, if true ignore the animation values and
+ * just return false.
+ * @return {@code true} if the previous values are still valid, false if they should be replaced
+ * with the default.
+ */
+ boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
+ switch (exitAnimId) {
+ case R.anim.rotation_animation_xfade_exit:
+ case R.anim.rotation_animation_jump_exit:
+ // These are the only cases that matter.
+ if (forceDefault) {
+ return false;
+ }
+ final RotationAnimationPair anim = selectRotationAnimation();
+ return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
+ default:
+ return true;
+ }
}
void restoreSettings(int userRotationMode, int userRotation,
@@ -327,7 +717,7 @@
}
void freezeRotation(int rotation) {
- rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
+ rotation = (rotation == -1) ? mRotation : rotation;
setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
}
@@ -407,6 +797,30 @@
}
/**
+ * Temporarily pauses rotation changes until resumed.
+ * <p>
+ * This can be used to prevent rotation changes from occurring while the user is performing
+ * certain operations, such as drag and drop.
+ * <p>
+ * This call nests and must be matched by an equal number of calls to {@link #resume}.
+ */
+ void pause() {
+ mDeferredRotationPauseCount++;
+ }
+
+ /** Resumes normal rotation changes after being paused. */
+ void resume() {
+ if (mDeferredRotationPauseCount <= 0) {
+ return;
+ }
+
+ mDeferredRotationPauseCount--;
+ if (mDeferredRotationPauseCount == 0) {
+ updateRotationAndSendNewConfigIfChanged();
+ }
+ }
+
+ /**
* Various use cases for invoking this function:
* <li>Screen turning off, should always disable listeners if already enabled.</li>
* <li>Screen turned on and current app has sensor based orientation, enable listeners
@@ -515,14 +929,28 @@
}
/**
- * Given an orientation constant, returns the appropriate surface rotation,
- * taking into account sensors, docking mode, rotation lock, and other factors.
+ * If this is true we have updated our desired orientation, but not yet changed the real
+ * orientation our applied our screen rotation animation. For example, because a previous
+ * screen rotation was in progress.
+ *
+ * @return {@code true} if the there is an ongoing rotation change.
+ */
+ boolean needsUpdate() {
+ final int oldRotation = mRotation;
+ final int rotation = rotationForOrientation(mLastOrientation, oldRotation);
+ return oldRotation != rotation;
+ }
+
+ /**
+ * Given an orientation constant, returns the appropriate surface rotation, taking into account
+ * sensors, docking mode, rotation lock, and other factors.
*
* @param orientation An orientation constant, such as
- * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * {@link ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
* @param lastRotation The most recently used rotation.
* @return The surface rotation to use.
*/
+ @VisibleForTesting
int rotationForOrientation(int orientation, int lastRotation) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "rotationForOrientation(orient="
@@ -614,15 +1042,16 @@
|| orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
// Otherwise, use sensor only if requested by the application or enabled
// by default for USER or UNSPECIFIED modes. Does not apply to NOSENSOR.
- if (mAllowAllRotations < 0) {
- // Can't read this during init() because the context doesn't
- // have display metrics at that time so we cannot determine
- // tablet vs. phone then.
+ if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
+ // Can't read this during init() because the context doesn't have display metrics at
+ // that time so we cannot determine tablet vs. phone then.
mAllowAllRotations = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0;
+ R.bool.config_allowAllRotations)
+ ? ALLOW_ALL_ROTATIONS_ENABLED
+ : ALLOW_ALL_ROTATIONS_DISABLED;
}
if (sensorRotation != Surface.ROTATION_180
- || mAllowAllRotations == 1
+ || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
preferredRotation = sensorRotation;
@@ -883,6 +1312,10 @@
pw.println(prefix + "DisplayRotation");
pw.println(prefix + " mCurrentAppOrientation="
+ ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
+ pw.println(prefix + " mLastOrientation=" + mLastOrientation);
+ pw.print(prefix + " mRotation=" + mRotation);
+ pw.println(" mDeferredRotationPauseCount=" + mDeferredRotationPauseCount);
+
pw.print(prefix + " mLandscapeRotation=" + Surface.rotationToString(mLandscapeRotation));
pw.println(" mSeascapeRotation=" + Surface.rotationToString(mSeascapeRotation));
pw.print(prefix + " mPortraitRotation=" + Surface.rotationToString(mPortraitRotation));
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c8f7af5..c48f07c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -305,7 +305,7 @@
if (DEBUG_ORIENTATION) {
Slog.d(TAG_WM, "Pausing rotation during drag");
}
- mDisplayContent.pauseRotationLocked();
+ mDisplayContent.getDisplayRotation().pause();
}
void tearDown() {
@@ -324,7 +324,7 @@
if (DEBUG_ORIENTATION) {
Slog.d(TAG_WM, "Resuming rotation after drag");
}
- mDisplayContent.resumeRotationLocked();
+ mDisplayContent.getDisplayRotation().resume();
}
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 3401de6..eb5d096 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -636,7 +636,7 @@
final DisplayContent displayContent = mRootWindowContainer.getDisplayContent(displayId);
Configuration config = null;
if (displayContent != null) {
- config = displayContent.updateOrientationFromAppTokens(
+ config = displayContent.updateOrientation(
getDisplayOverrideConfiguration(displayId),
starting != null && starting.mayFreezeScreenLocked(starting.app)
? starting.appToken : null,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a00bee0..968d02b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -925,8 +925,7 @@
boolean updateRotationUnchecked() {
boolean changed = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final DisplayContent displayContent = mChildren.get(i);
- if (displayContent.updateRotationAndSendNewConfigIfNeeded()) {
+ if (mChildren.get(i).getDisplayRotation().updateRotationAndSendNewConfigIfChanged()) {
changed = true;
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9f8f265..4a76042 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -40,6 +40,7 @@
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.view.DisplayCutout;
@@ -57,9 +58,7 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.function.BiConsumer;
/**
@@ -75,9 +74,9 @@
SurfaceSession mSurfaceSession;
private int mNumWindow = 0;
// Set of visible application overlay window surfaces connected to this session.
- private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
+ private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
// Set of visible alert window surfaces connected to this session.
- private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+ private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
final boolean mCanHideNonSystemOverlayWindows;
@@ -609,8 +608,15 @@
return mStringName;
}
- boolean hasAlertWindowSurfaces() {
- return !mAlertWindowSurfaces.isEmpty();
+ /** @return {@code true} if there is an alert window surface on the given display. */
+ boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
+ for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
+ final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
+ if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ return true;
+ }
+ }
+ return false;
}
public void blessInputSurface(int displayId, SurfaceControl surface,
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 2fc64ea..8b0b6ce 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -304,7 +304,7 @@
if (DEBUG_ORIENTATION) {
Slog.d(TAG, "Pausing rotation during re-position");
}
- mDisplayContent.pauseRotationLocked();
+ mDisplayContent.getDisplayRotation().pause();
// Notify InputMonitor to take mDragWindowHandle.
mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
@@ -347,7 +347,7 @@
if (DEBUG_ORIENTATION) {
Slog.d(TAG, "Resuming rotation after re-position");
}
- mDisplayContent.resumeRotationLocked();
+ mDisplayContent.getDisplayRotation().resume();
mDisplayContent = null;
mClientCallback.unlinkToDeath(this, 0 /* flags */);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3874fdc..4821cf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -141,7 +141,6 @@
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -667,19 +666,6 @@
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
- /**
- * A count of the windows which are 'seamlessly rotated', e.g. a surface
- * at an old orientation is being transformed. We freeze orientation updates
- * while any windows are seamlessly rotated, so we need to track when this
- * hits zero so we can apply deferred orientation updates.
- */
- private int mSeamlessRotationCount = 0;
- /**
- * True in the interval from starting seamless rotation until the last rotated
- * window draws in the new orientation.
- */
- private boolean mRotatingSeamlessly = false;
-
private final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -1247,7 +1233,6 @@
return res;
}
- boolean reportNewConfig = false;
WindowState parentWindow = null;
long origId;
final int callingUid = Binder.getCallingUid();
@@ -1623,11 +1608,7 @@
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
- if (win.isVisibleOrAdding() && displayContent.updateOrientationFromAppTokens()) {
- reportNewConfig = true;
- }
-
- if (reportNewConfig) {
+ if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
}
@@ -1791,11 +1772,11 @@
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "postWindowRemoveCleanupLocked: " + win);
mWindowMap.remove(win.mClient.asBinder());
- markForSeamlessRotation(win, false);
+ final DisplayContent dc = win.getDisplayContent();
+ dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
win.resetAppOpsState();
- final DisplayContent dc = win.getDisplayContent();
if (dc.mCurrentFocus == null) {
dc.mWinRemovedSinceNullFocus.add(win);
}
@@ -2241,9 +2222,8 @@
displayContent.mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
}
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "relayoutWindow: updateOrientationFromAppTokens");
- configChanged = displayContent.updateOrientationFromAppTokens();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: updateOrientation");
+ configChanged = displayContent.updateOrientation();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (toBeDisplayed && win.mIsWallpaper) {
@@ -4859,7 +4839,7 @@
case SEAMLESS_ROTATION_TIMEOUT: {
final DisplayContent displayContent = (DisplayContent) msg.obj;
synchronized (mGlobalLock) {
- displayContent.onSeamlessRotationTimeout();
+ displayContent.getDisplayRotation().onSeamlessRotationTimeout();
}
break;
}
@@ -5321,7 +5301,7 @@
void startFreezingDisplayLocked(int exitAnim, int enterAnim,
DisplayContent displayContent) {
- if (mDisplayFrozen || mRotatingSeamlessly) {
+ if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
return;
}
@@ -5430,8 +5410,8 @@
if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
- if (!displayContent.getDisplayPolicy()
- .validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
+ if (!displayContent.getDisplayRotation().validateRotationAnimation(
+ mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
mExitAnimId = mEnterAnimId = 0;
}
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
@@ -5458,7 +5438,7 @@
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
// now to catch that.
- configChanged = displayContent != null && displayContent.updateOrientationFromAppTokens();
+ configChanged = displayContent != null && displayContent.updateOrientation();
// A little kludge: a lot could have happened while the
// display was frozen, so now that we are coming back we
@@ -6984,26 +6964,6 @@
mPolicy.requestUserActivityNotification();
}
- void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
- if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
- return;
- }
- w.mSeamlesslyRotated = seamlesslyRotated;
- if (seamlesslyRotated) {
- mSeamlessRotationCount++;
- } else {
- mSeamlessRotationCount--;
- }
- if (mSeamlessRotationCount == 0) {
- if (DEBUG_ORIENTATION) {
- Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
- }
- finishSeamlessRotation();
-
- w.getDisplayContent().updateRotationAndSendNewConfigIfNeeded();
- }
- }
-
private final class LocalService extends WindowManagerInternal {
@Override
public void requestTraversalFromDisplayManager() {
@@ -7531,22 +7491,6 @@
return mSurfaceBuilderFactory.make(s);
}
- void startSeamlessRotation() {
- // We are careful to reset this in case a window was removed before it finished
- // seamless rotation.
- mSeamlessRotationCount = 0;
-
- mRotatingSeamlessly = true;
- }
-
- boolean isRotatingSeamlessly() {
- return mRotatingSeamlessly;
- }
-
- void finishSeamlessRotation() {
- mRotatingSeamlessly = false;
- }
-
/**
* Called when the state of lock task mode changes. This should be used to disable immersive
* mode confirmation.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d87a0ed..4900869 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -652,7 +652,8 @@
if (mForceSeamlesslyRotate || requested) {
mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
mPendingSeamlessRotate.unrotate(transaction, this);
- mWmService.markForSeamlessRotation(this, true);
+ getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
+ true /* seamlesslyRotated */);
}
}
@@ -661,7 +662,8 @@
mPendingSeamlessRotate.finish(this, timeout);
mFinishSeamlessRotateFrameNumber = getFrameNumber();
mPendingSeamlessRotate = null;
- mWmService.markForSeamlessRotation(this, false);
+ getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
+ false /* seamlesslyRotated */);
}
}
@@ -2086,7 +2088,7 @@
// So just update orientation if needed.
if (wasVisible) {
final DisplayContent displayContent = getDisplayContent();
- if (displayContent.updateOrientationFromAppTokens()) {
+ if (displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index e387e18..1f634b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -36,7 +36,6 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
@@ -160,7 +159,7 @@
// Set initial orientation and update.
mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mDisplayContent.updateOrientationFromAppTokens(
+ mDisplayContent.updateOrientation(
mDisplayContent.getRequestedOverrideConfiguration(),
null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
@@ -168,7 +167,7 @@
// Update the orientation to perform 180 degree rotation and check that resize was reported.
mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- mDisplayContent.updateOrientationFromAppTokens(
+ mDisplayContent.updateOrientation(
mDisplayContent.getRequestedOverrideConfiguration(),
null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
// In this test, DC will not get config update. Set the waiting flag to false.
@@ -181,8 +180,8 @@
@Test
public void testLandscapeSeascapeRotationByPolicy() {
+ // This instance has been spied in {@link TestActivityDisplay}.
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
- spyOn(displayRotation);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6289768..44f3ee41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -445,7 +445,7 @@
.computeSafeInsets(200, 400).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
- dc.setRotation(Surface.ROTATION_0);
+ dc.getDisplayRotation().setRotation(Surface.ROTATION_0);
dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
assertEquals(cutout, dc.getDisplayInfo().displayCutout);
@@ -476,7 +476,7 @@
.computeSafeInsets(displayWidth, displayHeight).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
- dc.setRotation(Surface.ROTATION_90);
+ dc.getDisplayRotation().setRotation(Surface.ROTATION_90);
dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
// ----o---------- -------------
@@ -608,18 +608,18 @@
portraitDisplay.mInitialDisplayHeight = 2000;
portraitDisplay.mInitialDisplayWidth = 1000;
- portraitDisplay.setRotation(Surface.ROTATION_0);
+ portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
assertFalse(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
- portraitDisplay.setRotation(Surface.ROTATION_90);
+ portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
assertTrue(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
final DisplayContent landscapeDisplay = createNewDisplay();
landscapeDisplay.mInitialDisplayHeight = 1000;
landscapeDisplay.mInitialDisplayWidth = 2000;
- landscapeDisplay.setRotation(Surface.ROTATION_0);
+ landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
assertTrue(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
- landscapeDisplay.setRotation(Surface.ROTATION_90);
+ landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 6a3c81a..3ead977 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -28,16 +28,12 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
@@ -52,7 +48,6 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.Surface;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -213,32 +208,15 @@
}
@Test
- public void testShouldRotateSeamlessly() {
- final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ public void testComputeTopFullscreenOpaqueWindow() {
final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
attrs.x = attrs.y = 0;
attrs.height = attrs.width = WindowManager.LayoutParams.MATCH_PARENT;
- attrs.rotationAnimation = ROTATION_ANIMATION_SEAMLESS;
- final DisplayRotation displayRotation = mock(DisplayRotation.class);
- doReturn(Surface.ROTATION_180).when(displayRotation).getUpsideDownRotation();
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ policy.applyPostLayoutPolicyLw(
+ mAppWindow, attrs, null /* attached */, null /* imeTarget */);
- synchronized (mWm.mGlobalLock) {
- policy.focusChangedLw(null /* lastFocus */, mAppWindow);
- policy.applyPostLayoutPolicyLw(
- mAppWindow, attrs, null /* attached */, null /* imeTarget */);
- spyOn(policy);
- doReturn(true).when(policy).navigationBarCanMove();
- // The focused fullscreen opaque window without override bounds should be able to be
- // rotated seamlessly.
- assertTrue(policy.shouldRotateSeamlessly(
- displayRotation, Surface.ROTATION_0, Surface.ROTATION_90));
-
- spyOn(mAppWindow.mAppToken);
- doReturn(false).when(mAppWindow.mAppToken).matchParentBounds();
- // No seamless rotation if the window may be positioned with offset after rotation.
- assertFalse(policy.shouldRotateSeamlessly(
- displayRotation, Surface.ROTATION_0, Surface.ROTATION_90));
- }
+ assertEquals(mAppWindow, policy.getTopFullscreenOpaqueWindow());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 49d38c0..059ff3d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atMost;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
@@ -55,6 +56,7 @@
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.Surface;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -584,6 +586,33 @@
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
}
+ @Test
+ public void testShouldRotateSeamlessly() throws Exception {
+ mBuilder.build();
+
+ final WindowState win = mock(WindowState.class);
+ win.mAppToken = mock(AppWindowToken.class);
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+ attrs.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+
+ doReturn(attrs).when(win).getAttrs();
+ doReturn(true).when(mMockDisplayPolicy).navigationBarCanMove();
+ doReturn(win).when(mMockDisplayPolicy).getTopFullscreenOpaqueWindow();
+ mMockDisplayContent.mCurrentFocus = win;
+ mTarget.mUpsideDownRotation = Surface.ROTATION_180;
+
+ doReturn(true).when(win.mAppToken).matchParentBounds();
+ // The focused fullscreen opaque window without override bounds should be able to be
+ // rotated seamlessly.
+ assertTrue(mTarget.shouldRotateSeamlessly(
+ Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
+
+ doReturn(false).when(win.mAppToken).matchParentBounds();
+ // No seamless rotation if the window may be positioned with offset after rotation.
+ assertFalse(mTarget.shouldRotateSeamlessly(
+ Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
+ }
+
// ========================
// Non-rotation API Tests
// ========================
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
index c143969..778f0ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
@@ -69,20 +69,22 @@
mSupervisor = supervisor;
spyOn(this);
spyOn(mDisplayContent);
+
+ final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
doAnswer(invocation -> {
// Bypass all the rotation animation and display freezing stuff for testing and just
// set the rotation we want for the display
- final DisplayContent dc = mDisplayContent;
- final int oldRotation = dc.getRotation();
- final int rotation = dc.getDisplayRotation().rotationForOrientation(
- dc.getLastOrientation(), oldRotation);
+ final int oldRotation = displayRotation.getRotation();
+ final int rotation = displayRotation.rotationForOrientation(
+ displayRotation.getLastOrientation(), oldRotation);
if (oldRotation == rotation) {
return false;
}
- dc.setLayoutNeeded();
- dc.setRotation(rotation);
+ mDisplayContent.setLayoutNeeded();
+ displayRotation.setRotation(rotation);
return true;
- }).when(mDisplayContent).updateRotationUnchecked(anyBoolean());
+ }).when(displayRotation).updateRotationUnchecked(anyBoolean());
}
@SuppressWarnings("TypeParameterUnusedInFormals")