Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ebf80c2 authored by Charles Chen's avatar Charles Chen
Browse files

Exclude overlay when reporting top non-finishing container by default

The overlay container is always on the top, and don't split with other
containers. Thus we need the topmost non-overlay container for most
scenario.

This CL also consolidates the usages of #getTopActiveContainer to
TaskContainer#getTopNonfinishingTaskFragmentContainer since it's
the only usage and we usually finish a container after it becomes
empty.

Bug: 243518738
Test: atest OverlayPresentationTest

Change-Id: I1c22a372e3f2c1f31b6ac13c79ddc387b8c253b0
parent 0c0eefe2
Loading
Loading
Loading
Loading
+3 −27
Original line number Original line Diff line number Diff line
@@ -892,7 +892,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                        != container) {
                        != container) {
            // Do not resolve if the launched activity is not the top-most container (excludes
            // 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;
            return true;
        }
        }


@@ -1755,31 +1755,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                false /* shouldFinishDependent */, mPresenter, wct, this);
                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
     * 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.
     * have a placeholder, it will also update the other part of the split.
@@ -1968,7 +1943,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    /** Whether or not to allow activity in this container to launch placeholder. */
    /** Whether or not to allow activity in this container to launch placeholder. */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
    private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
        final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
        final TaskFragmentContainer topContainer = container.getTaskContainer()
                .getTopNonFinishingTaskFragmentContainer();
        if (container != topContainer) {
        if (container != topContainer) {
            // The container is not the top most.
            // The container is not the top most.
            if (!container.isVisible()) {
            if (!container.isVisible()) {
+9 −0
Original line number Original line Diff line number Diff line
@@ -191,11 +191,20 @@ class TaskContainer {


    @Nullable
    @Nullable
    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
    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--) {
        for (int i = mContainers.size() - 1; i >= 0; i--) {
            final TaskFragmentContainer container = mContainers.get(i);
            final TaskFragmentContainer container = mContainers.get(i);
            if (!includePin && isTaskFragmentContainerPinned(container)) {
            if (!includePin && isTaskFragmentContainerPinned(container)) {
                continue;
                continue;
            }
            }
            if (!includeOverlay && container.isOverlay()) {
                continue;
            }
            if (!container.isFinished()) {
            if (!container.isFinished()) {
                return container;
                return container;
            }
            }
+58 −0
Original line number Original line Diff line number Diff line
@@ -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_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
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;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
@@ -364,6 +365,54 @@ public class OverlayPresentationTest {
        assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
        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
     * A simplified version of {@link SplitController.ActivityStartMonitor
     * #createOrUpdateOverlayTaskFragmentIfNeeded}
     * #createOrUpdateOverlayTaskFragmentIfNeeded}
@@ -375,6 +424,15 @@ public class OverlayPresentationTest {
                taskId, mIntent, mActivity);
                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
    @NonNull
    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+8 −53
Original line number Original line Diff line number Diff line
@@ -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.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
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.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
@@ -175,52 +174,6 @@ public class SplitControllerTest {
        mActivity = createMockActivity();
        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
    @Test
    public void testOnTaskFragmentVanished() {
    public void testOnTaskFragmentVanished() {
        final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
        final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
@@ -306,7 +259,9 @@ public class SplitControllerTest {


        mSplitController.updateContainer(mTransaction, tf);
        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.
        // Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
        doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
        doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -321,7 +276,7 @@ public class SplitControllerTest {
        doReturn(tf).when(splitContainer).getSecondaryContainer();
        doReturn(tf).when(splitContainer).getSecondaryContainer();
        doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
        doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
        doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
        doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
        taskContainer = mSplitController.getTaskContainer(TASK_ID);
        taskContainer.addSplitContainer(splitContainer);
        taskContainer.addSplitContainer(splitContainer);
        // Add a mock SplitContainer on top of splitContainer
        // Add a mock SplitContainer on top of splitContainer
        final SplitContainer splitContainer2 = mock(SplitContainer.class);
        final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -1569,9 +1524,9 @@ public class SplitControllerTest {
        addSplitTaskFragments(primaryActivity, thirdActivity);
        addSplitTaskFragments(primaryActivity, thirdActivity);


        // Ensure another SplitContainer is added and the pinned TaskFragment still on top
        // Ensure another SplitContainer is added and the pinned TaskFragment still on top
        assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
        assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
        assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
        assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
                == secondaryActivity);
                .getTopNonFinishingActivity(), secondaryActivity);
    }
    }


    /** Creates a mock activity in the organizer process. */
    /** Creates a mock activity in the organizer process. */