diff options
2 files changed, 114 insertions, 103 deletions
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 8e1fde066277..e71d844ade59 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -119,7 +119,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without // association. It's not set in WM Extensions nor Wm Jetpack library currently. - private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY = + @VisibleForTesting + static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY = "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity"; @VisibleForTesting @@ -2742,89 +2743,72 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final int taskId = getTaskId(launchActivity); - if (!overlayContainers.isEmpty()) { - for (final TaskFragmentContainer overlayContainer : overlayContainers) { - final boolean isTopNonFinishingOverlay = overlayContainer.equals( - overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer( - true /* includePin */, true /* includeOverlay */)); - if (taskId != overlayContainer.getTaskId()) { - // If there's an overlay container with same tag in a different task, - // dismiss the overlay container since the tag must be unique per process. - if (overlayTag.equals(overlayContainer.getOverlayTag())) { - Log.w(TAG, "The overlay container with tag:" - + overlayContainer.getOverlayTag() + " is dismissed because" - + " there's an existing overlay container with the same tag but" - + " different task ID:" + overlayContainer.getTaskId() + ". " - + "The new associated activity is " + launchActivity); - mPresenter.cleanupContainer(wct, overlayContainer, - false /* shouldFinishDependant */); - } - continue; - } - if (!overlayTag.equals(overlayContainer.getOverlayTag())) { - // If there's an overlay container with different tag on top in the same - // task, dismiss the existing overlay container. - if (isTopNonFinishingOverlay) { - mPresenter.cleanupContainer(wct, overlayContainer, - false /* shouldFinishDependant */); - } - continue; - } - // The overlay container has the same tag and task ID with the new launching - // overlay container. - if (!isTopNonFinishingOverlay) { - // Dismiss the invisible overlay container regardless of activity - // association if it collides the tag of new launched overlay container . - Log.w(TAG, "The invisible overlay container with tag:" - + overlayContainer.getOverlayTag() + " is dismissed because" - + " there's a launching overlay container with the same tag." - + " The new associated activity is " + launchActivity); - mPresenter.cleanupContainer(wct, overlayContainer, - false /* shouldFinishDependant */); - continue; - } - // Requesting an always-on-top overlay. - if (!associateLaunchingActivity) { - if (overlayContainer.isOverlayWithActivityAssociation()) { - // Dismiss the overlay container since it has associated with an activity. - Log.w(TAG, "The overlay container with tag:" - + overlayContainer.getOverlayTag() + " is dismissed because" - + " there's an existing overlay container with the same tag but" - + " different associated launching activity. The overlay container" - + " doesn't associate with any activity."); - mPresenter.cleanupContainer(wct, overlayContainer, - false /* shouldFinishDependant */); - continue; - } else { - // The existing overlay container doesn't associate an activity as well. - // Just update the overlay and return. - // Note that going to this condition means the tag, task ID matches a - // visible always-on-top overlay, and won't dismiss any overlay any more. - mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs, - getMinDimensions(intent)); - return overlayContainer; - } - } - if (launchActivity.getActivityToken() - != overlayContainer.getAssociatedActivityToken()) { - Log.w(TAG, "The overlay container with tag:" - + overlayContainer.getOverlayTag() + " is dismissed because" - + " there's an existing overlay container with the same tag but" - + " different associated launching activity. The new associated" - + " activity is " + launchActivity); - // The associated activity must be the same, or it will be dismissed. - mPresenter.cleanupContainer(wct, overlayContainer, - false /* shouldFinishDependant */); - continue; - } - // Reaching here means the launching activity launch an overlay container with the - // same task ID, tag, while there's a previously launching visible overlay - // container. We'll regard it as updating the existing overlay container. + // Overlay container policy: + // 1. Overlay tag must be unique per process. + // a. For associated overlay, if a new launched overlay container has the same tag as + // an existing one, the existing overlay will be dismissed regardless of its task + // and window hierarchy. + // b. For always-on-top overlay, if there's an overlay container has the same tag in the + // launched task, the overlay container will be re-used, which means the + // ActivityStackAttributes will be applied and the launched activity will be positioned + // on top of the overlay container. + // 2. There must be at most one overlay that partially occludes a visible activity per task. + // a. For associated overlay, only the top visible overlay container in the launched task + // will be dismissed. + // b. Always-on-top overlay is always visible. If there's an overlay with different tags + // in the same task, the overlay will be dismissed in case an activity above + // the overlay is dismissed and the overlay is shown unexpectedly. + for (final TaskFragmentContainer overlayContainer : overlayContainers) { + final boolean isTopNonFinishingOverlay = overlayContainer.equals( + overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer( + true /* includePin */, true /* includeOverlay */)); + final boolean areInSameTask = taskId == overlayContainer.getTaskId(); + final boolean haveSameTag = overlayTag.equals(overlayContainer.getOverlayTag()); + if (!associateLaunchingActivity && overlayContainer.isAlwaysOnTopOverlay() + && haveSameTag && areInSameTask) { + // Just launch the activity and update the existing always-on-top overlay + // if the requested overlay is an always-on-top overlay with the same tag + // as the existing one. mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs, getMinDimensions(intent)); return overlayContainer; - } + if (haveSameTag) { + // For other tag match, we should clean up the existing overlay since the overlay + // tag must be unique per process. + Log.w(TAG, "The overlay container with tag:" + + overlayContainer.getOverlayTag() + " is dismissed with " + + " the launching activity=" + launchActivity + + " because there's an existing overlay container with the same tag."); + mPresenter.cleanupContainer(wct, overlayContainer, + false /* shouldFinishDependant */); + } + if (!areInSameTask) { + // Early return here because we won't clean-up or update overlay from different + // tasks except tag collision. + continue; + } + if (associateLaunchingActivity) { + // For associated overlay, we only dismiss the overlay if it's the top non-finishing + // child of its parent container. + if (isTopNonFinishingOverlay) { + Log.w(TAG, "The on-top overlay container with tag:" + + overlayContainer.getOverlayTag() + " is dismissed with " + + " the launching activity=" + launchActivity + + "because we only allow one overlay on top."); + mPresenter.cleanupContainer(wct, overlayContainer, + false /* shouldFinishDependant */); + } + continue; + } + // Otherwise, we should clean up the overlay in the task because we only allow one + // overlay when an always-on-top overlay is launched. + Log.w(TAG, "The overlay container with tag:" + + overlayContainer.getOverlayTag() + " is dismissed with " + + " the launching activity=" + launchActivity + + "because an always-on-top overlay is launched."); + mPresenter.cleanupContainer(wct, overlayContainer, + false /* shouldFinishDependant */); } // Launch the overlay container to the task with taskId. return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 1c4c8870b26f..f799bf9a1052 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -30,6 +30,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; +import static androidx.window.extensions.embedding.SplitController.KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; @@ -94,6 +95,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -267,7 +269,7 @@ public class OverlayPresentationTest { } @Test - public void testCreateOrUpdateOverlay_visibleOverlaySameTagInTask_dismissOverlay() { + public void testCreateOrUpdateOverlay_visibleOverlayInTask_dismissOverlay() { createExistingOverlayContainers(); final TaskFragmentContainer overlayContainer = @@ -295,26 +297,6 @@ public class OverlayPresentationTest { } @Test - public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() { - createExistingOverlayContainers(); - - final Rect bounds = new Rect(0, 0, 100, 100); - mSplitController.setActivityStackAttributesCalculator(params -> - new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build()); - final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( - mOverlayContainer1.getOverlayTag()); - - assertWithMessage("overlayContainer1 must be updated since the new overlay container" - + " is launched with the same tag and task") - .that(mSplitController.getAllNonFinishingOverlayContainers()) - .containsExactly(mOverlayContainer1, mOverlayContainer2); - - assertThat(overlayContainer).isEqualTo(mOverlayContainer1); - verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction), - eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds)); - } - - @Test public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() { createExistingOverlayContainers(); @@ -362,6 +344,43 @@ public class OverlayPresentationTest { } @Test + public void testCreateOrUpdateAlwaysOnTopOverlay_dismissMultipleOverlaysInTask() { + createExistingOverlayContainers(); + // Create another overlay in task. + final TaskFragmentContainer overlayContainer3 = + createTestOverlayContainer(TASK_ID, "test3"); + assertThat(mSplitController.getAllNonFinishingOverlayContainers()) + .containsExactly(mOverlayContainer1, mOverlayContainer2, overlayContainer3); + + final TaskFragmentContainer overlayContainer = + createOrUpdateAlwaysOnTopOverlay("test4"); + + assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed") + .that(mSplitController.getAllNonFinishingOverlayContainers()) + .containsExactly(mOverlayContainer2, overlayContainer); + } + + @Test + public void testCreateOrUpdateAlwaysOnTopOverlay_updateOverlay() { + createExistingOverlayContainers(); + // Create another overlay in task. + final TaskFragmentContainer alwaysOnTopOverlay = createTestOverlayContainer(TASK_ID, + "test3", true /* isVisible */, false /* associateLaunchingActivity */); + final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder() + .setRelativeBounds(new Rect(0, 0, 100, 100)).build(); + mSplitController.setActivityStackAttributesCalculator(params -> attrs); + + Mockito.clearInvocations(mSplitPresenter); + final TaskFragmentContainer overlayContainer = + createOrUpdateAlwaysOnTopOverlay(alwaysOnTopOverlay.getOverlayTag()); + + assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed") + .that(mSplitController.getAllNonFinishingOverlayContainers()) + .containsExactly(mOverlayContainer2, alwaysOnTopOverlay); + assertThat(overlayContainer).isEqualTo(alwaysOnTopOverlay); + } + + @Test public void testCreateOrUpdateOverlay_launchFromSplit_returnNull() { final Activity primaryActivity = createMockActivity(); final Activity secondaryActivity = createMockActivity(); @@ -966,6 +985,16 @@ public class OverlayPresentationTest { launchOptions, mIntent, activity); } + @Nullable + private TaskFragmentContainer createOrUpdateAlwaysOnTopOverlay( + @NonNull String tag) { + final Bundle launchOptions = new Bundle(); + launchOptions.putBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, false); + launchOptions.putString(KEY_OVERLAY_TAG, tag); + return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, + launchOptions, mIntent, createMockActivity()); + } + /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ @NonNull private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { @@ -1002,8 +1031,6 @@ public class OverlayPresentationTest { null /* launchingActivity */); } - // TODO(b/243518738): add more test coverage on overlay container without activity association - // once we have use cases. @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag, boolean isVisible, boolean associateLaunchingActivity, |