Merge "Fix App List pages flickers"
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c02dd6a..680e8f7 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -127,6 +127,13 @@
      */
     public static final String SETTINGS_ENABLE_SPA = "settings_enable_spa";
 
+    /**
+     * Enable new pages implemented with SPA besides the SPA pages controlled by the {@code
+     * settings_enable_spa} flag.
+     * @hide
+     */
+    public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2";
+
     /** Flag to enable/disable adb log metrics
      *  @hide
      */
@@ -225,6 +232,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
@@ -252,6 +260,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
+        PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
         PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index 9e0a48b..e2106e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -216,7 +216,6 @@
         args.argi1 = homeTaskVisible ? 1 : 0;
         args.argi2 = clearedTask ? 1 : 0;
         args.argi3 = wasVisible ? 1 : 0;
-        mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
         mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ffb2893..111cfd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1409,7 +1409,6 @@
             @PipAnimationController.TransitionDirection int direction,
             @PipAnimationController.AnimationType int type) {
         final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
-        final boolean isPipTopLeft = isPipTopLeft();
         mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
@@ -1455,9 +1454,11 @@
                             null /* callback */, false /* withStartDelay */);
                 });
             } else {
-                applyFinishBoundsResize(wct, direction, isPipTopLeft);
+                applyFinishBoundsResize(wct, direction, false);
             }
         } else {
+            final boolean isPipTopLeft =
+                    direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
             applyFinishBoundsResize(wct, direction, isPipTopLeft);
         }
 
@@ -1526,6 +1527,14 @@
         return topLeft.contains(mPipBoundsState.getBounds());
     }
 
+    private boolean isPipToTopLeft() {
+        if (!mSplitScreenOptional.isPresent()) {
+            return false;
+        }
+        return mSplitScreenOptional.get().getActivateSplitPosition(mTaskInfo)
+                == SPLIT_POSITION_TOP_OR_LEFT;
+    }
+
     /**
      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
      * and can be overridden to restore to an alternate windowing mode.
@@ -1662,8 +1671,7 @@
         final Rect topLeft = new Rect();
         final Rect bottomRight = new Rect();
         mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-        final boolean isPipTopLeft = isPipTopLeft();
-        destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
+        destinationBoundsOut.set(isPipToTopLeft()  ? topLeft : bottomRight);
         return true;
     }
 
@@ -1747,6 +1755,11 @@
         mSurfaceControlTransactionFactory = factory;
     }
 
+    public boolean isLaunchToSplit(TaskInfo taskInfo) {
+        return mSplitScreenOptional.isPresent()
+                && mSplitScreenOptional.get().isLaunchToSplit(taskInfo);
+    }
+
     /**
      * Dumps internal states.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4958b25..ec34f73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -570,8 +570,12 @@
                         if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
                             return;
                         }
-                        mTouchHandler.getMotionHelper().expandLeavePip(
-                                clearedTask /* skipAnimation */);
+                        if (mPipTaskOrganizer.isLaunchToSplit(task)) {
+                            mTouchHandler.getMotionHelper().expandIntoSplit();
+                        } else {
+                            mTouchHandler.getMotionHelper().expandLeavePip(
+                                    clearedTask /* skipAnimation */);
+                        }
                     }
                 });
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 36da4cf..c7ad4fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -42,6 +42,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
+import android.app.TaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
@@ -421,6 +422,14 @@
         mStageCoordinator.goToFullscreenFromSplit();
     }
 
+    public boolean isLaunchToSplit(TaskInfo taskInfo) {
+        return mStageCoordinator.isLaunchToSplit(taskInfo);
+    }
+
+    public int getActivateSplitPosition(TaskInfo taskInfo) {
+        return mStageCoordinator.getActivateSplitPosition(taskInfo);
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1cf3a89..2b21e77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -22,6 +22,8 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -179,6 +181,33 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    void applyDismissTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull WindowContainerToken topRoot,
+            @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+            @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        if (mPendingDismiss.mDismissTop != STAGE_TYPE_UNDEFINED) {
+            mFinishCallback = finishCallback;
+            mAnimatingTransition = transition;
+            mFinishTransaction = finishTransaction;
+
+            final SplitDecorManager topDecor = mPendingDismiss.mDismissTop == STAGE_TYPE_MAIN
+                    ? mainDecor : sideDecor;
+            topDecor.fadeOutDecor(() -> {
+                mTransitions.getMainExecutor().execute(() -> {
+                    onFinish(null /* wct */, null /* wctCB */);
+                });
+            });
+
+            startTransaction.apply();
+        } else {
+            playAnimation(transition, info, startTransaction, finishTransaction,
+                    finishCallback, mainRoot, sideRoot, topRoot);
+        }
+    }
+
     void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
@@ -200,8 +229,11 @@
 
                 SplitDecorManager decor = mainRoot.equals(change.getContainer())
                         ? mainDecor : sideDecor;
+
+                // This is to ensure onFinished be called after all animations ended.
                 ValueAnimator va = new ValueAnimator();
                 mAnimations.add(va);
+
                 decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
                 decor.onResized(startTransaction, () -> {
                     mTransitions.getMainExecutor().execute(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2a4bae9..5af7598 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -74,6 +74,7 @@
 import android.app.ActivityOptions;
 import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
+import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -124,6 +125,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.common.split.SplitWindowManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
@@ -208,6 +210,36 @@
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
+    private SplitRequest mSplitRequest;
+
+    class SplitRequest {
+        @SplitPosition
+        int mActivatePosition;
+        int mActivateTaskId;
+        int mActivateTaskId2;
+        Intent mStartIntent;
+        Intent mStartIntent2;
+
+        SplitRequest(int taskId, Intent startIntent, int position) {
+            mActivateTaskId = taskId;
+            mStartIntent = startIntent;
+            mActivatePosition = position;
+        }
+        SplitRequest(Intent startIntent, int position) {
+            mStartIntent = startIntent;
+            mActivatePosition = position;
+        }
+        SplitRequest(Intent startIntent, Intent startIntent2, int position) {
+            mStartIntent = startIntent;
+            mStartIntent2 = startIntent2;
+            mActivatePosition = position;
+        }
+        SplitRequest(int taskId1, int taskId2, int position) {
+            mActivateTaskId = taskId1;
+            mActivateTaskId2 = taskId2;
+            mActivatePosition = position;
+        }
+    }
 
     private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
             new SplitWindowManager.ParentContainerCallbacks() {
@@ -393,6 +425,23 @@
         setSideStagePosition(sideStagePosition, wct);
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         targetStage.evictAllChildren(evictWct);
+
+        // Apply surface bounds before animation start.
+        SurfaceControl.Transaction startT = mTransactionPool.acquire();
+        if (startT != null) {
+            updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
+            startT.apply();
+            mTransactionPool.release(startT);
+        }
+        // reparent the task to an invisible split root will make the activity invisible.  Reorder
+        // the root task to front to make the entering transition from pip to split smooth.
+        wct.reorder(mRootTaskInfo.token, true);
+        wct.setForceTranslucent(mRootTaskInfo.token, true);
+        wct.reorder(targetStage.mRootTaskInfo.token, true);
+        wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true);
+        // prevent the fling divider to center transition
+        mIsDropEntering = true;
+
         targetStage.addTask(task, wct);
 
         if (ENABLE_SHELL_TRANSITIONS) {
@@ -547,7 +596,7 @@
                     }
                 }
 
-                if (isEnteringSplit && !openingToSide) {
+                if (isEnteringSplit && !openingToSide && apps != null) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
                             EXIT_REASON_UNKNOWN));
@@ -587,7 +636,7 @@
         if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
             updateWindowBounds(mSplitLayout, wct);
         }
-
+        mSplitRequest = new SplitRequest(intent.getIntent(), position);
         wct.sendPendingIntent(intent, fillInIntent, options);
         mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
     }
@@ -686,6 +735,7 @@
 
         addActivityOptions(options1, mSideStage);
         wct.startTask(taskId1, options1);
+        mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
         startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
                 instanceId);
     }
@@ -719,6 +769,8 @@
             wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
         } else {
             wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+            mSplitRequest = new SplitRequest(pendingIntent1.getIntent(),
+                    pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
         }
         startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
                 splitPosition, splitRatio, adapter, instanceId);
@@ -743,6 +795,7 @@
 
         addActivityOptions(options1, mSideStage);
         wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+        mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
         startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
                 instanceId);
     }
@@ -815,7 +868,11 @@
         // Set false to avoid record new bounds with old task still on top;
         mShouldUpdateRecents = false;
         mIsSplitEntering = true;
-
+        if (mSplitRequest == null) {
+            mSplitRequest = new SplitRequest(mainTaskId,
+                    mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
+                    sidePosition);
+        }
         setSideStagePosition(sidePosition, wct);
         if (!mMainStage.isActive()) {
             mMainStage.activate(wct, false /* reparent */);
@@ -906,6 +963,7 @@
             WindowContainerTransaction evictWct) {
         mIsSplitEntering = false;
         mShouldUpdateRecents = true;
+        mSplitRequest = null;
         // If any stage has no child after animation finished, it means that split will display
         // nothing, such status will happen if task and intent is same app but not support
         // multi-instance, we should exit split and expand that app as full screen.
@@ -1296,8 +1354,18 @@
     private void prepareExitSplitScreen(@StageType int stageToTop,
             @NonNull WindowContainerTransaction wct) {
         if (!mMainStage.isActive()) return;
-        mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
-        mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+        // Set the dismiss-to-top side to fullscreen for dismiss transition.
+        // Reparent the non-dismiss-to-top side to properly update its visibility.
+        if (stageToTop == STAGE_TYPE_MAIN) {
+            wct.setBounds(mMainStage.mRootTaskInfo.token, null /* bounds */);
+            mSideStage.removeAllTasks(wct, false /* toTop */);
+        } else if (stageToTop == STAGE_TYPE_SIDE) {
+            wct.setBounds(mSideStage.mRootTaskInfo.token, null /* bounds */);
+            mMainStage.deactivate(wct, false /* toTop */);
+        } else {
+            mSideStage.removeAllTasks(wct, false /* toTop */);
+            mMainStage.deactivate(wct, false /* toTop */);
+        }
     }
 
     private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -1739,7 +1807,7 @@
             mSplitLayout.init();
 
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            if (mLogger.isEnterRequestedByDrag()) {
+            if (mIsDropEntering) {
                 prepareEnterSplitScreen(wct);
             } else {
                 // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
@@ -1764,6 +1832,7 @@
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
             mShouldUpdateRecents = true;
+            mSplitRequest = null;
             updateRecentTasksSplitPair();
 
             if (!mLogger.hasStartedSession()) {
@@ -2214,6 +2283,13 @@
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+            if (shouldAnimate) {
+                mSplitTransitions.applyDismissTransition(transition, info,
+                        startTransaction, finishTransaction, finishCallback, mRootTaskInfo.token,
+                        mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+                        mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
+                return true;
+            }
         } else if (mSplitTransitions.isPendingResize(transition)) {
             mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
                     finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
@@ -2307,6 +2383,33 @@
         mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
     }
 
+    boolean isLaunchToSplit(TaskInfo taskInfo) {
+        return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
+    }
+
+    int getActivateSplitPosition(TaskInfo taskInfo) {
+        if (mSplitRequest == null || taskInfo == null) {
+            return SPLIT_POSITION_UNDEFINED;
+        }
+        if (mSplitRequest.mActivateTaskId != 0
+                && mSplitRequest.mActivateTaskId2 == taskInfo.taskId) {
+            return mSplitRequest.mActivatePosition;
+        }
+        if (mSplitRequest.mActivateTaskId == taskInfo.taskId) {
+            return mSplitRequest.mActivatePosition;
+        }
+        final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent);
+        final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent);
+        if (packageName1 != null && packageName1.equals(basePackageName)) {
+            return mSplitRequest.mActivatePosition;
+        }
+        final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2);
+        if (packageName2 != null && packageName2.equals(basePackageName)) {
+            return mSplitRequest.mActivatePosition;
+        }
+        return SPLIT_POSITION_UNDEFINED;
+    }
+
     /** Synchronize split-screen state with transition and make appropriate preparations. */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@@ -2317,37 +2420,44 @@
         // aren't serialized with transition callbacks.
         // TODO(b/184679596): Find a way to either include task-org information in
         //                    the transition, or synchronize task-org callbacks.
-        if (mMainStage.getChildCount() != 0) {
-            final StringBuilder tasksLeft = new StringBuilder();
-            for (int i = 0; i < mMainStage.getChildCount(); ++i) {
-                tasksLeft.append(i != 0 ? ", " : "");
-                tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+        if (toStage == STAGE_TYPE_UNDEFINED) {
+            if (mMainStage.getChildCount() != 0) {
+                final StringBuilder tasksLeft = new StringBuilder();
+                for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+                    tasksLeft.append(i != 0 ? ", " : "");
+                    tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+                }
+                Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+                        + " to have been called with [" + tasksLeft.toString()
+                        + "] before startAnimation().");
             }
-            Log.w(TAG, "Expected onTaskVanished on " + mMainStage
-                    + " to have been called with [" + tasksLeft.toString()
-                    + "] before startAnimation().");
-        }
-        if (mSideStage.getChildCount() != 0) {
-            final StringBuilder tasksLeft = new StringBuilder();
-            for (int i = 0; i < mSideStage.getChildCount(); ++i) {
-                tasksLeft.append(i != 0 ? ", " : "");
-                tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+            if (mSideStage.getChildCount() != 0) {
+                final StringBuilder tasksLeft = new StringBuilder();
+                for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+                    tasksLeft.append(i != 0 ? ", " : "");
+                    tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+                }
+                Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+                        + " to have been called with [" + tasksLeft.toString()
+                        + "] before startAnimation().");
             }
-            Log.w(TAG, "Expected onTaskVanished on " + mSideStage
-                    + " to have been called with [" + tasksLeft.toString()
-                    + "] before startAnimation().");
         }
 
         mRecentTasks.ifPresent(recentTasks -> {
             // Notify recents if we are exiting in a way that breaks the pair, and disable further
             // updates to splits in the recents until we enter split again
             if (shouldBreakPairedTaskInRecents(dismissReason) && mShouldUpdateRecents) {
-                for (TransitionInfo.Change change : info.getChanges()) {
-                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                    if (taskInfo != null
-                            && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                        recentTasks.removeSplitPair(taskInfo.taskId);
+                if (toStage == STAGE_TYPE_UNDEFINED) {
+                    for (TransitionInfo.Change change : info.getChanges()) {
+                        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                        if (taskInfo != null
+                                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+                            recentTasks.removeSplitPair(taskInfo.taskId);
+                        }
                     }
+                } else {
+                    recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
+                    recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
                 }
             }
         });
@@ -2360,6 +2470,12 @@
         // Reset crops so they don't interfere with subsequent launches
         t.setCrop(mMainStage.mRootLeash, null);
         t.setCrop(mSideStage.mRootLeash, null);
+        // Hide the non-top stage and set the top one to the fullscreen position.
+        if (toStage != STAGE_TYPE_UNDEFINED) {
+            t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
+            t.setPosition(toStage == STAGE_TYPE_MAIN
+                    ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+        }
 
         if (toStage == STAGE_TYPE_UNDEFINED) {
             logExit(dismissReason);
@@ -2386,6 +2502,18 @@
             mSplitLayout.release(t);
             mSplitTransitions.mPendingDismiss = null;
             return false;
+        } else {
+            final @SplitScreen.StageType int dismissTop = dismissTransition.mDismissTop;
+            // Reparent all tasks after dismiss transition finished.
+            dismissTransition.setFinishedCallback(
+                    new SplitScreenTransitions.TransitionFinishedCallback() {
+                        @Override
+                        public void onFinished(WindowContainerTransaction wct,
+                                SurfaceControl.Transaction t) {
+                            mSideStage.removeAllTasks(wct, dismissTop == STAGE_TYPE_SIDE);
+                            mMainStage.deactivate(wct, dismissTop == STAGE_TYPE_MAIN);
+                        }
+                    });
         }
 
         addDividerBarToTransition(info, finishT, false /* show */);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index d0f02e2..d3c6820 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesInvisible
@@ -106,15 +105,8 @@
     fun secondaryAppBoundsIsFullscreenAtEnd() {
         flicker.assertLayers {
             this.isVisible(secondaryApp)
-                .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
                 .then()
                 .isInvisible(secondaryApp)
-                .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-                .then()
-                .isVisible(secondaryApp, isOptional = true)
-                .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT, isOptional = true)
-                .then()
-                .contains(SPLIT_SCREEN_DIVIDER_COMPONENT)
                 .then()
                 .invoke("secondaryAppBoundsIsFullscreenAtEnd") {
                     val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e9f1df2..ed0ac5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -371,7 +371,8 @@
         IBinder transition = mock(IBinder.class);
         WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
 
-        assertTrue(containsSplitExit(result));
+        // Don't reparent tasks until the animation is complete.
+        assertFalse(containsSplitExit(result));
 
         // make sure we haven't made any local changes yet (need to wait until transition is ready)
         assertTrue(mStageCoordinator.isSplitScreenVisible());
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index f1b1abd..621e6ea 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -119,22 +119,22 @@
                 arguments = spp.parameter,
                 enterTransition = {
                     slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
                     ) + fadeIn(animationSpec = fadeEffect)
                 },
                 exitTransition = {
                     slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
                 popEnterTransition = {
                     slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
                     ) + fadeIn(animationSpec = fadeEffect)
                 },
                 popExitTransition = {
                     slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
+                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
             ) { navBackStackEntry ->
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
index 0137572..57bb838 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
@@ -201,11 +201,7 @@
         transition.AnimatedContent(
             modifier,
             transitionSpec = {
-                val zIndex = if (composeNavigator.isPop.value) {
-                    visibleEntries.indexOf(initialState).toFloat()
-                } else {
-                    visibleEntries.indexOf(targetState).toFloat()
-                }
+                val zIndex = composeNavigator.backStack.value.size.toFloat()
                 // If the initialState of the AnimatedContent is not in visibleEntries, we are in
                 // a case where visible has cleared the old state for some reason, so instead of
                 // attempting to animate away from the initialState, we skip the animation.
diff --git a/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml b/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml
new file mode 100644
index 0000000..d5b4c9e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml
@@ -0,0 +1,25 @@
+<!--
+   Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+         android:width="24dp"
+         android:height="24dp"
+         android:viewportWidth="24"
+         android:viewportHeight="24">
+<path
+    android:pathData="M7,20L7,7L2,7L2,4h13v3h-5v13ZM16,20v-8h-3L13,9h9v3h-3v8Z"
+    android:fillColor="#041E49"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d9e9c5db..7716fa9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -81,7 +81,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bf894d7..85edbec 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -662,6 +662,8 @@
     <string name="quick_settings_inversion_label">Color inversion</string>
     <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_color_correction_label">Color correction</string>
+    <!-- QuickSettings: Label for font size scaling. [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_font_scaling_label">Font size</string>
     <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_more_user_settings">Manage users</string>
     <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index c809551..7020d54 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -318,4 +318,14 @@
         <item>Off</item>
         <item>On</item>
     </string-array>
+
+    <!-- State names for font scaling tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_font_scaling">
+        <item>Unavailable</item>
+        <item>Off</item>
+        <item>On</item>
+    </string-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 24a4f60b..dec6e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.DreamTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
+import com.android.systemui.qs.tiles.FontScalingTile;
 import com.android.systemui.qs.tiles.HotspotTile;
 import com.android.systemui.qs.tiles.InternetTile;
 import com.android.systemui.qs.tiles.LocationTile;
@@ -94,6 +95,7 @@
     private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider;
     private final Provider<OneHandedModeTile> mOneHandedModeTileProvider;
     private final Provider<DreamTile> mDreamTileProvider;
+    private final Provider<FontScalingTile> mFontScalingTileProvider;
 
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -129,7 +131,8 @@
             Provider<QRCodeScannerTile> qrCodeScannerTileProvider,
             Provider<OneHandedModeTile> oneHandedModeTileProvider,
             Provider<ColorCorrectionTile> colorCorrectionTileProvider,
-            Provider<DreamTile> dreamTileProvider) {
+            Provider<DreamTile> dreamTileProvider,
+            Provider<FontScalingTile> fontScalingTileProvider) {
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
 
@@ -161,6 +164,7 @@
         mOneHandedModeTileProvider = oneHandedModeTileProvider;
         mColorCorrectionTileProvider = colorCorrectionTileProvider;
         mDreamTileProvider = dreamTileProvider;
+        mFontScalingTileProvider = fontScalingTileProvider;
     }
 
     /** Creates a tile with a type based on {@code tileSpec} */
@@ -232,6 +236,8 @@
                 return mColorCorrectionTileProvider.get();
             case "dream":
                 return mDreamTileProvider.get();
+            case "font_scaling":
+                return mFontScalingTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 29d7fb0..d0b04c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -694,7 +694,8 @@
         "alarm" to R.array.tile_states_alarm,
         "onehanded" to R.array.tile_states_onehanded,
         "color_correction" to R.array.tile_states_color_correction,
-        "dream" to R.array.tile_states_dream
+        "dream" to R.array.tile_states_dream,
+        "font_scaling" to R.array.tile_states_font_scaling
     )
 
     fun getSubtitleId(spec: String?): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
new file mode 100644
index 0000000..4d8f89e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import javax.inject.Inject
+
+class FontScalingTile
+@Inject
+constructor(
+    host: QSHost,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    falsingManager: FalsingManager,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger
+) :
+    QSTileImpl<QSTile.State?>(
+        host,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+    ) {
+    private val mIcon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
+
+    override fun isAvailable(): Boolean {
+        return false
+    }
+
+    override fun newTileState(): QSTile.State {
+        val state = QSTile.State()
+        state.handlesLongClick = false
+        return state
+    }
+
+    override fun handleClick(view: View?) {}
+
+    override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+        state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
+        state?.icon = mIcon
+    }
+
+    override fun getLongClickIntent(): Intent? {
+        return null
+    }
+
+    override fun getTileLabel(): CharSequence {
+        return mContext.getString(R.string.quick_settings_font_scaling_label)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 3281fa9..4635b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.tiles.DndTile
 import com.android.systemui.qs.tiles.DreamTile
 import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.FontScalingTile
 import com.android.systemui.qs.tiles.HotspotTile
 import com.android.systemui.qs.tiles.InternetTile
 import com.android.systemui.qs.tiles.LocationTile
@@ -87,7 +88,8 @@
         "qr_code_scanner" to QRCodeScannerTile::class.java,
         "onehanded" to OneHandedModeTile::class.java,
         "color_correction" to ColorCorrectionTile::class.java,
-        "dream" to DreamTile::class.java
+        "dream" to DreamTile::class.java,
+        "font_scaling" to FontScalingTile::class.java
 )
 
 @RunWith(AndroidTestingRunner::class)
@@ -126,6 +128,7 @@
     @Mock private lateinit var oneHandedModeTile: OneHandedModeTile
     @Mock private lateinit var colorCorrectionTile: ColorCorrectionTile
     @Mock private lateinit var dreamTile: DreamTile
+    @Mock private lateinit var fontScalingTile: FontScalingTile
 
     private lateinit var factory: QSFactoryImpl
 
@@ -167,7 +170,8 @@
                 { qrCodeScannerTile },
                 { oneHandedModeTile },
                 { colorCorrectionTile },
-                { dreamTile }
+                { dreamTile },
+                { fontScalingTile }
         )
         // When adding/removing tiles, fix also [specMap]
     }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 7d9ae87..dcafe80 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -223,7 +223,7 @@
         if (op == null) return;
         if (op.mDrawTransaction != null) {
             // Unblock the window to show its latest content.
-            mDisplayContent.getPendingTransaction().merge(op.mDrawTransaction);
+            windowToken.getSyncTransaction().merge(op.mDrawTransaction);
             op.mDrawTransaction = null;
             if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild());
         }
@@ -235,7 +235,7 @@
         } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
                 && op.mLeash != null && op.mLeash.isValid()) {
             if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
-            mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), op.mLeash);
+            mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
         }
     }
 
@@ -322,7 +322,8 @@
         if (mTimeoutRunnable == null) {
             mTimeoutRunnable = () -> {
                 synchronized (mService.mGlobalLock) {
-                    Slog.i(TAG, "Async rotation timeout: " + mTargetWindowTokens);
+                    Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
+                            ? " start transaction is not committed" : mTargetWindowTokens));
                     mIsStartTransactionCommitted = true;
                     mDisplayContent.finishAsyncRotationIfPossible();
                     mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -484,12 +485,17 @@
      * by this controller.
      */
     boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) {
-        if (mTransitionOp == OP_LEGACY || postDrawTransaction == null || !mIsSyncDrawRequested) {
+        if (mTransitionOp == OP_LEGACY) {
             return false;
         }
         final Operation op = mTargetWindowTokens.get(w.mToken);
-        if (op == null || op.canDrawBeforeStartTransaction()) return false;
+        if (op == null) return false;
         if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
+        if (postDrawTransaction == null || !mIsSyncDrawRequested
+                || op.canDrawBeforeStartTransaction()) {
+            mDisplayContent.finishAsyncRotation(w.mToken);
+            return false;
+        }
         if (op.mDrawTransaction == null) {
             if (w.isClientLocal()) {
                 // Use a new transaction to merge the draw transaction of local window because the
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 719edee..fe9306b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2137,6 +2137,10 @@
         if (!shellTransitions) {
             forAllWindows(w -> {
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
+                if (!rotateSeamlessly && w.mHasSurface) {
+                    ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
+                    w.setOrientationChanging(true);
+                }
             }, true /* traverseTopToBottom */);
             mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
             if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
@@ -2148,14 +2152,6 @@
         mWmService.mDisplayManagerInternal.performTraversal(transaction);
         scheduleAnimation();
 
-        forAllWindows(w -> {
-            if (!w.mHasSurface) return;
-            if (!rotateSeamlessly) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
-                w.setOrientationChanging(true);
-            }
-        }, true /* traverseTopToBottom */);
-
         for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) {
             final WindowManagerService.RotationWatcher rotationWatcher
                     = mWmService.mRotationWatchers.get(i);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index f56fae9..cdbcecc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -867,9 +867,6 @@
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
         assertNotNull(asyncRotationController);
-        for (WindowState w : windows) {
-            w.setOrientationChanging(true);
-        }
         player.startTransition();
 
         assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
@@ -879,15 +876,11 @@
         assertTrue(asyncRotationController.isTargetToken(decorToken));
         assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
-        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
-            // Only seamless window syncs its draw transaction with transition.
-            assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
-            assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
-        }
-        screenDecor.setOrientationChanging(false);
+        // Only seamless window syncs its draw transaction with transition.
+        assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
         // executed until the transaction is committed, so it is still in target tokens.
-        statusBar.setOrientationChanging(false);
+        assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
         assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
 
         final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
@@ -901,7 +894,7 @@
 
         // Navigation bar finishes drawing after the start transaction, so its fade-in animation
         // can execute directly.
-        navBar.setOrientationChanging(false);
+        asyncRotationController.handleFinishDrawing(navBar, mMockT);
         assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
         assertNull(mDisplayContent.getAsyncRotationController());
     }
@@ -935,7 +928,6 @@
         assertNotNull(asyncRotationController);
         assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
-        statusBar.setOrientationChanging(true);
         player.startTransition();
         // Non-app windows should not be collected.
         assertFalse(statusBar.mToken.inTransition());
@@ -998,7 +990,6 @@
         mDisplayContent.mTransitionController.dispatchLegacyAppTransitionFinished(app);
         assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
 
-        statusBar.setOrientationChanging(true);
         player.startTransition();
         // Non-app windows should not be collected.
         assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
@@ -1010,7 +1001,6 @@
 
         // The controller should be cleared if the target windows are drawn.
         statusBar.finishDrawing(mWm.mTransactionFactory.get(), Integer.MAX_VALUE);
-        statusBar.setOrientationChanging(false);
         assertNull(mDisplayContent.getAsyncRotationController());
     }