diff options
5 files changed, 83 insertions, 5 deletions
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 0c1f33ccedbc..92c0987d5636 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -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; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a14354041b91..eaea53d555e2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -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(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index bfaf6fcd3110..dfaa17494855 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -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. diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index abf21a57dd40..7eab06ac8b95 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -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, 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 ffecafb7b4ed..5154d17f2e6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1488,6 +1488,47 @@ public class TransitionTests extends WindowTestsBase { } @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); controller.setSyncEngine(mWm.mSyncEngine); |