Loading services/core/java/com/android/server/wm/ActivityStarter.java +6 −1 Original line number Diff line number Diff line Loading @@ -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()); } Loading Loading @@ -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"); } Loading services/core/java/com/android/server/wm/TaskFragment.java +4 −0 Original line number Diff line number Diff line Loading @@ -1012,6 +1012,10 @@ class TaskFragment extends WindowContainer<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; Loading services/core/java/com/android/server/wm/Transition.java +54 −24 Original line number Diff line number Diff line Loading @@ -194,6 +194,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ private ArrayMap<ActivityRecord, Task> 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<Task> mTransientHideTasks; /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; Loading Loading @@ -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); } }); } 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 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; } } return false; } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + "transient-launch", mSyncId, activity); } /** @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; } } Loading Loading @@ -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 Loading Loading @@ -1175,15 +1206,14 @@ 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. // 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; // 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()); } } // Take task snapshots before the animation so that we can capture IME before it gets // transferred. If transition is transient, IME won't be moved during the transition and Loading services/core/java/com/android/server/wm/TransitionController.java +2 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +15 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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 Loading @@ -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)); Loading Loading
services/core/java/com/android/server/wm/ActivityStarter.java +6 −1 Original line number Diff line number Diff line Loading @@ -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()); } Loading Loading @@ -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"); } Loading
services/core/java/com/android/server/wm/TaskFragment.java +4 −0 Original line number Diff line number Diff line Loading @@ -1012,6 +1012,10 @@ class TaskFragment extends WindowContainer<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; Loading
services/core/java/com/android/server/wm/Transition.java +54 −24 Original line number Diff line number Diff line Loading @@ -194,6 +194,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ private ArrayMap<ActivityRecord, Task> 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<Task> mTransientHideTasks; /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; Loading Loading @@ -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); } }); } 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 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; } } return false; } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + "transient-launch", mSyncId, activity); } /** @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; } } Loading Loading @@ -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 Loading Loading @@ -1175,15 +1206,14 @@ 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. // 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; // 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()); } } // Take task snapshots before the animation so that we can capture IME before it gets // transferred. If transition is transient, IME won't be moved during the transition and Loading
services/core/java/com/android/server/wm/TransitionController.java +2 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +15 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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 Loading @@ -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)); Loading