blob: 282ed42468ddfe98e25b3d199fe1a9eeb281be81 [file] [log] [blame]
/*
* 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 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.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
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.net.Uri;
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.WindowOrientationListener;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Defines the mapping between orientation and rotation of a display.
* Non-public methods are assumed to run inside WM lock.
*/
public class DisplayRotation {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mDisplayPolicy;
private final DisplayWindowSettings mDisplayWindowSettings;
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;
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
// 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;
/**
* Flag that indicates this is a display that may run better when fixed to user rotation.
*/
private boolean mDefaultFixedToUserRotation;
/**
* No overridden behavior is provided in terms of fixing rotation to user rotation. Use other
* flags to derive the default behavior, such as {@link WindowManagerService#mIsPc} and
* {@link WindowManagerService#mForceDesktopModeOnExternalDisplays}.
*/
static final int FIXED_TO_USER_ROTATION_DEFAULT = 0;
/**
* Don't fix display rotation to {@link #mUserRotation} only. Always allow other factors to play
* a role in deciding display rotation.
*/
static final int FIXED_TO_USER_ROTATION_DISABLED = 1;
/**
* Only use {@link #mUserRotation} as the display rotation.
*/
static final int FIXED_TO_USER_ROTATION_ENABLED = 2;
@IntDef({ FIXED_TO_USER_ROTATION_DEFAULT, FIXED_TO_USER_ROTATION_DISABLED,
FIXED_TO_USER_ROTATION_ENABLED })
@Retention(RetentionPolicy.SOURCE)
@interface FixedToUserRotation {}
/**
* A flag to indicate if the display rotation should be fixed to user specified rotation
* regardless of all other states (including app requrested orientation). {@code true} the
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
*/
private int mFixedToUserRotation = FIXED_TO_USER_ROTATION_DEFAULT;
private int mDemoHdmiRotation;
private int mDemoRotation;
private boolean mDemoHdmiRotationLock;
private boolean mDemoRotationLock;
DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
this(service, displayContent, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
}
@VisibleForTesting
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
Context context, Object lock) {
mService = service;
mDisplayContent = displayContent;
mDisplayPolicy = displayPolicy;
mDisplayWindowSettings = displayWindowSettings;
mContext = context;
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);
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;
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;
}
}
// 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);
// It's physically impossible to rotate the car's screen.
final boolean isCar = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE);
// It's also not likely to rotate a TV screen.
final boolean isTv = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
final boolean forceDesktopMode =
mService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay;
mDefaultFixedToUserRotation =
(isCar || isTv || mService.mIsPc || forceDesktopMode)
// 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();
}
}
}
void restoreSettings(int userRotationMode, int userRotation,
@FixedToUserRotation int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
// We will retrieve user rotation and user rotation mode from settings for default display.
if (isDefaultDisplay) {
return;
}
if (userRotationMode != WindowManagerPolicy.USER_ROTATION_FREE
&& userRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) {
Slog.w(TAG, "Trying to restore an invalid user rotation mode " + userRotationMode
+ " for " + mDisplayContent);
userRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
}
if (userRotation < Surface.ROTATION_0 || userRotation > Surface.ROTATION_270) {
Slog.w(TAG, "Trying to restore an invalid user rotation " + userRotation
+ " for " + mDisplayContent);
userRotation = Surface.ROTATION_0;
}
mUserRotationMode = userRotationMode;
mUserRotation = userRotation;
}
void setFixedToUserRotation(@FixedToUserRotation int fixedToUserRotation) {
if (mFixedToUserRotation == fixedToUserRotation) {
return;
}
mFixedToUserRotation = fixedToUserRotation;
mDisplayWindowSettings.setFixedToUserRotation(mDisplayContent, fixedToUserRotation);
mService.updateRotation(true /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
private void setUserRotation(int userRotationMode, int userRotation) {
if (isDefaultDisplay) {
// We'll be notified via settings listener, so we don't need to update internal values.
final ContentResolver res = mContext.getContentResolver();
final int accelerometerRotation =
userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;
Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,
accelerometerRotation, UserHandle.USER_CURRENT);
Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,
UserHandle.USER_CURRENT);
return;
}
boolean changed = false;
if (mUserRotationMode != userRotationMode) {
mUserRotationMode = userRotationMode;
changed = true;
}
if (mUserRotation != userRotation) {
mUserRotation = userRotation;
changed = true;
}
mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
userRotation);
if (changed) {
mService.updateRotation(true /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
void freezeRotation(int rotation) {
rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
}
void thawRotation() {
setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
}
boolean isRotationFrozen() {
if (!isDefaultDisplay) {
return mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED;
}
return Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) == 0;
}
boolean isFixedToUserRotation() {
switch (mFixedToUserRotation) {
case FIXED_TO_USER_ROTATION_DISABLED:
return false;
case FIXED_TO_USER_ROTATION_ENABLED:
return true;
default:
return mDefaultFixedToUserRotation;
}
}
/**
* Returns {@code true} if this display rotation takes app requested orientation into
* consideration; {@code false} otherwise. For the time being the only case where this is {@code
* false} is when {@link #isFixedToUserRotation()} is {@code true}.
*/
boolean respectAppRequestedOrientation() {
return !isFixedToUserRotation();
}
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 DisplayPolicy getDisplayPolicy() {
return mDisplayPolicy;
}
public WindowOrientationListener getOrientationListener() {
return mOrientationListener;
}
public int getUserRotation() {
return mUserRotation;
}
public int getUserRotationMode() {
return mUserRotationMode;
}
public void updateOrientationListener() {
synchronized (mLock) {
updateOrientationListenerLw();
}
}
/**
* 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();
}
}
/**
* 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 (isFixedToUserRotation()) {
// We are sure we only respect user rotation settings, so we are sure we will not
// support sensor rotation.
return false;
}
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.
* @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 (isFixedToUserRotation()) {
return mUserRotation;
}
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 user rotation.
preferredRotation = mUserRotation;
} 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.
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;
}
}
private boolean isLandscapeOrSeascape(int rotation) {
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
}
private boolean isAnyPortrait(int rotation) {
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
}
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:
// 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 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 (isFixedToUserRotation()) {
return false; // Rotation is forced to user settings.
}
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;
}
/** 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 =
ActivityManager.isLowRamDeviceStatic()
? Settings.Secure.SHOW_ROTATION_SUGGESTIONS_DISABLED
: 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) {
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));
pw.println(prefix + " mSupportAutoRotation=" + mSupportAutoRotation);
if (mOrientationListener != null) {
mOrientationListener.dump(pw, prefix + " ");
}
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));
pw.println(prefix + " mFixedToUserRotation=" + isFixedToUserRotation());
}
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 */);
}
}
}
@VisibleForTesting
interface ContentObserverRegister {
void registerContentObserver(Uri uri, boolean notifyForDescendants,
ContentObserver observer, @UserIdInt int userHandle);
}
}