Prevent autorotation during hinge movement

Pauses autorotation when hinge angle sensor
reports values and display switch.
This reduces accidental rotation while
folding or unfolding a foldable device.

Bug: 233055896
Test: atest WmTests:DisplayRotationTests
Change-Id: I1b07e1e84ab1cafb0f2794d62d99e114908fe840
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ef19fc1..639e54b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -680,6 +680,20 @@
      rotation. -->
     <bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
 
+    <!-- Indicates whether the window manager pauses autorotation when folding or unfolding
+     a foldable device based on hinge angle sensor events and physical display switch events. -->
+    <bool name="config_windowManagerPauseRotationWhenUnfolding">false</bool>
+
+    <!-- Amount of time during which autorotation will be disabled since last hinge angle event -->
+    <integer name="config_pauseRotationWhenUnfolding_maxHingeAngle">0</integer>
+
+    <!-- Maximum hinge angle event to be considered to disable autorotation when folding or
+     unfolding -->
+    <integer name="config_pauseRotationWhenUnfolding_hingeEventTimeout">0</integer>
+
+    <!-- Amount of time during which autorotation will be disabled since last display switch -->
+    <integer name="config_pauseRotationWhenUnfolding_displaySwitchTimeout">0</integer>
+
     <!-- When a device enters any of these states, it should be woken up. States are defined in
          device_state_configuration.xml. -->
     <integer-array name="config_deviceStatesOnWhichToWakeUp">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8855d5b..5022e32 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4017,6 +4017,10 @@
   <java-symbol type="array" name="config_halfFoldedDeviceStates" />
   <java-symbol type="array" name="config_rearDisplayDeviceStates" />
   <java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
+  <java-symbol type="bool" name="config_windowManagerPauseRotationWhenUnfolding" />
+  <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_hingeEventTimeout" />
+  <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_maxHingeAngle" />
+  <java-symbol type="integer" name="config_pauseRotationWhenUnfolding_displaySwitchTimeout" />
   <java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
   <java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
   <java-symbol type="string" name="config_foldedArea" />
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 89cb13a..ab109fca 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2921,6 +2921,7 @@
                         /* includeRotationSettings */ false);
                 mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId,
                         mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
+                mDisplayRotation.physicalDisplayChanged();
             }
 
             // If there is an override set for base values - use it, otherwise use new values.
@@ -3291,7 +3292,6 @@
             mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
             handleAnimatingStoppedAndTransition();
             mWmService.stopFreezingDisplayLocked();
-            mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
             mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
@@ -3305,6 +3305,7 @@
             mWindowingLayer.release();
             mInputMonitor.onDisplayRemoved();
             mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
+            mDisplayRotation.onDisplayRemoved();
             mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
             mRootWindowContainer.mTaskSupervisor
                     .getKeyguardController().onDisplayRemoved(mDisplayId);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6af1c7c9..628f4d3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -51,8 +51,13 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.hardware.power.Boost;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -1085,6 +1090,10 @@
             return false;
         }
 
+        if (mFoldController != null && mFoldController.shouldDisableRotationSensor()) {
+            return false;
+        }
+
         if (mSupportAutoRotation) {
             if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                     || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
@@ -1183,6 +1192,9 @@
         int sensorRotation = mOrientationListener != null
                 ? mOrientationListener.getProposedRotation() // may be -1
                 : -1;
+        if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
+            sensorRotation = -1;
+        }
         if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
             sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
         }
@@ -1425,6 +1437,11 @@
             return false;
         }
 
+        // Do not show rotation choice when fold controller blocks rotation sensor
+        if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
+            return false;
+        }
+
         // Don't show rotation choice if we are in tabletop or book modes.
         if (isTabletopAutoRotateOverrideEnabled()) return false;
 
@@ -1527,6 +1544,13 @@
         }
     }
 
+    void onDisplayRemoved() {
+        removeDefaultDisplayRotationChangedCallback();
+        if (mFoldController != null) {
+            mFoldController.onDisplayRemoved();
+        }
+    }
+
     /** Return whether the rotation settings has changed. */
     private boolean updateSettings() {
         final ContentResolver resolver = mContext.getContentResolver();
@@ -1622,6 +1646,22 @@
         pw.println(prefix + "  mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
         pw.println(prefix + "  mFixedToUserRotation=" + isFixedToUserRotation());
 
+        if (mFoldController != null) {
+            pw.println(prefix + "FoldController");
+            pw.println(prefix + "  mPauseAutorotationDuringUnfolding="
+                    + mFoldController.mPauseAutorotationDuringUnfolding);
+            pw.println(prefix + "  mShouldDisableRotationSensor="
+                    + mFoldController.mShouldDisableRotationSensor);
+            pw.println(prefix + "  mShouldIgnoreSensorRotation="
+                    + mFoldController.mShouldIgnoreSensorRotation);
+            pw.println(prefix + "  mLastDisplaySwitchTime="
+                    + mFoldController.mLastDisplaySwitchTime);
+            pw.println(prefix + "  mLastHingeAngleEventTime="
+                    + mFoldController.mLastHingeAngleEventTime);
+            pw.println(prefix + "  mDeviceState="
+                    + mFoldController.mDeviceState);
+        }
+
         if (!mRotationHistory.mRecords.isEmpty()) {
             pw.println();
             pw.println(prefix + "  RotationHistory");
@@ -1663,13 +1703,37 @@
         }
     }
 
-    private class FoldController {
+    /**
+     * Called by the DisplayContent when the physical display changes
+     */
+    void physicalDisplayChanged() {
+        if (mFoldController != null) {
+            mFoldController.onPhysicalDisplayChanged();
+        }
+    }
+
+    @VisibleForTesting
+    long uptimeMillis() {
+        return SystemClock.uptimeMillis();
+    }
+
+    class FoldController {
+        private final boolean mPauseAutorotationDuringUnfolding;
         @Surface.Rotation
         private int mHalfFoldSavedRotation = -1; // No saved rotation
         private DeviceStateController.DeviceState mDeviceState =
                 DeviceStateController.DeviceState.UNKNOWN;
+        private long mLastHingeAngleEventTime = 0;
+        private long mLastDisplaySwitchTime = 0;
+        private boolean mShouldIgnoreSensorRotation;
+        private boolean mShouldDisableRotationSensor;
         private boolean mInHalfFoldTransition = false;
+        private int mDisplaySwitchRotationBlockTimeMs;
+        private int mHingeAngleRotationBlockTimeMs;
+        private int mMaxHingeAngle;
         private final boolean mIsDisplayAlwaysSeparatingHinge;
+        private SensorManager mSensorManager;
+        private SensorEventListener mHingeAngleSensorEventListener;
         private final Set<Integer> mTabletopRotations;
         private final Runnable mActivityBoundsUpdateCallback;
 
@@ -1726,6 +1790,48 @@
                     }
                 }
             };
+
+            mPauseAutorotationDuringUnfolding = mContext.getResources().getBoolean(
+                    R.bool.config_windowManagerPauseRotationWhenUnfolding);
+
+            if (mPauseAutorotationDuringUnfolding) {
+                mDisplaySwitchRotationBlockTimeMs = mContext.getResources().getInteger(
+                        R.integer.config_pauseRotationWhenUnfolding_displaySwitchTimeout);
+                mHingeAngleRotationBlockTimeMs = mContext.getResources().getInteger(
+                        R.integer.config_pauseRotationWhenUnfolding_hingeEventTimeout);
+                mMaxHingeAngle = mContext.getResources().getInteger(
+                        R.integer.config_pauseRotationWhenUnfolding_maxHingeAngle);
+                registerSensorManager();
+            }
+        }
+
+        private void registerSensorManager() {
+            mSensorManager = mContext.getSystemService(SensorManager.class);
+            if (mSensorManager != null) {
+                final Sensor hingeAngleSensor = mSensorManager
+                        .getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
+
+                if (hingeAngleSensor != null) {
+                    mHingeAngleSensorEventListener = new SensorEventListener() {
+                        @Override
+                        public void onSensorChanged(SensorEvent event) {
+                            onHingeAngleChanged(event.values[0]);
+                        }
+
+                        @Override
+                        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+                        }
+                    };
+                    mSensorManager.registerListener(mHingeAngleSensorEventListener,
+                            hingeAngleSensor, SensorManager.SENSOR_DELAY_FASTEST, getHandler());
+                }
+            }
+        }
+
+        void onDisplayRemoved() {
+            if (mSensorManager != null && mHingeAngleSensorEventListener != null) {
+                mSensorManager.unregisterListener(mHingeAngleSensorEventListener);
+            }
         }
 
         boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
@@ -1755,6 +1861,7 @@
         boolean shouldRevertOverriddenRotation() {
             // When transitioning to open.
             return mDeviceState == DeviceStateController.DeviceState.OPEN
+                    && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
                     && mInHalfFoldTransition
                     && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
                     && mUserRotationMode
@@ -1801,6 +1908,80 @@
             UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
                     FOLDING_RECOMPUTE_CONFIG_DELAY_MS);
         }
+
+        boolean shouldIgnoreSensorRotation() {
+            return mShouldIgnoreSensorRotation;
+        }
+
+        boolean shouldDisableRotationSensor() {
+            return mShouldDisableRotationSensor;
+        }
+
+        private void updateSensorRotationBlockIfNeeded() {
+            final long currentTime = uptimeMillis();
+            final boolean newShouldIgnoreRotation =
+                    currentTime - mLastDisplaySwitchTime < mDisplaySwitchRotationBlockTimeMs
+                    || currentTime - mLastHingeAngleEventTime < mHingeAngleRotationBlockTimeMs;
+
+            if (newShouldIgnoreRotation != mShouldIgnoreSensorRotation) {
+                mShouldIgnoreSensorRotation = newShouldIgnoreRotation;
+
+                // Resuming the autorotation
+                if (!mShouldIgnoreSensorRotation) {
+                    if (mShouldDisableRotationSensor) {
+                        // Sensor was disabled, let's re-enable it
+                        mShouldDisableRotationSensor = false;
+                        updateOrientationListenerLw();
+                    } else {
+                        // Sensor was not disabled, let's update the rotation in case if we received
+                        // some rotation sensor updates when autorotate was disabled
+                        updateRotationAndSendNewConfigIfChanged();
+                    }
+                }
+            }
+        }
+
+        void onPhysicalDisplayChanged() {
+            if (!mPauseAutorotationDuringUnfolding) return;
+
+            mLastDisplaySwitchTime = uptimeMillis();
+
+            final boolean isUnfolding =
+                    mDeviceState == DeviceStateController.DeviceState.OPEN
+                    || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
+
+            if (isUnfolding) {
+                // Temporary disable rotation sensor updates when unfolding
+                mShouldDisableRotationSensor = true;
+                updateOrientationListenerLw();
+            }
+
+            updateSensorRotationBlockIfNeeded();
+            getHandler().postDelayed(() -> {
+                synchronized (mLock) {
+                    updateSensorRotationBlockIfNeeded();
+                };
+            }, mDisplaySwitchRotationBlockTimeMs);
+        }
+
+        void onHingeAngleChanged(float hingeAngle) {
+            if (hingeAngle < mMaxHingeAngle) {
+                mLastHingeAngleEventTime = uptimeMillis();
+
+                updateSensorRotationBlockIfNeeded();
+
+                getHandler().postDelayed(() -> {
+                    synchronized (mLock) {
+                        updateSensorRotationBlockIfNeeded();
+                    };
+                }, mHingeAngleRotationBlockTimeMs);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    Handler getHandler() {
+        return mService.mH;
     }
 
     private class OrientationListener extends WindowOrientationListener implements Runnable {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 146ed34..495f868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -62,6 +62,7 @@
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
 import android.os.SystemClock;
+import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.DisplayAddress;
@@ -76,6 +77,9 @@
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+import com.android.server.wm.DisplayContent.FixedRotationTransitionListener;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -104,6 +108,9 @@
     private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50;
 
     private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
+    private static final OffsettableClock sClock = new OffsettableClock.Stopped();
+    private static TestHandler sHandler;
+    private static long sCurrentUptimeMillis = 10_000;
 
     private static WindowManagerService sMockWm;
     private DisplayContent mMockDisplayContent;
@@ -113,6 +120,7 @@
     private Resources mMockRes;
     private SensorManager mMockSensorManager;
     private Sensor mFakeOrientationSensor;
+    private Sensor mFakeHingeAngleSensor;
     private DisplayWindowSettings mMockDisplayWindowSettings;
     private ContentResolver mMockResolver;
     private FakeSettingsProvider mFakeSettingsProvider;
@@ -125,6 +133,9 @@
     private ContentObserver mUserRotationObserver;
     private SensorEventListener mOrientationSensorListener;
 
+    ArgumentCaptor<SensorEventListener> mHingeAngleSensorListenerCaptor = ArgumentCaptor.forClass(
+            SensorEventListener.class);
+
     private DisplayRotationBuilder mBuilder;
 
     private DeviceStateController mDeviceStateController;
@@ -135,6 +146,7 @@
         sMockWm = mock(WindowManagerService.class);
         sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
         sMockWm.mPolicy = mock(WindowManagerPolicy.class);
+        sHandler = new TestHandler(null, sClock);
     }
 
     @AfterClass
@@ -468,12 +480,16 @@
     }
 
     private SensorEvent createSensorEvent(int rotation) throws Exception {
+        return createSensorEvent(mFakeOrientationSensor, rotation);
+    }
+
+    private SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
         final Constructor<SensorEvent> constructor =
                 SensorEvent.class.getDeclaredConstructor(int.class);
         constructor.setAccessible(true);
         final SensorEvent event = constructor.newInstance(1);
-        event.sensor = mFakeOrientationSensor;
-        event.values[0] = rotation;
+        event.sensor = sensor;
+        event.values[0] = value;
         event.timestamp = SystemClock.elapsedRealtimeNanos();
         return event;
     }
@@ -943,6 +959,120 @@
                 Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
     }
 
+    @Test
+    public void testSensorRotationAfterDisplayChangeBeforeTimeout_ignoresSensor() throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(true)
+                .setDisplaySwitchRotationBlockTimeMs(1000)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        mTarget.physicalDisplayChanged();
+
+        moveTimeForward(900);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testSensorRotationAfterDisplayChangeAfterTimeout_usesSensor() throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(true)
+                .setDisplaySwitchRotationBlockTimeMs(1000)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        mTarget.physicalDisplayChanged();
+
+        moveTimeForward(1100);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testSensorRotationAfterHingeEventBeforeTimeout_ignoresSensor() throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(true)
+                .setMaxHingeAngle(165)
+                .setHingeAngleRotationBlockTimeMs(400)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        sendHingeAngleEvent(130);
+
+        moveTimeForward( 300);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testSensorRotationAfterHingeEventBeforeTimeoutFlagDisabled_usesSensorData()
+            throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(false)
+                .setMaxHingeAngle(165)
+                .setHingeAngleRotationBlockTimeMs(400)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        sendHingeAngleEvent(130);
+
+        moveTimeForward( 300);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testSensorRotationAfterHingeEventAfterTimeout_usesSensorData() throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(true)
+                .setMaxHingeAngle(165)
+                .setHingeAngleRotationBlockTimeMs(400)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        sendHingeAngleEvent(180);
+
+        moveTimeForward(1010);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+
+    @Test
+    public void testSensorRotationAfterLargeHingeEventBeforeTimeout_usesSensor() throws Exception {
+        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+                .setPauseRotationWhenUnfolding(true)
+                .setMaxHingeAngle(165)
+                .setHingeAngleRotationBlockTimeMs(400)
+                .build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        thawRotation();
+        enableOrientationSensor();
+
+        sendHingeAngleEvent(180);
+
+        moveTimeForward(300);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
     // ========================
     // Non-rotation API Tests
     // ========================
@@ -963,6 +1093,12 @@
                 + " fixed to user rotation.", mTarget.isFixedToUserRotation());
     }
 
+    private void moveTimeForward(long timeMillis) {
+        sCurrentUptimeMillis += timeMillis;
+        sClock.fastForward(timeMillis);
+        sHandler.timeAdvance();
+    }
+
     /**
      * Call {@link DisplayRotation#configure(int, int)} to configure {@link #mTarget}
      * according to given parameters.
@@ -995,6 +1131,17 @@
         mTarget.configure(width, height);
     }
 
+    private void sendHingeAngleEvent(int hingeAngle) {
+        mHingeAngleSensorListenerCaptor.getAllValues().forEach(sensorEventListener -> {
+            try {
+                sensorEventListener.onSensorChanged(createSensorEvent(mFakeHingeAngleSensor,
+                            hingeAngle));
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
     private void freezeRotation(int rotation) {
         mTarget.freezeRotation(rotation);
 
@@ -1016,7 +1163,11 @@
     private class DisplayRotationBuilder {
         private boolean mIsDefaultDisplay = true;
         private boolean mSupportAutoRotation = true;
+        private boolean mPauseRotationWhenUnfolding = false;
         private boolean mSupportHalfFoldAutoRotateOverride = false;
+        private int mDisplaySwitchRotationBlockTimeMs;
+        private int mHingeAngleRotationBlockTimeMs;
+        private int mMaxHingeAngle;
 
         private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
         private int mCarDockRotation;
@@ -1028,6 +1179,29 @@
             return this;
         }
 
+        public DisplayRotationBuilder setPauseRotationWhenUnfolding(
+                boolean pauseRotationWhenUnfolding) {
+            mPauseRotationWhenUnfolding = pauseRotationWhenUnfolding;
+            return this;
+        }
+
+        public DisplayRotationBuilder setDisplaySwitchRotationBlockTimeMs(
+                int displaySwitchRotationBlockTimeMs) {
+            mDisplaySwitchRotationBlockTimeMs = displaySwitchRotationBlockTimeMs;
+            return this;
+        }
+
+        public DisplayRotationBuilder setHingeAngleRotationBlockTimeMs(
+                int hingeAngleRotationBlockTimeMs) {
+            mHingeAngleRotationBlockTimeMs = hingeAngleRotationBlockTimeMs;
+            return this;
+        }
+
+        public DisplayRotationBuilder setMaxHingeAngle(int maxHingeAngle) {
+            mMaxHingeAngle = maxHingeAngle;
+            return this;
+        }
+
         private DisplayRotationBuilder setSupportAutoRotation(boolean supportAutoRotation) {
             mSupportAutoRotation = supportAutoRotation;
             return this;
@@ -1153,10 +1327,27 @@
             when(mMockDisplayContent.getWindowConfiguration())
                     .thenReturn(new WindowConfiguration());
 
+            Field field = DisplayContent.class
+                    .getDeclaredField("mFixedRotationTransitionListener");
+            field.setAccessible(true);
+            field.set(mMockDisplayContent, mock(FixedRotationTransitionListener.class));
+
             mMockDisplayPolicy = mock(DisplayPolicy.class);
 
             mMockRes = mock(Resources.class);
             when(mMockContext.getResources()).thenReturn((mMockRes));
+            when(mMockRes.getBoolean(com.android.internal.R.bool
+                    .config_windowManagerPauseRotationWhenUnfolding))
+                    .thenReturn(mPauseRotationWhenUnfolding);
+            when(mMockRes.getInteger(com.android.internal.R.integer
+                    .config_pauseRotationWhenUnfolding_displaySwitchTimeout))
+                    .thenReturn(mDisplaySwitchRotationBlockTimeMs);
+            when(mMockRes.getInteger(com.android.internal.R.integer
+                    .config_pauseRotationWhenUnfolding_hingeEventTimeout))
+                    .thenReturn(mHingeAngleRotationBlockTimeMs);
+            when(mMockRes.getInteger(com.android.internal.R.integer
+                    .config_pauseRotationWhenUnfolding_maxHingeAngle))
+                    .thenReturn(mMaxHingeAngle);
             when(mMockRes.getBoolean(com.android.internal.R.bool.config_supportAutoRotation))
                     .thenReturn(mSupportAutoRotation);
             when(mMockRes.getInteger(com.android.internal.R.integer.config_lidOpenRotation))
@@ -1169,11 +1360,16 @@
                     .thenReturn(convertRotationToDegrees(mUndockedHdmiRotation));
 
             mMockSensorManager = mock(SensorManager.class);
+            when(mMockContext.getSystemService(SensorManager.class))
+                    .thenReturn(mMockSensorManager);
             when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
                     .thenReturn(mMockSensorManager);
             mFakeOrientationSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
             when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
                     Collections.singletonList(mFakeOrientationSensor));
+            mFakeHingeAngleSensor = mock(Sensor.class);
+            when(mMockSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)).thenReturn(
+                    mFakeHingeAngleSensor);
 
             when(mMockContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
@@ -1200,6 +1396,10 @@
 
             reset(sMockWm);
 
+            verify(mMockSensorManager, atLeast(0)).registerListener(
+                    mHingeAngleSensorListenerCaptor.capture(), eq(mFakeHingeAngleSensor), anyInt(),
+                    any());
+
             captureObservers();
         }
     }
@@ -1226,5 +1426,15 @@
                 mProposedRotationCallback.accept(rotation);
             }
         }
+
+        @Override
+        Handler getHandler() {
+            return sHandler;
+        }
+
+        @Override
+        long uptimeMillis() {
+            return sCurrentUptimeMillis;
+        }
     }
 }