Add a flag to fix rotation to user rotation only.

It also does a few more things:
1. Add a command to manually test the flag.
2. Refactor DisplayRotation a bit to make it unit testable.
3. Add some core unit tests for DisplayRotation.

Bug: 113252523
Test: Manual test.
atest WmTests:DisplayRotationTests

Change-Id: I00c4a44e4d2f637590e8b13f7e6194624c04c58f
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f1d1e49..7aabc15 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.power.V1_0.PowerHint;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -57,6 +59,7 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mDisplayPolicy;
+    private final DisplayWindowSettings mDisplayWindowSettings;
     private final Context mContext;
     private final Object mLock;
 
@@ -71,10 +74,6 @@
     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
@@ -93,6 +92,13 @@
     private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
     private int mUserRotation = Surface.ROTATION_0;
 
+    /**
+     * A flag to indicate if the display rotation should be fixed to user specified rotation
+     * regardless of all other states (including app requrested orientation). {@code true} the
+     * display rotation should be fixed to user specified rotation, {@code false} otherwise.
+     */
+    private boolean mFixedToUserRotation;
+
     private int mDemoHdmiRotation;
     private int mDemoRotation;
     private boolean mDemoHdmiRotationLock;
@@ -100,15 +106,17 @@
 
     DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
         this(service, displayContent, displayContent.getDisplayPolicy(),
-                service.mContext, service.getWindowManagerLock());
+                service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
     }
 
     @VisibleForTesting
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
-            DisplayPolicy displayPolicy, Context context, Object lock) {
+            DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
+            Context context, Object lock) {
         mService = service;
         mDisplayContent = displayContent;
         mDisplayPolicy = displayPolicy;
+        mDisplayWindowSettings = displayWindowSettings;
         mContext = context;
         mLock = lock;
         isDefaultDisplay = displayContent.isDefaultDisplay;
@@ -204,12 +212,19 @@
         // 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"));
+        final boolean forceDefaultOrientationInRes =
+                res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation);
+        final boolean forceDefaultOrienation =
+                ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv)
+                        && forceDefaultOrientationInRes
+                        // 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"));
+        // Configuration says we force to use the default orientation. We can fall back to fix
+        // rotation to only user rotation. As long as OEM doesn't change user rotation then the
+        // rotation of this display is effectively stuck at 0 deg.
+        setFixedToUserRotation(forceDefaultOrienation);
     }
 
     void setRotation(int rotation) {
@@ -227,7 +242,14 @@
         }
     }
 
-    void restoreUserRotation(int userRotationMode, int userRotation) {
+    void restoreSettings(int userRotationMode, int userRotation,
+            boolean 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
@@ -243,6 +265,18 @@
         mUserRotation = userRotation;
     }
 
+    void setFixedToUserRotation(boolean fixedToUserRotation) {
+        if (mFixedToUserRotation == fixedToUserRotation) {
+            return;
+        }
+
+        mFixedToUserRotation = fixedToUserRotation;
+        mDisplayWindowSettings.setFixedToUserRotation(mDisplayContent,
+                fixedToUserRotation);
+        mService.updateRotation(true /* alwaysSendConfiguration */,
+                false /* forceRelayout */);
+    }
+
     private void setUserRotation(int userRotationMode, int userRotation) {
         if (isDefaultDisplay) {
             // We'll be notified via settings listener, so we don't need to update internal values.
@@ -265,7 +299,7 @@
             mUserRotation = userRotation;
             changed = true;
         }
-        mService.mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
+        mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
                 userRotation);
         if (changed) {
             mService.updateRotation(true /* alwaysSendConfiguration */,
@@ -291,9 +325,8 @@
                 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;
+    boolean isFixedToUserRotation() {
+        return mFixedToUserRotation;
     }
 
     public int getLandscapeRotation() {
@@ -399,6 +432,12 @@
      * screen is switched off.
      */
     private boolean needSensorRunning() {
+        if (mFixedToUserRotation) {
+            // We are sure we only respect user rotation settings, so we are sure we will not
+            // support sensor rotation.
+            return false;
+        }
+
         if (mSupportAutoRotation) {
             if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                     || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
@@ -459,8 +498,8 @@
                         );
         }
 
-        if (mForceDefaultOrientation) {
-            return Surface.ROTATION_0;
+        if (mFixedToUserRotation) {
+            return mUserRotation;
         }
 
         int sensorRotation = mOrientationListener != null
@@ -701,8 +740,8 @@
         // demo, hdmi, vr, etc mode.
 
         // Determine if the rotation is currently forced.
-        if (mForceDefaultOrientation) {
-            return false; // Rotation is forced to default orientation.
+        if (mFixedToUserRotation) {
+            return false; // Rotation is forced to user settings.
         }
 
         final int lidState = mDisplayPolicy.getLidState();
@@ -861,6 +900,7 @@
         pw.print(" mDemoHdmiRotationLock=" + mDemoHdmiRotationLock);
         pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation));
         pw.println(prefix + "  mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
+        pw.println(prefix + "  mFixedToUserRotation=" + mFixedToUserRotation);
     }
 
     private class OrientationListener extends WindowOrientationListener {
@@ -945,4 +985,10 @@
             }
         }
     }
+
+    @VisibleForTesting
+    interface ContentObserverRegister {
+        void registerContentObserver(Uri uri, boolean notifyForDescendants,
+                ContentObserver observer, @UserIdInt int userHandle);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index f7dfd3f..45d77de 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -80,6 +80,7 @@
         private boolean mShouldShowWithInsecureKeyguard = false;
         private boolean mShouldShowSystemDecors = false;
         private boolean mShouldShowIme = false;
+        private boolean mFixedToUserRotation;
 
         private Entry(String name) {
             mName = name;
@@ -97,7 +98,8 @@
                     && mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED
                     && !mShouldShowWithInsecureKeyguard
                     && !mShouldShowSystemDecors
-                    && !mShouldShowIme;
+                    && !mShouldShowIme
+                    && !mFixedToUserRotation;
         }
     }
 
@@ -186,6 +188,13 @@
         writeSettingsIfNeeded(entry, displayInfo);
     }
 
+    void setFixedToUserRotation(DisplayContent displayContent, boolean fixedToUserRotation) {
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final Entry entry = getOrCreateEntry(displayInfo);
+        entry.mFixedToUserRotation = fixedToUserRotation;
+        writeSettingsIfNeeded(entry, displayInfo);
+    }
+
     private int getWindowingModeLocked(Entry entry, int displayId) {
         int windowingMode = entry != null ? entry.mWindowingMode
                 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -331,7 +340,8 @@
         displayInfo.overscanRight = entry.mOverscanRight;
         displayInfo.overscanBottom = entry.mOverscanBottom;
 
-        dc.getDisplayRotation().restoreUserRotation(entry.mUserRotationMode, entry.mUserRotation);
+        dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
+                entry.mUserRotation, entry.mFixedToUserRotation);
 
         if (entry.mForcedDensity != 0) {
             dc.mBaseDisplayDensity = entry.mForcedDensity;
@@ -458,6 +468,8 @@
                     "shouldShowWithInsecureKeyguard");
             entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors");
             entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme");
+            entry.mFixedToUserRotation = getBooleanAttribute(parser,
+                    "fixedToUserRotation");
             mEntries.put(name, entry);
         }
         XmlUtils.skipCurrentTag(parser);
@@ -541,6 +553,10 @@
                 if (entry.mShouldShowIme) {
                     out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme));
                 }
+                if (entry.mFixedToUserRotation) {
+                    out.attribute(null, "fixedToUserRotation",
+                            Boolean.toString(entry.mFixedToUserRotation));
+                }
                 out.endTag(null, "display");
             }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index df97027..e0c913b 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -222,7 +222,7 @@
     }
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
-            boolean forceDefaultOrientation, boolean isSecure, WindowManagerService service) {
+            boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
         mService = service;
         mContext = context;
         mDisplayContent = displayContent;
@@ -234,7 +234,7 @@
         final int originalWidth;
         final int originalHeight;
         DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        if (forceDefaultOrientation) {
+        if (fixedToUserRotation) {
             // Emulated orientation.
             mForceDefaultOrientation = true;
             originalWidth = displayContent.mBaseDisplayWidth;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 25f3128..0995c43 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3577,6 +3577,17 @@
         }
     }
 
+    void setRotateForApp(int displayId, boolean enabled) {
+        synchronized (mGlobalLock) {
+            final DisplayContent display = mRoot.getDisplayContent(displayId);
+            if (display == null) {
+                Slog.w(TAG, "Trying to set rotate for app for a missing display.");
+                return;
+            }
+            display.getDisplayRotation().setFixedToUserRotation(enabled);
+        }
+    }
+
     @Override
     public void freezeRotation(int rotation) {
         freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
@@ -5383,7 +5394,7 @@
 
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    displayContent.getDisplayRotation().isDefaultOrientationForced(), isSecure,
+                    displayContent.getDisplayRotation().isFixedToUserRotation(), isSecure,
                     this);
             mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
                     screenRotationAnimation);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bf77ba8..6865ce3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -76,6 +76,8 @@
                             getNextArgRequired());
                 case "set-user-rotation":
                     return runSetDisplayUserRotation(pw);
+                case "set-fix-to-user-rotation":
+                    return runSetFixToUserRotation(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -297,6 +299,32 @@
         }
     }
 
+    private int runSetFixToUserRotation(PrintWriter pw) {
+        int displayId = Display.DEFAULT_DISPLAY;
+        String arg = getNextArgRequired();
+        if ("-d".equals(arg)) {
+            displayId = Integer.parseInt(getNextArgRequired());
+            arg = getNextArgRequired();
+        }
+
+        final boolean enabled;
+        switch (arg) {
+            case "enabled":
+                enabled = true;
+                break;
+            case "disabled":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expecting enabled or disabled, but we get "
+                        + arg);
+                return -1;
+        }
+
+        mInternal.setRotateForApp(displayId, enabled);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -316,6 +344,8 @@
         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.");
+        pw.println("  set-fix-to-user-rotation [-d DISPLAY_ID] [enabled|disabled]");
+        pw.println("    Enable or disable rotating display for app requested orientation.");
         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/DisplayWindowSettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index b823e70..7f390a5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -27,7 +27,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.WindowConfiguration;
 import android.platform.test.annotations.Presubmit;
@@ -378,6 +383,33 @@
                 mSecondaryDisplay.getDisplayRotation().getUserRotation());
     }
 
+    @Test
+    public void testNotFixedToUserRotationByDefault() {
+        mTarget.setUserRotation(mPrimaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_0);
+
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        mPrimaryDisplay = spy(mPrimaryDisplay);
+        when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+
+        mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+        verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(false));
+    }
+
+    @Test
+    public void testSetFixedToUserRotation() {
+        mTarget.setFixedToUserRotation(mPrimaryDisplay, true);
+
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        mPrimaryDisplay = spy(mPrimaryDisplay);
+        when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+
+        applySettingsToDisplayByNewInstance(mPrimaryDisplay);
+
+        verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(true));
+    }
+
     private static void assertOverscan(DisplayContent display, int left, int top, int right,
             int bottom) {
         final DisplayInfo info = display.getDisplayInfo();
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f128b4e2..67acec6 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -37,6 +37,8 @@
 
     <application android:debuggable="true"
                  android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityA" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityB" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityRequestedOrientationChange" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
new file mode 100644
index 0000000..e988994
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -0,0 +1,823 @@
+/*
+ * 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.atMost;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.Surface;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for {@link DisplayRotation}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayRotationTests
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(detail = "Confirm stable in post-submit before removing")
+public class DisplayRotationTests {
+    private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50;
+
+    private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
+
+    private WindowManagerService mMockWm;
+    private DisplayContent mMockDisplayContent;
+    private DisplayPolicy mMockDisplayPolicy;
+    private Context mMockContext;
+    private Resources mMockRes;
+    private SensorManager mMockSensorManager;
+    private Sensor mFakeSensor;
+    private DisplayWindowSettings mMockDisplayWindowSettings;
+    private ContentResolver mMockResolver;
+    private FakeSettingsProvider mFakeSettingsProvider;
+    private StatusBarManagerInternal mMockStatusBarManagerInternal;
+
+    // Fields below are callbacks captured from test target.
+    private ContentObserver mShowRotationSuggestionsObserver;
+    private ContentObserver mAccelerometerRotationObserver;
+    private ContentObserver mUserRotationObserver;
+    private SensorEventListener mOrientationSensorListener;
+
+    private DisplayRotationBuilder mBuilder;
+
+    private DisplayRotation mTarget;
+
+    @Before
+    public void setUp() {
+        FakeSettingsProvider.clearSettingsProvider();
+
+        mMockWm = mock(WindowManagerService.class);
+        mMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
+
+        mPreviousStatusBarManagerInternal = LocalServices.getService(
+                StatusBarManagerInternal.class);
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
+
+        mBuilder = new DisplayRotationBuilder();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        if (mPreviousStatusBarManagerInternal != null) {
+            LocalServices.addService(StatusBarManagerInternal.class,
+                    mPreviousStatusBarManagerInternal);
+            mPreviousStatusBarManagerInternal = null;
+        }
+    }
+
+    // ================================
+    // Display Settings Related Tests
+    // ================================
+    @Test
+    public void testLocksUserRotation_LockRotation_DefaultDisplay() throws Exception {
+        mBuilder.build();
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode());
+        assertEquals(Surface.ROTATION_180, mTarget.getUserRotation());
+
+        assertEquals(0, Settings.System.getInt(mMockResolver,
+                Settings.System.ACCELEROMETER_ROTATION));
+        assertEquals(Surface.ROTATION_180, Settings.System.getInt(mMockResolver,
+                Settings.System.USER_ROTATION));
+    }
+
+    @Test
+    public void testPersistsUserRotation_LockRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.mIsDefaultDisplay = false;
+
+        mBuilder.build();
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode());
+        assertEquals(Surface.ROTATION_180, mTarget.getUserRotation());
+
+        verify(mMockDisplayWindowSettings).setUserRotation(mMockDisplayContent,
+                WindowManagerPolicy.USER_ROTATION_LOCKED, Surface.ROTATION_180);
+    }
+
+    @Test
+    public void testPersistUserRotation_UnlockRotation_DefaultDisplay() throws Exception {
+        mBuilder.build();
+
+        thawRotation();
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_FREE, mTarget.getUserRotationMode());
+
+        assertEquals(1, Settings.System.getInt(mMockResolver,
+                Settings.System.ACCELEROMETER_ROTATION));
+    }
+
+    @Test
+    public void testPersistsUserRotation_UnlockRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.mIsDefaultDisplay = false;
+
+        mBuilder.build();
+
+        thawRotation();
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_FREE, mTarget.getUserRotationMode());
+
+        verify(mMockDisplayWindowSettings).setUserRotation(same(mMockDisplayContent),
+                eq(WindowManagerPolicy.USER_ROTATION_FREE), anyInt());
+    }
+
+    @Test
+    public void testPersistsFixedToUserRotation() throws Exception {
+        mBuilder.build();
+
+        mTarget.setFixedToUserRotation(true);
+
+        verify(mMockDisplayWindowSettings).setFixedToUserRotation(mMockDisplayContent, true);
+
+        reset(mMockDisplayWindowSettings);
+        mTarget.setFixedToUserRotation(false);
+
+        verify(mMockDisplayWindowSettings).setFixedToUserRotation(mMockDisplayContent, false);
+    }
+
+    // ========================================
+    // Tests for User Rotation based Rotation
+    // ========================================
+    @Test
+    public void testReturnsUserRotation_UserRotationLocked_NoAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsUserRotation_UserRotationLocked_CompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsSidesays_UserRotationLocked_IncompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        final int rotation = mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, Surface.ROTATION_90);
+        assertTrue("Rotation should be sideways, but it's "
+                        + Surface.rotationToString(rotation),
+                rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+    }
+
+    // =================================
+    // Tests for Sensor based Rotation
+    // =================================
+    private void verifyOrientationListenerRegistration(int numOfInvocation) {
+        final ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(
+                SensorEventListener.class);
+        verify(mMockSensorManager, times(numOfInvocation)).registerListener(
+                listenerCaptor.capture(),
+                same(mFakeSensor),
+                anyInt(),
+                any());
+        if (numOfInvocation > 0) {
+            mOrientationSensorListener = listenerCaptor.getValue();
+        }
+    }
+
+    @Test
+    public void testNotEnablesSensor_AutoRotationNotSupported() throws Exception {
+        mBuilder.setSupportAutoRotation(false).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ScreenNotOn() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(false);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_NotAwake() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(false);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_KeyguardNotDrawnCompletely() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(false);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_WindowManagerNotDrawnCompletely() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(false);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_FixedUserRotation() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.setFixedToUserRotation(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation_Car() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, true, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation_Tv() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, true);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    private void enableOrientationSensor() {
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(1);
+    }
+
+    private SensorEvent createSensorEvent(int rotation) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(1);
+        event.sensor = mFakeSensor;
+        event.values[0] = rotation;
+        event.timestamp = SystemClock.elapsedRealtimeNanos();
+        return event;
+    }
+
+    @Test
+    public void testReturnsSensorRotation_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    private boolean waitForUiHandler() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        UiThread.getHandler().post(latch::countDown);
+        return latch.await(UI_HANDLER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void testUpdatesRotationWhenSensorUpdates_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertTrue(waitForUiHandler());
+
+        verify(mMockWm).updateRotation(false, false);
+    }
+
+    @Test
+    public void testNotifiesChoiceWhenSensorUpdates_RotationLocked() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertTrue(waitForUiHandler());
+
+        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+    }
+
+    @Test
+    public void testReturnsCompatibleRotation_SensorEnabled_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        final int rotation = mTarget.rotationForOrientation(SCREEN_ORIENTATION_LANDSCAPE,
+                Surface.ROTATION_0);
+        assertTrue("Rotation should be sideways but it's "
+                + Surface.rotationToString(rotation),
+                rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+    }
+
+    @Test
+    public void testReturnsUserRotation_SensorEnabled_RotationLocked() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    // =================================
+    // Tests for Policy based Rotation
+    // =================================
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation_Car() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, true, false);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation_Tv() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, true);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsLidOpenRotation_LidOpen() throws Exception {
+        mBuilder.setLidOpenRotation(Surface.ROTATION_90).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getLidState()).thenReturn(
+                WindowManagerPolicy.WindowManagerFuncs.LID_OPEN);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testReturnsCarDockRotation_CarDockedMode() throws Exception {
+        mBuilder.setCarDockRotation(Surface.ROTATION_270).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_CAR);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsDeskDockRotation_DeskDockedMode() throws Exception {
+        mBuilder.setDeskDockRotation(Surface.ROTATION_270).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_DESK);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsUserRotation_FixedToUserRotation_IgnoreIncompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        mTarget.setFixedToUserRotation(true);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        final int rotation = mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, Surface.ROTATION_90);
+        assertEquals(Surface.ROTATION_180, rotation);
+    }
+
+    @Test
+    public void testReturnsUserRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.setIsDefaultDisplay(false).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    /**
+     * Call {@link DisplayRotation#configure(int, int, int, int)} to configure {@link #mTarget}
+     * according to given parameters.
+     */
+    private void configureDisplayRotation(int displayOrientation, boolean isCar, boolean isTv) {
+        final int width;
+        final int height;
+        switch (displayOrientation) {
+            case SCREEN_ORIENTATION_LANDSCAPE:
+                width = 1920;
+                height = 1080;
+                break;
+            case SCREEN_ORIENTATION_PORTRAIT:
+                width = 1080;
+                height = 1920;
+                break;
+            default:
+                throw new IllegalArgumentException("displayOrientation needs to be either landscape"
+                        + " or portrait, but we got "
+                        + ActivityInfo.screenOrientationToString(displayOrientation));
+        }
+
+        final PackageManager mockPackageManager = mock(PackageManager.class);
+        when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
+        when(mockPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+                .thenReturn(isCar);
+        when(mockPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+                .thenReturn(isTv);
+
+        final int shortSizeDp = (isCar || isTv) ? 540 : 720;
+        final int longSizeDp = 960;
+        mTarget.configure(width, height, shortSizeDp, longSizeDp);
+    }
+
+    private void freezeRotation(int rotation) {
+        mTarget.freezeRotation(rotation);
+
+        if (mTarget.isDefaultDisplay) {
+            mAccelerometerRotationObserver.onChange(false);
+            mUserRotationObserver.onChange(false);
+        }
+    }
+
+    private void thawRotation() {
+        mTarget.thawRotation();
+
+        if (mTarget.isDefaultDisplay) {
+            mAccelerometerRotationObserver.onChange(false);
+            mUserRotationObserver.onChange(false);
+        }
+    }
+
+    private class DisplayRotationBuilder {
+        private boolean mIsDefaultDisplay = true;
+        private boolean mSupportAutoRotation = true;
+
+        private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+        private int mCarDockRotation;
+        private int mDeskDockRotation;
+        private int mUndockedHdmiRotation;
+
+        private DisplayRotationBuilder setIsDefaultDisplay(boolean isDefaultDisplay) {
+            mIsDefaultDisplay = isDefaultDisplay;
+            return this;
+        }
+
+        private DisplayRotationBuilder setSupportAutoRotation(boolean supportAutoRotation) {
+            mSupportAutoRotation = supportAutoRotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setLidOpenRotation(int rotation) {
+            mLidOpenRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setCarDockRotation(int rotation) {
+            mCarDockRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setDeskDockRotation(int rotation) {
+            mDeskDockRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setUndockedHdmiRotation(int rotation) {
+            mUndockedHdmiRotation = rotation;
+            return this;
+        }
+
+        private void captureObservers() {
+            ArgumentCaptor<ContentObserver> captor = ArgumentCaptor.forClass(
+                    ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.Secure.getUriFor(Settings.Secure.SHOW_ROTATION_SUGGESTIONS)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mShowRotationSuggestionsObserver = captor.getValue();
+            }
+
+            captor = ArgumentCaptor.forClass(ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mAccelerometerRotationObserver = captor.getValue();
+            }
+
+            captor = ArgumentCaptor.forClass(ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.System.getUriFor(Settings.System.USER_ROTATION)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mUserRotationObserver = captor.getValue();
+            }
+        }
+
+        private Sensor createSensor(int type) throws Exception {
+            Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+            constr.setAccessible(true);
+            Sensor sensor = constr.newInstance();
+
+            setSensorType(sensor, type);
+            setSensorField(sensor, "mName", "Mock " + sensor.getStringType() + "/" + type);
+            setSensorField(sensor, "mVendor", "Mock Vendor");
+            setSensorField(sensor, "mVersion", 1);
+            setSensorField(sensor, "mHandle", -1);
+            setSensorField(sensor, "mMaxRange", 10);
+            setSensorField(sensor, "mResolution", 1);
+            setSensorField(sensor, "mPower", 1);
+            setSensorField(sensor, "mMinDelay", 1000);
+            setSensorField(sensor, "mMaxDelay", 1000000000);
+            setSensorField(sensor, "mFlags", 0);
+            setSensorField(sensor, "mId", -1);
+
+            return sensor;
+        }
+
+        private void setSensorType(Sensor sensor, int type) throws Exception {
+            Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+            setter.setAccessible(true);
+            setter.invoke(sensor, type);
+        }
+
+        private void setSensorField(Sensor sensor, String fieldName, Object value)
+                throws Exception {
+            Field field = Sensor.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            field.set(sensor, value);
+        }
+
+        private int convertRotationToDegrees(@Surface.Rotation int rotation) {
+            switch (rotation) {
+                case Surface.ROTATION_0:
+                    return 0;
+                case Surface.ROTATION_90:
+                    return 90;
+                case Surface.ROTATION_180:
+                    return 180;
+                case Surface.ROTATION_270:
+                    return 270;
+                default:
+                    return -1;
+            }
+        }
+
+        private void build() throws Exception {
+            mMockContext = mock(Context.class);
+
+            mMockDisplayContent = mock(WindowTestUtils.TestDisplayContent.class);
+            mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
+
+            mMockDisplayPolicy = mock(DisplayPolicy.class);
+
+            mMockRes = mock(Resources.class);
+            when(mMockContext.getResources()).thenReturn((mMockRes));
+            when(mMockRes.getBoolean(com.android.internal.R.bool.config_supportAutoRotation))
+                    .thenReturn(mSupportAutoRotation);
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_lidOpenRotation))
+                    .thenReturn(convertRotationToDegrees(mLidOpenRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_carDockRotation))
+                    .thenReturn(convertRotationToDegrees(mCarDockRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_deskDockRotation))
+                    .thenReturn(convertRotationToDegrees(mDeskDockRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_undockedHdmiRotation))
+                    .thenReturn(convertRotationToDegrees(mUndockedHdmiRotation));
+
+            mMockSensorManager = mock(SensorManager.class);
+            when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
+                    .thenReturn(mMockSensorManager);
+            mFakeSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
+            when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
+                    Collections.singletonList(mFakeSensor));
+
+            mMockResolver = mock(ContentResolver.class);
+            when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
+            mFakeSettingsProvider = new FakeSettingsProvider();
+            when(mMockResolver.acquireProvider(Settings.AUTHORITY))
+                    .thenReturn(mFakeSettingsProvider.getIContentProvider());
+
+            mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
+            mTarget = new DisplayRotation(mMockWm, mMockDisplayContent, mMockDisplayPolicy,
+                    mMockDisplayWindowSettings, mMockContext, new Object());
+
+            captureObservers();
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index e56edab..3c87721 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -65,7 +65,7 @@
 
             final DisplayRotation displayRotation = new DisplayRotation(
                     mock(WindowManagerService.class), displayContent, displayPolicy,
-                    context, new Object());
+                    mock(DisplayWindowSettings.class), context, new Object());
             displayRotation.mPortraitRotation = Surface.ROTATION_0;
             displayRotation.mLandscapeRotation = Surface.ROTATION_90;
             displayRotation.mUpsideDownRotation = Surface.ROTATION_180;