Loading services/core/java/com/android/server/wm/TaskFragment.java +1 −1 Original line number Diff line number Diff line Loading @@ -1020,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final WindowContainer<?> parent = getParent(); final Task thisTask = asTask(); if (thisTask != null && parent.asTask() == null && mTransitionController.isTransientHide(thisTask)) { && mTransitionController.isTransientVisible(thisTask)) { // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule. return TASK_FRAGMENT_VISIBILITY_VISIBLE; } Loading services/core/java/com/android/server/wm/Transition.java +30 −0 Original line number Diff line number Diff line Loading @@ -407,6 +407,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } /** 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; int occludedCount = 0; final int numTransient = mTransientLaunches.size(); for (int i = numTransient - 1; i >= 0; --i) { final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask(); if (transientRoot == null) continue; final WindowContainer<?> rootParent = transientRoot.getParent(); if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor .mOpaqueActivityHelper.getOpaqueActivity(rootParent); if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { occludedCount++; } } if (occludedCount == numTransient) { for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { if (mTransientLaunches.keyAt(i).isDescendantOf(task)) { // Keep transient activity visible until transition finished, so it won't pause // with transient-hide tasks that may delay resuming the next top. return true; } } // Let transient-hide activities pause before transition is finished. return false; } return isInTransientHide(task); } boolean canApplyDim(@NonNull Task task) { if (mTransientLaunches == null) return true; final Dimmer dimmer = task.getDimmer(); Loading services/core/java/com/android/server/wm/TransitionController.java +10 −3 Original line number Diff line number Diff line Loading @@ -477,15 +477,22 @@ class TransitionController { if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; } for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { if (mWaitingTransitions.get(i).isInTransientHide(task)) return true; } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isInTransientHide(task)) return true; } return false; } boolean isTransientVisible(@NonNull Task task) { if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) { return true; } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isTransientVisible(task)) return true; } return false; } boolean canApplyDim(@Nullable Task task) { if (task == null) { // Always allow non-activity window. Loading services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +1 −1 Original line number Diff line number Diff line Loading @@ -656,7 +656,7 @@ public class RootTaskTests extends WindowTestsBase { topSplitPrimary.getVisibility(null /* starting */)); // Make primary split root transient-hide. spyOn(splitPrimary.mTransitionController); doReturn(true).when(splitPrimary.mTransitionController).isTransientHide( doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible( organizer.mPrimary); // The split root and its top become visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, Loading services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +41 −0 Original line number Diff line number Diff line Loading @@ -1483,6 +1483,47 @@ public class TransitionTests extends WindowTestsBase { assertTrue(enteringAnimReports.contains(activity2)); } @Test public void testIsTransientVisible() { final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true) .setVisible(false).build(); final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true) .setVisible(false).build(); final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task taskA = appA.getTask(); final Task taskB = appB.getTask(); final Task taskRecent = recent.getTask(); registerTestTransitionPlayer(); final TransitionController controller = mRootWindowContainer.mTransitionController; final Transition transition = createTestTransition(TRANSIT_OPEN, controller); controller.moveToCollecting(transition); transition.collect(recent); transition.collect(taskA); transition.setTransientLaunch(recent, taskA); taskRecent.moveToFront("move-recent-to-front"); // During collecting and playing, the recent is on top so it is visible naturally. // While B needs isTransientVisible to keep visibility because it is occluded by recents. assertFalse(controller.isTransientVisible(taskB)); assertTrue(controller.isTransientVisible(taskA)); assertFalse(controller.isTransientVisible(taskRecent)); // Switch to playing state. transition.onTransactionReady(transition.getSyncId(), mMockT); assertTrue(controller.isTransientVisible(taskA)); // Switch to another task. For example, use gesture navigation to switch tasks. taskB.moveToFront("move-b-to-front"); // The previous app (taskA) should be paused first so it loses transient visible. Because // visually it is taskA -> taskB, the pause -> resume order should be the same. assertFalse(controller.isTransientVisible(taskA)); // Keep the recent visible so there won't be 2 activities pausing at the same time. It is // to avoid the latency to resume the current top, i.e. appB. assertTrue(controller.isTransientVisible(taskRecent)); // The recent is paused after the transient transition is finished. controller.finishTransition(transition); assertFalse(controller.isTransientVisible(taskRecent)); } @Test public void testNotReadyPushPop() { final TransitionController controller = new TestTransitionController(mAtm); Loading Loading
services/core/java/com/android/server/wm/TaskFragment.java +1 −1 Original line number Diff line number Diff line Loading @@ -1020,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final WindowContainer<?> parent = getParent(); final Task thisTask = asTask(); if (thisTask != null && parent.asTask() == null && mTransitionController.isTransientHide(thisTask)) { && mTransitionController.isTransientVisible(thisTask)) { // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule. return TASK_FRAGMENT_VISIBILITY_VISIBLE; } Loading
services/core/java/com/android/server/wm/Transition.java +30 −0 Original line number Diff line number Diff line Loading @@ -407,6 +407,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } /** 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; int occludedCount = 0; final int numTransient = mTransientLaunches.size(); for (int i = numTransient - 1; i >= 0; --i) { final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask(); if (transientRoot == null) continue; final WindowContainer<?> rootParent = transientRoot.getParent(); if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor .mOpaqueActivityHelper.getOpaqueActivity(rootParent); if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) { occludedCount++; } } if (occludedCount == numTransient) { for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { if (mTransientLaunches.keyAt(i).isDescendantOf(task)) { // Keep transient activity visible until transition finished, so it won't pause // with transient-hide tasks that may delay resuming the next top. return true; } } // Let transient-hide activities pause before transition is finished. return false; } return isInTransientHide(task); } boolean canApplyDim(@NonNull Task task) { if (mTransientLaunches == null) return true; final Dimmer dimmer = task.getDimmer(); Loading
services/core/java/com/android/server/wm/TransitionController.java +10 −3 Original line number Diff line number Diff line Loading @@ -477,15 +477,22 @@ class TransitionController { if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; } for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { if (mWaitingTransitions.get(i).isInTransientHide(task)) return true; } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isInTransientHide(task)) return true; } return false; } boolean isTransientVisible(@NonNull Task task) { if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) { return true; } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { if (mPlayingTransitions.get(i).isTransientVisible(task)) return true; } return false; } boolean canApplyDim(@Nullable Task task) { if (task == null) { // Always allow non-activity window. Loading
services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +1 −1 Original line number Diff line number Diff line Loading @@ -656,7 +656,7 @@ public class RootTaskTests extends WindowTestsBase { topSplitPrimary.getVisibility(null /* starting */)); // Make primary split root transient-hide. spyOn(splitPrimary.mTransitionController); doReturn(true).when(splitPrimary.mTransitionController).isTransientHide( doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible( organizer.mPrimary); // The split root and its top become visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, Loading
services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +41 −0 Original line number Diff line number Diff line Loading @@ -1483,6 +1483,47 @@ public class TransitionTests extends WindowTestsBase { assertTrue(enteringAnimReports.contains(activity2)); } @Test public void testIsTransientVisible() { final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true) .setVisible(false).build(); final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true) .setVisible(false).build(); final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task taskA = appA.getTask(); final Task taskB = appB.getTask(); final Task taskRecent = recent.getTask(); registerTestTransitionPlayer(); final TransitionController controller = mRootWindowContainer.mTransitionController; final Transition transition = createTestTransition(TRANSIT_OPEN, controller); controller.moveToCollecting(transition); transition.collect(recent); transition.collect(taskA); transition.setTransientLaunch(recent, taskA); taskRecent.moveToFront("move-recent-to-front"); // During collecting and playing, the recent is on top so it is visible naturally. // While B needs isTransientVisible to keep visibility because it is occluded by recents. assertFalse(controller.isTransientVisible(taskB)); assertTrue(controller.isTransientVisible(taskA)); assertFalse(controller.isTransientVisible(taskRecent)); // Switch to playing state. transition.onTransactionReady(transition.getSyncId(), mMockT); assertTrue(controller.isTransientVisible(taskA)); // Switch to another task. For example, use gesture navigation to switch tasks. taskB.moveToFront("move-b-to-front"); // The previous app (taskA) should be paused first so it loses transient visible. Because // visually it is taskA -> taskB, the pause -> resume order should be the same. assertFalse(controller.isTransientVisible(taskA)); // Keep the recent visible so there won't be 2 activities pausing at the same time. It is // to avoid the latency to resume the current top, i.e. appB. assertTrue(controller.isTransientVisible(taskRecent)); // The recent is paused after the transient transition is finished. controller.finishTransition(transition); assertFalse(controller.isTransientVisible(taskRecent)); } @Test public void testNotReadyPushPop() { final TransitionController controller = new TestTransitionController(mAtm); Loading