summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java30
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java41
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);