blob: d37661312cfb195fcaa6ba6de3cdb7c8a0e854cd [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 android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
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.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
import static com.android.server.wm.DisplayRotationProto.FROZEN_TO_USER_ROTATION;
import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATION;
import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION;
import static com.android.server.wm.DisplayRotationProto.ROTATION;
import static com.android.server.wm.DisplayRotationProto.USER_ROTATION;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_HALF_FOLD;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
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.NonNull;
import android.annotation.Nullable;
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.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.RotationUtils;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayAddress;
import android.view.IWindowManager;
import android.view.Surface;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.Set;
/**
* 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;
// Delay in milliseconds when updating config due to folding events. This prevents
// config changes and unexpected jumps while folding the device to closed state.
private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800;
private static final int ROTATION_UNDEFINED = -1;
private static class RotationAnimationPair {
@AnimRes
int mEnter;
@AnimRes
int mExit;
}
@Nullable
final FoldController mFoldController;
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mDisplayPolicy;
private final DisplayWindowSettings mDisplayWindowSettings;
private final Context mContext;
private final Object mLock;
@Nullable
private final DisplayRotationImmersiveAppCompatPolicy mCompatPolicyForImmersiveApps;
public final boolean isDefaultDisplay;
private final boolean mSupportAutoRotation;
private final boolean mAllowRotationResolver;
private final int mLidOpenRotation;
private final int mCarDockRotation;
private final int mDeskDockRotation;
private final int mUndockedHdmiRotation;
private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
private final RotationHistory mRotationHistory = new RotationHistory();
private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
private OrientationListener mOrientationListener;
private StatusBarManagerInternal mStatusBarManagerInternal;
private SettingsObserver mSettingsObserver;
@NonNull
private final DeviceStateController mDeviceStateController;
@NonNull
private final DisplayRotationCoordinator mDisplayRotationCoordinator;
@NonNull
@VisibleForTesting
final Runnable mDefaultDisplayRotationChangedCallback;
@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
@VisibleForTesting
int mSeascapeRotation; // "other" landscape, 180 degrees from mLandscapeRotation
@VisibleForTesting
int mPortraitRotation; // default portrait
@VisibleForTesting
int mUpsideDownRotation; // "other" portrait
int mLastSensorRotation = -1;
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;
/**
* The most recent {@link Surface.Rotation} choice shown to the user for confirmation, or
* {@link #ROTATION_UNDEFINED}
*/
private int mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
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;
private static final int CAMERA_ROTATION_DISABLED = 0;
private static final int CAMERA_ROTATION_ENABLED = 1;
private int mCameraRotationMode = CAMERA_ROTATION_DISABLED;
/**
* Flag that indicates this is a display that may run better when fixed to user rotation.
*/
private boolean mDefaultFixedToUserRotation;
/**
* A flag to indicate if the display rotation should be fixed to user specified rotation
* regardless of all other states (including app requested orientation). {@code true} the
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
*/
private int mFixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
private int mDemoHdmiRotation;
private int mDemoRotation;
private boolean mDemoHdmiRotationLock;
private boolean mDemoRotationLock;
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController,
@NonNull DisplayRotationCoordinator displayRotationCoordinator) {
this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(),
deviceStateController, displayRotationCoordinator);
}
@VisibleForTesting
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayAddress displayAddress, DisplayPolicy displayPolicy,
DisplayWindowSettings displayWindowSettings, Context context, Object lock,
@NonNull DeviceStateController deviceStateController,
@NonNull DisplayRotationCoordinator displayRotationCoordinator) {
mService = service;
mDisplayContent = displayContent;
mDisplayPolicy = displayPolicy;
mDisplayWindowSettings = displayWindowSettings;
mContext = context;
mLock = lock;
mDeviceStateController = deviceStateController;
isDefaultDisplay = displayContent.isDefaultDisplay;
mCompatPolicyForImmersiveApps = initImmersiveAppCompatPolicy(service, displayContent);
mSupportAutoRotation =
mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
mAllowRotationResolver =
mContext.getResources().getBoolean(R.bool.config_allowRotationResolver);
mLidOpenRotation = readRotation(R.integer.config_lidOpenRotation);
mCarDockRotation = readRotation(R.integer.config_carDockRotation);
mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
int defaultRotation = readDefaultDisplayRotation(displayAddress, displayContent);
mRotation = defaultRotation;
mDisplayRotationCoordinator = displayRotationCoordinator;
if (isDefaultDisplay) {
mDisplayRotationCoordinator.setDefaultDisplayDefaultRotation(mRotation);
}
mDefaultDisplayRotationChangedCallback = this::updateRotationAndSendNewConfigIfChanged;
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(displayContent)
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
mDefaultDisplayRotationChangedCallback);
}
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener =
new OrientationListener(mContext, uiHandler, defaultRotation);
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
if (mSupportAutoRotation && isFoldable(mContext)) {
mFoldController = new FoldController();
} else {
mFoldController = null;
}
} else {
mFoldController = null;
}
}
private static boolean isFoldable(Context context) {
return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0;
}
@VisibleForTesting
@Nullable
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
WindowManagerService service, DisplayContent displayContent) {
return DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
service.mLetterboxConfiguration, this, displayContent);
}
// Change the default value to the value specified in the sysprop
// ro.bootanim.set_orientation_<physical_display_id> or
// ro.bootanim.set_orientation_logical_<logical_display_id>.
// Four values are supported: ORIENTATION_0,
// ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
// If the value isn't specified or is ORIENTATION_0, nothing will be changed.
// This is needed to support having default orientation different from the natural
// device orientation. For example, on tablets that may want to keep natural orientation
// portrait for applications compatibility but have landscape orientation as a default choice
// from the UX perspective.
// On watches that may want to keep the wrist orientation as the default.
@Surface.Rotation
private int readDefaultDisplayRotation(DisplayAddress displayAddress,
DisplayContent displayContent) {
String syspropValue = "";
if (displayAddress instanceof DisplayAddress.Physical) {
final DisplayAddress.Physical physicalAddress =
(DisplayAddress.Physical) displayAddress;
syspropValue = SystemProperties.get(
"ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(), "");
}
if ("".equals(syspropValue) && displayContent.isDefaultDisplay) {
syspropValue = SystemProperties.get(
"ro.bootanim.set_orientation_logical_" + displayContent.getDisplayId(), "");
}
if (syspropValue.equals("ORIENTATION_90")) {
return Surface.ROTATION_90;
} else if (syspropValue.equals("ORIENTATION_180")) {
return Surface.ROTATION_180;
} else if (syspropValue.equals("ORIENTATION_270")) {
return Surface.ROTATION_270;
}
return Surface.ROTATION_0;
}
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;
}
@VisibleForTesting
boolean useDefaultSettingsProvider() {
return isDefaultDisplay;
}
/**
* 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) {
final Resources res = mContext.getResources();
if (width > height) {
mLandscapeRotation = Surface.ROTATION_0;
mSeascapeRotation = Surface.ROTATION_180;
if (res.getBoolean(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(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);
mDefaultFixedToUserRotation =
(isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
|| !mDisplayContent.shouldRotateWithContent())
// 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 applyCurrentRotation(@Surface.Rotation int rotation) {
mRotationHistory.addRecord(this, rotation);
if (mOrientationListener != null) {
mOrientationListener.setCurrentRotation(rotation);
}
}
@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.
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, rotation is paused.");
return false;
}
if (mDisplayContent.inTransition()
&& mDisplayContent.getDisplayPolicy().isScreenOnFully()
&& !mDisplayContent.mTransitionController.useShellTransitionsRotation()) {
// 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.
ProtoLog.v(WM_DEBUG_ORIENTATION, "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.
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Deferring rotation, still finishing previous rotation");
return false;
}
if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
// the difference from the latest orientation source.
mLastOrientation = SCREEN_ORIENTATION_UNSET;
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
// rotation update while the animation is running.
return false;
}
}
if (!mService.mDisplayEnabled) {
// No point choosing a rotation if the display is not enabled.
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, display is not enabled.");
return false;
}
@Surface.Rotation
final int oldRotation = mRotation;
@ScreenOrientation
final int lastOrientation = mLastOrientation;
@Surface.Rotation
int rotation = rotationForOrientation(lastOrientation, oldRotation);
// Use the saved rotation for tabletop mode, if set.
if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
int prevRotation = rotation;
rotation = mFoldController.revertOverriddenRotation();
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Reverting orientation. Rotating to %s from %s rather than %s.",
Surface.rotationToString(rotation),
Surface.rotationToString(oldRotation),
Surface.rotationToString(prevRotation));
}
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(
mDisplayRotationCoordinator.getDefaultDisplayCurrentRotation());
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
Surface.rotationToString(rotation), rotation,
displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(oldRotation), oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(rotation), rotation);
if (oldRotation == rotation) {
// No change.
return false;
}
if (isDefaultDisplay) {
mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
}
// Preemptively cancel the running recents animation -- SysUI can't currently handle this
// case properly since the signals it receives all happen post-change. We do this earlier
// in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
// to happen too late.
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
if (recentsAnimationController != null) {
recentsAnimationController.cancelAnimationForDisplayChange();
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
mRotation = rotation;
mDisplayContent.setLayoutNeeded();
mDisplayContent.mWaitingForConfig = true;
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
: new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
oldRotation, mRotation);
mDisplayContent.requestChangeTransitionIfNeeded(
ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
if (wasCollecting) {
// Use remote-rotation infra since the transition has already been requested
// TODO(shell-transitions): Remove this once lifecycle management can cover all
// rotation cases.
startRemoteRotation(oldRotation, mRotation);
}
return true;
}
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
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();
}
// Give a remote handler (system ui) some time to reposition things.
startRemoteRotation(oldRotation, mRotation);
return true;
}
private void startRemoteRotation(int fromRotation, int toRotation) {
mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
fromRotation, toRotation, null /* newDisplayAreaInfo */,
(transaction) -> continueRotation(toRotation, transaction)
);
}
private void continueRotation(int targetRotation, WindowContainerTransaction t) {
if (targetRotation != mRotation) {
// Drop it, this is either coming from an outdated remote rotation; or, we've
// already moved on.
return;
}
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
if (!mDisplayContent.mTransitionController.isCollecting()) {
// The remote may be too slow to response before transition timeout.
Slog.e(TAG, "Trying to continue rotation outside a transition");
}
mDisplayContent.mTransitionController.collect(mDisplayContent);
}
mService.mAtmService.deferWindowLayout();
try {
mDisplayContent.sendNewConfiguration();
if (t != null) {
mService.mAtmService.mWindowOrganizerController.applyTransaction(t);
}
} finally {
mService.mAtmService.continueWindowLayout();
}
}
void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
final RotationAnimationPair anim = selectRotationAnimation();
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
/**
* This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
* set by previous {@link #updateRotationUnchecked}, but another orientation change happens
* before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished)
* and it doesn't choose seamless rotation.
*/
void cancelSeamlessRotation() {
if (!mRotatingSeamlessly) {
return;
}
mDisplayContent.forAllWindows(w -> {
if (w.mSeamlesslyRotated) {
w.cancelSeamlessRotation();
w.mSeamlesslyRotated = false;
}
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
mDisplayContent.finishAsyncRotationIfPossible();
}
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;
}
boolean hasSeamlessRotatingWindow() {
return mSeamlessRotationCount > 0;
}
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
// rotation already, so the rest of the windows can use seamless rotation.
if (mDisplayContent.hasTopFixedRotationLaunchingApp()) {
return true;
}
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.inMultiWindowMode()
|| w.isAnimatingLw()) {
return false;
}
if (!canRotateSeamlessly(oldRotation, newRotation)) {
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.mActivityRecord != null && !w.mActivityRecord.matchParentBounds()) {
return false;
}
// In the presence of the PINNED root task or System Alert windows we unfortunately can not
// seamlessly rotate.
if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask()
|| 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;
}
boolean canRotateSeamlessly(int oldRotation, int newRotation) {
// 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 true;
}
// 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.
return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180;
}
void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
return;
}
w.mSeamlesslyRotated = seamlesslyRotated;
if (seamlesslyRotated) {
mSeamlessRotationCount++;
} else {
mSeamlessRotationCount--;
}
if (mSeamlessRotationCount == 0) {
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
mDisplayContent.finishAsyncRotationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
}
/**
* 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(false /* ignoreScreenOn */);
final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
+ " rotationAnimation=%d forceJumpcut=%b",
topFullscreen,
topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
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, 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(int fixedToUserRotation) {
if (mFixedToUserRotation == fixedToUserRotation) {
return;
}
mFixedToUserRotation = fixedToUserRotation;
mDisplayWindowSettings.setFixedToUserRotation(mDisplayContent, fixedToUserRotation);
if (mDisplayContent.mFocusedApp != null) {
// We record the last focused TDA that respects orientation request, check if this
// change may affect it.
mDisplayContent.onLastFocusedTaskDisplayAreaChanged(
mDisplayContent.mFocusedApp.getDisplayArea());
}
mDisplayContent.updateOrientation();
}
@VisibleForTesting
void setUserRotation(int userRotationMode, int userRotation, String caller) {
mRotationLockHistory.addRecord(userRotationMode, userRotation, caller);
mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
if (useDefaultSettingsProvider()) {
// 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(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
void freezeRotation(int rotation, String caller) {
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
}
rotation = (rotation == -1) ? mRotation : rotation;
setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation, caller);
}
void thawRotation(String caller) {
setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation, caller);
}
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 IWindowManager.FIXED_TO_USER_ROTATION_DISABLED:
return false;
case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED:
return true;
case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION:
return false;
default:
return mDefaultFixedToUserRotation;
}
}
int getFixedToUserRotationMode() {
return mFixedToUserRotation;
}
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();
}
}
/**
* 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
* 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.
ProtoLog.v(WM_DEBUG_ORIENTATION,
"screenOnEarly=%b, awake=%b, currentAppOrientation=%d, "
+ "orientationSensorEnabled=%b, keyguardDrawComplete=%b, "
+ "windowManagerDrawComplete=%b",
screenOnEarly, awake, mCurrentAppOrientation, mOrientationListener.mEnabled,
keyguardDrawComplete, windowManagerDrawComplete);
boolean disable = true;
// If the orientation listener uses a wake sensor, keep the orientation listener on if the
// screen is on (regardless of wake state). This allows the AoD to rotate.
//
// 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 || mOrientationListener.shouldStayEnabledWhileDreaming())
&& ((keyguardDrawComplete && windowManagerDrawComplete))) {
if (needSensorRunning()) {
disable = false;
// Enable listener if not already enabled.
if (!mOrientationListener.mEnabled) {
mOrientationListener.enable();
}
}
}
// Check if sensors need to be disabled.
if (disable) {
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 (mFoldController != null && mFoldController.shouldDisableRotationSensor()) {
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;
}
/**
* 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;
}
/**
* Resets whether the screen can be rotated via the accelerometer in all 4 rotations as the
* default behavior.
*
* To be called if there is potential that the value changed. For example if the active display
* changed.
*
* At the moment it is called from
* {@link DisplayWindowSettings#applyRotationSettingsToDisplayLocked}.
*/
void resetAllowAllRotations() {
mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
}
/**
* 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 ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
* @param lastRotation The most recently used rotation.
* @return The surface rotation to use.
*/
@VisibleForTesting
@Surface.Rotation
int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"rotationForOrientation(orient=%s (%d), last=%s (%d)); user=%s (%d) %s",
ActivityInfo.screenOrientationToString(orientation), orientation,
Surface.rotationToString(lastRotation), lastRotation,
Surface.rotationToString(mUserRotation), mUserRotation,
mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
? "USER_ROTATION_LOCKED" : "");
if (isFixedToUserRotation()) {
return mUserRotation;
}
@Surface.Rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
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();
@Surface.Rotation
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)
&& !(orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED
|| orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)) {
// Ignore sensor when in desk dock unless explicitly enabled.
// This case can 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 (mFixedToUserRotation == IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION) {
preferredRotation = mUserRotation;
} else {
// 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
|| isTabletopAutoRotateOverrideEnabled())
&& (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 (sensorRotation != Surface.ROTATION_180
|| getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED
|| 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
&& orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
&& orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
&& orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
&& orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
// Apply rotation lock. Does not apply to NOSENSOR or specific rotations.
// 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).
// Also avoid setting user rotation when app has preference over one particular rotation
// to avoid leaving the rotation to the reverse of it which has the compatible
// orientation, but isn't what app wants, when the user rotation is the reverse of the
// preferred rotation.
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 int getAllowAllRotations() {
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(
R.bool.config_allowAllRotations)
? ALLOW_ALL_ROTATIONS_ENABLED
: ALLOW_ALL_ROTATIONS_DISABLED;
}
return mAllowAllRotations;
}
boolean isLandscapeOrSeascape(@Surface.Rotation final int rotation) {
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
}
boolean isAnyPortrait(@Surface.Rotation final 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:
// When all rotations enabled it works with any of the 4 rotations
if (getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED) {
return preferredRotation >= 0;
}
// Works with any rotation except upside down.
return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180);
}
return false;
}
private boolean isTabletopAutoRotateOverrideEnabled() {
return mFoldController != null && mFoldController.overrideFrozenRotation();
}
private boolean isRotationChoiceAllowed(@Surface.Rotation final int proposedRotation) {
final boolean isRotationLockEnforced = mCompatPolicyForImmersiveApps != null
&& mCompatPolicyForImmersiveApps.isRotationLockEnforced(proposedRotation);
// Don't show rotation choice button if
if (!isRotationLockEnforced // not enforcing locked rotation
// and the screen rotation is not locked by the user.
&& mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) {
return false;
}
// Don't show rotation choice if we are in tabletop or book modes.
if (isTabletopAutoRotateOverrideEnabled()) 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 (mCurrentAppOrientation) {
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);
}
}
void dispatchProposedRotation(@Surface.Rotation int rotation) {
if (mService.mRotationWatcherController.hasProposedRotationListeners()) {
synchronized (mLock) {
mService.mRotationWatcherController.dispatchProposedRotation(
mDisplayContent, rotation);
}
}
}
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);
}
}
void onDisplayRemoved() {
removeDefaultDisplayRotationChangedCallback();
if (mFoldController != null) {
mFoldController.onDisplayRemoved();
}
}
/** 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.
}
final int cameraRotationMode = Settings.Secure.getIntForUser(resolver,
Settings.Secure.CAMERA_AUTOROTATE, 0,
UserHandle.USER_CURRENT);
if (mCameraRotationMode != cameraRotationMode) {
mCameraRotationMode = cameraRotationMode;
shouldUpdateRotation = true;
}
}
return shouldUpdateRotation;
}
void removeDefaultDisplayRotationChangedCallback() {
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
}
}
/**
* Called from {@link ActivityRecord#setRequestedOrientation(int)}
*/
void onSetRequestedOrientation() {
if (mCompatPolicyForImmersiveApps == null
|| mRotationChoiceShownToUserForConfirmation == ROTATION_UNDEFINED) {
return;
}
mOrientationListener.onProposedRotationChanged(mRotationChoiceShownToUserForConfirmation);
}
void dump(String prefix, PrintWriter pw) {
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));
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.print(" mCameraRotationMode=" + mCameraRotationMode);
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());
if (mFoldController != null) {
pw.println(prefix + "FoldController");
pw.println(prefix + " mPauseAutorotationDuringUnfolding="
+ mFoldController.mPauseAutorotationDuringUnfolding);
pw.println(prefix + " mShouldDisableRotationSensor="
+ mFoldController.mShouldDisableRotationSensor);
pw.println(prefix + " mShouldIgnoreSensorRotation="
+ mFoldController.mShouldIgnoreSensorRotation);
pw.println(prefix + " mLastDisplaySwitchTime="
+ mFoldController.mLastDisplaySwitchTime);
pw.println(prefix + " mLastHingeAngleEventTime="
+ mFoldController.mLastHingeAngleEventTime);
pw.println(prefix + " mDeviceState="
+ mFoldController.mDeviceState);
}
if (!mRotationHistory.mRecords.isEmpty()) {
pw.println();
pw.println(prefix + " RotationHistory");
prefix = " " + prefix;
for (RotationHistory.Record r : mRotationHistory.mRecords) {
r.dump(prefix, pw);
}
}
if (!mRotationLockHistory.mRecords.isEmpty()) {
pw.println();
pw.println(prefix + " RotationLockHistory");
prefix = " " + prefix;
for (RotationLockHistory.Record r : mRotationLockHistory.mRecords) {
r.dump(prefix, pw);
}
}
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(ROTATION, getRotation());
proto.write(FROZEN_TO_USER_ROTATION, isRotationFrozen());
proto.write(USER_ROTATION, getUserRotation());
proto.write(FIXED_TO_USER_ROTATION_MODE, mFixedToUserRotation);
proto.write(LAST_ORIENTATION, mLastOrientation);
proto.write(IS_FIXED_TO_USER_ROTATION, isFixedToUserRotation());
proto.end(token);
}
boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
if (mFoldController == null) return false;
return mFoldController.isDeviceInPosture(state, isTabletop);
}
boolean isDisplaySeparatingHinge() {
return mFoldController != null && mFoldController.isSeparatingHinge();
}
/**
* Called by the display manager just before it applied the device state, it is guaranteed
* that in case of physical display change the {@link DisplayRotation#physicalDisplayChanged}
* method will be invoked *after* this one.
*/
void foldStateChanged(DeviceStateController.DeviceState deviceState) {
if (mFoldController != null) {
synchronized (mLock) {
mFoldController.foldStateChanged(deviceState);
}
}
}
/**
* Called by the DisplayContent when the physical display changes
*/
void physicalDisplayChanged() {
if (mFoldController != null) {
mFoldController.onPhysicalDisplayChanged();
}
}
@VisibleForTesting
long uptimeMillis() {
return SystemClock.uptimeMillis();
}
class FoldController {
private final boolean mPauseAutorotationDuringUnfolding;
@Surface.Rotation
private int mHalfFoldSavedRotation = -1; // No saved rotation
private DeviceStateController.DeviceState mDeviceState =
DeviceStateController.DeviceState.UNKNOWN;
private long mLastHingeAngleEventTime = 0;
private long mLastDisplaySwitchTime = 0;
private boolean mShouldIgnoreSensorRotation;
private boolean mShouldDisableRotationSensor;
private boolean mInHalfFoldTransition = false;
private int mDisplaySwitchRotationBlockTimeMs;
private int mHingeAngleRotationBlockTimeMs;
private int mMaxHingeAngle;
private final boolean mIsDisplayAlwaysSeparatingHinge;
private SensorManager mSensorManager;
private SensorEventListener mHingeAngleSensorEventListener;
private final Set<Integer> mTabletopRotations;
private final Runnable mActivityBoundsUpdateCallback;
private final boolean mAllowHalfFoldAutoRotationOverride;
FoldController() {
mAllowHalfFoldAutoRotationOverride = mContext.getResources().getBoolean(
R.bool.config_windowManagerHalfFoldAutoRotateOverride);
mTabletopRotations = new ArraySet<>();
int[] tabletop_rotations = mContext.getResources().getIntArray(
R.array.config_deviceTabletopRotations);
if (tabletop_rotations != null) {
for (int angle : tabletop_rotations) {
switch (angle) {
case 0:
mTabletopRotations.add(Surface.ROTATION_0);
break;
case 90:
mTabletopRotations.add(Surface.ROTATION_90);
break;
case 180:
mTabletopRotations.add(Surface.ROTATION_180);
break;
case 270:
mTabletopRotations.add(Surface.ROTATION_270);
break;
default:
ProtoLog.e(WM_DEBUG_ORIENTATION,
"Invalid surface rotation angle in "
+ "config_deviceTabletopRotations: %d",
angle);
}
}
} else {
ProtoLog.w(WM_DEBUG_ORIENTATION,
"config_deviceTabletopRotations is not defined. Half-fold "
+ "letterboxing will work inconsistently.");
}
mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean(
R.bool.config_isDisplayHingeAlwaysSeparating);
mActivityBoundsUpdateCallback = new Runnable() {
public void run() {
if (mDeviceState == DeviceStateController.DeviceState.OPEN
|| mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
synchronized (mLock) {
final Task topFullscreenTask =
mDisplayContent.getTask(
t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
if (topFullscreenTask != null) {
final ActivityRecord top =
topFullscreenTask.topRunningActivity();
if (top != null) {
top.recomputeConfiguration();
}
}
}
}
}
};
mPauseAutorotationDuringUnfolding = mContext.getResources().getBoolean(
R.bool.config_windowManagerPauseRotationWhenUnfolding);
if (mPauseAutorotationDuringUnfolding) {
mDisplaySwitchRotationBlockTimeMs = mContext.getResources().getInteger(
R.integer.config_pauseRotationWhenUnfolding_displaySwitchTimeout);
mHingeAngleRotationBlockTimeMs = mContext.getResources().getInteger(
R.integer.config_pauseRotationWhenUnfolding_hingeEventTimeout);
mMaxHingeAngle = mContext.getResources().getInteger(
R.integer.config_pauseRotationWhenUnfolding_maxHingeAngle);
registerSensorManager();
}
}
private void registerSensorManager() {
mSensorManager = mContext.getSystemService(SensorManager.class);
if (mSensorManager != null) {
final Sensor hingeAngleSensor = mSensorManager
.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
if (hingeAngleSensor != null) {
mHingeAngleSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
onHingeAngleChanged(event.values[0]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
mSensorManager.registerListener(mHingeAngleSensorEventListener,
hingeAngleSensor, SensorManager.SENSOR_DELAY_FASTEST, getHandler());
}
}
}
void onDisplayRemoved() {
if (mSensorManager != null && mHingeAngleSensorEventListener != null) {
mSensorManager.unregisterListener(mHingeAngleSensorEventListener);
}
}
boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
if (state != mDeviceState) {
return false;
}
if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
return isTabletop == mTabletopRotations.contains(mRotation);
}
return true;
}
DeviceStateController.DeviceState getFoldState() {
return mDeviceState;
}
boolean isSeparatingHinge() {
return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED
|| (mDeviceState == DeviceStateController.DeviceState.OPEN
&& mIsDisplayAlwaysSeparatingHinge);
}
boolean overrideFrozenRotation() {
return mAllowHalfFoldAutoRotationOverride
&& mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
}
boolean shouldRevertOverriddenRotation() {
// When transitioning to open.
return mAllowHalfFoldAutoRotationOverride
&& mDeviceState == DeviceStateController.DeviceState.OPEN
&& !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
&& mInHalfFoldTransition
&& mDisplayContent.getRotationReversionController().isOverrideActive(
REVERSION_TYPE_HALF_FOLD)
&& mUserRotationMode
== WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
}
int revertOverriddenRotation() {
int savedRotation = mHalfFoldSavedRotation;
mHalfFoldSavedRotation = -1;
mDisplayContent.getRotationReversionController()
.revertOverride(REVERSION_TYPE_HALF_FOLD);
mInHalfFoldTransition = false;
return savedRotation;
}
void foldStateChanged(DeviceStateController.DeviceState newState) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"foldStateChanged: displayId %d, halfFoldStateChanged %s, "
+ "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, "
+ "mLastOrientation: %d, mRotation: %d",
mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation,
mUserRotation, mLastSensorRotation, mLastOrientation, mRotation);
if (mDeviceState == DeviceStateController.DeviceState.UNKNOWN) {
mDeviceState = newState;
return;
}
if (newState == DeviceStateController.DeviceState.HALF_FOLDED
&& mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) {
// The device has transitioned to HALF_FOLDED state: save the current rotation and
// update the device rotation.
mDisplayContent.getRotationReversionController().beforeOverrideApplied(
REVERSION_TYPE_HALF_FOLD);
mHalfFoldSavedRotation = mRotation;
mDeviceState = newState;
// Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
// return true, so rotation is unlocked.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
} else {
mInHalfFoldTransition = true;
mDeviceState = newState;
// Tell the device to update its orientation.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
// Alert the activity of possible new bounds.
UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback);
UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
FOLDING_RECOMPUTE_CONFIG_DELAY_MS);
}
boolean shouldIgnoreSensorRotation() {
return mShouldIgnoreSensorRotation;
}
boolean shouldDisableRotationSensor() {
return mShouldDisableRotationSensor;
}
private void updateSensorRotationBlockIfNeeded() {
final long currentTime = uptimeMillis();
final boolean newShouldIgnoreRotation =
currentTime - mLastDisplaySwitchTime < mDisplaySwitchRotationBlockTimeMs
|| currentTime - mLastHingeAngleEventTime < mHingeAngleRotationBlockTimeMs;
if (newShouldIgnoreRotation != mShouldIgnoreSensorRotation) {
mShouldIgnoreSensorRotation = newShouldIgnoreRotation;
// Resuming the autorotation
if (!mShouldIgnoreSensorRotation) {
if (mShouldDisableRotationSensor) {
// Sensor was disabled, let's re-enable it
mShouldDisableRotationSensor = false;
updateOrientationListenerLw();
} else {
// Sensor was not disabled, let's update the rotation in case if we received
// some rotation sensor updates when autorotate was disabled
updateRotationAndSendNewConfigIfChanged();
}
}
}
}
void onPhysicalDisplayChanged() {
if (!mPauseAutorotationDuringUnfolding) return;
mLastDisplaySwitchTime = uptimeMillis();
final boolean isUnfolding =
mDeviceState == DeviceStateController.DeviceState.OPEN
|| mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
if (isUnfolding) {
// Temporary disable rotation sensor updates when unfolding
mShouldDisableRotationSensor = true;
updateOrientationListenerLw();
}
updateSensorRotationBlockIfNeeded();
getHandler().postDelayed(() -> {
synchronized (mLock) {
updateSensorRotationBlockIfNeeded();
};
}, mDisplaySwitchRotationBlockTimeMs);
}
void onHingeAngleChanged(float hingeAngle) {
if (hingeAngle < mMaxHingeAngle) {
mLastHingeAngleEventTime = uptimeMillis();
updateSensorRotationBlockIfNeeded();
getHandler().postDelayed(() -> {
synchronized (mLock) {
updateSensorRotationBlockIfNeeded();
};
}, mHingeAngleRotationBlockTimeMs);
}
}
}
@VisibleForTesting
Handler getHandler() {
return mService.mH;
}
private class OrientationListener extends WindowOrientationListener implements Runnable {
transient boolean mEnabled;
OrientationListener(Context context, Handler handler,
@Surface.Rotation int defaultRotation) {
super(context, handler, defaultRotation);
}
@Override
public boolean isKeyguardShowingAndNotOccluded() {
return mService.isKeyguardShowingAndNotOccluded();
}
@Override
public boolean isRotationResolverEnabled() {
return mAllowRotationResolver
&& mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
&& mCameraRotationMode == CAMERA_ROTATION_ENABLED
&& !mService.mPowerManager.isPowerSaveMode();
}
@Override
public void onProposedRotationChanged(@Surface.Rotation int rotation) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
// Send interaction power boost to improve redraw performance.
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
dispatchProposedRotation(rotation);
if (isRotationChoiceAllowed(rotation)) {
mRotationChoiceShownToUserForConfirmation = rotation;
final boolean isValid = isValidRotationChoice(rotation);
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
} else {
mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
@Override
public void enable() {
mEnabled = true;
getHandler().post(this);
ProtoLog.v(WM_DEBUG_ORIENTATION, "Enabling listeners");
}
@Override
public void disable() {
mEnabled = false;
getHandler().post(this);
ProtoLog.v(WM_DEBUG_ORIENTATION, "Disabling listeners");
}
@Override
public void run() {
if (mEnabled) {
super.enable();
} else {
super.disable();
}
}
}
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);
resolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_AUTOROTATE), false, this,
UserHandle.USER_ALL);
updateSettings();
}
@Override
public void onChange(boolean selfChange) {
if (updateSettings()) {
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
}
private static class RotationLockHistory {
private static final int MAX_SIZE = 8;
private static class Record {
@WindowManagerPolicy.UserRotationMode final int mUserRotationMode;
@Surface.Rotation final int mUserRotation;
final String mCaller;
final long mTimestamp = System.currentTimeMillis();
private Record(int userRotationMode, int userRotation, String caller) {
mUserRotationMode = userRotationMode;
mUserRotation = userRotation;
mCaller = caller;
}
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp) + ": "
+ "mode=" + WindowManagerPolicy.userRotationModeToString(mUserRotationMode)
+ ", rotation=" + Surface.rotationToString(mUserRotation)
+ ", caller=" + mCaller);
}
}
private final ArrayDeque<RotationLockHistory.Record> mRecords = new ArrayDeque<>(MAX_SIZE);
void addRecord(@WindowManagerPolicy.UserRotationMode int userRotationMode,
@Surface.Rotation int userRotation, String caller) {
if (mRecords.size() >= MAX_SIZE) {
mRecords.removeFirst();
}
mRecords.addLast(new Record(userRotationMode, userRotation, caller));
}
}
private static class RotationHistory {
private static final int MAX_SIZE = 8;
private static final int NO_FOLD_CONTROLLER = -2;
private static class Record {
final @Surface.Rotation int mFromRotation;
final @Surface.Rotation int mToRotation;
final @Surface.Rotation int mUserRotation;
final @WindowManagerPolicy.UserRotationMode int mUserRotationMode;
final int mSensorRotation;
final boolean mIgnoreOrientationRequest;
final String mNonDefaultRequestingTaskDisplayArea;
final String mLastOrientationSource;
final @ActivityInfo.ScreenOrientation int mSourceOrientation;
final long mTimestamp = System.currentTimeMillis();
final int mHalfFoldSavedRotation;
final boolean mInHalfFoldTransition;
final DeviceStateController.DeviceState mDeviceState;
@Nullable final boolean[] mRotationReversionSlots;
@Nullable final String mDisplayRotationCompatPolicySummary;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
mFromRotation = fromRotation;
mToRotation = toRotation;
mUserRotation = dr.mUserRotation;
mUserRotationMode = dr.mUserRotationMode;
final OrientationListener listener = dr.mOrientationListener;
mSensorRotation = (listener == null || !listener.mEnabled)
? -2 /* disabled */ : dr.mLastSensorRotation;
final DisplayContent dc = dr.mDisplayContent;
mIgnoreOrientationRequest = dc.getIgnoreOrientationRequest();
final TaskDisplayArea requestingTda = dc.getOrientationRequestingTaskDisplayArea();
mNonDefaultRequestingTaskDisplayArea = requestingTda == null
? "none" : requestingTda != dc.getDefaultTaskDisplayArea()
? requestingTda.toString() : null;
final WindowContainer<?> source = dc.getLastOrientationSource();
if (source != null) {
mLastOrientationSource = source.toString();
final WindowState w = source.asWindowState();
mSourceOrientation = w != null
? w.mAttrs.screenOrientation
: source.getOverrideOrientation();
} else {
mLastOrientationSource = null;
mSourceOrientation = SCREEN_ORIENTATION_UNSET;
}
if (dr.mFoldController != null) {
mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
mDeviceState = dr.mFoldController.mDeviceState;
} else {
mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
mInHalfFoldTransition = false;
mDeviceState = DeviceStateController.DeviceState.UNKNOWN;
}
mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
? null
: dc.mDisplayRotationCompatPolicy
.getSummaryForDisplayRotationHistoryRecord();
mRotationReversionSlots =
dr.mDisplayContent.getRotationReversionController().getSlotsCopy();
}
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp)
+ " " + Surface.rotationToString(mFromRotation)
+ " to " + Surface.rotationToString(mToRotation));
pw.println(prefix + " source=" + mLastOrientationSource
+ " " + ActivityInfo.screenOrientationToString(mSourceOrientation));
pw.println(prefix + " mode="
+ WindowManagerPolicy.userRotationModeToString(mUserRotationMode)
+ " user=" + Surface.rotationToString(mUserRotation)
+ " sensor=" + Surface.rotationToString(mSensorRotation));
if (mIgnoreOrientationRequest) pw.println(prefix + " ignoreRequest=true");
if (mNonDefaultRequestingTaskDisplayArea != null) {
pw.println(prefix + " requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
}
if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
pw.println(prefix + " halfFoldSavedRotation="
+ mHalfFoldSavedRotation
+ " mInHalfFoldTransition=" + mInHalfFoldTransition
+ " mFoldState=" + mDeviceState);
}
if (mDisplayRotationCompatPolicySummary != null) {
pw.println(prefix + mDisplayRotationCompatPolicySummary);
}
if (mRotationReversionSlots != null) {
pw.println(prefix + " reversionSlots= NOSENSOR "
+ mRotationReversionSlots[REVERSION_TYPE_NOSENSOR] + ", CAMERA "
+ mRotationReversionSlots[REVERSION_TYPE_CAMERA_COMPAT] + " HALF_FOLD "
+ mRotationReversionSlots[REVERSION_TYPE_HALF_FOLD]);
}
}
}
final ArrayDeque<Record> mRecords = new ArrayDeque<>(MAX_SIZE);
void addRecord(DisplayRotation dr, int toRotation) {
if (mRecords.size() >= MAX_SIZE) {
mRecords.removeFirst();
}
final int fromRotation = dr.mDisplayContent.getWindowConfiguration().getRotation();
mRecords.addLast(new Record(dr, fromRotation, toRotation));
}
}
}