diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index d3dc05fb92d4b..405ee14c23463 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -425,12 +425,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. removeContainer(container); - // Make sure the top container is updated. - final TaskFragmentContainer newTopContainer = getTopActiveContainer( - container.getTaskId()); - if (newTopContainer != null) { - updateContainer(wct, newTopContainer); - } + // Make sure the containers in the Task are up-to-date. + updateContainersInTaskIfVisible(wct, container.getTaskId()); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } @@ -470,6 +466,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTask(wct, taskContainer); } + void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer != null && taskContainer.isVisible()) { + updateContainersInTask(wct, taskContainer); + } + } + private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be @@ -1320,9 +1323,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen void removeContainer(@NonNull TaskFragmentContainer container) { // Remove all split containers that included this one final TaskContainer taskContainer = container.getTaskContainer(); - if (taskContainer == null) { - return; - } taskContainer.mContainers.remove(container); // Marked as a pending removal which will be removed after it is actually removed on the // server side (#onTaskFragmentVanished). @@ -1515,14 +1515,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskFragmentContainer container = getContainerWithActivity(activity); - // Don't launch placeholder if the container is occluded. - if (container != null && container != getTopActiveContainer(container.getTaskId())) { - return false; - } - - final SplitContainer splitContainer = getActiveSplitForContainer(container); - if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { - // Don't launch placeholder in primary split container + if (container != null && !allowLaunchPlaceholder(container)) { + // We don't allow activity in this TaskFragment to launch placeholder. return false; } @@ -1550,6 +1544,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } + /** Whether or not to allow activity in this container to launch placeholder. */ + @GuardedBy("mLock") + private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) { + final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId()); + if (container != topContainer) { + // The container is not the top most. + if (!container.isVisible()) { + // In case the container is visible (the one on top may be transparent), we may + // still want to launch placeholder even if it is not the top most. + return false; + } + if (topContainer.isWaitingActivityAppear()) { + // When the top container appeared info is not sent by the server yet, the visible + // check above may not be reliable. + return false; + } + } + + final SplitContainer splitContainer = getActiveSplitForContainer(container); + if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { + // Don't launch placeholder for primary split container. + return false; + } + return true; + } + /** * Gets the activity options for starting the placeholder activity. In case the placeholder is * launched when the Task is in the background, we don't want to bring the Task to the front. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 47253d388f0dd..a432e2b10c142 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -154,12 +154,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void cleanupContainer(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { container.finish(shouldFinishDependent, this, wct, mController); - - final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer( - container.getTaskId()); - if (newTopContainer != null) { - mController.updateContainer(wct, newTopContainer); - } + // Make sure the containers in the Task is up-to-date. + mController.updateContainersInTaskIfVisible(wct, container.getTaskId()); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index fcf0ac78af387..8240874ca53d8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -184,6 +184,11 @@ class TaskFragmentContainer { return allActivities; } + /** Whether this TaskFragment is visible. */ + boolean isVisible() { + return mInfo != null && mInfo.isVisible(); + } + /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/ boolean isInIntermediateState() { if (mInfo == null) { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index bc03e4ec303df..2f92a577baa21 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -163,11 +163,17 @@ public class EmbeddingTestUtils { /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity) { + return createMockTaskFragmentInfo(container, activity, true /* isVisible */); + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity, boolean isVisible) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 1, - true /* isVisible */, + isVisible, Collections.singletonList(activity.getActivityToken()), new Point(), false /* isTaskClearedForReuse */, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 6725dfdb94e81..3cc31f9761c12 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1254,6 +1254,68 @@ public class SplitControllerTest { verify(mEmbeddingCallback).accept(any()); } + @Test + public void testLaunchPlaceholderIfNecessary_nonEmbeddedActivity() { + // Launch placeholder for non embedded activity. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), + any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment() { + // Launch placeholder for activity in top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + assertTrue(container.hasActivity(mActivity.getActivityToken())); + verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), + any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment() { + // Do not launch placeholder for invisible activity below the top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, + TASK_ID); + bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, + false /* isVisible */)); + topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); + assertFalse(bottomTf.isVisible()); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment() { + // Launch placeholder for visible activity below the top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, + TASK_ID); + bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, + true /* isVisible */)); + topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); + assertTrue(bottomTf.isVisible()); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 5c3ba72e23619..98772360a3211 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -500,6 +500,29 @@ public class TaskFragmentContainerTest { assertEquals(2, taskContainer.indexOf(tf1)); } + @Test + public void testIsVisible() { + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer container = new TaskFragmentContainer( + null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, + null /* pairedPrimaryTaskFragment */); + + // Not visible when there is not appeared. + assertFalse(container.isVisible()); + + // Respect info.isVisible. + TaskFragmentInfo info = createMockTaskFragmentInfo(container, mActivity, + true /* isVisible */); + container.setInfo(mTransaction, info); + + assertTrue(container.isVisible()); + + info = createMockTaskFragmentInfo(container, mActivity, false /* isVisible */); + container.setInfo(mTransaction, info); + + assertFalse(container.isVisible()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index ae4f8947cc863..40ab68b011ef0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2552,7 +2552,7 @@ class TaskFragment extends WindowContainer { mRemoteToken.toWindowContainerToken(), getConfiguration(), getNonFinishingActivityCount(), - isVisibleRequested(), + shouldBeVisible(null /* starting */), childActivities, positionInParent, mClearedTaskForReuse, @@ -2848,6 +2848,8 @@ class TaskFragment extends WindowContainer { if (parentTf != null) { parentTf.onActivityVisibleRequestedChanged(); } + // Send the info changed to update the TaskFragment visibility. + sendTaskFragmentInfoChanged(); } @Nullable