Support rotation on secondary displays (3/N)

Move major operations of rotation/orientation from
PhoneWindowManager to DisplayRotation.

Create DisplayPolicy and move partial fields which
have dependency with rotation.

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