Support rotation on secondary displays (2/N)
Move partial operations of rotation/orientation from
PhoneWindowManager to DisplayRotation.
Bug: 111361251
Test: go/wm-smoke
Test: atest FrameworksServicesTests:PhoneWindowManagerLayoutTest
Test: atest FrameworksServicesTests:PhoneWindowManagerInsetsTest
Change-Id: I7507e25f14e7fd1733ffd2a71789673d6a7ee17d
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cea5f4c6..605705e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -173,7 +173,8 @@
* IMPORTANT: No method from this class should ever be used without holding
* WindowManagerService.mWindowMap.
*/
-class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {
+class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
+ implements WindowManagerPolicy.DisplayContentInfo {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
/** Unique identifier of this stack. */
@@ -234,6 +235,7 @@
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ final DisplayRotation mDisplayRotation;
DisplayFrames mDisplayFrames;
/**
@@ -762,6 +764,7 @@
mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo,
calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
+ mDisplayRotation = new DisplayRotation(this, mService.mPolicy, mService.mContext);
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
@@ -905,7 +908,8 @@
appToken.onRemovedFromDisplay();
}
- Display getDisplay() {
+ @Override
+ public Display getDisplay() {
return mDisplay;
}
@@ -917,6 +921,11 @@
return mDisplayMetrics;
}
+ @Override
+ public DisplayRotation getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
int getRotation() {
return mRotation;
}
@@ -924,6 +933,7 @@
@VisibleForTesting
void setRotation(int newRotation) {
mRotation = newRotation;
+ mDisplayRotation.setRotation(newRotation);
}
int getLastOrientation() {
@@ -977,6 +987,28 @@
}
/**
+ * 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 boolean oldAltOrientation = getAltOrientation();
+
+ final int rotation = mService.mPolicy.rotationForOrientationLw(
+ mDisplayRotation, lastOrientation, oldRotation);
+ final boolean altOrientation = !mDisplayRotation.rotationHasCompatibleMetrics(
+ lastOrientation, rotation);
+ if (oldRotation == rotation && oldAltOrientation == altOrientation) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Update rotation of the display.
*
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
@@ -1034,13 +1066,13 @@
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
final boolean oldAltOrientation = mAltOrientation;
- final int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation,
- isDefaultDisplay);
+ final int rotation = mService.mPolicy.rotationForOrientationLw(mDisplayRotation,
+ 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 = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
- rotation);
+ boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(mDisplayRotation,
+ oldRotation, rotation);
if (mayRotateSeamlessly) {
final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
@@ -1071,7 +1103,7 @@
// an orientation that has different metrics than it expected.
// eg. Portrait instead of Landscape.
- final boolean altOrientation = !mService.mPolicy.rotationHasCompatibleMetricsLw(
+ final boolean altOrientation = !mDisplayRotation.rotationHasCompatibleMetrics(
lastOrientation, rotation);
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
@@ -1093,11 +1125,8 @@
mService.mWaitingForConfig = true;
}
- mRotation = rotation;
+ setRotation(rotation);
mAltOrientation = altOrientation;
- if (isDefaultDisplay) {
- mService.mPolicy.setRotationLw(rotation);
- }
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
@@ -1191,9 +1220,7 @@
}
void configureDisplayPolicy() {
- mService.mPolicy.setInitialDisplaySize(getDisplay(),
- mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
-
+ mDisplayRotation.configure();
mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
}
@@ -2367,6 +2394,8 @@
pw.println();
mDisplayFrames.dump(prefix, pw);
pw.println();
+ mDisplayRotation.dump(prefix, pw);
+ pw.println();
mInputMonitor.dump(pw, " ");
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
new file mode 100644
index 0000000..2074ecf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.view.Surface;
+
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.policy.WindowManagerPolicy.RotationSource;
+
+import java.io.PrintWriter;
+
+/**
+ * Defines the mapping between orientation and rotation of a display.
+ */
+public class DisplayRotation {
+ private final DisplayContent mDisplayContent;
+ private final WindowManagerPolicy mPolicy;
+ private final Context mContext;
+ private RotationSource mRotationSource;
+
+ int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLandscapeRotation; // default landscape
+ int mSeascapeRotation; // "other" landscape, 180 degrees from mLandscapeRotation
+ int mPortraitRotation; // default portrait
+ int mUpsideDownRotation; // "other" portrait
+
+ DisplayRotation(DisplayContent displayContent, WindowManagerPolicy policy, Context context) {
+ mDisplayContent = displayContent;
+ mPolicy = policy;
+ mContext = context;
+ }
+
+ void configure() {
+ mRotationSource = mPolicy.getRotationSource(mDisplayContent.getDisplayId());
+
+ final int width = mDisplayContent.mBaseDisplayWidth;
+ final int height = mDisplayContent.mBaseDisplayHeight;
+ 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)) {
+ mPortraitRotation = Surface.ROTATION_90;
+ mUpsideDownRotation = Surface.ROTATION_270;
+ } else {
+ mPortraitRotation = Surface.ROTATION_270;
+ mUpsideDownRotation = Surface.ROTATION_90;
+ }
+ } else {
+ mPortraitRotation = Surface.ROTATION_0;
+ mUpsideDownRotation = Surface.ROTATION_180;
+ if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ mLandscapeRotation = Surface.ROTATION_270;
+ mSeascapeRotation = Surface.ROTATION_90;
+ } else {
+ mLandscapeRotation = Surface.ROTATION_90;
+ mSeascapeRotation = Surface.ROTATION_270;
+ }
+ }
+
+ mPolicy.setInitialDisplaySize(this, width, height, mDisplayContent.mBaseDisplayDensity);
+ }
+
+ public int getLandscapeRotation() {
+ return mLandscapeRotation;
+ }
+
+ public int getSeascapeRotation() {
+ return mSeascapeRotation;
+ }
+
+ public int getPortraitRotation() {
+ return mPortraitRotation;
+ }
+
+ public int getUpsideDownRotation() {
+ return mUpsideDownRotation;
+ }
+
+ public int getCurrentAppOrientation() {
+ return mCurrentAppOrientation;
+ }
+
+ public int getSensorRotation() {
+ return mRotationSource != null ? mRotationSource.getProposedRotation() : -1;
+ }
+
+ public boolean isDefaultDisplay() {
+ return mDisplayContent.isDefaultDisplay;
+ }
+
+ void setRotation(int rotation) {
+ if (mRotationSource != null) {
+ mRotationSource.setCurrentRotation(rotation);
+ }
+ }
+
+ void setCurrentOrientation(int newOrientation) {
+ if (newOrientation != mCurrentAppOrientation) {
+ mCurrentAppOrientation = newOrientation;
+ // TODO(multi-display): Separate orientation listeners.
+ if (mDisplayContent.isDefaultDisplay) {
+ mPolicy.updateOrientationListener();
+ }
+ }
+ }
+
+ public int rotationForOrientation(int orientation, int lastRotation, int preferredRotation) {
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ // Return portrait unless overridden.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mPortraitRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ // Return landscape unless overridden.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mLandscapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ // Return reverse portrait unless overridden.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mUpsideDownRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ // Return seascape unless overridden.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mSeascapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+ // Return either landscape rotation.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ if (isLandscapeOrSeascape(lastRotation)) {
+ return lastRotation;
+ }
+ return mLandscapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+ // Return either portrait rotation.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ if (isAnyPortrait(lastRotation)) {
+ return lastRotation;
+ }
+ return mPortraitRotation;
+
+ default:
+ // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,
+ // just return the preferred orientation we already calculated.
+ if (preferredRotation >= 0) {
+ return preferredRotation;
+ }
+ return Surface.ROTATION_0;
+ }
+ }
+
+ /**
+ * Given an orientation constant and a rotation, returns true if the rotation
+ * has compatible metrics to the requested orientation. For example, if
+ * the application requested landscape and got seascape, then the rotation
+ * has compatible metrics; if the application requested portrait and got landscape,
+ * then the rotation has incompatible metrics; if the application did not specify
+ * a preference, then anything goes.
+ *
+ * @param orientation An orientation constant, such as
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * @param rotation The rotation to check.
+ * @return True if the rotation is compatible with the requested orientation.
+ */
+ boolean rotationHasCompatibleMetrics(int orientation, int rotation) {
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return isAnyPortrait(rotation);
+
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return isLandscapeOrSeascape(rotation);
+
+ default:
+ return true;
+ }
+ }
+
+ public boolean isValidRotationChoice(final int preferredRotation) {
+ // Determine if the given app orientation is compatible with the provided rotation choice.
+ switch (mCurrentAppOrientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
+ // Works with any of the 4 rotations.
+ return preferredRotation >= 0;
+
+ case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+ // It's possible for the user pref to be set at 180 because of FULL_USER. This would
+ // make switching to USER_PORTRAIT appear at 180. Provide choice to back to portrait
+ // but never to go to 180.
+ return preferredRotation == mPortraitRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+ // Works landscape or seascape.
+ return isLandscapeOrSeascape(preferredRotation);
+
+ case ActivityInfo.SCREEN_ORIENTATION_USER:
+ case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
+ // Works with any rotation except upside down.
+ return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation);
+ }
+
+ return false;
+ }
+
+ private boolean isLandscapeOrSeascape(int rotation) {
+ return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
+ }
+
+ private boolean isAnyPortrait(int rotation) {
+ return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "DisplayRotation");
+ pw.println(prefix + " mCurrentAppOrientation="
+ + ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
+ pw.print(prefix + " mLandscapeRotation=" + Surface.rotationToString(mLandscapeRotation));
+ pw.println(" mSeascapeRotation=" + Surface.rotationToString(mSeascapeRotation));
+ pw.print(prefix + " mPortraitRotation=" + Surface.rotationToString(mPortraitRotation));
+ pw.println(" mUpsideDownRotation=" + Surface.rotationToString(mUpsideDownRotation));
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3df4eb7..ca4fa64 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -134,6 +134,9 @@
// transaction from the global transaction.
private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction();
+ private final Consumer<DisplayContent> mDisplayContentConfigChangesConsumer =
+ mService.mPolicy::onConfigurationChanged;
+
private final Consumer<WindowState> mCloseSystemDialogsConsumer = w -> {
if (w.mHasSurface) {
try {
@@ -379,7 +382,7 @@
prepareFreezingTaskBounds();
super.onConfigurationChanged(newParentConfig);
- mService.mPolicy.onConfigurationChanged();
+ forAllDisplays(mDisplayContentConfigChangesConsumer);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9b792e3..a2b3783 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2480,10 +2480,7 @@
dc.setLastOrientation(req);
//send a message to Policy indicating orientation change to take
//action like disabling/enabling sensors etc.,
- // TODO(multi-display): Implement policy for secondary displays.
- if (dc.isDefaultDisplay) {
- mPolicy.setCurrentOrientationLw(req);
- }
+ dc.mDisplayRotation.setCurrentOrientation(req);
return dc.updateRotationUnchecked(forceUpdate);
}
return false;
@@ -2492,27 +2489,6 @@
}
}
- // 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.
- boolean rotationNeedsUpdateLocked() {
- // TODO(multi-display): Check for updates on all displays. Need to have per-display policy
- // to implement WindowManagerPolicy#rotationForOrientationLw() correctly.
- final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
- final int lastOrientation = defaultDisplayContent.getLastOrientation();
- final int oldRotation = defaultDisplayContent.getRotation();
- final boolean oldAltOrientation = defaultDisplayContent.getAltOrientation();
-
- final int rotation = mPolicy.rotationForOrientationLw(lastOrientation, oldRotation,
- true /* defaultDisplay */);
- boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
- lastOrientation, rotation);
- if (oldRotation == rotation && oldAltOrientation == altOrientation) {
- return false;
- }
- return true;
- }
-
@Override
public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
@@ -3871,6 +3847,13 @@
}
@Override
+ public WindowManagerPolicy.DisplayContentInfo getDefaultDisplayContentInfo() {
+ synchronized (mWindowMap) {
+ return getDefaultDisplayContentLocked();
+ }
+ }
+
+ @Override
public int getDefaultDisplayRotation() {
synchronized (mWindowMap) {
return getDefaultDisplayContentLocked().getRotation();
@@ -6761,8 +6744,10 @@
public void onOverlayChanged() {
synchronized (mWindowMap) {
- mPolicy.onOverlayChangedLw();
- getDefaultDisplayContentLocked().updateDisplayInfo();
+ mRoot.forAllDisplays(displayContent -> {
+ mPolicy.onOverlayChangedLw(displayContent);
+ displayContent.updateDisplayInfo();
+ });
requestTraversal();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aced8e4..08decdf 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -569,7 +569,7 @@
// animation after the old one finally finishes. It's better to defer the
// app transition.
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
- mService.rotationNeedsUpdateLocked()) {
+ mService.getDefaultDisplayContentLocked().rotationNeedsUpdate()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
}