Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52ae087d authored by Winson Chung's avatar Winson Chung
Browse files

3b/ Migrate away from finishWCT usage in recents transition

- Instead of using a finishWCT to restore the state of the tasks at the
  end of the recents transition (only when returning back to the app),
  we use a "book end" transition, which ensures that changes can be
  applied in sync with other potential ongoing transitions in the system.
  This requires that all transitions when merged are either handled,
  or result in the cancelation of the existing recents transition,
  otherwise an unhandled merge could result in a queued transition that
  will block the new "book end" transition.
- To support these changes, WindowOrganizerController needs to handle
  restoring transient order in arbitrary transitions.  Because
  the requested visibility when restoring transient order w/ multiple
  tasks (in multiwindow) is not changed, we need to force the associated
  changes to be part of the new finish transition (necessary for
  the changes to be observed).

Bug: 346588978
Flag: EXEMPT adding new flag enable_shell_top_task_tracking
Test: Build SystemUI & Launcher
Test: atest WMShellUnitTests
Test: atest WmTests:TransitionTests
Test: atest WmTests:WindowOrganizerTests
Change-Id: I81d1c373a48ee269d3bbcd81ca92ed3e5a0a4912
parent cf51a08e
Loading
Loading
Loading
Loading
+132 −20
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT;

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 androidx.annotation.NonNull;
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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                    "RecentsTransitionHandler.mergeAnimation: no controller found");
            return;
        }
        controller.merge(info, t, finishCallback);
        controller.merge(info, t, mergeTarget, finishCallback);
    }

    @Override
@@ -367,6 +373,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
        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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
        // 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
            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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                        // 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
            }
        }

        /**
         * 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                return;
            }

            if (mFinishCB == null) {
            if (mFinishCB == null
                    || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
                Slog.e(TAG, "Duplicate call to finish");
                return;
            }
@@ -1254,19 +1293,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                    && !mWillFinishToHome
                    && mPausingTasks != null
                    && mState == STATE_NORMAL;
            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.
                    // 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                            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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
                }
            });
        }

        /**
         * 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. */
+8 −0
Original line number Diff line number Diff line
@@ -203,6 +203,12 @@ public class Transitions implements RemoteCallable<Transitions>,
    /** 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 @@ public class Transitions implements RemoteCallable<Transitions>,
            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) + ")";
+39 −1
Original line number Diff line number Diff line
@@ -465,6 +465,31 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {

        // 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                    || !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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            }
            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) {
+18 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.RemoteException;
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 @@ class TransitionController {
        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()
+46 −5
Original line number Diff line number Diff line
@@ -111,6 +111,7 @@ import android.os.RemoteException;
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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                break;
            }
            case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
                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: {