Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +3 −27 Original line number Diff line number Diff line Loading @@ -892,7 +892,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) != container) { // Do not resolve if the launched activity is not the top-most container (excludes // the pinned container) in the Task. // the pinned and overlay container) in the Task. return true; } Loading Loading @@ -1755,31 +1755,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen false /* shouldFinishDependent */, mPresenter, wct, this); } /** * Returns the topmost not finished container in Task of given task id. */ @GuardedBy("mLock") @Nullable TaskFragmentContainer getTopActiveContainer(int taskId) { final TaskContainer taskContainer = mTaskContainers.get(taskId); if (taskContainer == null) { return null; } final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); for (int i = containers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = containers.get(i); if (!container.isFinished() && (container.getRunningActivityCount() > 0 // We may be waiting for the top TaskFragment to become non-empty after // creation. In that case, we don't want to treat the TaskFragment below it as // top active, otherwise it may incorrectly launch placeholder on top of the // pending TaskFragment. || container.isWaitingActivityAppear())) { return container; } } return null; } /** * Updates the presentation of the container. If the container is part of the split or should * have a placeholder, it will also update the other part of the split. Loading Loading @@ -1968,7 +1943,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** 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()); final TaskFragmentContainer topContainer = container.getTaskContainer() .getTopNonFinishingTaskFragmentContainer(); if (container != topContainer) { // The container is not the top most. if (!container.isVisible()) { Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +9 −0 Original line number Diff line number Diff line Loading @@ -191,11 +191,20 @@ class TaskContainer { @Nullable TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) { return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */); } @Nullable TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin, boolean includeOverlay) { for (int i = mContainers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = mContainers.get(i); if (!includePin && isTaskFragmentContainerPinned(container)) { continue; } if (!includeOverlay && container.isOverlay()) { continue; } if (!container.isFinished()) { return container; } Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG; Loading Loading @@ -364,6 +365,54 @@ public class OverlayPresentationTest { assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag()); } @Test public void testGetTopNonFishingTaskFragmentContainerWithOverlay() { final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1"); // Add a SplitPinContainer, the overlay should be on top final Activity primaryActivity = createMockActivity(); final Activity secondaryActivity = createMockActivity(); final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer(primaryActivity); final TaskFragmentContainer secondaryContainer = createMockTaskFragmentContainer(secondaryActivity); final SplitPairRule splitPairRule = createSplitPairRuleBuilder( activityActivityPair -> true /* activityPairPredicate */, activityIntentPair -> true /* activityIntentPairPredicate */, parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity, secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes()); SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(), parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); mSplitController.pinTopActivityStack(TASK_ID, splitPinRule); final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID) .getSplitPinContainer().getSecondaryContainer(); // Add a normal container after the overlay, the overlay should still on top, // and the SplitPinContainer should also on top of the normal one. final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertThat(taskContainer.getTaskFragmentContainers()) .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer) .inOrder(); assertWithMessage("The pinned container must be returned excluding the overlay") .that(taskContainer.getTopNonFinishingTaskFragmentContainer()) .isEqualTo(topPinnedContainer); assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false)) .isEqualTo(container); assertWithMessage("The overlay container must be returned since it's always on top") .that(taskContainer.getTopNonFinishingTaskFragmentContainer( false /* includePin */, true /* includeOverlay */)) .isEqualTo(overlayContainer); } /** * A simplified version of {@link SplitController.ActivityStartMonitor * #createOrUpdateOverlayTaskFragmentIfNeeded} Loading @@ -375,6 +424,15 @@ public class OverlayPresentationTest { taskId, mIntent, mActivity); } /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ @NonNull private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { final TaskFragmentContainer container = mSplitController.newContainer(activity, activity.getTaskId()); setupTaskFragmentInfo(container, activity); return container; } @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) { TaskFragmentContainer overlayContainer = mSplitController.newContainer( Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +8 −53 Original line number Diff line number Diff line Loading @@ -48,13 +48,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; Loading Loading @@ -175,52 +174,6 @@ public class SplitControllerTest { mActivity = createMockActivity(); } @Test public void testGetTopActiveContainer() { final TaskContainer taskContainer = createTestTaskContainer(); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */); // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); taskContainer.addTaskFragmentContainer(tf2); // tf3 is finished so is not active. final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); doReturn(true).when(tf3).isFinished(); doReturn(false).when(tf3).isWaitingActivityAppear(); taskContainer.addTaskFragmentContainer(tf3); mSplitController.mTaskContainers.put(TASK_ID, taskContainer); assertWithMessage("Must return tf2 because tf3 is not active.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); taskContainer.removeTaskFragmentContainer(tf3); assertWithMessage("Must return tf2 because tf2 has running activity.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); taskContainer.removeTaskFragmentContainer(tf2); assertWithMessage("Must return tf because we are waiting for tf1 to appear.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); final TaskFragmentInfo info = mock(TaskFragmentInfo.class); doReturn(new ArrayList<>()).when(info).getActivities(); doReturn(true).when(info).isEmpty(); tf1.setInfo(mTransaction, info); assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after" + " creation.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); doReturn(false).when(info).isEmpty(); tf1.setInfo(mTransaction, info); assertWithMessage("Must return null because tf1 becomes empty.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull(); } @Test public void testOnTaskFragmentVanished() { final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); Loading Loading @@ -306,7 +259,9 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); verify(mSplitController, never()).getTopActiveContainer(TASK_ID); TaskContainer taskContainer = tf.getTaskContainer(); spyOn(taskContainer); verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer(); // Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called. doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf); Loading @@ -321,7 +276,7 @@ public class SplitControllerTest { doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); taskContainer = mSplitController.getTaskContainer(TASK_ID); taskContainer.addSplitContainer(splitContainer); // Add a mock SplitContainer on top of splitContainer final SplitContainer splitContainer2 = mock(SplitContainer.class); Loading Loading @@ -1569,9 +1524,9 @@ public class SplitControllerTest { addSplitTaskFragments(primaryActivity, thirdActivity); // Ensure another SplitContainer is added and the pinned TaskFragment still on top assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1); assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity() == secondaryActivity); assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1); assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer() .getTopNonFinishingActivity(), secondaryActivity); } /** Creates a mock activity in the organizer process. */ Loading Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +3 −27 Original line number Diff line number Diff line Loading @@ -892,7 +892,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */) != container) { // Do not resolve if the launched activity is not the top-most container (excludes // the pinned container) in the Task. // the pinned and overlay container) in the Task. return true; } Loading Loading @@ -1755,31 +1755,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen false /* shouldFinishDependent */, mPresenter, wct, this); } /** * Returns the topmost not finished container in Task of given task id. */ @GuardedBy("mLock") @Nullable TaskFragmentContainer getTopActiveContainer(int taskId) { final TaskContainer taskContainer = mTaskContainers.get(taskId); if (taskContainer == null) { return null; } final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); for (int i = containers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = containers.get(i); if (!container.isFinished() && (container.getRunningActivityCount() > 0 // We may be waiting for the top TaskFragment to become non-empty after // creation. In that case, we don't want to treat the TaskFragment below it as // top active, otherwise it may incorrectly launch placeholder on top of the // pending TaskFragment. || container.isWaitingActivityAppear())) { return container; } } return null; } /** * Updates the presentation of the container. If the container is part of the split or should * have a placeholder, it will also update the other part of the split. Loading Loading @@ -1968,7 +1943,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** 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()); final TaskFragmentContainer topContainer = container.getTaskContainer() .getTopNonFinishingTaskFragmentContainer(); if (container != topContainer) { // The container is not the top most. if (!container.isVisible()) { Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +9 −0 Original line number Diff line number Diff line Loading @@ -191,11 +191,20 @@ class TaskContainer { @Nullable TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) { return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */); } @Nullable TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin, boolean includeOverlay) { for (int i = mContainers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = mContainers.get(i); if (!includePin && isTaskFragmentContainerPinned(container)) { continue; } if (!includeOverlay && container.isOverlay()) { continue; } if (!container.isFinished()) { return container; } Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS; import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG; Loading Loading @@ -364,6 +365,54 @@ public class OverlayPresentationTest { assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag()); } @Test public void testGetTopNonFishingTaskFragmentContainerWithOverlay() { final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1"); // Add a SplitPinContainer, the overlay should be on top final Activity primaryActivity = createMockActivity(); final Activity secondaryActivity = createMockActivity(); final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer(primaryActivity); final TaskFragmentContainer secondaryContainer = createMockTaskFragmentContainer(secondaryActivity); final SplitPairRule splitPairRule = createSplitPairRuleBuilder( activityActivityPair -> true /* activityPairPredicate */, activityIntentPair -> true /* activityIntentPairPredicate */, parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity, secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes()); SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(), parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); mSplitController.pinTopActivityStack(TASK_ID, splitPinRule); final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID) .getSplitPinContainer().getSecondaryContainer(); // Add a normal container after the overlay, the overlay should still on top, // and the SplitPinContainer should also on top of the normal one. final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertThat(taskContainer.getTaskFragmentContainers()) .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer) .inOrder(); assertWithMessage("The pinned container must be returned excluding the overlay") .that(taskContainer.getTopNonFinishingTaskFragmentContainer()) .isEqualTo(topPinnedContainer); assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false)) .isEqualTo(container); assertWithMessage("The overlay container must be returned since it's always on top") .that(taskContainer.getTopNonFinishingTaskFragmentContainer( false /* includePin */, true /* includeOverlay */)) .isEqualTo(overlayContainer); } /** * A simplified version of {@link SplitController.ActivityStartMonitor * #createOrUpdateOverlayTaskFragmentIfNeeded} Loading @@ -375,6 +424,15 @@ public class OverlayPresentationTest { taskId, mIntent, mActivity); } /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ @NonNull private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { final TaskFragmentContainer container = mSplitController.newContainer(activity, activity.getTaskId()); setupTaskFragmentInfo(container, activity); return container; } @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) { TaskFragmentContainer overlayContainer = mSplitController.newContainer( Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +8 −53 Original line number Diff line number Diff line Loading @@ -48,13 +48,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; Loading Loading @@ -175,52 +174,6 @@ public class SplitControllerTest { mActivity = createMockActivity(); } @Test public void testGetTopActiveContainer() { final TaskContainer taskContainer = createTestTaskContainer(); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */); // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); taskContainer.addTaskFragmentContainer(tf2); // tf3 is finished so is not active. final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); doReturn(true).when(tf3).isFinished(); doReturn(false).when(tf3).isWaitingActivityAppear(); taskContainer.addTaskFragmentContainer(tf3); mSplitController.mTaskContainers.put(TASK_ID, taskContainer); assertWithMessage("Must return tf2 because tf3 is not active.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); taskContainer.removeTaskFragmentContainer(tf3); assertWithMessage("Must return tf2 because tf2 has running activity.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); taskContainer.removeTaskFragmentContainer(tf2); assertWithMessage("Must return tf because we are waiting for tf1 to appear.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); final TaskFragmentInfo info = mock(TaskFragmentInfo.class); doReturn(new ArrayList<>()).when(info).getActivities(); doReturn(true).when(info).isEmpty(); tf1.setInfo(mTransaction, info); assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after" + " creation.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); doReturn(false).when(info).isEmpty(); tf1.setInfo(mTransaction, info); assertWithMessage("Must return null because tf1 becomes empty.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull(); } @Test public void testOnTaskFragmentVanished() { final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); Loading Loading @@ -306,7 +259,9 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); verify(mSplitController, never()).getTopActiveContainer(TASK_ID); TaskContainer taskContainer = tf.getTaskContainer(); spyOn(taskContainer); verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer(); // Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called. doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf); Loading @@ -321,7 +276,7 @@ public class SplitControllerTest { doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); taskContainer = mSplitController.getTaskContainer(TASK_ID); taskContainer.addSplitContainer(splitContainer); // Add a mock SplitContainer on top of splitContainer final SplitContainer splitContainer2 = mock(SplitContainer.class); Loading Loading @@ -1569,9 +1524,9 @@ public class SplitControllerTest { addSplitTaskFragments(primaryActivity, thirdActivity); // Ensure another SplitContainer is added and the pinned TaskFragment still on top assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1); assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity() == secondaryActivity); assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1); assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer() .getTopNonFinishingActivity(), secondaryActivity); } /** Creates a mock activity in the organizer process. */ Loading