diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 4745220e6e56b..346aa3336c126 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -570,6 +570,24 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /** + * If `container` was brought to front as a transient-launch (eg. recents), this will reorder + * the container back to where it was prior to the transient-launch. This way if a transient + * launch is "aborted", the z-ordering of containers in WM should be restored to before the + * launch. + * @hide + */ + @NonNull + public WindowContainerTransaction restoreTransientOrder( + @NonNull WindowContainerToken container) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER) + .setContainer(container.asBinder()) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + /** * When this {@link WindowContainerTransaction} failed to finish on the server side, it will * trigger callback with this {@param errorCallbackToken}. @@ -974,6 +992,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12; public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13; public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 14; + public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index ace7938924732..98e48f64e9e66 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -132,12 +132,13 @@ public class RemoteTransitionCompat implements Parcelable { // the current going-away task on top of recents, though, so move it to front final ArrayList pausingTasks = new ArrayList<>(); WindowContainerToken pipTask = null; + WindowContainerToken recentsTask = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) { t.setLayer(leashMap.get(change.getLeash()), info.getChanges().size() * 3 - i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo == null) { continue; } @@ -147,11 +148,14 @@ public class RemoteTransitionCompat implements Parcelable { && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { pipTask = taskInfo.token; } - } else if (change.getTaskInfo() != null - && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_RECENTS) { + } else if (taskInfo != null + && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { // This task is for recents, keep it on top. t.setLayer(leashMap.get(change.getLeash()), info.getChanges().size() * 3 - i); + recentsTask = taskInfo.token; + } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + recentsTask = taskInfo.token; } } // Also make all the wallpapers opaque since we want the visible from the start @@ -160,7 +164,7 @@ public class RemoteTransitionCompat implements Parcelable { } t.apply(); mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask, - leashMap, mToken); + recentsTask, leashMap, mToken); recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0), new Rect()); } @@ -209,6 +213,7 @@ public class RemoteTransitionCompat implements Parcelable { private IRemoteTransitionFinishedCallback mFinishCB = null; private ArrayList mPausingTasks = null; private WindowContainerToken mPipTask = null; + private WindowContainerToken mRecentsTask = null; private TransitionInfo mInfo = null; private ArrayList mOpeningLeashes = null; private ArrayMap mLeashMap = null; @@ -218,7 +223,8 @@ public class RemoteTransitionCompat implements Parcelable { void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info, IRemoteTransitionFinishedCallback finishCB, ArrayList pausingTasks, WindowContainerToken pipTask, - ArrayMap leashMap, IBinder transition) { + WindowContainerToken recentsTask, ArrayMap leashMap, + IBinder transition) { if (mInfo != null) { throw new IllegalStateException("Trying to run a new recents animation while" + " recents is already active."); @@ -228,6 +234,7 @@ public class RemoteTransitionCompat implements Parcelable { mFinishCB = finishCB; mPausingTasks = pausingTasks; mPipTask = pipTask; + mRecentsTask = recentsTask; mLeashMap = leashMap; mTransition = transition; } @@ -329,6 +336,9 @@ public class RemoteTransitionCompat implements Parcelable { wct.reorder(mPausingTasks.get(i), true /* onTop */); t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); } + if (mRecentsTask != null) { + wct.restoreTransientOrder(mRecentsTask); + } } else { wct = null; if (mPipTask != null && mPipTransaction != null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 47bec3081dc34..fd2afd47bec49 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -208,6 +208,9 @@ class ActivityStarter { private boolean mAvoidMoveToFront; private boolean mFrozeTaskList; private boolean mTransientLaunch; + // The task which was above the targetTask before starting this activity. null if the targetTask + // was already on top or if the activity is in a new task. + private Task mPriorAboveTask; // We must track when we deliver the new intent since multiple code paths invoke // {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used @@ -1666,7 +1669,8 @@ class ActivityStarter { if (isTransient) { // `r` isn't guaranteed to be the actual relevant activity, so we must wait // until after we launched to identify the relevant activity. - transitionController.setTransientLaunch(mLastStartActivityRecord); + transitionController.setTransientLaunch(mLastStartActivityRecord, + mPriorAboveTask); } if (newTransition != null) { transitionController.requestStartTransition(newTransition, @@ -1785,6 +1789,10 @@ class ActivityStarter { return startResult; } + if (targetTask != null) { + mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask()); + } + final ActivityRecord targetTaskTop = newTask ? null : targetTask.getTopNonFinishingActivity(); if (targetTaskTop != null) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index bf33f86ac672d..0f5828c7efb02 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -167,8 +167,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe */ private final ArraySet mVisibleAtTransitionEndTokens = new ArraySet<>(); - /** Set of transient activities (lifecycle initially tied to this transition). */ - private ArraySet mTransientLaunches = null; + /** + * Set of transient activities (lifecycle initially tied to this transition) and their + * restore-below tasks. + */ + private ArrayMap mTransientLaunches = null; /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; @@ -196,17 +199,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** Records an activity as transient-launch. This activity must be already collected. */ - void setTransientLaunch(@NonNull ActivityRecord activity) { + void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) { if (mTransientLaunches == null) { - mTransientLaunches = new ArraySet<>(); + mTransientLaunches = new ArrayMap<>(); } - mTransientLaunches.add(activity); + mTransientLaunches.put(activity, restoreBelow); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + "transient-launch", mSyncId, activity); } boolean isTransientLaunch(@NonNull ActivityRecord activity) { - return mTransientLaunches != null && mTransientLaunches.contains(activity); + return mTransientLaunches != null && mTransientLaunches.containsKey(activity); + } + + Task getTransientLaunchRestoreTarget(@NonNull WindowContainer container) { + for (int i = 0; i < mTransientLaunches.size(); ++i) { + if (mTransientLaunches.keyAt(i).isDescendantOf(container)) { + return mTransientLaunches.valueAt(i); + } + } + return null; } boolean isOnDisplay(@NonNull DisplayContent dc) { @@ -464,7 +476,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe && ar.pictureInPictureArgs.isAutoEnterEnabled()) { if (mTransientLaunches != null) { for (int j = 0; j < mTransientLaunches.size(); ++j) { - if (mTransientLaunches.valueAt(j).isVisibleRequested()) { + if (mTransientLaunches.keyAt(j).isVisibleRequested()) { // force enable pip-on-task-switch now that we've committed // to actually launching to the transient activity. ar.supportsEnterPipOnTaskSwitch = true; @@ -543,7 +555,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget), // so re-compute in case the IME target is changed after transition. for (int t = 0; t < mTransientLaunches.size(); ++t) { - if (mTransientLaunches.valueAt(t).getDisplayContent() == dc) { + if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) { dc.computeImeTarget(true /* updateImeTarget */); break; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index c13ae95217b56..b5bac76a5ae4b 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -520,10 +520,12 @@ class TransitionController { /** * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently * tied to the transition). + * @param restoreBelowTask If non-null, the activity's task will be ordered right below this + * task if requested. */ - void setTransientLaunch(@NonNull ActivityRecord activity) { + void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelowTask) { if (mCollectingTransition == null) return; - mCollectingTransition.setTransientLaunch(activity); + mCollectingTransition.setTransientLaunch(activity, restoreBelowTask); // TODO(b/188669821): Remove once legacy recents behavior is moved to shell. // Also interpret HOME transient launch as recents diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4c7891b9fc519..c70a40c3b5fcc 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -30,6 +30,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; @@ -358,10 +359,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (t != null && callback != null) { syncId = startSyncWithOrganizer(callback); } + final Transition transition = Transition.fromBinder(transitionToken); // apply the incoming transaction before finish in case it alters the visibility // of the participants. if (t != null) { - applyTransaction(t, syncId, null /*transition*/, caller); + applyTransaction(t, syncId, null /*transition*/, caller, transition); } getTransitionController().finishTransition(transitionToken); if (syncId >= 0) { @@ -374,13 +376,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, + @Nullable Transition transition, @NonNull CallerInfo caller) { + applyTransaction(t, syncId, transition, caller, null /* finishTransition */); + } + /** * @param syncId If non-null, this will be a sync-transaction. * @param transition A transition to collect changes into. * @param caller Info about the calling process. + * @param finishTransition The transition that is currently being finished. */ private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, - @Nullable Transition transition, @NonNull CallerInfo caller) { + @Nullable Transition transition, @NonNull CallerInfo caller, + @Nullable Transition finishTransition) { int effects = 0; ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); @@ -433,7 +442,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub for (int i = 0; i < hopSize; ++i) { effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition, isInLockTaskMode, caller, t.getErrorCallbackToken(), - t.getTaskFragmentOrganizer()); + t.getTaskFragmentOrganizer(), finishTransition); } } // Queue-up bounds-change transactions for tasks which are now organized. Do @@ -604,7 +613,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects, int syncId, @Nullable Transition transition, boolean isInLockTaskMode, @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken, - @Nullable ITaskFragmentOrganizer organizer) { + @Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) { final int type = hop.getType(); switch (type) { case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: { @@ -873,6 +882,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } + case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: { + if (finishTransition == null) break; + final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); + if (container == null) break; + final Task thisTask = container.asActivityRecord() != null + ? container.asActivityRecord().getTask() : container.asTask(); + if (thisTask == null) break; + final Task restoreAt = finishTransition.getTransientLaunchRestoreTarget(container); + if (restoreAt == null) break; + final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea(); + taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt); + break; + } } return effects; } 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 d74e1be88e0b4..29289a4047911 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -713,7 +713,7 @@ public class TransitionTests extends WindowTestsBase { closeTransition.collectExistenceChange(activity1); closeTransition.collectExistenceChange(task2); closeTransition.collectExistenceChange(activity2); - closeTransition.setTransientLaunch(activity2); + closeTransition.setTransientLaunch(activity2, null /* restoreBelow */); activity1.mVisibleRequested = false; activity2.mVisibleRequested = true;