diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 211c230b4ece8..ce29564d0b02e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1680,6 +1680,11 @@ class ActivityStarter { targetTask.removeImmediately("bulky-task"); return START_ABORTED; } + // When running transient transition, the transient launch target should keep on top. + // So disallow the transient hide activity to move itself to front, e.g. trampoline. + if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) { + mAvoidMoveToFront = true; + } mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask()); } @@ -1796,7 +1801,7 @@ class ActivityStarter { // root-task to the will not update the focused root-task. If starting the new // activity now allows the task root-task to be focusable, then ensure that we // now update the focused root-task accordingly. - if (mTargetRootTask.isTopActivityFocusable() + if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) { mTargetRootTask.moveToFront("startActivityInner"); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 2ddb307ea430b..7c57dc17e8024 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1012,6 +1012,10 @@ class TaskFragment extends WindowContainer { if (isTopActivityLaunchedBehind()) { return TASK_FRAGMENT_VISIBILITY_VISIBLE; } + final Task thisTask = asTask(); + if (thisTask != null && mTransitionController.isTransientHide(thisTask)) { + return TASK_FRAGMENT_VISIBILITY_VISIBLE; + } boolean gotTranslucentFullscreen = false; boolean gotTranslucentAdjacent = false; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 89feb0cc5e4a9..97f48b7d03097 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -194,6 +194,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ private ArrayMap mTransientLaunches = null; + /** + * The tasks that may be occluded by the transient activity. Assume the task stack is + * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below + * task, and [B, C] are the transient-hide tasks. + */ + private ArrayList mTransientHideTasks; + /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; @@ -265,35 +272,51 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) { if (mTransientLaunches == null) { mTransientLaunches = new ArrayMap<>(); + mTransientHideTasks = new ArrayList<>(); } mTransientLaunches.put(activity, restoreBelow); setTransientLaunchToChanges(activity); if (restoreBelow != null) { - final ChangeInfo info = mChanges.get(restoreBelow); - if (info != null) { - info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; + // Collect all visible activities which can be occluded by the transient activity to + // make sure they are in the participants so their visibilities can be updated when + // finishing transition. + ((WindowContainer) restoreBelow.getParent()).forAllTasks(t -> { + if (t.isVisibleRequested() && !t.isAlwaysOnTop() + && !t.getWindowConfiguration().tasksAreFloating()) { + if (t.isRootTask()) { + mTransientHideTasks.add(t); + } + if (t.isLeafTask()) { + t.forAllActivities(r -> { + if (r.isVisibleRequested()) { + collect(r); + } + }); + } + } + return t == restoreBelow; + }); + // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks, + // so ChangeInfo#hasChanged() can return true to report the transition info. + for (int i = mChanges.size() - 1; i >= 0; --i) { + final WindowContainer wc = mChanges.keyAt(i); + if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue; + if (isInTransientHide(wc)) { + mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; + } } } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + "transient-launch", mSyncId, activity); } - boolean isTransientHide(@NonNull Task task) { - if (mTransientLaunches == null) return false; - for (int i = 0; i < mTransientLaunches.size(); ++i) { - if (mTransientLaunches.valueAt(i) == task) { - return true; - } - } - return false; - } - /** @return whether `wc` is a descendent of a transient-hide window. */ boolean isInTransientHide(@NonNull WindowContainer wc) { - if (mTransientLaunches == null) return false; - for (int i = 0; i < mTransientLaunches.size(); ++i) { - if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) { + if (mTransientHideTasks == null) return false; + for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) { + final Task task = mTransientHideTasks.get(i); + if (wc == task || wc.isDescendantOf(task)) { return true; } } @@ -816,6 +839,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } mController.mFinishingTransition = this; + if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) { + // The transient hide tasks could be occluded now, e.g. returning to home. So trigger + // the update to make the activities in the tasks invisible-requested, then the next + // step can continue to commit the visibility. + mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */, + 0 /* configChanges */, true /* preserveWindows */); + } + boolean hasParticipatedDisplay = false; boolean hasVisibleTransientLaunch = false; // Commit all going-invisible containers @@ -1175,14 +1206,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Record windowtokens (activity/wallpaper) that are expected to be visible after the // transition animation. This will be used in finishTransition to prevent prematurely - // committing visibility. - for (int i = mParticipants.size() - 1; i >= 0; --i) { - final WindowContainer wc = mParticipants.valueAt(i); - if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; - // don't include transient launches, though, since those are only temporarily visible. - if (mTransientLaunches != null && wc.asActivityRecord() != null - && mTransientLaunches.containsKey(wc.asActivityRecord())) continue; - mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); + // committing visibility. Skip transient launches since those are only temporarily visible. + if (mTransientLaunches == null) { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer wc = mParticipants.valueAt(i); + if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; + mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); + } } // Take task snapshots before the animation so that we can capture IME before it gets diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 59264e9b6ea00..86bb6b58d14c5 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -366,11 +366,11 @@ class TransitionController { } boolean isTransientHide(@NonNull Task task) { - if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) { + if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - if (mPlayingTransitions.get(i).isTransientHide(task)) return true; + if (mPlayingTransitions.get(i).isInTransientHide(task)) return true; } return false; } 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 3cbe2de137027..616d528c67fae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -40,6 +40,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static android.window.TransitionInfo.isIndependent; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -54,6 +55,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -1400,7 +1402,13 @@ public class TransitionTests extends WindowTestsBase { closeTransition.collectExistenceChange(activity1); closeTransition.collectExistenceChange(task2); closeTransition.collectExistenceChange(activity2); - closeTransition.setTransientLaunch(activity2, null /* restoreBelow */); + closeTransition.setTransientLaunch(activity2, task1); + final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1); + assertNotNull(task1ChangeInfo); + assertTrue(task1ChangeInfo.hasChanged()); + final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1); + assertNotNull(activity1ChangeInfo); + assertTrue(activity1ChangeInfo.hasChanged()); activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); @@ -1416,6 +1424,8 @@ public class TransitionTests extends WindowTestsBase { verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false)); enteringAnimReports.clear(); + doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), + anyInt(), anyBoolean(), anyBoolean()); final boolean[] wasInFinishingTransition = { false }; controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() { @Override @@ -1430,7 +1440,11 @@ public class TransitionTests extends WindowTestsBase { assertTrue(wasInFinishingTransition[0]); assertNull(controller.mFinishingTransition); + assertTrue(activity2.isVisible()); assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState()); + // Because task1 is occluded by task2, finishTransition should make activity1 invisible. + assertFalse(activity1.isVisibleRequested()); + assertFalse(activity1.isVisible()); assertFalse(activity1.app.hasActivityInVisibleTask()); verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));