Centralize rotation control to DisplayRotation

- Move seamless rotation logic. The scattered conditions
  (mayRotateSeamlessly) are also combined into shouldRotateSeamlessly.
- Move DisplayContent#updateRotationUnchecked and the related fields.
- Consolidate DisplayContent#updateOrientationFromAppTokens and
  DisplayRotation#setCurrentOrientation to DR#updateOrientation.

Bug: 117593656
Test: go/wm-smoke
Test: atest DisplayContentTests DisplayPolicyTests DisplayRotationTests

Change-Id: Ifd978a20a2773a39000a90edf683e6459adf0d2d
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index d4e95cf..6b2f9da 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -462,7 +462,7 @@
             // new rotation animation after the old one finally finishes. It's better to defer the
             // app transition.
             if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
-                    mService.getDefaultDisplayContentLocked().rotationNeedsUpdate()) {
+                    mDisplayContent.getDisplayRotation().needsUpdate()) {
                 if (DEBUG_APP_TRANSITIONS) {
                     Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
                 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e851a06..71a0126 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -130,9 +130,7 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowManagerService.logSurface;
 import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
@@ -148,6 +146,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -323,7 +322,7 @@
     private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final DisplayPolicy mDisplayPolicy;
-    private DisplayRotation mDisplayRotation;
+    private final DisplayRotation mDisplayRotation;
     DisplayFrames mDisplayFrames;
 
     private final RemoteCallbackList<ISystemGestureExclusionListener>
@@ -352,22 +351,6 @@
     float mCompatibleScreenScale;
 
     /**
-     * Current rotation of the display.
-     * Constants as per {@link android.view.Surface.Rotation}.
-     *
-     * @see #updateRotationUnchecked()
-     */
-    private int mRotation = 0;
-
-    /**
-     * Last applied orientation of the display.
-     * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}.
-     *
-     * @see #updateOrientationFromAppTokens()
-     */
-    private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-    /**
      * Orientation forced by some window. If there is no visible window that specifies orientation
      * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
      *
@@ -407,7 +390,6 @@
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
     int pendingLayoutChanges;
-    int mDeferredRotationPauseCount;
 
     /**
      * Used to gate application window layout until we have sent the complete configuration.
@@ -1138,25 +1120,17 @@
         return mInsetsStateController;
     }
 
-    @VisibleForTesting
-    void setDisplayRotation(DisplayRotation displayRotation) {
-        mDisplayRotation = displayRotation;
-    }
-
+    @Surface.Rotation
     int getRotation() {
-        return mRotation;
+        return mDisplayRotation.getRotation();
     }
 
-    @VisibleForTesting
-    void setRotation(int newRotation) {
-        mRotation = newRotation;
-        mDisplayRotation.setRotation(newRotation);
-    }
-
+    @ScreenOrientation
     int getLastOrientation() {
-        return mLastOrientation;
+        return mDisplayRotation.getLastOrientation();
     }
 
+    @ScreenOrientation
     int getLastWindowForcedOrientation() {
         return mLastWindowForcedOrientation;
     }
@@ -1166,48 +1140,6 @@
     }
 
     /**
-     * Temporarily pauses rotation changes until resumed.
-     *
-     * This can be used to prevent rotation changes from occurring while the user is
-     * performing certain operations, such as drag and drop.
-     *
-     * This call nests and must be matched by an equal number of calls to
-     * {@link #resumeRotationLocked}.
-     */
-    void pauseRotationLocked() {
-        mDeferredRotationPauseCount++;
-    }
-
-    /**
-     * Resumes normal rotation changes after being paused.
-     */
-    void resumeRotationLocked() {
-        if (mDeferredRotationPauseCount <= 0) {
-            return;
-        }
-
-        mDeferredRotationPauseCount--;
-        if (mDeferredRotationPauseCount == 0) {
-            updateRotationAndSendNewConfigIfNeeded();
-        }
-    }
-
-    /**
-     * If this is true we have updated our desired orientation, but not yet changed the real
-     * orientation our applied our screen rotation animation. For example, because a previous
-     * screen rotation was in progress.
-     *
-     * @return {@code true} if the there is an ongoing rotation change.
-     */
-    boolean rotationNeedsUpdate() {
-        final int lastOrientation = getLastOrientation();
-        final int oldRotation = getRotation();
-
-        final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);
-        return oldRotation != rotation;
-    }
-
-    /**
      * The display content may have configuration set from {@link #DisplayWindowSettings}. This
      * callback let the owner of container know there is existing configuration to prevent the
      * values from being replaced by the initializing {@link #ActivityDisplay}.
@@ -1225,7 +1157,7 @@
         configureDisplayPolicy();
         setLayoutNeeded();
 
-        boolean configChanged = updateOrientationFromAppTokens();
+        boolean configChanged = updateOrientation();
         final Configuration currentDisplayConfig = getConfiguration();
         mTmpConfiguration.setTo(currentDisplayConfig);
         computeScreenConfiguration(mTmpConfiguration);
@@ -1263,8 +1195,8 @@
     @Override
     boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
             ConfigurationContainer requestingContainer) {
-        final Configuration config = updateOrientationFromAppTokens(
-                getRequestedOverrideConfiguration(), freezeDisplayToken, false);
+        final Configuration config = updateOrientation(
+                getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */);
         // If display rotation class tells us that it doesn't consider app requested orientation,
         // this display won't rotate just because of an app changes its requested orientation. Thus
         // it indicates that this display chooses not to handle this request.
@@ -1298,31 +1230,35 @@
     /**
      * Determine the new desired orientation of this display.
      *
-     * The orientation is computed from non-application windows first. If none of the
-     * non-application windows specify orientation, the orientation is computed from application
-     * tokens.
-     *
-     * @return {@code true} if the orientation is changed.
+     * @see #getOrientation()
+     * @return {@code true} if the orientation is changed and the caller should call
+     *         {@link #sendNewConfiguration} if the method returns {@code true}.
      */
-    boolean updateOrientationFromAppTokens() {
-        return updateOrientationFromAppTokens(false /* forceUpdate */);
+    boolean updateOrientation() {
+        return mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */);
     }
 
     /**
-     * Update orientation of the target display, returning a non-null new Configuration if it has
+     * Update orientation of the display, returning a non-null new Configuration if it has
      * changed from the current orientation. If a non-null configuration is returned, someone must
      * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
      * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
-     * be done by calling {@link WindowManagerService#sendNewConfiguration(int)}.
+     * be done by calling {@link #sendNewConfiguration}.
+     *
+     * @param currentConfig The current requested override configuration (it is usually set from
+     *                      the last {@link #sendNewConfiguration}) of the display. It is used to
+     *                      check if the configuration container has the latest state.
+     * @param freezeDisplayToken Freeze the app window token if the orientation is changed.
+     * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
      */
-    Configuration updateOrientationFromAppTokens(Configuration currentConfig,
-            IBinder freezeDisplayToken, boolean forceUpdate) {
+    Configuration updateOrientation(Configuration currentConfig, IBinder freezeDisplayToken,
+            boolean forceUpdate) {
         if (!mDisplayReady) {
             return null;
         }
 
         Configuration config = null;
-        if (updateOrientationFromAppTokens(forceUpdate)) {
+        if (mDisplayRotation.updateOrientation(getOrientation(), forceUpdate)) {
             // If we changed the orientation but mOrientationChangeComplete is already true,
             // we used seamless rotation, and we don't need to freeze the screen.
             if (freezeDisplayToken != null && !mWmService.mRoot.mOrientationChangeComplete) {
@@ -1345,10 +1281,7 @@
             if (currentConfig.diff(mTmpConfiguration) != 0) {
                 mWaitingForConfig = true;
                 setLayoutNeeded();
-                int[] anim = new int[2];
-                getDisplayPolicy().selectRotationAnimationLw(anim);
-
-                mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);
+                mDisplayRotation.prepareNormalRotationAnimation();
                 config = new Configuration(mTmpConfiguration);
             }
         }
@@ -1356,163 +1289,14 @@
         return config;
     }
 
-
-    private boolean updateOrientationFromAppTokens(boolean forceUpdate) {
-        final int req = getOrientation();
-        if (req != mLastOrientation || forceUpdate) {
-            mLastOrientation = req;
-            mDisplayRotation.setCurrentOrientation(req);
-            return updateRotationUnchecked(forceUpdate);
-        }
-        return false;
-    }
-
-    /**
-     * Update rotation of the display and send configuration if the rotation is changed.
-     *
-     * @return {@code true} if the rotation has been changed and the new config is sent.
-     */
-    boolean updateRotationAndSendNewConfigIfNeeded() {
-        final boolean changed = updateRotationUnchecked(false /* forceUpdate */);
-        if (changed) {
-            sendNewConfiguration();
-        }
-        return changed;
-    }
-
     /**
      * Update rotation of the display.
      *
      * @return {@code true} if the rotation has been changed.  In this case YOU MUST CALL
-     *         {@link WindowManagerService#sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
+     *         {@link #sendNewConfiguration} TO UNFREEZE THE SCREEN.
      */
     boolean updateRotationUnchecked() {
-        return updateRotationUnchecked(false /* forceUpdate */);
-    }
-
-    /**
-     * Update rotation of the DisplayContent with an option to force the update. This updates
-     * the container's perception of rotation and, depending on the top activities, will freeze
-     * the screen or start seamless rotation. The display itself gets rotated in
-     * {@link #applyRotationLocked} during {@link DisplayContent#sendNewConfiguration}.
-     *
-     * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
-     *                    orientation because we're waiting for some rotation to finish or display
-     *                    to unfreeze, which results in configuration of the previously visible
-     *                    activity being applied to a newly visible one. Forcing the rotation
-     *                    update allows to workaround this issue.
-     * @return {@code true} if the rotation has been changed.  In this case YOU MUST CALL
-     *         {@link WindowManagerService#sendNewConfiguration(int)} TO COMPLETE THE ROTATION AND
-     *         UNFREEZE THE SCREEN.
-     */
-    boolean updateRotationUnchecked(boolean forceUpdate) {
-        ScreenRotationAnimation screenRotationAnimation;
-        if (!forceUpdate) {
-            if (mDeferredRotationPauseCount > 0) {
-                // Rotation updates have been paused temporarily.  Defer the update until
-                // updates have been resumed.
-                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
-                return false;
-            }
-
-            screenRotationAnimation =
-                    mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
-            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
-                // Rotation updates cannot be performed while the previous rotation change
-                // animation is still in progress.  Skip this update.  We will try updating
-                // again after the animation is finished and the display is unfrozen.
-                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
-                return false;
-            }
-            if (mWmService.mDisplayFrozen) {
-                // Even if the screen rotation animation has finished (e.g. isAnimating
-                // returns false), there is still some time where we haven't yet unfrozen
-                // the display. We also need to abort rotation here.
-                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
-                        "Deferring rotation, still finishing previous rotation");
-                return false;
-            }
-        }
-
-        if (!mWmService.mDisplayEnabled) {
-            // No point choosing a rotation if the display is not enabled.
-            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
-            return false;
-        }
-
-        final int oldRotation = mRotation;
-        final int lastOrientation = mLastOrientation;
-        final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id="
-                + mDisplayId + " based on lastOrientation=" + lastOrientation
-                + " and oldRotation=" + oldRotation);
-        boolean mayRotateSeamlessly = mDisplayPolicy.shouldRotateSeamlessly(mDisplayRotation,
-                oldRotation, rotation);
-
-        if (mayRotateSeamlessly) {
-            final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
-            if (seamlessRotated != null && !forceUpdate) {
-                // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
-                // to complete (that is, waiting for windows to redraw). It's tempting to check
-                // w.mSeamlessRotationCount but that could be incorrect in the case of
-                // window-removal.
-                return false;
-            }
-
-            // In the presence of the PINNED stack or System Alert
-            // windows we unfortunately can not seamlessly rotate.
-            if (hasPinnedStack()) {
-                mayRotateSeamlessly = false;
-            }
-            for (int i = 0; i < mWmService.mSessions.size(); i++) {
-                if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
-                    mayRotateSeamlessly = false;
-                    break;
-                }
-            }
-        }
-        final boolean rotateSeamlessly = mayRotateSeamlessly;
-
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
-                + " selected orientation " + lastOrientation
-                + ", got rotation " + rotation);
-
-        if (oldRotation == rotation) {
-            // No change.
-            return false;
-        }
-
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
-                + " rotation changed to " + rotation
-                + " from " + oldRotation
-                + ", lastOrientation=" + lastOrientation);
-
-        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
-            mWaitingForConfig = true;
-        }
-
-        mRotation = rotation;
-
-        mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-        mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
-                this, WINDOW_FREEZE_TIMEOUT_DURATION);
-
-        setLayoutNeeded();
-        final int[] anim = new int[2];
-        mDisplayPolicy.selectRotationAnimationLw(anim);
-
-        if (!rotateSeamlessly) {
-            mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);
-            // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
-        } else {
-            // The screen rotation animation uses a screenshot to freeze the screen
-            // while windows resize underneath.
-            // When we are rotating seamlessly, we allow the elements to transition
-            // to their rotated state independently and without a freeze required.
-            mWmService.startSeamlessRotation();
-        }
-
-        return true;
+        return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
     }
 
     /**
@@ -1523,10 +1307,11 @@
      * @param rotation the rotation to apply.
      */
     void applyRotationLocked(final int oldRotation, final int rotation) {
-        mDisplayRotation.setRotation(rotation);
-        final boolean rotateSeamlessly = mWmService.isRotatingSeamlessly();
-        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
+        mDisplayRotation.applyCurrentRotation(rotation);
+        final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
+        final ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
                 ? null : mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+        final Transaction transaction = getPendingTransaction();
         // We need to update our screen size information to match the new rotation. If the rotation
         // has actually changed then this method will return true and, according to the comment at
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -1537,15 +1322,14 @@
         // NOTE: We disable the rotation in the emulator because
         //       it doesn't support hardware OpenGL emulation yet.
         if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            screenRotationAnimation.setRotation(getPendingTransaction(), rotation);
+            screenRotationAnimation.setRotation(transaction, rotation);
         }
 
         forAllWindows(w -> {
-            w.seamlesslyRotateIfAllowed(getPendingTransaction(), oldRotation, rotation,
-                    rotateSeamlessly);
+            w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
         }, true /* traverseTopToBottom */);
 
-        mWmService.mDisplayManagerInternal.performTraversal(getPendingTransaction());
+        mWmService.mDisplayManagerInternal.performTraversal(transaction);
         scheduleAnimation();
 
         forAllWindows(w -> {
@@ -1627,19 +1411,20 @@
      */
     private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
         // Use the effective "visual" dimensions based on current rotation
-        final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270);
+        final int rotation = getRotation();
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
         final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
         final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
 
         // Update application display metrics.
-        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(mRotation);
+        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
         final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
 
-        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
+        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
                 displayCutout);
-        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
+        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
                 displayCutout);
-        mDisplayInfo.rotation = mRotation;
+        mDisplayInfo.rotation = rotation;
         mDisplayInfo.logicalWidth = dw;
         mDisplayInfo.logicalHeight = dh;
         mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
@@ -2044,7 +1829,7 @@
         return mTaskStackContainers.getPinnedStack();
     }
 
-    private boolean hasPinnedStack() {
+    boolean hasPinnedStack() {
         return mTaskStackContainers.getPinnedStack() != null;
     }
 
@@ -2198,6 +1983,11 @@
         return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
     }
 
+    /**
+     * In the general case, the orientation is computed from the above app windows first. If none of
+     * the above app windows specify orientation, the orientation is computed from the child window
+     * container, e.g. {@link AppWindowToken#getOrientation(int)}.
+     */
     @Override
     int getOrientation() {
         final WindowManagerPolicy policy = mWmService.mPolicy;
@@ -2222,8 +2012,8 @@
                 // things aren't stable while the display is frozen, for example the window could be
                 // momentarily unavailable due to activity relaunch.
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId
-                        + " is frozen while keyguard locked, return " + mLastOrientation);
-                return mLastOrientation;
+                        + " is frozen while keyguard locked, return " + getLastOrientation());
+                return getLastOrientation();
             }
         } else {
             final int orientation = mAboveAppWindowsContainers.getOrientation();
@@ -2835,7 +2625,7 @@
         }
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
-        proto.write(ROTATION, mRotation);
+        proto.write(ROTATION, getRotation());
         final ScreenRotationAnimation screenRotationAnimation =
                 mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
         if (screenRotationAnimation != null) {
@@ -2892,8 +2682,6 @@
 
         pw.println();
         pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
-        pw.print(prefix);
-        pw.print("mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
 
         pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
         if (mLastFocus != mCurrentFocus) {
@@ -3264,6 +3052,15 @@
         return mTmpWindow != null;
     }
 
+    boolean hasAlertWindowSurfaces() {
+        for (int i = mWmService.mSessions.size() - 1; i >= 0; --i) {
+            if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces(this)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Set input method window for the display.
      * @param win Set when window added or Null when destroyed.
@@ -3747,7 +3544,7 @@
 
             if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
-                if (updateOrientationFromAppTokens()) {
+                if (updateOrientation()) {
                     setLayoutNeeded();
                     sendNewConfiguration();
                 }
@@ -3887,7 +3684,7 @@
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
         // TODO: Not sure if we really need to set the rotation here since we are updating from
         // the display info above...
-        mDisplayFrames.mRotation = mRotation;
+        mDisplayFrames.mRotation = getRotation();
         mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
 
         int seq = mLayoutSeq + 1;
@@ -4002,25 +3799,6 @@
         }
     }
 
-    void onSeamlessRotationTimeout() {
-        // Used to indicate the layout is needed.
-        mTmpWindow = null;
-
-        forAllWindows(w -> {
-            if (!w.mSeamlesslyRotated) {
-                return;
-            }
-            mTmpWindow = w;
-            w.setDisplayLayoutNeeded();
-            w.finishSeamlessRotation(true /* timeout */);
-            mWmService.markForSeamlessRotation(w, false);
-        }, true /* traverseTopToBottom */);
-
-        if (mTmpWindow != null) {
-            mWmService.mWindowPlacerLocked.performSurfacePlacement();
-        }
-    }
-
     void setExitingTokensHasVisible(boolean hasVisible) {
         for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
             mExitingTokens.get(i).hasVisible = hasVisible;
@@ -4502,11 +4280,11 @@
             }
 
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
-                    "No app is requesting an orientation, return " + mLastOrientation
+                    "No app is requesting an orientation, return " + getLastOrientation()
                             + " for display id=" + mDisplayId);
             // The next app has not been requested to be visible, so we keep the current orientation
             // to prevent freezing/unfreezing the display too early.
-            return mLastOrientation;
+            return getLastOrientation();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e5962ae..8328770 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,10 +57,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -257,7 +253,6 @@
     private volatile boolean mNavigationBarCanMove;
     private volatile boolean mNavigationBarLetsThroughTaps;
     private volatile boolean mNavigationBarAlwaysShowOnSideGesture;
-    private volatile boolean mAllowSeamlessRotationDespiteNavBarMoving;
 
     // Written by vr manager thread, only read in this class.
     private volatile boolean mPersistentVrModeEnabled;
@@ -1151,81 +1146,6 @@
     }
 
     /**
-     * Determine the animation to run for a rotation transition based on the
-     * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
-     * and whether it is currently fullscreen and frontmost.
-     *
-     * @param anim The exiting animation resource id is stored in anim[0], the
-     * entering animation resource id is stored in anim[1].
-     */
-    public void selectRotationAnimationLw(int anim[]) {
-        // If the screen is off or non-interactive, force a jumpcut.
-        final boolean forceJumpcut = !mScreenOnFully || !mService.mPolicy.okToAnimate();
-        if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation mTopFullscreen="
-                + mTopFullscreenOpaqueWindowState + " rotationAnimation="
-                + (mTopFullscreenOpaqueWindowState == null
-                ? "0" : mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation)
-                + " forceJumpcut=" + forceJumpcut);
-        if (forceJumpcut) {
-            anim[0] = R.anim.rotation_animation_jump_exit;
-            anim[1] = R.anim.rotation_animation_enter;
-            return;
-        }
-        if (mTopFullscreenOpaqueWindowState != null) {
-            int animationHint = mTopFullscreenOpaqueWindowState.getRotationAnimationHint();
-            if (animationHint < 0 && mTopIsFullscreen) {
-                animationHint = mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation;
-            }
-            switch (animationHint) {
-                case ROTATION_ANIMATION_CROSSFADE:
-                case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
-                    anim[0] = R.anim.rotation_animation_xfade_exit;
-                    anim[1] = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_JUMPCUT:
-                    anim[0] = R.anim.rotation_animation_jump_exit;
-                    anim[1] = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_ROTATE:
-                default:
-                    anim[0] = anim[1] = 0;
-                    break;
-            }
-        } else {
-            anim[0] = anim[1] = 0;
-        }
-    }
-
-    /**
-     * Validate whether the current top fullscreen has specified the same
-     * {@link WindowManager.LayoutParams#rotationAnimation} value as that
-     * being passed in from the previous top fullscreen window.
-     *
-     * @param exitAnimId exiting resource id from the previous window.
-     * @param enterAnimId entering resource id from the previous window.
-     * @param forceDefault For rotation animations only, if true ignore the
-     * animation values and just return false.
-     * @return true if the previous values are still valid, false if they
-     * should be replaced with the default.
-     */
-    public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
-            boolean forceDefault) {
-        switch (exitAnimId) {
-            case R.anim.rotation_animation_xfade_exit:
-            case R.anim.rotation_animation_jump_exit:
-                // These are the only cases that matter.
-                if (forceDefault) {
-                    return false;
-                }
-                int anim[] = new int[2];
-                selectRotationAnimationLw(anim);
-                return (exitAnimId == anim[0] && enterAnimId == anim[1]);
-            default:
-                return true;
-        }
-    }
-
-    /**
      * 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.
@@ -2388,6 +2308,14 @@
         displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
     }
 
+    WindowState getTopFullscreenOpaqueWindow() {
+        return mTopFullscreenOpaqueWindowState;
+    }
+
+    boolean isTopLayoutFullscreen() {
+        return mTopIsFullscreen;
+    }
+
     /**
      * Called following layout of all windows before each window has policy applied.
      */
@@ -2773,8 +2701,7 @@
         mNavigationBarCanMove =
                 mDisplayContent.mBaseDisplayWidth != mDisplayContent.mBaseDisplayHeight
                         && res.getBoolean(R.bool.config_navBarCanMove);
-        mAllowSeamlessRotationDespiteNavBarMoving =
-                res.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+        mDisplayContent.getDisplayRotation().updateUserDependentConfiguration(res);
     }
 
     /**
@@ -3562,45 +3489,6 @@
         return (systemUiFlags & disableNavigationBar) == disableNavigationBar;
     }
 
-    boolean shouldRotateSeamlessly(DisplayRotation displayRotation, int oldRotation,
-            int newRotation) {
-        // For the upside down rotation we don't rotate seamlessly as the navigation
-        // bar moves position.
-        // Note most apps (using orientation:sensor or user as opposed to fullSensor)
-        // will not enter the reverse portrait orientation, so actually the
-        // orientation won't change at all.
-        if (oldRotation == displayRotation.getUpsideDownRotation()
-                || newRotation == displayRotation.getUpsideDownRotation()) {
-            return false;
-        }
-        // If the navigation bar can't change sides, then it will
-        // jump when we change orientations and we don't rotate
-        // seamlessly - unless that is allowed, eg. with gesture
-        // navigation where the navbar is low-profile enough that this isn't very noticeable.
-        if (!navigationBarCanMove() && !mAllowSeamlessRotationDespiteNavBarMoving) {
-            return false;
-        }
-
-        final WindowState w = mTopFullscreenOpaqueWindowState;
-        if (w == null || w != mFocusedWindow) {
-            return false;
-        }
-        // If the bounds of activity window is different from its parent, then reject to be seamless
-        // because the window position may change after rotation that will look like a sudden jump.
-        if (w.mAppToken != null && !w.mAppToken.matchParentBounds()) {
-            return false;
-        }
-
-        // We only enable seamless rotation if the top window has requested
-        // it and is in the fullscreen opaque state. Seamless rotation
-        // requires freezing various Surface states and won't work well
-        // with animations, so we disable it in the animation case for now.
-        if (!w.isAnimatingLw() && w.getAttrs().rotationAnimation == ROTATION_ANIMATION_SEAMLESS) {
-            return true;
-        }
-        return false;
-    }
-
     private final Runnable mHiddenNavPanic = new Runnable() {
         @Override
         public void run() {
@@ -3665,7 +3553,7 @@
     }
 
     void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("DisplayPolicy");
+        pw.print(prefix); pw.println("DisplayPolicy");
         prefix += "  ";
         pw.print(prefix);
         pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
@@ -3724,12 +3612,12 @@
             pw.print(prefix); pw.print("mForcingShowNavBarLayer=");
             pw.println(mForcingShowNavBarLayer);
         }
-        pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
+        pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
         pw.print(" mForceStatusBarFromKeyguard="); pw.println(mForceStatusBarFromKeyguard);
-        pw.print(" mForceShowSystemBarsFromExternal=");
-        pw.println(mForceShowSystemBarsFromExternal);
-        pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
+        pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal=");
+        pw.print(mForceShowSystemBarsFromExternal);
+        pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
         mStatusBarController.dump(pw, prefix);
         mNavigationBarController.dump(pw, prefix);
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 410cc94..4d188f4 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,11 +16,21 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
+import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
 
+import android.annotation.AnimRes;
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -28,6 +38,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -41,6 +52,7 @@
 import android.util.SparseArray;
 import android.view.Surface;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
@@ -59,6 +71,13 @@
 public class DisplayRotation {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
 
+    private static class RotationAnimationPair {
+        @AnimRes
+        int mEnter;
+        @AnimRes
+        int mExit;
+    }
+
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mDisplayPolicy;
@@ -72,12 +91,30 @@
     private final int mCarDockRotation;
     private final int mDeskDockRotation;
     private final int mUndockedHdmiRotation;
+    private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
 
     private OrientationListener mOrientationListener;
     private StatusBarManagerInternal mStatusBarManagerInternal;
     private SettingsObserver mSettingsObserver;
 
-    private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    @ScreenOrientation
+    private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    /**
+     * Last applied orientation of the display.
+     *
+     * @see #updateOrientationFromApp
+     */
+    @ScreenOrientation
+    private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    /**
+     * Current rotation of the display.
+     *
+     * @see #updateRotationUnchecked
+     */
+    @Surface.Rotation
+    private int mRotation;
 
     @VisibleForTesting
     int mLandscapeRotation;  // default landscape
@@ -88,11 +125,52 @@
     @VisibleForTesting
     int mUpsideDownRotation; // "other" portrait
 
-    // Behavior of rotation suggestions. (See Settings.Secure.SHOW_ROTATION_SUGGESTION)
+    private boolean mAllowSeamlessRotationDespiteNavBarMoving;
+
+    private int mDeferredRotationPauseCount;
+
+    /**
+     * A count of the windows which are 'seamlessly rotated', e.g. a surface at an old orientation
+     * is being transformed. We freeze orientation updates while any windows are seamlessly rotated,
+     * so we need to track when this hits zero so we can apply deferred orientation updates.
+     */
+    private int mSeamlessRotationCount;
+
+    /**
+     * True in the interval from starting seamless rotation until the last rotated window draws in
+     * the new orientation.
+     */
+    private boolean mRotatingSeamlessly;
+
+    /**
+     * Behavior of rotation suggestions.
+     *
+     * @see Settings.Secure#SHOW_ROTATION_SUGGESTIONS
+     */
     private int mShowRotationSuggestions;
 
-    private int mAllowAllRotations = -1;
+    private static final int ALLOW_ALL_ROTATIONS_UNDEFINED = -1;
+    private static final int ALLOW_ALL_ROTATIONS_DISABLED = 0;
+    private static final int ALLOW_ALL_ROTATIONS_ENABLED = 1;
+
+    @IntDef({ ALLOW_ALL_ROTATIONS_UNDEFINED, ALLOW_ALL_ROTATIONS_DISABLED,
+            ALLOW_ALL_ROTATIONS_ENABLED })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface AllowAllRotations {}
+
+    /**
+     * Whether to allow the screen to rotate to all rotations (including 180 degree) according to
+     * the sensor even when the current orientation is not
+     * {@link ActivityInfo#SCREEN_ORIENTATION_FULL_SENSOR} or
+     * {@link ActivityInfo#SCREEN_ORIENTATION_FULL_USER}.
+     */
+    @AllowAllRotations
+    private int mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
+
+    @WindowManagerPolicy.UserRotationMode
     private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+
+    @Surface.Rotation
     private int mUserRotation = Surface.ROTATION_0;
 
     /**
@@ -125,6 +203,7 @@
      * regardless of all other states (including app requrested orientation). {@code true} the
      * display rotation should be fixed to user specified rotation, {@code false} otherwise.
      */
+    @FixedToUserRotation
     private int mFixedToUserRotation = FIXED_TO_USER_ROTATION_DEFAULT;
 
     private int mDemoHdmiRotation;
@@ -149,21 +228,17 @@
         mLock = lock;
         isDefaultDisplay = displayContent.isDefaultDisplay;
 
-        mSupportAutoRotation = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_supportAutoRotation);
-        mLidOpenRotation = readRotation(
-                com.android.internal.R.integer.config_lidOpenRotation);
-        mCarDockRotation = readRotation(
-                com.android.internal.R.integer.config_carDockRotation);
-        mDeskDockRotation = readRotation(
-                com.android.internal.R.integer.config_deskDockRotation);
-        mUndockedHdmiRotation = readRotation(
-                com.android.internal.R.integer.config_undockedHdmiRotation);
+        mSupportAutoRotation =
+                mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
+        mLidOpenRotation = readRotation(R.integer.config_lidOpenRotation);
+        mCarDockRotation = readRotation(R.integer.config_carDockRotation);
+        mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
+        mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
 
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
             mOrientationListener = new OrientationListener(mContext, uiHandler);
-            mOrientationListener.setCurrentRotation(displayContent.getRotation());
+            mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
         }
@@ -188,12 +263,21 @@
         return -1;
     }
 
+    /**
+     * Updates the configuration which may have different values depending on current user, e.g.
+     * runtime resource overlay.
+     */
+    void updateUserDependentConfiguration(Resources currentUserRes) {
+        mAllowSeamlessRotationDespiteNavBarMoving =
+                currentUserRes.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+    }
+
     void configure(int width, int height, int shortSizeDp, int longSizeDp) {
         final Resources res = mContext.getResources();
         if (width > height) {
             mLandscapeRotation = Surface.ROTATION_0;
             mSeascapeRotation = Surface.ROTATION_180;
-            if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+            if (res.getBoolean(R.bool.config_reverseDefaultRotation)) {
                 mPortraitRotation = Surface.ROTATION_90;
                 mUpsideDownRotation = Surface.ROTATION_270;
             } else {
@@ -203,7 +287,7 @@
         } else {
             mPortraitRotation = Surface.ROTATION_0;
             mUpsideDownRotation = Surface.ROTATION_180;
-            if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+            if (res.getBoolean(R.bool.config_reverseDefaultRotation)) {
                 mLandscapeRotation = Surface.ROTATION_270;
                 mSeascapeRotation = Surface.ROTATION_90;
             } else {
@@ -246,19 +330,325 @@
                 && !"true".equals(SystemProperties.get("config.override_forced_orient"));
     }
 
-    void setRotation(int rotation) {
+    void applyCurrentRotation(@Surface.Rotation int rotation) {
         if (mOrientationListener != null) {
             mOrientationListener.setCurrentRotation(rotation);
         }
     }
 
-    void setCurrentOrientation(int newOrientation) {
+    @VisibleForTesting
+    void setRotation(@Surface.Rotation int rotation) {
+        mRotation = rotation;
+    }
+
+    @Surface.Rotation
+    int getRotation() {
+        return mRotation;
+    }
+
+    @ScreenOrientation
+    int getLastOrientation() {
+        return mLastOrientation;
+    }
+
+    boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {
+        if (newOrientation == mLastOrientation && !forceUpdate) {
+            return false;
+        }
+        mLastOrientation = newOrientation;
         if (newOrientation != mCurrentAppOrientation) {
             mCurrentAppOrientation = newOrientation;
             if (isDefaultDisplay) {
                 updateOrientationListenerLw();
             }
         }
+        return updateRotationUnchecked(forceUpdate);
+    }
+
+    /**
+     * Update rotation of the display and send configuration if the rotation is changed.
+     *
+     * @return {@code true} if the rotation has been changed and the new config is sent.
+     */
+    boolean updateRotationAndSendNewConfigIfChanged() {
+        final boolean changed = updateRotationUnchecked(false /* forceUpdate */);
+        if (changed) {
+            mDisplayContent.sendNewConfiguration();
+        }
+        return changed;
+    }
+
+    /**
+     * Update rotation with an option to force the update. This updates the container's perception
+     * of rotation and, depending on the top activities, will freeze the screen or start seamless
+     * rotation. The display itself gets rotated in {@link DisplayContent#applyRotationLocked}
+     * during {@link DisplayContent#sendNewConfiguration}.
+     *
+     * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
+     *                    orientation because we're waiting for some rotation to finish or display
+     *                    to unfreeze, which results in configuration of the previously visible
+     *                    activity being applied to a newly visible one. Forcing the rotation
+     *                    update allows to workaround this issue.
+     * @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
+     *         {@link DisplayContent#sendNewConfiguration} TO COMPLETE THE ROTATION AND UNFREEZE
+     *         THE SCREEN.
+     */
+    boolean updateRotationUnchecked(boolean forceUpdate) {
+        final int displayId = mDisplayContent.getDisplayId();
+        if (!forceUpdate) {
+            if (mDeferredRotationPauseCount > 0) {
+                // Rotation updates have been paused temporarily. Defer the update until updates
+                // have been resumed.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
+                return false;
+            }
+
+            final ScreenRotationAnimation screenRotationAnimation =
+                    mService.mAnimator.getScreenRotationAnimationLocked(displayId);
+            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+                // Rotation updates cannot be performed while the previous rotation change animation
+                // is still in progress. Skip this update. We will try updating again after the
+                // animation is finished and the display is unfrozen.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
+                return false;
+            }
+            if (mService.mDisplayFrozen) {
+                // Even if the screen rotation animation has finished (e.g. isAnimating returns
+                // false), there is still some time where we haven't yet unfrozen the display. We
+                // also need to abort rotation here.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                        "Deferring rotation, still finishing previous rotation");
+                return false;
+            }
+        }
+
+        if (!mService.mDisplayEnabled) {
+            // No point choosing a rotation if the display is not enabled.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
+            return false;
+        }
+
+        final int oldRotation = mRotation;
+        final int lastOrientation = mLastOrientation;
+        final int rotation = rotationForOrientation(lastOrientation, oldRotation);
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id="
+                + displayId + " based on lastOrientation=" + lastOrientation
+                + " and oldRotation=" + oldRotation);
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + displayId
+                + " selected orientation " + lastOrientation
+                + ", got rotation " + rotation);
+
+        if (oldRotation == rotation) {
+            // No change.
+            return false;
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + displayId
+                + " rotation changed to " + rotation
+                + " from " + oldRotation
+                + ", lastOrientation=" + lastOrientation);
+
+        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
+            mDisplayContent.mWaitingForConfig = true;
+        }
+
+        mRotation = rotation;
+
+        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
+        mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
+                mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
+
+        mDisplayContent.setLayoutNeeded();
+
+        if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
+            // The screen rotation animation uses a screenshot to freeze the screen while windows
+            // resize underneath. When we are rotating seamlessly, we allow the elements to
+            // transition to their rotated state independently and without a freeze required.
+            prepareSeamlessRotation();
+        } else {
+            prepareNormalRotationAnimation();
+        }
+
+        return true;
+    }
+
+    void prepareNormalRotationAnimation() {
+        final RotationAnimationPair anim = selectRotationAnimation();
+        mService.startFreezingDisplayLocked(anim.mExit, anim.mEnter, mDisplayContent);
+    }
+
+    private void prepareSeamlessRotation() {
+        // We are careful to reset this in case a window was removed before it finished
+        // seamless rotation.
+        mSeamlessRotationCount = 0;
+        mRotatingSeamlessly = true;
+    }
+
+    boolean isRotatingSeamlessly() {
+        return mRotatingSeamlessly;
+    }
+
+    @VisibleForTesting
+    boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
+        final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
+        if (w == null || w != mDisplayContent.mCurrentFocus) {
+            return false;
+        }
+        // We only enable seamless rotation if the top window has requested it and is in the
+        // fullscreen opaque state. Seamless rotation requires freezing various Surface states and
+        // won't work well with animations, so we disable it in the animation case for now.
+        if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
+            return false;
+        }
+
+        // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
+        // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
+        // will not enter the reverse portrait orientation, so actually the orientation won't change
+        // at all.
+        if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
+            return false;
+        }
+
+        // If the navigation bar can't change sides, then it will jump when we change orientations
+        // and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation
+        // where the navbar is low-profile enough that this isn't very noticeable.
+        if (!mAllowSeamlessRotationDespiteNavBarMoving && !mDisplayPolicy.navigationBarCanMove()) {
+            return false;
+        }
+
+        // If the bounds of activity window is different from its parent, then reject to be seamless
+        // because the window position may change after rotation that will look like a sudden jump.
+        if (w.mAppToken != null && !w.mAppToken.matchParentBounds()) {
+            return false;
+        }
+
+        // In the presence of the PINNED stack or System Alert windows we unfortunately can not
+        // seamlessly rotate.
+        if (mDisplayContent.hasPinnedStack() || mDisplayContent.hasAlertWindowSurfaces()) {
+            return false;
+        }
+
+        // We can't rotate (seamlessly or not) while waiting for the last seamless rotation to
+        // complete (that is, waiting for windows to redraw). It's tempting to check
+        // mSeamlessRotationCount but that could be incorrect in the case of window-removal.
+        if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
+        if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
+            return;
+        }
+
+        w.mSeamlesslyRotated = seamlesslyRotated;
+        if (seamlesslyRotated) {
+            mSeamlessRotationCount++;
+        } else {
+            mSeamlessRotationCount--;
+        }
+        if (mSeamlessRotationCount == 0) {
+            if (DEBUG_ORIENTATION) {
+                Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
+            }
+            // Finish seamless rotation.
+            mRotatingSeamlessly = false;
+
+            updateRotationAndSendNewConfigIfChanged();
+        }
+    }
+
+    void onSeamlessRotationTimeout() {
+        final boolean[] isLayoutNeeded = { false };
+
+        mDisplayContent.forAllWindows(w -> {
+            if (!w.mSeamlesslyRotated) {
+                return;
+            }
+            isLayoutNeeded[0] = true;
+            w.setDisplayLayoutNeeded();
+            w.finishSeamlessRotation(true /* timeout */);
+            markForSeamlessRotation(w, false /* seamlesslyRotated */);
+        }, true /* traverseTopToBottom */);
+
+        if (isLayoutNeeded[0]) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+    }
+
+    /**
+     * Returns the animation to run for a rotation transition based on the top fullscreen windows
+     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
+     * fullscreen and frontmost.
+     */
+    private RotationAnimationPair selectRotationAnimation() {
+        // If the screen is off or non-interactive, force a jumpcut.
+        final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
+                || !mService.mPolicy.okToAnimate();
+        final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
+        if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation topFullscreen="
+                + topFullscreen + " rotationAnimation="
+                + (topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation)
+                + " forceJumpcut=" + forceJumpcut);
+        if (forceJumpcut) {
+            mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
+            mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+            return mTmpRotationAnim;
+        }
+        if (topFullscreen != null) {
+            int animationHint = topFullscreen.getRotationAnimationHint();
+            if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
+                animationHint = topFullscreen.getAttrs().rotationAnimation;
+            }
+            switch (animationHint) {
+                case ROTATION_ANIMATION_CROSSFADE:
+                case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
+                    mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
+                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+                    break;
+                case ROTATION_ANIMATION_JUMPCUT:
+                    mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
+                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
+                    break;
+                case ROTATION_ANIMATION_ROTATE:
+                default:
+                    mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
+                    break;
+            }
+        } else {
+            mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
+        }
+        return mTmpRotationAnim;
+    }
+
+    /**
+     * Validate whether the current top fullscreen has specified the same
+     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
+     * in from the previous top fullscreen window.
+     *
+     * @param exitAnimId exiting resource id from the previous window.
+     * @param enterAnimId entering resource id from the previous window.
+     * @param forceDefault For rotation animations only, if true ignore the animation values and
+     *                     just return false.
+     * @return {@code true} if the previous values are still valid, false if they should be replaced
+     *         with the default.
+     */
+    boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
+        switch (exitAnimId) {
+            case R.anim.rotation_animation_xfade_exit:
+            case R.anim.rotation_animation_jump_exit:
+                // These are the only cases that matter.
+                if (forceDefault) {
+                    return false;
+                }
+                final RotationAnimationPair anim = selectRotationAnimation();
+                return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
+            default:
+                return true;
+        }
     }
 
     void restoreSettings(int userRotationMode, int userRotation,
@@ -327,7 +717,7 @@
     }
 
     void freezeRotation(int rotation) {
-        rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
+        rotation = (rotation == -1) ? mRotation : rotation;
         setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
     }
 
@@ -407,6 +797,30 @@
     }
 
     /**
+     * Temporarily pauses rotation changes until resumed.
+     * <p>
+     * This can be used to prevent rotation changes from occurring while the user is performing
+     * certain operations, such as drag and drop.
+     * <p>
+     * This call nests and must be matched by an equal number of calls to {@link #resume}.
+     */
+    void pause() {
+        mDeferredRotationPauseCount++;
+    }
+
+    /** Resumes normal rotation changes after being paused. */
+    void resume() {
+        if (mDeferredRotationPauseCount <= 0) {
+            return;
+        }
+
+        mDeferredRotationPauseCount--;
+        if (mDeferredRotationPauseCount == 0) {
+            updateRotationAndSendNewConfigIfChanged();
+        }
+    }
+
+    /**
      * Various use cases for invoking this function:
      * <li>Screen turning off, should always disable listeners if already enabled.</li>
      * <li>Screen turned on and current app has sensor based orientation, enable listeners
@@ -515,14 +929,28 @@
     }
 
     /**
-     * Given an orientation constant, returns the appropriate surface rotation,
-     * taking into account sensors, docking mode, rotation lock, and other factors.
+     * If this is true we have updated our desired orientation, but not yet changed the real
+     * orientation our applied our screen rotation animation. For example, because a previous
+     * screen rotation was in progress.
+     *
+     * @return {@code true} if the there is an ongoing rotation change.
+     */
+    boolean needsUpdate() {
+        final int oldRotation = mRotation;
+        final int rotation = rotationForOrientation(mLastOrientation, oldRotation);
+        return oldRotation != rotation;
+    }
+
+    /**
+     * Given an orientation constant, returns the appropriate surface rotation, taking into account
+     * sensors, docking mode, rotation lock, and other factors.
      *
      * @param orientation An orientation constant, such as
-     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+     *                    {@link ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
      * @param lastRotation The most recently used rotation.
      * @return The surface rotation to use.
      */
+    @VisibleForTesting
     int rotationForOrientation(int orientation, int lastRotation) {
         if (DEBUG_ORIENTATION) {
             Slog.v(TAG, "rotationForOrientation(orient="
@@ -614,15 +1042,16 @@
                 || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
             // Otherwise, use sensor only if requested by the application or enabled
             // by default for USER or UNSPECIFIED modes.  Does not apply to NOSENSOR.
-            if (mAllowAllRotations < 0) {
-                // Can't read this during init() because the context doesn't
-                // have display metrics at that time so we cannot determine
-                // tablet vs. phone then.
+            if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
+                // Can't read this during init() because the context doesn't have display metrics at
+                // that time so we cannot determine tablet vs. phone then.
                 mAllowAllRotations = mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0;
+                        R.bool.config_allowAllRotations)
+                                ? ALLOW_ALL_ROTATIONS_ENABLED
+                                : ALLOW_ALL_ROTATIONS_DISABLED;
             }
             if (sensorRotation != Surface.ROTATION_180
-                    || mAllowAllRotations == 1
+                    || mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED
                     || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                     || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
                 preferredRotation = sensorRotation;
@@ -883,6 +1312,10 @@
         pw.println(prefix + "DisplayRotation");
         pw.println(prefix + "  mCurrentAppOrientation="
                 + ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
+        pw.println(prefix + "  mLastOrientation=" + mLastOrientation);
+        pw.print(prefix + "  mRotation=" + mRotation);
+        pw.println(" mDeferredRotationPauseCount=" + mDeferredRotationPauseCount);
+
         pw.print(prefix + "  mLandscapeRotation=" + Surface.rotationToString(mLandscapeRotation));
         pw.println(" mSeascapeRotation=" + Surface.rotationToString(mSeascapeRotation));
         pw.print(prefix + "  mPortraitRotation=" + Surface.rotationToString(mPortraitRotation));
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c8f7af5..c48f07c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -305,7 +305,7 @@
             if (DEBUG_ORIENTATION) {
                 Slog.d(TAG_WM, "Pausing rotation during drag");
             }
-            mDisplayContent.pauseRotationLocked();
+            mDisplayContent.getDisplayRotation().pause();
         }
 
         void tearDown() {
@@ -324,7 +324,7 @@
             if (DEBUG_ORIENTATION) {
                 Slog.d(TAG_WM, "Resuming rotation after drag");
             }
-            mDisplayContent.resumeRotationLocked();
+            mDisplayContent.getDisplayRotation().resume();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 3401de6..eb5d096 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -636,7 +636,7 @@
         final DisplayContent displayContent = mRootWindowContainer.getDisplayContent(displayId);
         Configuration config = null;
         if (displayContent != null) {
-            config = displayContent.updateOrientationFromAppTokens(
+            config = displayContent.updateOrientation(
                     getDisplayOverrideConfiguration(displayId),
                     starting != null && starting.mayFreezeScreenLocked(starting.app)
                             ? starting.appToken : null,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a00bee0..968d02b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -925,8 +925,7 @@
     boolean updateRotationUnchecked() {
         boolean changed = false;
         for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final DisplayContent displayContent = mChildren.get(i);
-            if (displayContent.updateRotationAndSendNewConfigIfNeeded()) {
+            if (mChildren.get(i).getDisplayRotation().updateRotationAndSendNewConfigIfChanged()) {
                 changed = true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9f8f265..4a76042 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -40,6 +40,7 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.view.DisplayCutout;
@@ -57,9 +58,7 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.function.BiConsumer;
 
 /**
@@ -75,9 +74,9 @@
     SurfaceSession mSurfaceSession;
     private int mNumWindow = 0;
     // Set of visible application overlay window surfaces connected to this session.
-    private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
+    private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
     // Set of visible alert window surfaces connected to this session.
-    private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+    private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
     private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
     final boolean mCanHideNonSystemOverlayWindows;
@@ -609,8 +608,15 @@
         return mStringName;
     }
 
-    boolean hasAlertWindowSurfaces() {
-        return !mAlertWindowSurfaces.isEmpty();
+    /** @return {@code true} if there is an alert window surface on the given display. */
+    boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
+        for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
+            final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
+            if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public void blessInputSurface(int displayId, SurfaceControl surface,
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 2fc64ea..8b0b6ce 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -304,7 +304,7 @@
         if (DEBUG_ORIENTATION) {
             Slog.d(TAG, "Pausing rotation during re-position");
         }
-        mDisplayContent.pauseRotationLocked();
+        mDisplayContent.getDisplayRotation().pause();
 
         // Notify InputMonitor to take mDragWindowHandle.
         mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
@@ -347,7 +347,7 @@
         if (DEBUG_ORIENTATION) {
             Slog.d(TAG, "Resuming rotation after re-position");
         }
-        mDisplayContent.resumeRotationLocked();
+        mDisplayContent.getDisplayRotation().resume();
         mDisplayContent = null;
         mClientCallback.unlinkToDeath(this, 0 /* flags */);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3874fdc..4821cf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -141,7 +141,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Matrix;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -667,19 +666,6 @@
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
     SettingsObserver mSettingsObserver;
 
-    /**
-     * A count of the windows which are 'seamlessly rotated', e.g. a surface
-     * at an old orientation is being transformed. We freeze orientation updates
-     * while any windows are seamlessly rotated, so we need to track when this
-     * hits zero so we can apply deferred orientation updates.
-     */
-    private int mSeamlessRotationCount = 0;
-    /**
-     * True in the interval from starting seamless rotation until the last rotated
-     * window draws in the new orientation.
-     */
-    private boolean mRotatingSeamlessly = false;
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
                 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -1247,7 +1233,6 @@
             return res;
         }
 
-        boolean reportNewConfig = false;
         WindowState parentWindow = null;
         long origId;
         final int callingUid = Binder.getCallingUid();
@@ -1623,11 +1608,7 @@
             if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
                     + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
 
-            if (win.isVisibleOrAdding() && displayContent.updateOrientationFromAppTokens()) {
-                reportNewConfig = true;
-            }
-
-            if (reportNewConfig) {
+            if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
                 displayContent.sendNewConfiguration();
             }
         }
@@ -1791,11 +1772,11 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "postWindowRemoveCleanupLocked: " + win);
         mWindowMap.remove(win.mClient.asBinder());
 
-        markForSeamlessRotation(win, false);
+        final DisplayContent dc = win.getDisplayContent();
+        dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
 
         win.resetAppOpsState();
 
-        final DisplayContent dc = win.getDisplayContent();
         if (dc.mCurrentFocus == null) {
             dc.mWinRemovedSinceNullFocus.add(win);
         }
@@ -2241,9 +2222,8 @@
                 displayContent.mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
             }
 
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                    "relayoutWindow: updateOrientationFromAppTokens");
-            configChanged = displayContent.updateOrientationFromAppTokens();
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: updateOrientation");
+            configChanged = displayContent.updateOrientation();
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
             if (toBeDisplayed && win.mIsWallpaper) {
@@ -4859,7 +4839,7 @@
                 case SEAMLESS_ROTATION_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mGlobalLock) {
-                        displayContent.onSeamlessRotationTimeout();
+                        displayContent.getDisplayRotation().onSeamlessRotationTimeout();
                     }
                     break;
                 }
@@ -5321,7 +5301,7 @@
 
     void startFreezingDisplayLocked(int exitAnim, int enterAnim,
             DisplayContent displayContent) {
-        if (mDisplayFrozen || mRotatingSeamlessly) {
+        if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
             return;
         }
 
@@ -5430,8 +5410,8 @@
             if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
             // Get rotation animation again, with new top window
-            if (!displayContent.getDisplayPolicy()
-                    .validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
+            if (!displayContent.getDisplayRotation().validateRotationAnimation(
+                    mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
                 mExitAnimId = mEnterAnimId = 0;
             }
             if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
@@ -5458,7 +5438,7 @@
         // to avoid inconsistent states.  However, something interesting
         // could have actually changed during that time so re-evaluate it
         // now to catch that.
-        configChanged = displayContent != null && displayContent.updateOrientationFromAppTokens();
+        configChanged = displayContent != null && displayContent.updateOrientation();
 
         // A little kludge: a lot could have happened while the
         // display was frozen, so now that we are coming back we
@@ -6984,26 +6964,6 @@
         mPolicy.requestUserActivityNotification();
     }
 
-    void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
-        if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
-            return;
-        }
-        w.mSeamlesslyRotated = seamlesslyRotated;
-        if (seamlesslyRotated) {
-            mSeamlessRotationCount++;
-        } else {
-            mSeamlessRotationCount--;
-        }
-        if (mSeamlessRotationCount == 0) {
-            if (DEBUG_ORIENTATION) {
-                Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
-            }
-            finishSeamlessRotation();
-
-            w.getDisplayContent().updateRotationAndSendNewConfigIfNeeded();
-        }
-    }
-
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
@@ -7531,22 +7491,6 @@
         return mSurfaceBuilderFactory.make(s);
     }
 
-    void startSeamlessRotation() {
-        // We are careful to reset this in case a window was removed before it finished
-        // seamless rotation.
-        mSeamlessRotationCount = 0;
-
-        mRotatingSeamlessly = true;
-    }
-
-    boolean isRotatingSeamlessly() {
-        return mRotatingSeamlessly;
-    }
-
-    void finishSeamlessRotation() {
-        mRotatingSeamlessly = false;
-    }
-
     /**
      * Called when the state of lock task mode changes. This should be used to disable immersive
      * mode confirmation.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d87a0ed..4900869 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -652,7 +652,8 @@
         if (mForceSeamlesslyRotate || requested) {
             mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
             mPendingSeamlessRotate.unrotate(transaction, this);
-            mWmService.markForSeamlessRotation(this, true);
+            getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
+                    true /* seamlesslyRotated */);
         }
     }
 
@@ -661,7 +662,8 @@
             mPendingSeamlessRotate.finish(this, timeout);
             mFinishSeamlessRotateFrameNumber = getFrameNumber();
             mPendingSeamlessRotate = null;
-            mWmService.markForSeamlessRotation(this, false);
+            getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
+                    false /* seamlesslyRotated */);
         }
     }
 
@@ -2086,7 +2088,7 @@
             // So just update orientation if needed.
             if (wasVisible) {
                 final DisplayContent displayContent = getDisplayContent();
-                if (displayContent.updateOrientationFromAppTokens()) {
+                if (displayContent.updateOrientation()) {
                     displayContent.sendNewConfiguration();
                 }
             }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index e387e18..1f634b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -36,7 +36,6 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
@@ -160,7 +159,7 @@
 
         // Set initial orientation and update.
         mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        mDisplayContent.updateOrientationFromAppTokens(
+        mDisplayContent.updateOrientation(
                 mDisplayContent.getRequestedOverrideConfiguration(),
                 null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
@@ -168,7 +167,7 @@
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
         mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
-        mDisplayContent.updateOrientationFromAppTokens(
+        mDisplayContent.updateOrientation(
                 mDisplayContent.getRequestedOverrideConfiguration(),
                 null /* freezeThisOneIfNeeded */, false /* forceUpdate */);
         // In this test, DC will not get config update. Set the waiting flag to false.
@@ -181,8 +180,8 @@
 
     @Test
     public void testLandscapeSeascapeRotationByPolicy() {
+        // This instance has been spied in {@link TestActivityDisplay}.
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
-        spyOn(displayRotation);
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6289768..44f3ee41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -445,7 +445,7 @@
                     .computeSafeInsets(200, 400).getDisplayCutout();
 
             dc.mInitialDisplayCutout = cutout;
-            dc.setRotation(Surface.ROTATION_0);
+            dc.getDisplayRotation().setRotation(Surface.ROTATION_0);
             dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
 
             assertEquals(cutout, dc.getDisplayInfo().displayCutout);
@@ -476,7 +476,7 @@
                     .computeSafeInsets(displayWidth, displayHeight).getDisplayCutout();
 
             dc.mInitialDisplayCutout = cutout;
-            dc.setRotation(Surface.ROTATION_90);
+            dc.getDisplayRotation().setRotation(Surface.ROTATION_90);
             dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
 
             // ----o----------      -------------
@@ -608,18 +608,18 @@
         portraitDisplay.mInitialDisplayHeight = 2000;
         portraitDisplay.mInitialDisplayWidth = 1000;
 
-        portraitDisplay.setRotation(Surface.ROTATION_0);
+        portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
         assertFalse(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
-        portraitDisplay.setRotation(Surface.ROTATION_90);
+        portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
         assertTrue(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
 
         final DisplayContent landscapeDisplay = createNewDisplay();
         landscapeDisplay.mInitialDisplayHeight = 1000;
         landscapeDisplay.mInitialDisplayWidth = 2000;
 
-        landscapeDisplay.setRotation(Surface.ROTATION_0);
+        landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
         assertTrue(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
-        landscapeDisplay.setRotation(Surface.ROTATION_90);
+        landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
         assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 6a3c81a..3ead977 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -28,16 +28,12 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
 
@@ -52,7 +48,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import androidx.test.filters.FlakyTest;
@@ -213,32 +208,15 @@
     }
 
     @Test
-    public void testShouldRotateSeamlessly() {
-        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+    public void testComputeTopFullscreenOpaqueWindow() {
         final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
         attrs.x = attrs.y = 0;
         attrs.height = attrs.width = WindowManager.LayoutParams.MATCH_PARENT;
-        attrs.rotationAnimation = ROTATION_ANIMATION_SEAMLESS;
-        final DisplayRotation displayRotation = mock(DisplayRotation.class);
-        doReturn(Surface.ROTATION_180).when(displayRotation).getUpsideDownRotation();
+        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        policy.applyPostLayoutPolicyLw(
+                mAppWindow, attrs, null /* attached */, null /* imeTarget */);
 
-        synchronized (mWm.mGlobalLock) {
-            policy.focusChangedLw(null /* lastFocus */, mAppWindow);
-            policy.applyPostLayoutPolicyLw(
-                    mAppWindow, attrs, null /* attached */, null /* imeTarget */);
-            spyOn(policy);
-            doReturn(true).when(policy).navigationBarCanMove();
-            // The focused fullscreen opaque window without override bounds should be able to be
-            // rotated seamlessly.
-            assertTrue(policy.shouldRotateSeamlessly(
-                    displayRotation, Surface.ROTATION_0, Surface.ROTATION_90));
-
-            spyOn(mAppWindow.mAppToken);
-            doReturn(false).when(mAppWindow.mAppToken).matchParentBounds();
-            // No seamless rotation if the window may be positioned with offset after rotation.
-            assertFalse(policy.shouldRotateSeamlessly(
-                    displayRotation, Surface.ROTATION_0, Surface.ROTATION_90));
-        }
+        assertEquals(mAppWindow, policy.getTopFullscreenOpaqueWindow());
     }
 
     @Test
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 49d38c0..059ff3d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -24,6 +24,7 @@
 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.doReturn;
 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;
@@ -55,6 +56,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -584,6 +586,33 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void testShouldRotateSeamlessly() throws Exception {
+        mBuilder.build();
+
+        final WindowState win = mock(WindowState.class);
+        win.mAppToken = mock(AppWindowToken.class);
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+        attrs.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+
+        doReturn(attrs).when(win).getAttrs();
+        doReturn(true).when(mMockDisplayPolicy).navigationBarCanMove();
+        doReturn(win).when(mMockDisplayPolicy).getTopFullscreenOpaqueWindow();
+        mMockDisplayContent.mCurrentFocus = win;
+        mTarget.mUpsideDownRotation = Surface.ROTATION_180;
+
+        doReturn(true).when(win.mAppToken).matchParentBounds();
+        // The focused fullscreen opaque window without override bounds should be able to be
+        // rotated seamlessly.
+        assertTrue(mTarget.shouldRotateSeamlessly(
+                Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
+
+        doReturn(false).when(win.mAppToken).matchParentBounds();
+        // No seamless rotation if the window may be positioned with offset after rotation.
+        assertFalse(mTarget.shouldRotateSeamlessly(
+                Surface.ROTATION_0, Surface.ROTATION_90, false /* forceUpdate */));
+    }
+
     // ========================
     // Non-rotation API Tests
     // ========================
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
index c143969..778f0ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
@@ -69,20 +69,22 @@
         mSupervisor = supervisor;
         spyOn(this);
         spyOn(mDisplayContent);
+
+        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+        spyOn(displayRotation);
         doAnswer(invocation -> {
             // Bypass all the rotation animation and display freezing stuff for testing and just
             // set the rotation we want for the display
-            final DisplayContent dc = mDisplayContent;
-            final int oldRotation = dc.getRotation();
-            final int rotation = dc.getDisplayRotation().rotationForOrientation(
-                    dc.getLastOrientation(), oldRotation);
+            final int oldRotation = displayRotation.getRotation();
+            final int rotation = displayRotation.rotationForOrientation(
+                    displayRotation.getLastOrientation(), oldRotation);
             if (oldRotation == rotation) {
                 return false;
             }
-            dc.setLayoutNeeded();
-            dc.setRotation(rotation);
+            mDisplayContent.setLayoutNeeded();
+            displayRotation.setRotation(rotation);
             return true;
-        }).when(mDisplayContent).updateRotationUnchecked(anyBoolean());
+        }).when(displayRotation).updateRotationUnchecked(anyBoolean());
     }
 
     @SuppressWarnings("TypeParameterUnusedInFormals")