summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java144
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java73
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,