Support rotation on secondary displays (3/N)
Move major operations of rotation/orientation from
PhoneWindowManager to DisplayRotation.
Create DisplayPolicy and move partial fields which
have dependency with rotation.
Bug: 111361251
Test: go/wm-smoke
Test: atest FrameworksServicesTests:AppWindowTokenTests
Test: atest FrameworksServicesTests:PhoneWindowManagerLayoutTest
Test: atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests
Change-Id: I2dfd15ffcc41e10a9074087e6aa15975c92b4979
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 2074ecf..685c444 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,42 +16,140 @@
package com.android.server.wm;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+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 android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowManagerPolicy.RotationSource;
+import com.android.server.policy.WindowOrientationListener;
+import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
/**
* Defines the mapping between orientation and rotation of a display.
+ * Non-public methods are assumed to run inside WM lock.
*/
public class DisplayRotation {
- private final DisplayContent mDisplayContent;
- private final WindowManagerPolicy mPolicy;
- private final Context mContext;
- private RotationSource mRotationSource;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
- int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ private final WindowManagerService mService;
+ private final DisplayPolicy mDisplayPolicy;
+ private final Context mContext;
+ private final Object mLock;
+
+ public final boolean isDefaultDisplay;
+ private final boolean mSupportAutoRotation;
+ private final int mLidOpenRotation;
+ private final int mCarDockRotation;
+ private final int mDeskDockRotation;
+ private final int mUndockedHdmiRotation;
+
+ private OrientationListener mOrientationListener;
+ private StatusBarManagerInternal mStatusBarManagerInternal;
+ private SettingsObserver mSettingsObserver;
+
+ // Default display does not rotate, apps that require non-default orientation will have to
+ // have the orientation emulated.
+ private boolean mForceDefaultOrientation;
+
+ private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+ @VisibleForTesting
int mLandscapeRotation; // default landscape
+ @VisibleForTesting
int mSeascapeRotation; // "other" landscape, 180 degrees from mLandscapeRotation
+ @VisibleForTesting
int mPortraitRotation; // default portrait
+ @VisibleForTesting
int mUpsideDownRotation; // "other" portrait
- DisplayRotation(DisplayContent displayContent, WindowManagerPolicy policy, Context context) {
- mDisplayContent = displayContent;
- mPolicy = policy;
- mContext = context;
+ // Behavior of rotation suggestions. (See Settings.Secure.SHOW_ROTATION_SUGGESTION)
+ private int mShowRotationSuggestions;
+
+ private int mAllowAllRotations = -1;
+ private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+ private int mUserRotation = Surface.ROTATION_0;
+
+ private int mDemoHdmiRotation;
+ private int mDemoRotation;
+ private boolean mDemoHdmiRotationLock;
+ private boolean mDemoRotationLock;
+
+ DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
+ this(service, displayContent, displayContent.getDisplayPolicy(),
+ service.mContext, service.getWindowManagerLock());
}
- void configure() {
- mRotationSource = mPolicy.getRotationSource(mDisplayContent.getDisplayId());
+ @VisibleForTesting
+ DisplayRotation(WindowManagerService service, DisplayContent displayContent,
+ DisplayPolicy displayPolicy, Context context, Object lock) {
+ mService = service;
+ mDisplayPolicy = displayPolicy;
+ mContext = context;
+ mLock = lock;
+ isDefaultDisplay = displayContent.isDefaultDisplay;
- final int width = mDisplayContent.mBaseDisplayWidth;
- final int height = mDisplayContent.mBaseDisplayHeight;
+ 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);
+
+ if (isDefaultDisplay) {
+ final Handler uiHandler = UiThread.getHandler();
+ mOrientationListener = new OrientationListener(mContext, uiHandler);
+ mOrientationListener.setCurrentRotation(displayContent.getRotation());
+ mSettingsObserver = new SettingsObserver(uiHandler);
+ mSettingsObserver.observe();
+ }
+ }
+
+ private int readRotation(int resID) {
+ try {
+ final int rotation = mContext.getResources().getInteger(resID);
+ switch (rotation) {
+ case 0:
+ return Surface.ROTATION_0;
+ case 90:
+ return Surface.ROTATION_90;
+ case 180:
+ return Surface.ROTATION_180;
+ case 270:
+ return Surface.ROTATION_270;
+ }
+ } catch (Resources.NotFoundException e) {
+ // fall through
+ }
+ return -1;
+ }
+
+ void configure(int width, int height, int shortSizeDp, int longSizeDp) {
final Resources res = mContext.getResources();
if (width > height) {
mLandscapeRotation = Surface.ROTATION_0;
@@ -75,7 +173,60 @@
}
}
- mPolicy.setInitialDisplaySize(this, width, height, mDisplayContent.mBaseDisplayDensity);
+ // For demo purposes, allow the rotation of the HDMI display to be controlled.
+ // By default, HDMI locks rotation to landscape.
+ if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
+ mDemoHdmiRotation = mPortraitRotation;
+ } else {
+ mDemoHdmiRotation = mLandscapeRotation;
+ }
+ mDemoHdmiRotationLock = SystemProperties.getBoolean("persist.demo.hdmirotationlock", false);
+
+ // For demo purposes, allow the rotation of the remote display to be controlled.
+ // By default, remote display locks rotation to landscape.
+ if ("portrait".equals(SystemProperties.get("persist.demo.remoterotation"))) {
+ mDemoRotation = mPortraitRotation;
+ } else {
+ mDemoRotation = mLandscapeRotation;
+ }
+ mDemoRotationLock = SystemProperties.getBoolean("persist.demo.rotationlock", false);
+
+ // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
+ // http://developer.android.com/guide/practices/screens_support.html#range
+ // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
+ // so if the orientation is forced, we need to respect that no matter what.
+ final boolean isCar = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ // For TV, it's usually 960dp x 540dp, ignore the size limitation.
+ // so if the orientation is forced, we need to respect that no matter what.
+ final boolean isTv = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK);
+ mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv) &&
+ res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
+ // For debug purposes the next line turns this feature off with:
+ // $ adb shell setprop config.override_forced_orient true
+ // $ adb shell wm size reset
+ !"true".equals(SystemProperties.get("config.override_forced_orient"));
+ }
+
+ void setRotation(int rotation) {
+ if (mOrientationListener != null) {
+ mOrientationListener.setCurrentRotation(rotation);
+ }
+ }
+
+ void setCurrentOrientation(int newOrientation) {
+ if (newOrientation != mCurrentAppOrientation) {
+ mCurrentAppOrientation = newOrientation;
+ if (isDefaultDisplay) {
+ updateOrientationListenerLw();
+ }
+ }
+ }
+
+ /** @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true. */
+ boolean isDefaultOrientationForced() {
+ return mForceDefaultOrientation;
}
public int getLandscapeRotation() {
@@ -98,31 +249,261 @@
return mCurrentAppOrientation;
}
- public int getSensorRotation() {
- return mRotationSource != null ? mRotationSource.getProposedRotation() : -1;
+ public DisplayPolicy getDisplayPolicy() {
+ return mDisplayPolicy;
}
- public boolean isDefaultDisplay() {
- return mDisplayContent.isDefaultDisplay;
+ public WindowOrientationListener getOrientationListener() {
+ return mOrientationListener;
}
- void setRotation(int rotation) {
- if (mRotationSource != null) {
- mRotationSource.setCurrentRotation(rotation);
+ public int getUserRotation() {
+ return mUserRotation;
+ }
+
+ public int getUserRotationMode() {
+ return mUserRotationMode;
+ }
+
+ public void updateOrientationListener() {
+ synchronized (mLock) {
+ updateOrientationListenerLw();
}
}
- void setCurrentOrientation(int newOrientation) {
- if (newOrientation != mCurrentAppOrientation) {
- mCurrentAppOrientation = newOrientation;
- // TODO(multi-display): Separate orientation listeners.
- if (mDisplayContent.isDefaultDisplay) {
- mPolicy.updateOrientationListener();
+ /**
+ * 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
+ * if not already enabled.</li>
+ * <li>Screen turned on and current app does not have sensor orientation, disable listeners
+ * if already enabled.</li>
+ * <li>Screen turning on and current app has sensor based orientation, enable listeners
+ * if needed.</li>
+ * <li>screen turning on and current app has nosensor based orientation, do nothing.</li>
+ */
+ private void updateOrientationListenerLw() {
+ if (mOrientationListener == null || !mOrientationListener.canDetectOrientation()) {
+ // If sensor is turned off or nonexistent for some reason.
+ return;
+ }
+
+ final boolean screenOnEarly = mDisplayPolicy.isScreenOnEarly();
+ final boolean awake = mDisplayPolicy.isAwake();
+ final boolean keyguardDrawComplete = mDisplayPolicy.isKeyguardDrawComplete();
+ final boolean windowManagerDrawComplete = mDisplayPolicy.isWindowManagerDrawComplete();
+
+ // Could have been invoked due to screen turning on or off or
+ // change of the currently visible window's orientation.
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "screenOnEarly=" + screenOnEarly
+ + ", awake=" + awake + ", currentAppOrientation=" + mCurrentAppOrientation
+ + ", orientationSensorEnabled=" + mOrientationListener.mEnabled
+ + ", keyguardDrawComplete=" + keyguardDrawComplete
+ + ", windowManagerDrawComplete=" + windowManagerDrawComplete);
+
+ boolean disable = true;
+ // Note: We postpone the rotating of the screen until the keyguard as well as the
+ // window manager have reported a draw complete or the keyguard is going away in dismiss
+ // mode.
+ if (screenOnEarly && awake && ((keyguardDrawComplete && windowManagerDrawComplete))) {
+ if (needSensorRunning()) {
+ disable = false;
+ // Enable listener if not already enabled.
+ if (!mOrientationListener.mEnabled) {
+ // Don't clear the current sensor orientation if the keyguard is going away in
+ // dismiss mode. This allows window manager to use the last sensor reading to
+ // determine the orientation vs. falling back to the last known orientation if
+ // the sensor reading was cleared which can cause it to relaunch the app that
+ // will show in the wrong orientation first before correcting leading to app
+ // launch delays.
+ mOrientationListener.enable(true /* clearCurrentRotation */);
+ }
}
}
+ // Check if sensors need to be disabled.
+ if (disable && mOrientationListener.mEnabled) {
+ mOrientationListener.disable();
+ }
}
- public int rotationForOrientation(int orientation, int lastRotation, int preferredRotation) {
+ /**
+ * We always let the sensor be switched on by default except when
+ * the user has explicitly disabled sensor based rotation or when the
+ * screen is switched off.
+ */
+ private boolean needSensorRunning() {
+ if (mSupportAutoRotation) {
+ if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
+ // If the application has explicitly requested to follow the
+ // orientation, then we need to turn the sensor on.
+ return true;
+ }
+ }
+
+ final int dockMode = mDisplayPolicy.getDockMode();
+ if ((mDisplayPolicy.isCarDockEnablesAccelerometer()
+ && dockMode == Intent.EXTRA_DOCK_STATE_CAR)
+ || (mDisplayPolicy.isDeskDockEnablesAccelerometer()
+ && (dockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK))) {
+ // Enable accelerometer if we are docked in a dock that enables accelerometer
+ // orientation management.
+ return true;
+ }
+
+ if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
+ // If the setting for using the sensor by default is enabled, then
+ // we will always leave it on. Note that the user could go to
+ // a window that forces an orientation that does not use the
+ // sensor and in theory we could turn it off... however, when next
+ // turning it on we won't have a good value for the current
+ // orientation for a little bit, which can cause orientation
+ // changes to lag, so we'd like to keep it always on. (It will
+ // still be turned off when the screen is off.)
+
+ // When locked we can provide rotation suggestions users can approve to change the
+ // current screen rotation. To do this the sensor needs to be running.
+ return mSupportAutoRotation &&
+ mShowRotationSuggestions == Settings.Secure.SHOW_ROTATION_SUGGESTIONS_ENABLED;
+ }
+ return mSupportAutoRotation;
+ }
+
+ /**
+ * 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}.
+ * @param lastRotation The most recently used rotation.
+ * @param defaultDisplay Flag indicating whether the rotation is computed for the default
+ * display. Currently for all non-default displays sensors, docking mode,
+ * rotation lock and other factors are ignored.
+ * @return The surface rotation to use.
+ */
+ int rotationForOrientation(int orientation, int lastRotation) {
+ if (DEBUG_ORIENTATION) {
+ Slog.v(TAG, "rotationForOrientation(orient="
+ + orientation + ", last=" + lastRotation
+ + "); user=" + mUserRotation + " "
+ + (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
+ ? "USER_ROTATION_LOCKED" : "")
+ );
+ }
+
+ if (mForceDefaultOrientation) {
+ return Surface.ROTATION_0;
+ }
+
+ int sensorRotation = mOrientationListener != null
+ ? mOrientationListener.getProposedRotation() // may be -1
+ : -1;
+ if (sensorRotation < 0) {
+ sensorRotation = lastRotation;
+ }
+
+ final int lidState = mDisplayPolicy.getLidState();
+ final int dockMode = mDisplayPolicy.getDockMode();
+ final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
+ final boolean carDockEnablesAccelerometer =
+ mDisplayPolicy.isCarDockEnablesAccelerometer();
+ final boolean deskDockEnablesAccelerometer =
+ mDisplayPolicy.isDeskDockEnablesAccelerometer();
+
+ final int preferredRotation;
+ if (!isDefaultDisplay) {
+ // For secondary displays we ignore things like displays sensors, docking mode and
+ // rotation lock, and always prefer a default rotation.
+ preferredRotation = Surface.ROTATION_0;
+ } else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
+ // Ignore sensor when lid switch is open and rotation is forced.
+ preferredRotation = mLidOpenRotation;
+ } else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
+ && (carDockEnablesAccelerometer || mCarDockRotation >= 0)) {
+ // Ignore sensor when in car dock unless explicitly enabled.
+ // This case can override the behavior of NOSENSOR, and can also
+ // enable 180 degree rotation while docked.
+ preferredRotation = carDockEnablesAccelerometer ? sensorRotation : mCarDockRotation;
+ } else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
+ && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)) {
+ // Ignore sensor when in desk dock unless explicitly enabled.
+ // This case can override the behavior of NOSENSOR, and can also
+ // enable 180 degree rotation while docked.
+ preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation;
+ } else if (hdmiPlugged && mDemoHdmiRotationLock) {
+ // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled.
+ // Note that the dock orientation overrides the HDMI orientation.
+ preferredRotation = mDemoHdmiRotation;
+ } else if (hdmiPlugged && dockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
+ && mUndockedHdmiRotation >= 0) {
+ // Ignore sensor when plugged into HDMI and an undocked orientation has
+ // been specified in the configuration (only for legacy devices without
+ // full multi-display support).
+ // Note that the dock orientation overrides the HDMI orientation.
+ preferredRotation = mUndockedHdmiRotation;
+ } else if (mDemoRotationLock) {
+ // Ignore sensor when demo rotation lock is enabled.
+ // Note that the dock orientation and HDMI rotation lock override this.
+ preferredRotation = mDemoRotation;
+ } else if (mDisplayPolicy.isPersistentVrModeEnabled()) {
+ // While in VR, apps always prefer a portrait rotation. This does not change
+ // any apps that explicitly set landscape, but does cause sensors be ignored,
+ // and ignored any orientation lock that the user has set (this conditional
+ // should remain above the ORIENTATION_LOCKED conditional below).
+ preferredRotation = mPortraitRotation;
+ } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ // Application just wants to remain locked in the last rotation.
+ preferredRotation = lastRotation;
+ } else if (!mSupportAutoRotation) {
+ // If we don't support auto-rotation then bail out here and ignore
+ // the sensor and any rotation lock settings.
+ preferredRotation = -1;
+ } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+ && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || 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.
+ mAllowAllRotations = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0;
+ }
+ if (sensorRotation != Surface.ROTATION_180
+ || mAllowAllRotations == 1
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
+ preferredRotation = sensorRotation;
+ } else {
+ preferredRotation = lastRotation;
+ }
+ } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
+ && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ // Apply rotation lock. Does not apply to NOSENSOR.
+ // The idea is that the user rotation expresses a weak preference for the direction
+ // of gravity and as NOSENSOR is never affected by gravity, then neither should
+ // NOSENSOR be affected by rotation lock (although it will be affected by docks).
+ preferredRotation = mUserRotation;
+ } else {
+ // No overriding preference.
+ // We will do exactly what the application asked us to do.
+ preferredRotation = -1;
+ }
+
switch (orientation) {
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
// Return portrait unless overridden.
@@ -184,6 +565,14 @@
}
}
+ private boolean isLandscapeOrSeascape(int rotation) {
+ return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
+ }
+
+ private boolean isAnyPortrait(int rotation) {
+ return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
+ }
+
/**
* Given an orientation constant and a rotation, returns true if the rotation
* has compatible metrics to the requested orientation. For example, if
@@ -214,7 +603,7 @@
}
}
- public boolean isValidRotationChoice(final int preferredRotation) {
+ private 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:
@@ -240,12 +629,143 @@
return false;
}
- private boolean isLandscapeOrSeascape(int rotation) {
- return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
+ private boolean isRotationChoicePossible(int orientation) {
+ // Rotation choice is only shown when the user is in locked mode.
+ if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
+
+ // We should only enable rotation choice if the rotation isn't forced by the lid, dock,
+ // demo, hdmi, vr, etc mode.
+
+ // Determine if the rotation is currently forced.
+ if (mForceDefaultOrientation) {
+ return false; // Rotation is forced to default orientation.
+ }
+
+ final int lidState = mDisplayPolicy.getLidState();
+ if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
+ return false; // Rotation is forced mLidOpenRotation.
+ }
+
+ final int dockMode = mDisplayPolicy.getDockMode();
+ final boolean carDockEnablesAccelerometer = false;
+ if (dockMode == Intent.EXTRA_DOCK_STATE_CAR && !carDockEnablesAccelerometer) {
+ return false; // Rotation forced to mCarDockRotation.
+ }
+
+ final boolean deskDockEnablesAccelerometer =
+ mDisplayPolicy.isDeskDockEnablesAccelerometer();
+ if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
+ && !deskDockEnablesAccelerometer) {
+ return false; // Rotation forced to mDeskDockRotation.
+ }
+
+ final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
+ if (hdmiPlugged && mDemoHdmiRotationLock) {
+ return false; // Rotation forced to mDemoHdmiRotation.
+
+ } else if (hdmiPlugged && dockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
+ && mUndockedHdmiRotation >= 0) {
+ return false; // Rotation forced to mUndockedHdmiRotation.
+
+ } else if (mDemoRotationLock) {
+ return false; // Rotation forced to mDemoRotation.
+
+ } else if (mDisplayPolicy.isPersistentVrModeEnabled()) {
+ return false; // Rotation forced to mPortraitRotation.
+
+ } else if (!mSupportAutoRotation) {
+ return false;
+ }
+
+ // Ensure that some rotation choice is possible for the given orientation.
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
+ case ActivityInfo.SCREEN_ORIENTATION_USER:
+ case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+ // NOSENSOR description is ambiguous, in reality WM ignores user choice.
+ return true;
+ }
+
+ // Rotation is forced, should be controlled by system.
+ return false;
}
- private boolean isAnyPortrait(int rotation) {
- return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
+ /** Notify the StatusBar that system rotation suggestion has changed. */
+ private void sendProposedRotationChangeToStatusBarInternal(int rotation, boolean isValid) {
+ if (mStatusBarManagerInternal == null) {
+ mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
+ }
+ if (mStatusBarManagerInternal != null) {
+ mStatusBarManagerInternal.onProposedRotationChanged(rotation, isValid);
+ }
+ }
+
+ private static String allowAllRotationsToString(int allowAll) {
+ switch (allowAll) {
+ case -1:
+ return "unknown";
+ case 0:
+ return "false";
+ case 1:
+ return "true";
+ default:
+ return Integer.toString(allowAll);
+ }
+ }
+
+ public void onUserSwitch() {
+ if (mSettingsObserver != null) {
+ mSettingsObserver.onChange(false);
+ }
+ }
+
+ /** Return whether the rotation settings has changed. */
+ private boolean updateSettings() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ boolean shouldUpdateRotation = false;
+
+ synchronized (mLock) {
+ boolean shouldUpdateOrientationListener = false;
+
+ // Configure rotation suggestions.
+ final int showRotationSuggestions = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS_DEFAULT,
+ UserHandle.USER_CURRENT);
+ if (mShowRotationSuggestions != showRotationSuggestions) {
+ mShowRotationSuggestions = showRotationSuggestions;
+ shouldUpdateOrientationListener = true;
+ }
+
+ // Configure rotation lock.
+ final int userRotation = Settings.System.getIntForUser(resolver,
+ Settings.System.USER_ROTATION, Surface.ROTATION_0,
+ UserHandle.USER_CURRENT);
+ if (mUserRotation != userRotation) {
+ mUserRotation = userRotation;
+ shouldUpdateRotation = true;
+ }
+
+ final int userRotationMode = Settings.System.getIntForUser(resolver,
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0
+ ? WindowManagerPolicy.USER_ROTATION_FREE
+ : WindowManagerPolicy.USER_ROTATION_LOCKED;
+ if (mUserRotationMode != userRotationMode) {
+ mUserRotationMode = userRotationMode;
+ shouldUpdateOrientationListener = true;
+ shouldUpdateRotation = true;
+ }
+
+ if (shouldUpdateOrientationListener) {
+ updateOrientationListenerLw(); // Enable or disable the orientation listener.
+ }
+ }
+
+ return shouldUpdateRotation;
}
void dump(String prefix, PrintWriter pw) {
@@ -256,5 +776,106 @@
pw.println(" mSeascapeRotation=" + Surface.rotationToString(mSeascapeRotation));
pw.print(prefix + " mPortraitRotation=" + Surface.rotationToString(mPortraitRotation));
pw.println(" mUpsideDownRotation=" + Surface.rotationToString(mUpsideDownRotation));
+
+ pw.print(prefix + " mSupportAutoRotation=" + mSupportAutoRotation);
+ if (mOrientationListener != null) {
+ pw.print(" mOrientationSensorEnabled=" + mOrientationListener.mEnabled);
+ }
+ pw.println();
+
+ pw.print(prefix + " mCarDockRotation=" + Surface.rotationToString(mCarDockRotation));
+ pw.println(" mDeskDockRotation=" + Surface.rotationToString(mDeskDockRotation));
+ pw.print(prefix + " mUserRotationMode="
+ + WindowManagerPolicy.userRotationModeToString(mUserRotationMode));
+ pw.print(" mUserRotation=" + Surface.rotationToString(mUserRotation));
+ pw.println(" mAllowAllRotations=" + allowAllRotationsToString(mAllowAllRotations));
+
+ pw.print(prefix + " mDemoHdmiRotation=" + Surface.rotationToString(mDemoHdmiRotation));
+ pw.print(" mDemoHdmiRotationLock=" + mDemoHdmiRotationLock);
+ pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation));
+ pw.println(prefix + " mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
+ }
+
+ private class OrientationListener extends WindowOrientationListener {
+ final SparseArray<Runnable> mRunnableCache = new SparseArray<>(5);
+ boolean mEnabled;
+
+ OrientationListener(Context context, Handler handler) {
+ super(context, handler);
+ }
+
+ private class UpdateRunnable implements Runnable {
+ final int mRotation;
+
+ UpdateRunnable(int rotation) {
+ mRotation = rotation;
+ }
+
+ @Override
+ public void run() {
+ // Send interaction hint to improve redraw performance.
+ mService.mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
+ if (isRotationChoicePossible(mCurrentAppOrientation)) {
+ final boolean isValid = isValidRotationChoice(mRotation);
+ sendProposedRotationChangeToStatusBarInternal(mRotation, isValid);
+ } else {
+ mService.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ }
+ }
+ }
+
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation);
+ Runnable r = mRunnableCache.get(rotation, null);
+ if (r == null) {
+ r = new UpdateRunnable(rotation);
+ mRunnableCache.put(rotation, r);
+ }
+ getHandler().post(r);
+ }
+
+ @Override
+ public void enable(boolean clearCurrentRotation) {
+ super.enable(clearCurrentRotation);
+ mEnabled = true;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Enabling listeners");
+ }
+
+ @Override
+ public void disable() {
+ super.disable();
+ mEnabled = false;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Disabling listeners");
+ }
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.ACCELEROMETER_ROTATION), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.USER_ROTATION), false, this,
+ UserHandle.USER_ALL);
+ updateSettings();
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (updateSettings()) {
+ mService.updateRotation(true /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ }
+ }
}
}