Persist user rotations of external displays.

It also introduces a command line tool to rotate external displays for
testing purposes.

Instead of changing signatures of freezeRotation(), thawRotation() and
isRotationFrozen(), introduce 3 new methods to IWindowManager interface.
The old methods are being used pervasively.

Also resolved some style issues in DisplaySettings class.

Bug: 113252523
Bug: 111361251
Test: Rotation locks via settings still work. User rotation mode and
user rotation values for external devices are persisted as expected in
storage. DisplaySettingsTests passes.

Change-Id: I1746bea98a588f0bbd30c9f0617a7a23dc6e4209
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 3e559d9..0c3a295 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -226,27 +226,52 @@
     int getPreferredOptionsPanelGravity(int displayId);
 
     /**
-     * Lock the device orientation to the specified rotation, or to the
-     * current rotation if -1.  Sensor input will be ignored until
-     * thawRotation() is called.
-     * @hide
+     * Equivalent to calling {@link #freezeDisplayRotation(int, int)} with {@link
+     * android.view.Display#DEFAULT_DISPLAY} and given rotation.
      */
     void freezeRotation(int rotation);
 
     /**
-     * Release the orientation lock imposed by freezeRotation().
-     * @hide
+     * Equivalent to calling {@link #thawDisplayRotation(int)} with {@link
+     * android.view.Display#DEFAULT_DISPLAY}.
      */
     void thawRotation();
 
     /**
-     * Gets whether the rotation is frozen.
-     *
-     * @return Whether the rotation is frozen.
+     * Equivelant to call {@link #isDisplayRotationFrozen(int)} with {@link
+     * android.view.Display#DEFAULT_DISPLAY}.
      */
     boolean isRotationFrozen();
 
     /**
+     * Lock the display orientation to the specified rotation, or to the current
+     * rotation if -1. Sensor input will be ignored until thawRotation() is called.
+     *
+     * @param displayId the ID of display which rotation should be frozen.
+     * @param rotation one of {@link android.view.Surface#ROTATION_0},
+     *        {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180},
+     *        {@link android.view.Surface#ROTATION_270} or -1 to freeze it to current rotation.
+     * @hide
+     */
+    void freezeDisplayRotation(int displayId, int rotation);
+
+    /**
+     * Release the orientation lock imposed by freezeRotation() on the display.
+     *
+     * @param displayId the ID of display which rotation should be thawed.
+     * @hide
+     */
+    void thawDisplayRotation(int displayId);
+
+    /**
+     * Gets whether the rotation is frozen on the display.
+     *
+     * @param displayId the ID of display which frozen is needed.
+     * @return Whether the rotation is frozen.
+     */
+    boolean isDisplayRotationFrozen(int displayId);
+
+    /**
      * Screenshot the current wallpaper layer, including the whole screen.
      */
     Bitmap screenshotWallpaper();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 06ee935..7489267 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6860,37 +6860,6 @@
     }
 
     @Override
-    public int getUserRotationMode() {
-        return Settings.System.getIntForUser(mContext.getContentResolver(),
-                Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0 ?
-                        WindowManagerPolicy.USER_ROTATION_FREE :
-                                WindowManagerPolicy.USER_ROTATION_LOCKED;
-    }
-
-    // User rotation: to be used when all else fails in assigning an orientation to the device
-    @Override
-    public void setUserRotationMode(int mode, int rot) {
-        ContentResolver res = mContext.getContentResolver();
-
-        // mUserRotationMode and mUserRotation will be assigned by the content observer
-        if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
-            Settings.System.putIntForUser(res,
-                    Settings.System.USER_ROTATION,
-                    rot,
-                    UserHandle.USER_CURRENT);
-            Settings.System.putIntForUser(res,
-                    Settings.System.ACCELEROMETER_ROTATION,
-                    0,
-                    UserHandle.USER_CURRENT);
-        } else {
-            Settings.System.putIntForUser(res,
-                    Settings.System.ACCELEROMETER_ROTATION,
-                    1,
-                    UserHandle.USER_CURRENT);
-        }
-    }
-
-    @Override
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
         if (safeMode) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 27ab3ef..37b769b 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -82,7 +82,6 @@
 import android.view.IWindowManager;
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
-import android.view.Surface;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicyConstants;
@@ -1483,26 +1482,6 @@
     public void keepScreenOnStoppedLw();
 
     /**
-     * Gets the current user rotation mode.
-     *
-     * @return The rotation mode.
-     *
-     * @see #USER_ROTATION_LOCKED
-     * @see #USER_ROTATION_FREE
-     */
-    @UserRotationMode
-    public int getUserRotationMode();
-
-    /**
-     * Inform the policy that the user has chosen a preferred orientation ("rotation lock").
-     *
-     * @param mode One of {@link #USER_ROTATION_LOCKED} or {@link #USER_ROTATION_FREE}.
-     * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
-     *                 {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
-     */
-    public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation);
-
-    /**
      * Called when a new system UI visibility is being reported, allowing
      * the policy to adjust what is actually reported.
      * @param visibility The raw visibility reported by the status bar.
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index bd82553..847cff9 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -55,6 +55,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
 
     private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
     private final DisplayPolicy mDisplayPolicy;
     private final Context mContext;
     private final Object mLock;
@@ -106,6 +107,7 @@
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
             DisplayPolicy displayPolicy, Context context, Object lock) {
         mService = service;
+        mDisplayContent = displayContent;
         mDisplayPolicy = displayPolicy;
         mContext = context;
         mLock = lock;
@@ -225,6 +227,70 @@
         }
     }
 
+    void restoreUserRotation(int userRotationMode, int userRotation) {
+        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;
+    }
+
+    private void setUserRotation(int userRotationMode, int userRotation) {
+        if (isDefaultDisplay) {
+            // We'll be notified via settings listener, so we don't need to update internal values.
+            final ContentResolver res = mContext.getContentResolver();
+            final int accelerometerRotation =
+                    userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;
+            Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,
+                    accelerometerRotation, UserHandle.USER_CURRENT);
+            Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,
+                    UserHandle.USER_CURRENT);
+            return;
+        }
+
+        boolean changed = false;
+        if (mUserRotationMode != userRotationMode) {
+            mUserRotationMode = userRotationMode;
+            changed = true;
+        }
+        if (mUserRotation != userRotation) {
+            mUserRotation = userRotation;
+            changed = true;
+        }
+        mService.mDisplaySettings.setUserRotation(mDisplayContent, userRotationMode, userRotation);
+        if (changed) {
+            mService.updateRotation(true /* alwaysSendConfiguration */,
+                    false /* forceRelayout */);
+            mService.mDisplaySettings.writeSettingsLocked();
+        }
+    }
+
+    void freezeRotation(int rotation) {
+        rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
+        setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
+    }
+
+    void thawRotation() {
+        setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
+    }
+
+    boolean isRotationFrozen() {
+        if (!isDefaultDisplay) {
+            return mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED;
+        }
+
+        return Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) == 0;
+    }
+
     /** @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true. */
     boolean isDefaultOrientationForced() {
         return mForceDefaultOrientation;
@@ -381,9 +447,6 @@
      * @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) {
@@ -418,8 +481,8 @@
         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;
+            // 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;
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
index bbb690f..28dc008 100644
--- a/services/core/java/com/android/server/wm/DisplaySettings.java
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -20,20 +20,23 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Environment;
-import android.provider.Settings;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
+import com.android.server.policy.WindowManagerPolicy;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -43,10 +46,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 /**
  * Current persistent settings about a display
  */
@@ -58,15 +57,25 @@
     private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
 
     private static class Entry {
-        private final String name;
-        private int overscanLeft;
-        private int overscanTop;
-        private int overscanRight;
-        private int overscanBottom;
-        private int windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+        private final String mName;
+        private int mOverscanLeft;
+        private int mOverscanTop;
+        private int mOverscanRight;
+        private int mOverscanBottom;
+        private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+        private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+        private int mUserRotation = Surface.ROTATION_0;
 
         private Entry(String _name) {
-            name = _name;
+            mName = _name;
+        }
+
+        private boolean isEmpty() {
+            return mOverscanLeft == 0 && mOverscanTop == 0 && mOverscanRight == 0
+                    && mOverscanBottom == 0
+                    && mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED
+                    && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+                    && mUserRotation == Surface.ROTATION_0;
         }
     }
 
@@ -90,13 +99,30 @@
         return entry;
     }
 
+    private Entry getOrCreateEntry(String uniqueId, String name) {
+        Entry entry = getEntry(uniqueId, name);
+        if (entry == null) {
+            entry = new Entry(uniqueId);
+            mEntries.put(uniqueId, entry);
+        }
+        return entry;
+    }
+
+    private void removeEntryIfEmpty(String uniqueId, String name) {
+        final Entry entry = getEntry(uniqueId, name);
+        if (entry.isEmpty()) {
+            mEntries.remove(uniqueId);
+            mEntries.remove(name);
+        }
+    }
+
     private void getOverscanLocked(String name, String uniqueId, Rect outRect) {
         final Entry entry = getEntry(name, uniqueId);
         if (entry != null) {
-            outRect.left = entry.overscanLeft;
-            outRect.top = entry.overscanTop;
-            outRect.right = entry.overscanRight;
-            outRect.bottom = entry.overscanBottom;
+            outRect.left = entry.mOverscanLeft;
+            outRect.top = entry.mOverscanTop;
+            outRect.right = entry.mOverscanRight;
+            outRect.bottom = entry.mOverscanBottom;
         } else {
             outRect.set(0, 0, 0, 0);
         }
@@ -104,28 +130,22 @@
 
     void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
             int bottom) {
-        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
-            // Right now all we are storing is overscan; if there is no overscan,
-            // we have no need for the entry.
-            mEntries.remove(uniqueId);
-            // Legacy name might have been in used, so we need to clear it.
-            mEntries.remove(name);
+        Entry entry = mEntries.get(uniqueId);
+        if (left == 0 && top == 0 && right == 0 && bottom == 0 && entry == null) {
+            // All default value, no action needed.
             return;
         }
-        Entry entry = mEntries.get(uniqueId);
-        if (entry == null) {
-            entry = new Entry(uniqueId);
-            mEntries.put(uniqueId, entry);
-        }
-        entry.overscanLeft = left;
-        entry.overscanTop = top;
-        entry.overscanRight = right;
-        entry.overscanBottom = bottom;
+        entry = getOrCreateEntry(uniqueId, name);
+        entry.mOverscanLeft = left;
+        entry.mOverscanTop = top;
+        entry.mOverscanRight = right;
+        entry.mOverscanBottom = bottom;
+        removeEntryIfEmpty(uniqueId, name);
     }
 
     private int getWindowingModeLocked(String name, String uniqueId, int displayId) {
         final Entry entry = getEntry(name, uniqueId);
-        int windowingMode = entry != null ? entry.windowingMode
+        int windowingMode = entry != null ? entry.mWindowingMode
                 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
         // back to fullscreen.
@@ -148,6 +168,36 @@
         return windowingMode;
     }
 
+    void setUserRotation(DisplayContent dc, int rotationMode, int rotation) {
+        final DisplayInfo displayInfo = dc.getDisplayInfo();
+
+        final String uniqueId = displayInfo.uniqueId;
+        final String name = displayInfo.name;
+        Entry entry = getEntry(displayInfo.name, uniqueId);
+        if (rotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+                && rotation == Surface.ROTATION_0 && entry == null) {
+            // All default values. No action needed.
+            return;
+        }
+
+        entry = getOrCreateEntry(uniqueId, name);
+        entry.mUserRotationMode = rotationMode;
+        entry.mUserRotation = rotation;
+        removeEntryIfEmpty(uniqueId, name);
+    }
+
+    private void restoreUserRotation(DisplayContent dc) {
+        final DisplayInfo info = dc.getDisplayInfo();
+
+        final Entry entry = getEntry(info.name, info.uniqueId);
+        final int userRotationMode = entry != null ? entry.mUserRotationMode
+                : WindowManagerPolicy.USER_ROTATION_FREE;
+        final int userRotation = entry != null ? entry.mUserRotation
+                : Surface.ROTATION_0;
+
+        dc.getDisplayRotation().restoreUserRotation(userRotationMode, userRotation);
+    }
+
     void applySettingsToDisplayLocked(DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
 
@@ -161,6 +211,8 @@
         displayInfo.overscanTop = rect.top;
         displayInfo.overscanRight = rect.right;
         displayInfo.overscanBottom = rect.bottom;
+
+        restoreUserRotation(dc);
     }
 
     void readSettingsLocked() {
@@ -244,12 +296,16 @@
         String name = parser.getAttributeValue(null, "name");
         if (name != null) {
             Entry entry = new Entry(name);
-            entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
-            entry.overscanTop = getIntAttribute(parser, "overscanTop");
-            entry.overscanRight = getIntAttribute(parser, "overscanRight");
-            entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
-            entry.windowingMode = getIntAttribute(parser, "windowingMode",
+            entry.mOverscanLeft = getIntAttribute(parser, "overscanLeft");
+            entry.mOverscanTop = getIntAttribute(parser, "overscanTop");
+            entry.mOverscanRight = getIntAttribute(parser, "overscanRight");
+            entry.mOverscanBottom = getIntAttribute(parser, "overscanBottom");
+            entry.mWindowingMode = getIntAttribute(parser, "windowingMode",
                     WindowConfiguration.WINDOWING_MODE_UNDEFINED);
+            entry.mUserRotationMode = getIntAttribute(parser, "userRotationMode",
+                    WindowManagerPolicy.USER_ROTATION_FREE);
+            entry.mUserRotation = getIntAttribute(parser, "userRotation",
+                    Surface.ROTATION_0);
             mEntries.put(name, entry);
         }
         XmlUtils.skipCurrentTag(parser);
@@ -272,21 +328,28 @@
 
             for (Entry entry : mEntries.values()) {
                 out.startTag(null, "display");
-                out.attribute(null, "name", entry.name);
-                if (entry.overscanLeft != 0) {
-                    out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
+                out.attribute(null, "name", entry.mName);
+                if (entry.mOverscanLeft != 0) {
+                    out.attribute(null, "overscanLeft", Integer.toString(entry.mOverscanLeft));
                 }
-                if (entry.overscanTop != 0) {
-                    out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
+                if (entry.mOverscanTop != 0) {
+                    out.attribute(null, "overscanTop", Integer.toString(entry.mOverscanTop));
                 }
-                if (entry.overscanRight != 0) {
-                    out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
+                if (entry.mOverscanRight != 0) {
+                    out.attribute(null, "overscanRight", Integer.toString(entry.mOverscanRight));
                 }
-                if (entry.overscanBottom != 0) {
-                    out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
+                if (entry.mOverscanBottom != 0) {
+                    out.attribute(null, "overscanBottom", Integer.toString(entry.mOverscanBottom));
                 }
-                if (entry.windowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-                    out.attribute(null, "windowingMode", Integer.toString(entry.windowingMode));
+                if (entry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+                    out.attribute(null, "windowingMode", Integer.toString(entry.mWindowingMode));
+                }
+                if (entry.mUserRotationMode != WindowManagerPolicy.USER_ROTATION_FREE) {
+                    out.attribute(null, "userRotationMode",
+                            Integer.toString(entry.mUserRotationMode));
+                }
+                if (entry.mUserRotation != Surface.ROTATION_0) {
+                    out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation));
                 }
                 out.endTag(null, "display");
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8b1b327..e0916fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3672,14 +3672,19 @@
         }
     }
 
+    @Override
+    public void freezeRotation(int rotation) {
+        freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
+    }
+
     /**
      * Freeze rotation changes.  (Enable "rotation lock".)
      * Persists across reboots.
-     * @param rotation The desired rotation to freeze to, or -1 to use the
-     * current rotation.
+     * @param displayId The ID of the display to freeze.
+     * @param rotation The desired rotation to freeze to, or -1 to use the current rotation.
      */
     @Override
-    public void freezeRotation(int rotation) {
+    public void freezeDisplayRotation(int displayId, int rotation) {
         // TODO(multi-display): Track which display is rotated.
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
                 "freezeRotation()")) {
@@ -3690,14 +3695,16 @@
                     + "rotation constant.");
         }
 
-        final int defaultDisplayRotation = getDefaultDisplayRotation();
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "freezeRotation: mRotation="
-                + defaultDisplayRotation);
-
         long origId = Binder.clearCallingIdentity();
         try {
-            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED,
-                    rotation == -1 ? defaultDisplayRotation : rotation);
+            synchronized (mWindowMap) {
+                final DisplayContent display = mRoot.getDisplayContent(displayId);
+                if (display == null) {
+                    Slog.w(TAG, "Trying to freeze rotation for a missing display.");
+                    return;
+                }
+                display.getDisplayRotation().freezeRotation(rotation);
+            }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
@@ -3705,12 +3712,17 @@
         updateRotationUnchecked(false, false);
     }
 
+    @Override
+    public void thawRotation() {
+        thawDisplayRotation(Display.DEFAULT_DISPLAY);
+    }
+
     /**
      * Thaw rotation changes.  (Disable "rotation lock".)
      * Persists across reboots.
      */
     @Override
-    public void thawRotation() {
+    public void thawDisplayRotation(int displayId) {
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
                 "thawRotation()")) {
             throw new SecurityException("Requires SET_ORIENTATION permission");
@@ -3721,8 +3733,14 @@
 
         long origId = Binder.clearCallingIdentity();
         try {
-            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE,
-                    777); // rot not used
+            synchronized (mWindowMap) {
+                final DisplayContent display = mRoot.getDisplayContent(displayId);
+                if (display == null) {
+                    Slog.w(TAG, "Trying to thaw rotation for a missing display.");
+                    return;
+                }
+                display.getDisplayRotation().thawRotation();
+            }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
@@ -3730,6 +3748,23 @@
         updateRotationUnchecked(false, false);
     }
 
+    @Override
+    public boolean isRotationFrozen() {
+        return isDisplayRotationFrozen(Display.DEFAULT_DISPLAY);
+    }
+
+    @Override
+    public boolean isDisplayRotationFrozen(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent display = mRoot.getDisplayContent(displayId);
+            if (display == null) {
+                Slog.w(TAG, "Trying to thaw rotation for a missing display.");
+                return false;
+            }
+            return display.getDisplayRotation().isRotationFrozen();
+        }
+    }
+
     /**
      * Recalculate the current rotation.
      *
@@ -3799,11 +3834,6 @@
     }
 
     @Override
-    public boolean isRotationFrozen() {
-        return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
-    }
-
-    @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
         synchronized (mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 831418b..bf2d0df 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -26,6 +26,7 @@
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.IWindowManager;
+import android.view.Surface;
 
 import java.io.PrintWriter;
 import java.util.regex.Matcher;
@@ -73,6 +74,8 @@
                     // trace files can be written.
                     return mInternal.mWindowTracing.onShellCommand(this,
                             getNextArgRequired());
+                case "set-user-rotation":
+                    return runSetDisplayUserRotation(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -262,6 +265,36 @@
         return Integer.parseInt(s);
     }
 
+    private int runSetDisplayUserRotation(PrintWriter pw) {
+        final String lockMode = getNextArgRequired();
+
+        int displayId = Display.DEFAULT_DISPLAY;
+        String arg = getNextArg();
+        if ("-d".equals(arg)) {
+            displayId = Integer.parseInt(getNextArgRequired());
+            arg = getNextArg();
+        }
+
+        if ("free".equals(lockMode)) {
+            mInternal.thawDisplayRotation(displayId);
+            return 0;
+        }
+
+        if (!lockMode.equals("lock")) {
+            getErrPrintWriter().println("Error: lock mode needs to be either free or lock.");
+            return -1;
+        }
+
+        try {
+            final int rotation = arg != null ? Integer.parseInt(arg) : Surface.ROTATION_0;
+            mInternal.freezeDisplayRotation(displayId, rotation);
+            return 0;
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println("Error: " + e.getMessage());
+            return -1;
+        }
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -279,6 +312,8 @@
         pw.println("    Set display scaling mode.");
         pw.println("  dismiss-keyguard");
         pw.println("    Dismiss the keyguard, prompting user for auth if necessary.");
+        pw.println("  set-user-rotation [free|lock] [-d DISPLAY_ID] [rotation]");
+        pw.println("    Set user rotation mode and user rotation.");
         if (!IS_USER) {
             pw.println("  tracing (start | stop)");
             pw.println("    Start or stop window tracing.");
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
index 07eafa5..a028d5ee 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
@@ -18,23 +18,34 @@
 package com.android.server.wm;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.app.WindowConfiguration;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Surface;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.policy.WindowManagerPolicy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+/**
+ * Tests for the {@link DisplaySettings} class.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:com.android.server.wm.DisplaySettingsTests
+ */
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -155,6 +166,71 @@
         assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
     }
 
+    @Test
+    public void testDefaultToFreeUserRotation() {
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        final DisplayRotation rotation = mSecondaryDisplay.getDisplayRotation();
+        assertEquals(WindowManagerPolicy.USER_ROTATION_FREE, rotation.getUserRotationMode());
+        assertFalse(rotation.isRotationFrozen());
+    }
+
+    @Test
+    public void testDefaultTo0DegRotation() {
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        assertEquals(Surface.ROTATION_0, mSecondaryDisplay.getDisplayRotation().getUserRotation());
+    }
+
+    @Test
+    public void testPersistUserRotationModeInSameInstance() {
+        mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_90);
+
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        final DisplayRotation rotation = mSecondaryDisplay.getDisplayRotation();
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation.getUserRotationMode());
+        assertTrue(rotation.isRotationFrozen());
+    }
+
+    @Test
+    public void testPersistUserRotationInSameInstance() {
+        mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_90);
+
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        assertEquals(Surface.ROTATION_90, mSecondaryDisplay.getDisplayRotation().getUserRotation());
+    }
+
+    @Test
+    public void testPersistUserRotationModeAcrossInstances() {
+        mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_270);
+        mTarget.writeSettingsLocked();
+
+        DisplaySettings target = new DisplaySettings(sWm, mTestFolder);
+        target.readSettingsLocked();
+
+        target.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        final DisplayRotation rotation = mSecondaryDisplay.getDisplayRotation();
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation.getUserRotationMode());
+        assertTrue(rotation.isRotationFrozen());
+    }
+
+    @Test
+    public void testPersistUserRotationAcrossInstances() {
+        mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_270);
+
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+        assertEquals(Surface.ROTATION_270,
+                mSecondaryDisplay.getDisplayRotation().getUserRotation());
+    }
+
     private static void assertOverscan(DisplayContent display, int left, int top, int right,
             int bottom) {
         final DisplayInfo info = display.getDisplayInfo();
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 474e5b7..5159551 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -432,17 +432,6 @@
     }
 
     @Override
-    public int getUserRotationMode() {
-        return 0;
-    }
-
-    @Override
-    public void setUserRotationMode(int mode,
-            int rotation) {
-
-    }
-
-    @Override
     public int adjustSystemUiVisibilityLw(int visibility) {
         return 0;
     }