Merge "3b/ Migrate away from finishWCT usage in recents transition" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 40065b9..9016c45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -34,6 +34,8 @@
 
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
 import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
 
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -67,6 +69,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUtils;
@@ -216,8 +219,11 @@
                 break;
             }
         }
-        final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
-                mixer == null ? this : mixer);
+        final int transitionType = Flags.enableShellTopTaskTracking()
+                ? TRANSIT_START_RECENTS_TRANSITION
+                : TRANSIT_TO_FRONT;
+        final IBinder transition = mTransitions.startTransition(transitionType,
+                wct, mixer == null ? this : mixer);
         if (mixer != null) {
             setTransitionForMixer.accept(transition);
         }
@@ -300,7 +306,7 @@
                     "RecentsTransitionHandler.mergeAnimation: no controller found");
             return;
         }
-        controller.merge(info, t, finishCallback);
+        controller.merge(info, t, mergeTarget, finishCallback);
     }
 
     @Override
@@ -367,6 +373,8 @@
         private boolean mPausingSeparateHome = false;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
+        // This is the transition that backs the entire recents transition, and the one that the
+        // pending finish transition below will be merged into
         private IBinder mTransition = null;
         private boolean mKeyguardLocked = false;
         private boolean mWillFinishToHome = false;
@@ -386,6 +394,10 @@
         // next called.
         private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
 
+        // Used to track a pending finish transition
+        private IBinder mPendingFinishTransition;
+        private IResultReceiver mPendingRunnerFinishCb;
+
         RecentsController(IRecentsAnimationRunner listener) {
             mInstanceId = System.identityHashCode(this);
             mListener = listener;
@@ -523,6 +535,11 @@
             mInfo = null;
             mTransition = null;
             mPendingPauseSnapshotsForCancel = null;
+            mPipTaskId = -1;
+            mPipTask = null;
+            mPipTransaction = null;
+            mPendingRunnerFinishCb = null;
+            mPendingFinishTransition = null;
             mControllers.remove(this);
             for (int i = 0; i < mStateListeners.size(); i++) {
                 mStateListeners.get(i).onAnimationStateChanged(false);
@@ -734,6 +751,8 @@
                         // the pausing apps.
                         t.setLayer(target.leash, layer);
                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  not handling home taskId=%d", taskInfo.taskId);
                         // do nothing
                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -872,16 +891,35 @@
             }
         }
 
+        /**
+         * Note: because we use a book-end transition to finish the recents transition, we must
+         * either always merge the incoming transition, or always cancel the recents transition
+         * if we don't handle the incoming transition to ensure that the end transition is queued
+         * before any unhandled transitions.
+         */
         @SuppressLint("NewApi")
-        void merge(TransitionInfo info, SurfaceControl.Transaction t,
+        void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget,
                 Transitions.TransitionFinishCallback finishCallback) {
             if (mFinishCB == null) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                         "[%d] RecentsController.merge: skip, no finish callback",
                         mInstanceId);
-                // This was no-op'd (likely a repeated start) and we've already sent finish.
+                // This was no-op'd (likely a repeated start) and we've already completed finish.
                 return;
             }
+
+            if (Flags.enableShellTopTaskTracking()
+                    && info.getType() == TRANSIT_END_RECENTS_TRANSITION
+                    && mergeTarget == mTransition) {
+                // This is a pending finish, so merge the end transition to trigger completing the
+                // cleanup of the recents transition
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                        "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
+                        mInstanceId);
+                finishCallback.onTransitionFinished(null /* wct */);
+                return;
+            }
+
             if (info.getType() == TRANSIT_SLEEP) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                         "[%d] RecentsController.merge: transit_sleep", mInstanceId);
@@ -1245,7 +1283,8 @@
                 return;
             }
 
-            if (mFinishCB == null) {
+            if (mFinishCB == null
+                    || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
                 Slog.e(TAG, "Duplicate call to finish");
                 return;
             }
@@ -1254,19 +1293,22 @@
                     && !mWillFinishToHome
                     && mPausingTasks != null
                     && mState == STATE_NORMAL;
-            if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
-                mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
-            } else if (!toHome) {
-                // For some transitions, we may have notified home activity that it became visible.
-                // We need to notify the observer that we are no longer going home.
-                mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+            if (!Flags.enableShellTopTaskTracking()) {
+                // This is only necessary when the recents transition is finished using a finishWCT,
+                // otherwise a new transition will notify the relevant observers
+                if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
+                    mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+                } else if (!toHome) {
+                    // For some transitions, we may have notified home activity that it became
+                    // visible. We need to notify the observer that we are no longer going home.
+                    mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+                }
             }
+
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
-                            + "willFinishToHome=%b state=%d",
-                    mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState);
-            final Transitions.TransitionFinishCallback finishCB = mFinishCB;
-            mFinishCB = null;
+                            + "willFinishToHome=%b state=%d reason=%s",
+                    mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState, reason);
 
             final SurfaceControl.Transaction t = mFinishTransaction;
             final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1328,6 +1370,7 @@
                 for (int i = 0; i < mClosingTasks.size(); ++i) {
                     cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
                 }
+
                 if (mPipTransaction != null && sendUserLeaveHint) {
                     SurfaceControl pipLeash = null;
                     TransitionInfo.Change pipChange = null;
@@ -1379,15 +1422,50 @@
                             mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */);
                             // We need to clear the WCT to send finishWCT=null for Recents.
                             wct.clear();
+
+                            if (Flags.enableShellTopTaskTracking()) {
+                                // In this case, we've already started the PIP transition, so we can
+                                // clean up immediately
+                                mPendingRunnerFinishCb = runnerFinishCb;
+                                onFinishInner(null);
+                                return;
+                            }
                         }
                     }
-                    mPipTaskId = -1;
-                    mPipTask = null;
-                    mPipTransaction = null;
                 }
             }
+
+            if (Flags.enableShellTopTaskTracking()) {
+                if (!wct.isEmpty()) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "[%d] RecentsController.finishInner: "
+                                    + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
+                    mPendingRunnerFinishCb = runnerFinishCb;
+                    mPendingFinishTransition = mTransitions.startTransition(
+                            TRANSIT_END_RECENTS_TRANSITION, wct,
+                            new PendingFinishTransitionHandler());
+                } else {
+                    // If there's no work to do, just go ahead and clean up
+                    mPendingRunnerFinishCb = runnerFinishCb;
+                    onFinishInner(null /* wct */);
+                }
+            } else {
+                mPendingRunnerFinishCb = runnerFinishCb;
+                onFinishInner(wct);
+            }
+        }
+
+        /**
+         * Runs the actual logic to finish the recents transition.
+         */
+        private void onFinishInner(@Nullable WindowContainerTransaction wct) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "[%d] RecentsController.finishInner: Completing finish", mInstanceId);
+            final Transitions.TransitionFinishCallback finishCb = mFinishCB;
+            final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb;
+
             cleanUp();
-            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
+            finishCb.onTransitionFinished(wct);
             if (runnerFinishCb != null) {
                 try {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1472,6 +1550,40 @@
                 }
             });
         }
+
+        /**
+         * A temporary transition handler used with the pending finish transition, which runs the
+         * cleanup/finish logic once the pending transition is merged/handled.
+         * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+         */
+        private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
+            @Override
+            public boolean startAnimation(@NonNull IBinder transition,
+                    @NonNull TransitionInfo info,
+                    @NonNull SurfaceControl.Transaction startTransaction,
+                    @NonNull SurfaceControl.Transaction finishTransaction,
+                    @NonNull Transitions.TransitionFinishCallback finishCallback) {
+                return false;
+            }
+
+            @Nullable
+            @Override
+            public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+                    @NonNull TransitionRequestInfo request) {
+                return null;
+            }
+
+            @Override
+            public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+                    @Nullable SurfaceControl.Transaction finishTransaction) {
+                // Once we have merged (or not if the WCT didn't result in any changes), then we can
+                // run the pending finish logic
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                        "[%d] RecentsController.onTransitionConsumed: "
+                                + "Consumed pending finish transition", mInstanceId);
+                onFinishInner(null /* wct */);
+            }
+        };
     };
 
     /** Utility class to track the state of a task as-seen by recents. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 1d456ae..3f19149 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -203,6 +203,12 @@
     /** Transition type to minimize a task. */
     public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20;
 
+    /** Transition to start the recents transition */
+    public static final int TRANSIT_START_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 21;
+
+    /** Transition to end the recents transition */
+    public static final int TRANSIT_END_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 22;
+
     /** Transition type for desktop mode transitions. */
     public static final int TRANSIT_DESKTOP_MODE_TYPES =
             WindowManager.TRANSIT_FIRST_CUSTOM + 100;
@@ -1875,6 +1881,8 @@
             case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
             case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
             case TRANSIT_MINIMIZE -> "MINIMIZE";
+            case TRANSIT_START_RECENTS_TRANSITION -> "START_RECENTS_TRANSITION";
+            case TRANSIT_END_RECENTS_TRANSITION -> "END_RECENTS_TRANSITION";
             default -> "";
         };
         return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a603466..20481f2 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -465,6 +465,31 @@
         return false;
     }
 
+    /**
+     * This ensures that all changes for previously transient-hide containers are flagged such that
+     * they will report changes and be included in this transition.
+     */
+    void updateChangesForRestoreTransientHideTasks(Transition transientLaunchTransition) {
+        if (transientLaunchTransition.mTransientHideTasks == null) {
+            // Skip if the transient-launch transition has no transient-hide tasks
+            ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Skipping update changes for restore transient hide tasks");
+            return;
+        }
+
+        // For each change, if it was previously transient-hidden, then we should force a flag to
+        // ensure that it is included in the next transition
+        for (int i = 0; i < mChanges.size(); i++) {
+            final WindowContainer container = mChanges.keyAt(i);
+            if (transientLaunchTransition.isInTransientHide(container)) {
+                ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "Force update transient hide task for restore %d: %s", mSyncId, container);
+                final ChangeInfo info = mChanges.valueAt(i);
+                info.mRestoringTransientHide = true;
+            }
+        }
+    }
+
     /** Returns {@code true} if the task should keep visible if this is a transient transition. */
     boolean isTransientVisible(@NonNull Task task) {
         if (mTransientLaunches == null) return false;
@@ -3478,6 +3503,10 @@
 
         // State tracking
         boolean mExistenceChanged = false;
+        // This state indicates that we are restoring transient order as a part of an
+        // end-transition. Because the visibility for transient hide containers has not actually
+        // changed, we need to ensure that hasChanged() still reports the relevant changes
+        boolean mRestoringTransientHide = false;
         // before change state
         boolean mVisible;
         int mWindowingMode;
@@ -3552,7 +3581,11 @@
                     || !mContainer.getBounds().equals(mAbsoluteBounds)
                     || mRotation != mContainer.getWindowConfiguration().getRotation()
                     || mDisplayId != getDisplayId(mContainer)
-                    || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
+                    || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0
+                    // If we are restoring transient-hide containers, then we should consider them
+                    // important for the transition as well (their requested visibilities would not
+                    // have changed for the checks below to consider it).
+                    || mRestoringTransientHide;
         }
 
         @TransitionInfo.TransitionMode
@@ -3565,6 +3598,11 @@
             }
             final boolean nowVisible = wc.isVisibleRequested();
             if (nowVisible == mVisible) {
+                if (mRestoringTransientHide) {
+                    // The requested visibility has not changed for transient-hide containers, but
+                    // we are restoring them so we should considering them moving to front again
+                    return TRANSIT_TO_FRONT;
+                }
                 return TRANSIT_CHANGE;
             }
             if (mExistenceChanged) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 87bdfa4..143d1b7 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -524,6 +525,23 @@
         return false;
     }
 
+    /**
+     * @return A pair of the transition and restore-behind target for the given {@param container}.
+     * @param container An ancestor of a transient-launch activity
+     */
+    @Nullable
+    Pair<Transition, Task> getTransientLaunchTransitionAndTarget(
+            @NonNull WindowContainer container) {
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            final Transition transition = mPlayingTransitions.get(i);
+            final Task restoreBehindTask = transition.getTransientLaunchRestoreTarget(container);
+            if (restoreBehindTask != null) {
+                return new Pair<>(transition, restoreBehindTask);
+            }
+        }
+        return null;
+    }
+
     /** Returns {@code true} if the display contains a transient-launch transition. */
     boolean hasTransientLaunch(@NonNull DisplayContent dc) {
         if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dac8f69..ead1282 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -111,6 +111,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.RemoteAnimationAdapter;
 import android.view.SurfaceControl;
@@ -1375,16 +1376,56 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
-                if (!chain.isFinishing()) break;
+                if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+                    // Only allow restoring transient order when finishing a transition
+                    if (!chain.isFinishing()) break;
+                }
+                // Validate the container
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
-                if (container == null) break;
+                if (container == null) {
+                    ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "Restoring transient order: invalid container");
+                    break;
+                }
                 final Task thisTask = container.asActivityRecord() != null
                         ? container.asActivityRecord().getTask() : container.asTask();
-                if (thisTask == null) break;
-                final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container);
-                if (restoreAt == null) break;
+                if (thisTask == null) {
+                    ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "Restoring transient order: invalid task");
+                    break;
+                }
+
+                // Find the task to restore behind
+                final Pair<Transition, Task> transientRestore =
+                        mTransitionController.getTransientLaunchTransitionAndTarget(container);
+                if (transientRestore == null) {
+                    ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "Restoring transient order: no restore task");
+                    break;
+                }
+                final Transition transientLaunchTransition = transientRestore.first;
+                final Task restoreAt = transientRestore.second;
+                ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "Restoring transient order: restoring behind task=%d", restoreAt.mTaskId);
+
+                // Restore the position of the given container behind the target task
                 final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
                 taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
+
+                if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+                    // Because we are in a transient launch transition, the requested visibility of
+                    // tasks does not actually change for the transient-hide tasks, but we do want
+                    // the restoration of these transient-hide tasks to top to be a part of this
+                    // finish transition
+                    final Transition collectingTransition =
+                            mTransitionController.getCollectingTransition();
+                    if (collectingTransition != null) {
+                        collectingTransition.updateChangesForRestoreTransientHideTasks(
+                                transientLaunchTransition);
+                    }
+                }
+
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
             case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: {