Move some display logic into hierarchy [2/2]

Move display update logic from WM into ATM hierarchy by letting
ActivityStack watch for relevant changes and calculate bounds
accordingly and removing configuration updates from WM side
outside of calls from the display-level controller (like how
the other levels work).

One of the main display-changes to account for is rotation. To
make this work without drastically modifying things was to leave
display freeze/startSeamless in WM but move the actual rotation
to ATM while handling SEND_NEW_CONFIGURATION. This prevents
changes to the wm-side hierarchy outside of ATMS's control.

To facilitate this extra communication between ATMS and WM,
this adds rotation into WindowConfiguration. This makes rotation
available to the hierarchy update for policies that care about it
(things like split). It will also replace TaskStack's mRotation
in an upcoming CL and should also let us remove the one-off
variable for landscape->seascape orientation changes (needs
some more research though).

Bug: 113900640
Test: go/wm-smoke + relevant am/wm servicestests
Change-Id: I12c79cc5eb94d48d846f1cf27765c9f9f6741435
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 5fb1def..678d233 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
@@ -51,6 +52,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.UserHandle;
 import android.util.IntArray;
@@ -156,7 +158,7 @@
     }
 
     void updateBounds() {
-        mDisplay.getSize(mTmpDisplaySize);
+        mDisplay.getRealSize(mTmpDisplaySize);
         setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
     }
 
@@ -938,6 +940,25 @@
         return mStacks.indexOf(stack);
     }
 
+    @Override
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        final int currRotation = getOverrideConfiguration().windowConfiguration.getRotation();
+        if (currRotation != ROTATION_UNDEFINED
+                && currRotation != overrideConfiguration.windowConfiguration.getRotation()
+                && getWindowContainerController() != null) {
+            getWindowContainerController().applyRotation(currRotation,
+                    overrideConfiguration.windowConfiguration.getRotation());
+        }
+        super.onOverrideConfigurationChanged(overrideConfiguration);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        // update resources before cascade so that docked/pinned stacks use the correct info
+        getWindowContainerController().preOnConfigurationChanged();
+        super.onConfigurationChanged(newParentConfig);
+    }
+
     void onLockTaskPackagesUpdated() {
         for (int i = mStacks.size() - 1; i >= 0; --i) {
             mStacks.get(i).onLockTaskPackagesUpdated();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index a8b4a9d..c269f79 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -46,6 +46,13 @@
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
+import static com.android.server.am.ActivityStackProto.BOUNDS;
+import static com.android.server.am.ActivityStackProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.ActivityStackProto.DISPLAY_ID;
+import static com.android.server.am.ActivityStackProto.FULLSCREEN;
+import static com.android.server.am.ActivityStackProto.ID;
+import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY;
+import static com.android.server.am.ActivityStackProto.TASKS;
 import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM;
 import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
 import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
@@ -56,13 +63,6 @@
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.am.ActivityStackProto.BOUNDS;
-import static com.android.server.am.ActivityStackProto.CONFIGURATION_CONTAINER;
-import static com.android.server.am.ActivityStackProto.DISPLAY_ID;
-import static com.android.server.am.ActivityStackProto.FULLSCREEN;
-import static com.android.server.am.ActivityStackProto.ID;
-import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY;
-import static com.android.server.am.ActivityStackProto.TASKS;
 import static com.android.server.wm.ActivityStackSupervisor.FindTaskResult;
 import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -513,14 +513,23 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWindowingMode = getWindowingMode();
         final boolean prevIsAlwaysOnTop = isAlwaysOnTop();
-        super.onConfigurationChanged(newParentConfig);
         final ActivityDisplay display = getDisplay();
+
+        getBounds(mTmpRect2);
+        final boolean hasNewBounds = display != null && getWindowContainerController() != null
+                && getWindowContainerController().updateBoundsForConfigChange(
+                        newParentConfig, getConfiguration(), mTmpRect2);
+
+        super.onConfigurationChanged(newParentConfig);
         if (display == null) {
           return;
         }
         if (prevWindowingMode != getWindowingMode()) {
             display.onStackWindowingModeChanged(this);
         }
+        if (hasNewBounds) {
+            resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+        }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
             // Since always on top is only on when the stack is freeform or pinned, the state
             // can be toggled when the windowing mode changes. We must make sure the stack is
@@ -752,6 +761,12 @@
      * @param bounds Updated bounds.
      */
     private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
+        if (mDisplayId != activityDisplay.mDisplayId) {
+            // rotations are relative to the display, so pretend like our current rotation is
+            // the same as the new display so we don't try to rotate bounds.
+            getConfiguration().windowConfiguration.setRotation(
+                    activityDisplay.getWindowConfiguration().getRotation());
+        }
         mDisplayId = activityDisplay.mDisplayId;
         setBounds(bounds);
         onParentChanged();
@@ -810,10 +825,6 @@
         outBounds.setEmpty();
     }
 
-    void getBoundsForNewConfiguration(Rect outBounds) {
-        mWindowContainerController.getBoundsForNewConfiguration(outBounds);
-    }
-
     void positionChildWindowContainerAtTop(TaskRecord child) {
         mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
                 true /* includingParents */);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3ede8bc8..aede239 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -114,17 +114,17 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRACKER_MSG;
-import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
-import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
-import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
+import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRACKER_MSG;
+import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -4873,21 +4873,6 @@
                 mH.sendMessage(msg);
             }
         }
-
-        // Update the configuration with WM first and check if any of the stacks need to be resized
-        // due to the configuration change. If so, resize the stacks now and do any relaunches if
-        // necessary. This way we don't need to relaunch again afterwards in
-        // ensureActivityConfiguration().
-        if (mWindowManager != null) {
-            final int[] resizedStacks =
-                    mWindowManager.setNewDisplayOverrideConfiguration(mTempConfig, displayId);
-            if (resizedStacks != null) {
-                for (int stackId : resizedStacks) {
-                    resizeStackWithBoundsFromWindowManager(stackId, deferResume);
-                }
-            }
-        }
-
         return changes;
     }
 
@@ -5277,28 +5262,6 @@
         return "focused app: " + packageName;
     }
 
-    /** Helper method that requests bounds from WM and applies them to stack. */
-    private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
-        final Rect newStackBounds = new Rect();
-        final ActivityStack stack = mStackSupervisor.getStack(stackId);
-
-        // TODO(b/71548119): Revert CL introducing below once cause of mismatch is found.
-        if (stack == null) {
-            final StringWriter writer = new StringWriter();
-            final PrintWriter printWriter = new PrintWriter(writer);
-            mStackSupervisor.dumpDisplays(printWriter);
-            printWriter.flush();
-
-            Log.wtf(TAG, "stack not found:" + stackId + " displays:" + writer);
-        }
-
-        stack.getBoundsForNewConfiguration(newStackBounds);
-        mStackSupervisor.resizeStackLocked(
-                stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
-                null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
-    }
-
     /** Applies latest configuration and/or visibility updates if needed. */
     private boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
         boolean kept = true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4b34297..fece980 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1153,14 +1153,19 @@
     }
 
     /**
-     * Update rotation of the display with an option to force the update.
+     * 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 WindowManagerService#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 UNFREEZE THE SCREEN.
+     *         {@link WindowManagerService#sendNewConfiguration(int)} TO COMPLETE THE ROTATION AND
+     *         UNFREEZE THE SCREEN.
      */
     boolean updateRotationUnchecked(boolean forceUpdate) {
         ScreenRotationAnimation screenRotationAnimation;
@@ -1258,7 +1263,7 @@
             mService.mWaitingForConfig = true;
         }
 
-        setRotation(rotation);
+        mRotation = rotation;
         mAltOrientation = altOrientation;
 
         mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
@@ -1272,18 +1277,29 @@
         if (!rotateSeamlessly) {
             mService.startFreezingDisplayLocked(anim[0], anim[1], this);
             // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
-            screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked(
-                    mDisplayId);
         } 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.
-            screenRotationAnimation = null;
-
             mService.startSeamlessRotation();
         }
 
+        return true;
+    }
+
+    /**
+     * Applies the rotation transaction. This must be called after {@link #updateRotationUnchecked}
+     * (if it returned {@code true}) to actually finish the rotation.
+     *
+     * @param oldRotation the rotation we are coming from.
+     * @param rotation the rotation to apply.
+     */
+    void applyRotationLocked(final int oldRotation, final int rotation) {
+        mDisplayRotation.setRotation(rotation);
+        final boolean rotateSeamlessly = mService.isRotatingSeamlessly();
+        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
+                ? null : mService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
         // 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().
@@ -1344,8 +1360,6 @@
                 && isDefaultDisplay) {
             mService.mAccessibilityController.onRotationChangedLocked(this);
         }
-
-        return true;
     }
 
     void configureDisplayPolicy() {
@@ -1455,7 +1469,6 @@
                     mCompatDisplayMetrics);
         }
 
-        updateBounds();
         return mDisplayInfo;
     }
 
@@ -1489,11 +1502,14 @@
      */
     void computeScreenConfiguration(Configuration config) {
         final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode);
+        calculateBounds(displayInfo, mTmpBounds);
+        config.windowConfiguration.setBounds(mTmpBounds);
 
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
         config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         config.windowConfiguration.setWindowingMode(getWindowingMode());
+        config.windowConfiguration.setRotation(displayInfo.rotation);
 
         final float density = mDisplayMetrics.density;
         config.screenWidthDp =
@@ -1858,11 +1874,26 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         super.onConfigurationChanged(newParentConfig);
 
+        // If there was no pinned stack, we still need to notify the controller of the display info
+        // update as a result of the config change.
+        if (mPinnedStackControllerLocked != null && !hasPinnedStack()) {
+            mPinnedStackControllerLocked.onDisplayInfoChanged(getDisplayInfo());
+        }
+
         // The display size information is heavily dependent on the resources in the current
         // configuration, so we need to reconfigure it every time the configuration changes.
         // See {@link #configureDisplayPolicy}...sigh...
         mService.reconfigureDisplayLocked(this);
 
+    }
+
+    /**
+     * Updates the resources used by docked/pinned controllers. This needs to be called at the
+     * beginning of a configuration update cascade since the metrics from these resources are used
+     * for bounds calculations. Since ActivityDisplay initiates the configuration update, this
+     * should be called from there instead of DisplayContent's onConfigurationChanged.
+     */
+    void preOnConfigurationChanged() {
         final DockedStackDividerController dividerController = getDockedDividerController();
 
         if (dividerController != null) {
@@ -1876,26 +1907,6 @@
         }
     }
 
-    /**
-     * Callback used to trigger bounds update after configuration change and get ids of stacks whose
-     * bounds were updated.
-     */
-    void updateStackBoundsAfterConfigChange(@NonNull List<TaskStack> changedStackList) {
-        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.getChildAt(i);
-            if (stack.updateBoundsAfterConfigChange()) {
-                changedStackList.add(stack);
-            }
-        }
-
-        // If there was no pinned stack, we still need to notify the controller of the display info
-        // update as a result of the config change.  We do this here to consolidate the flow between
-        // changes when there is and is not a stack.
-        if (!hasPinnedStack()) {
-            mPinnedStackControllerLocked.onDisplayInfoChanged(getDisplayInfo());
-        }
-    }
-
     @Override
     boolean fillsParent() {
         return true;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index f772216..632494b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -88,9 +88,34 @@
 
     @Override
     public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
-        // TODO: The container receives override configuration changes through other means. enabling
-        // callbacks through the controller causes layout issues. Investigate consolidating
-        // override configuration propagation to just here.
+        synchronized (mGlobalLock) {
+            if (mContainer != null) {
+                mContainer.mService.setNewDisplayOverrideConfiguration(overrideConfiguration,
+                        mContainer);
+            }
+        }
+    }
+
+    /**
+     * Updates the docked/pinned controller resources to the current system context.
+     */
+    public void preOnConfigurationChanged() {
+        synchronized (mGlobalLock) {
+            if (mContainer != null) {
+                mContainer.preOnConfigurationChanged();
+            }
+        }
+    }
+
+  /**
+   * @see DisplayContent#applyRotationLocked(int, int)
+   */
+    public void applyRotation(int oldRotation, int newRotation) {
+        synchronized (mGlobalLock) {
+            if (mContainer != null) {
+                mContainer.applyRotationLocked(oldRotation, newRotation);
+            }
+        }
     }
 
     public int getDisplayId() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 67fe5c4..8c0c073 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -54,6 +54,7 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.content.res.Configuration;
 import android.hardware.power.V1_0.PowerHint;
 import android.os.Binder;
@@ -75,12 +76,10 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.function.Consumer;
 
 /** Root {@link WindowContainer} for the device. */
@@ -115,9 +114,6 @@
     boolean mOrientationChangeComplete = true;
     boolean mWallpaperActionPending = false;
 
-    private final ArrayList<TaskStack> mTmpStackList = new ArrayList();
-    private final ArrayList<Integer> mTmpStackIds = new ArrayList<>();
-
     final WallpaperController mWallpaperController;
 
     private final Handler mHandler;
@@ -313,57 +309,33 @@
     }
 
     /**
-     * Set new display override config and return array of ids of stacks that were changed during
-     * update. If called for the default display, global configuration will also be updated. Stacks
-     * that are marked for deferred removal are excluded from the returned array.
+     * Set new display override config. If called for the default display, global configuration
+     * will also be updated.
      */
-    int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) {
-        final DisplayContent displayContent = getDisplayContent(displayId);
-        if (displayContent == null) {
-            throw new IllegalArgumentException("Display not found for id: " + displayId);
-        }
+    void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration,
+            @NonNull DisplayContent displayContent) {
 
         final Configuration currentConfig = displayContent.getOverrideConfiguration();
         final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
         if (!configChanged) {
-            return null;
+            return;
         }
 
         displayContent.onOverrideConfigurationChanged(newConfiguration);
 
-        mTmpStackList.clear();
-        if (displayId == DEFAULT_DISPLAY) {
+        if (displayContent.getDisplayId() == DEFAULT_DISPLAY) {
             // Override configuration of the default display duplicates global config. In this case
             // we also want to update the global config.
-            setGlobalConfigurationIfNeeded(newConfiguration, mTmpStackList);
-        } else {
-            updateStackBoundsAfterConfigChange(displayId, mTmpStackList);
+            setGlobalConfigurationIfNeeded(newConfiguration);
         }
-
-        mTmpStackIds.clear();
-        final int stackCount = mTmpStackList.size();
-
-        for (int i = 0; i < stackCount; ++i) {
-            final TaskStack stack = mTmpStackList.get(i);
-
-            // We only include stacks that are not marked for removal as they do not exist outside
-            // of WindowManager at this point.
-            if (!stack.mDeferRemoval) {
-                mTmpStackIds.add(stack.mStackId);
-            }
-        }
-
-        return mTmpStackIds.isEmpty() ? null : ArrayUtils.convertToIntArray(mTmpStackIds);
     }
 
-    private void setGlobalConfigurationIfNeeded(Configuration newConfiguration,
-            List<TaskStack> changedStacks) {
+    private void setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
         final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
         if (!configChanged) {
             return;
         }
         onConfigurationChanged(newConfiguration);
-        updateStackBoundsAfterConfigChange(changedStacks);
     }
 
     @Override
@@ -374,24 +346,6 @@
         forAllDisplays(mDisplayContentConfigChangesConsumer);
     }
 
-    /**
-     * Callback used to trigger bounds update after configuration change and get ids of stacks whose
-     * bounds were updated.
-     */
-    private void updateStackBoundsAfterConfigChange(List<TaskStack> changedStacks) {
-        final int numDisplays = mChildren.size();
-        for (int i = 0; i < numDisplays; ++i) {
-            final DisplayContent dc = mChildren.get(i);
-            dc.updateStackBoundsAfterConfigChange(changedStacks);
-        }
-    }
-
-    /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */
-    private void updateStackBoundsAfterConfigChange(int displayId, List<TaskStack> changedStacks) {
-        final DisplayContent dc = getDisplayContent(displayId);
-        dc.updateStackBoundsAfterConfigChange(changedStacks);
-    }
-
     private void prepareFreezingTaskBounds() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             mChildren.get(i).prepareFreezingTaskBounds();
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index c920b9e..d8e1ebf 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -245,12 +245,6 @@
         }
     }
 
-    public void getBoundsForNewConfiguration(Rect outBounds) {
-        synchronized (mGlobalLock) {
-            mContainer.getBoundsForNewConfiguration(outBounds);
-        }
-    }
-
     /**
      * Adjusts the screen size in dp's for the {@param config} for the given params. The provided
      * params represent the desired state of a configuration change. Since this utility is used
@@ -378,6 +372,14 @@
         mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
     }
 
+    /** @see TaskStack.updateBoundsForConfigChange(Configuration, Configuration, Rect) */
+    public boolean updateBoundsForConfigChange(
+            Configuration parentConfig, Configuration prevConfig, Rect outBounds) {
+        synchronized (mGlobalLock) {
+            return mContainer.updateBoundsForConfigChange(parentConfig, prevConfig, outBounds);
+        }
+    }
+
     @Override
     public String toString() {
         return "{StackWindowController stackId=" + mStackId + "}";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3f394a2..30eca89 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -290,12 +290,7 @@
         if (displayContent != null) {
             rotation = displayContent.getDisplayInfo().rotation;
         } else if (bounds == null) {
-            // Can't set to fullscreen if we don't have a display to get bounds from...
-            return BOUNDS_CHANGE_NONE;
-        }
-
-        if (equivalentOverrideBounds(bounds)) {
-            return BOUNDS_CHANGE_NONE;
+            return super.setBounds(bounds);
         }
 
         final int boundsChange = super.setBounds(bounds);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 70a275b..4dc2b8e 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -23,6 +23,9 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -144,10 +147,6 @@
     private Rect mBoundsAnimationTarget = new Rect();
     private Rect mBoundsAnimationSourceHintBounds = new Rect();
 
-    // Temporary storage for the new bounds that should be used after the configuration change.
-    // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
-    private final Rect mBoundsAfterRotation = new Rect();
-
     Rect mPreAnimationBounds = new Rect();
 
     private Dimmer mDimmer = new Dimmer(this);
@@ -293,8 +292,9 @@
     private int setBounds(Rect existing, Rect bounds) {
         int rotation = Surface.ROTATION_0;
         int density = DENSITY_DPI_UNDEFINED;
-        if (mDisplayContent != null) {
-            mDisplayContent.getBounds(mTmpRect);
+        WindowContainer parent = getParent();
+        if (parent != null) {
+            parent.getBounds(mTmpRect);
             rotation = mDisplayContent.getDisplayInfo().rotation;
             density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
         }
@@ -444,20 +444,33 @@
         // If the rotation or density didn't match, we'll update it in onConfigurationChanged.
     }
 
-    /** @return true if bounds were updated to some non-empty value. */
-    boolean updateBoundsAfterConfigChange() {
-        if (mDisplayContent == null) {
-            // If the stack is already detached we're not updating anything,
-            // as it's going away soon anyway.
-            return false;
-        }
-
-        if (inPinnedWindowingMode()) {
-            getAnimationOrCurrentBounds(mTmpRect2);
+    /**
+     * Updates the passed-in {@code inOutBounds} based on how it would change when this container's
+     * override configuration is applied to the specified {@code parentConfig} and
+     * {@code prevConfig}. This gets run *after* the override configuration is updated, so it's
+     * safe to rely on wm hierarchy state in here (though eventually this dependence should be
+     * removed).
+     *
+     * This does NOT modify this TaskStack's configuration. However, it does, for the time-being,
+     * update various controller state (pinned/docked).
+     *
+     * @param parentConfig a parent configuration to compute relative to.
+     * @param prevConfig the full configuration used to produce the incoming {@code inOutBounds}.
+     * @param inOutBounds the bounds to update (both input and output).
+     * @return true if bounds were updated to some non-empty value. */
+    boolean updateBoundsForConfigChange(
+            Configuration parentConfig, Configuration prevConfig, Rect inOutBounds) {
+        if (getOverrideWindowingMode() == WINDOWING_MODE_PINNED) {
+            if ((mBoundsAnimatingRequested || mBoundsAnimating)
+                    && !mBoundsAnimationTarget.isEmpty()) {
+                getFinalAnimationBounds(mTmpRect2);
+            } else {
+                mTmpRect2.set(prevConfig.windowConfiguration.getBounds());
+            }
             boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
                     mTmpRect2, mTmpRect3);
             if (updated) {
-                mBoundsAfterRotation.set(mTmpRect3);
+                inOutBounds.set(mTmpRect3);
 
                 // Once we've set the bounds based on the rotation of the old bounds in the new
                 // orientation, clear the animation target bounds since they are obsolete, and
@@ -465,53 +478,47 @@
                 mBoundsAnimationTarget.setEmpty();
                 mBoundsAnimationSourceHintBounds.setEmpty();
                 mCancelCurrentBoundsAnimation = true;
-                return true;
             }
+            return updated;
         }
 
-        final int newRotation = getDisplayInfo().rotation;
-        final int newDensity = getDisplayInfo().logicalDensityDpi;
+        final int newRotation = parentConfig.windowConfiguration.getRotation();
+        final int newDensity = parentConfig.densityDpi;
 
-        if (mRotation == newRotation && mDensity == newDensity) {
-            // Nothing to do here as we already update the state in updateDisplayInfo.
+        if (prevConfig.windowConfiguration.getRotation() == newRotation
+                && prevConfig.densityDpi == newDensity) {
             return false;
         }
 
         if (matchParentBounds()) {
-            // Update stack bounds again since rotation changed since updateDisplayInfo().
-            setBounds(null);
-            // Return false since we don't need the client to resize.
             return false;
         }
 
-        mTmpRect2.set(getRawBounds());
-        mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
-        if (inSplitScreenPrimaryWindowingMode()) {
-            final Configuration parentConfig = getParent().getConfiguration();
-            repositionSplitScreenStackAfterRotation(parentConfig, true /* primary */, mTmpRect2);
+        mDisplayContent.rotateBounds(parentConfig.windowConfiguration.getBounds(),
+                prevConfig.windowConfiguration.getRotation(), newRotation, inOutBounds);
+        if (getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            boolean primary = getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+            repositionSplitScreenStackAfterRotation(parentConfig, primary, inOutBounds);
             final DisplayCutout cutout = mDisplayContent.getDisplayInfo().displayCutout;
-            snapDockedStackAfterRotation(parentConfig, cutout, mTmpRect2);
-            final int newDockSide = getDockSide(mDisplayContent, parentConfig, mTmpRect2);
+            snapDockedStackAfterRotation(parentConfig, cutout, inOutBounds);
+            if (primary) {
+                final int newDockSide = getDockSide(mDisplayContent, parentConfig, inOutBounds);
 
-            // Update the dock create mode and clear the dock create bounds, these
-            // might change after a rotation and the original values will be invalid.
-            mService.setDockedStackCreateStateLocked(
-                    (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
-                            ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                            : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
-                    null);
-            mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
+                // Update the dock create mode and clear the dock create bounds, these
+                // might change after a rotation and the original values will be invalid.
+                mService.setDockedStackCreateStateLocked(
+                        (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+                                ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                                : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
+                        null);
+                mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
+            }
         }
 
-        mBoundsAfterRotation.set(mTmpRect2);
         return true;
     }
 
-    void getBoundsForNewConfiguration(Rect outBounds) {
-        outBounds.set(mBoundsAfterRotation);
-        mBoundsAfterRotation.setEmpty();
-    }
-
     /**
      * Some primary split screen sides are not allowed by the policy. This method queries the policy
      * and moves the primary stack around if needed.
@@ -528,7 +535,7 @@
         if (mDisplayContent.getDockedDividerController()
                 .canPrimaryStackDockTo(primaryDockSide,
                         parentConfig.windowConfiguration.getBounds(),
-                        mDisplayContent.getDisplayInfo().rotation)) {
+                        parentConfig.windowConfiguration.getRotation())) {
             return;
         }
         final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
@@ -571,7 +578,7 @@
         final int displayHeight = parentConfig.windowConfiguration.getBounds().height();
 
         // Snap the position to a target.
-        final int rotation = mDisplayContent.getDisplayInfo().rotation;
+        final int rotation = parentConfig.windowConfiguration.getRotation();
         final int orientation = parentConfig.orientation;
         mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight,
                 displayCutout, outBounds);
@@ -1503,7 +1510,7 @@
         }
         return dc.getDockedDividerController().getDockSide(bounds,
                 parentConfig.windowConfiguration.getBounds(),
-                parentConfig.orientation, dc.getDisplayInfo().rotation);
+                parentConfig.orientation, parentConfig.windowConfiguration.getRotation());
     }
 
     boolean hasTaskForUser(int userId) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a641f75..7606a0b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2403,20 +2403,13 @@
         return config;
     }
 
-    @Override
-    public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
-        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
-            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+    void setNewDisplayOverrideConfiguration(Configuration overrideConfig, DisplayContent dc) {
+        if (mWaitingForConfig) {
+            mWaitingForConfig = false;
+            mLastFinishedFreezeSource = "new-config";
         }
 
-        synchronized (mGlobalLock) {
-            if (mWaitingForConfig) {
-                mWaitingForConfig = false;
-                mLastFinishedFreezeSource = "new-config";
-            }
-
-            return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
-        }
+        mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, dc);
     }
 
     // TODO(multi-display): remove when no default display use case.
@@ -7139,6 +7132,10 @@
         mRotatingSeamlessly = true;
     }
 
+    boolean isRotatingSeamlessly() {
+        return mRotatingSeamlessly;
+    }
+
     void finishSeamlessRotation() {
         mRotatingSeamlessly = false;
     }