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

Commit ce9b6177 authored by Charles Chen's avatar Charles Chen Committed by Android (Google) Code Review
Browse files

Merge "Expand the launching container if necessary" into main

parents 1f5254c8 d060f8fb
Loading
Loading
Loading
Loading
+39 −34
Original line number Diff line number Diff line
@@ -1520,12 +1520,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
        // Skip resolving if started from an isolated navigated TaskFragmentContainer.
        if (launchingActivity != null) {
            final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
                    launchingActivity);
            if (taskFragmentContainer != null
                    && taskFragmentContainer.isIsolatedNavigationEnabled()) {
                // Skip resolving if started from an isolated navigated TaskFragmentContainer.
                return null;
            }
            if (isAssociatedWithOverlay(launchingActivity)) {
                // Skip resolving if the launching activity associated with an overlay.
                return null;
            }
        }
@@ -1659,9 +1663,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // is the first embedded TF in the task.
        final TaskContainer taskContainer = container.getTaskContainer();
        // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
        final Rect taskBounds = taskContainer.getBounds();
        final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
                getMinDimensions(intent), taskBounds);
                getMinDimensions(intent), container);
        final int windowingMode = taskContainer
                .getWindowingModeForTaskFragment(sanitizedBounds);
        mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
@@ -1722,17 +1725,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
        // Check pending appeared activity first because there can be a delay for the server
        // update.
        TaskFragmentContainer taskFragmentContainer =
                getContainer(container -> container.hasPendingAppearedActivity(activityToken));
        if (taskFragmentContainer != null) {
            return taskFragmentContainer;
        for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
            final TaskFragmentContainer container = mTaskContainers.valueAt(i)
                    .getContainerWithActivity(activityToken);
            if (container != null) {
                return container;
            }


        // Check appeared activity if there is no such pending appeared activity.
        return getContainer(container -> container.hasAppearedActivity(activityToken));
        }
        return null;
    }

    @GuardedBy("mLock")
@@ -2096,19 +2096,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (container == null) {
            return null;
        }
        final List<SplitContainer> splitContainers =
                container.getTaskContainer().getSplitContainers();
        if (splitContainers.isEmpty()) {
            return null;
        }
        for (int i = splitContainers.size() - 1; i >= 0; i--) {
            final SplitContainer splitContainer = splitContainers.get(i);
            if (container.equals(splitContainer.getSecondaryContainer())
                    || container.equals(splitContainer.getPrimaryContainer())) {
                return splitContainer;
            }
        }
        return null;
        return container.getTaskContainer().getActiveSplitForContainer(container);
    }

    /**
@@ -2158,6 +2146,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            return false;
        }

        if (isAssociatedWithOverlay(activity)) {
            // Can't launch the placeholder if the activity associates an overlay.
            return false;
        }

        final TaskFragmentContainer container = getContainerWithActivity(activity);
        if (container != null && !allowLaunchPlaceholder(container)) {
            // We don't allow activity in this TaskFragment to launch placeholder.
@@ -2197,6 +2190,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
    @GuardedBy("mLock")
    private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
        if (container.isOverlay()) {
            // Don't launch placeholder if the container is an overlay.
            return false;
        }

        final TaskFragmentContainer topContainer = container.getTaskContainer()
                .getTopNonFinishingTaskFragmentContainer();
        if (container != topContainer) {
@@ -2470,15 +2468,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
                    .getTaskFragmentContainers();
            for (int j = containers.size() - 1; j >= 0; j--) {
                final TaskFragmentContainer container = containers.get(j);
                if (predicate.test(container)) {
            final TaskFragmentContainer container = mTaskContainers.valueAt(i)
                    .getContainer(predicate);
            if (container != null) {
                return container;
            }
        }
        }
        return null;
    }

@@ -2641,6 +2636,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return overlayContainers;
    }

    @GuardedBy("mLock")
    private boolean isAssociatedWithOverlay(@NonNull Activity activity) {
        final TaskContainer taskContainer = getTaskContainer(getTaskId(activity));
        if (taskContainer == null) {
            return false;
        }
        return taskContainer.getContainer(c -> c.isOverlay() && !c.isFinished()
                && c.getAssociatedActivityToken() == activity.getActivityToken()) != null;
    }

    /**
     * Creates an overlay container or updates a visible overlay container if its
     * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()}
@@ -2734,7 +2739,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                }
                // Requesting an always-on-top overlay.
                if (!associateLaunchingActivity) {
                    if (overlayContainer.isAssociatedWithActivity()) {
                    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"
+26 −5
Original line number Diff line number Diff line
@@ -591,16 +591,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            @NonNull TaskFragmentContainer container,
            @NonNull ActivityStackAttributes attributes,
            @Nullable Size minDimensions) {
        final Rect taskBounds = container.getTaskContainer().getBounds();
        final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
                taskBounds);
                container);
        final boolean isFillParent = relativeBounds.isEmpty();
        // Note that we only set isolated navigation for overlay container without activity
        // association. Activity will be launched to an expanded container on top of the overlay
        // if the overlay is associated with an activity. Thus, an overlay with activity association
        // will never be isolated navigated.
        final boolean isIsolatedNavigated = container.isOverlay()
                && !container.isAssociatedWithActivity() && !isFillParent;
        final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent;
        final boolean dimOnTask = !isFillParent
                && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
                && Flags.fullscreenDimFlag();
@@ -624,7 +622,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     */
    @NonNull
    static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
                               @NonNull Rect taskBounds) {
                        @NonNull TaskFragmentContainer container) {
        if (bounds.isEmpty()) {
            // Don't need to check if the bounds follows the task bounds.
            return bounds;
@@ -633,10 +631,33 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            // Expand the bounds if the bounds are smaller than minimum dimensions.
            return new Rect();
        }
        final TaskContainer taskContainer = container.getTaskContainer();
        final Rect taskBounds = taskContainer.getBounds();
        if (!taskBounds.contains(bounds)) {
            // Expand the bounds if the bounds exceed the task bounds.
            return new Rect();
        }

        if (!container.isOverlay()) {
            // Stop here if the container is not an overlay.
            return bounds;
        }

        final IBinder associatedActivityToken = container.getAssociatedActivityToken();

        if (associatedActivityToken == null) {
            // Stop here if the container is an always-on-top overlay.
            return bounds;
        }

        // Expand the overlay with activity association if the associated activity is part of a
        // split, or we may need to handle three change transition together.
        final TaskFragmentContainer associatedContainer = taskContainer
                .getContainerWithActivity(associatedActivityToken);
        if (taskContainer.getActiveSplitForContainer(associatedContainer) != null) {
            return new Rect();
        }

        return bounds;
    }

+33 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
@@ -318,6 +319,38 @@ class TaskContainer {
        return null;
    }

    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
        return getContainer(container -> container.hasAppearedActivity(activityToken)
                || container.hasPendingAppearedActivity(activityToken));
    }

    @Nullable
    TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
        for (int i = mContainers.size() - 1; i >= 0; i--) {
            final TaskFragmentContainer container = mContainers.get(i);
            if (predicate.test(container)) {
                return container;
            }
        }
        return null;
    }

    @Nullable
    SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
        if (container == null) {
            return null;
        }
        for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
            final SplitContainer splitContainer = mSplitContainers.get(i);
            if (container.equals(splitContainer.getSecondaryContainer())
                    || container.equals(splitContainer.getPrimaryContainer())) {
                return splitContainer;
            }
        }
        return null;
    }

    /**
     * Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist.
     */
+8 −8
Original line number Diff line number Diff line
@@ -465,7 +465,7 @@ class TaskFragmentContainer {
            return;
        }
        // Early return if this container is not an overlay with activity association.
        if (!isOverlay() || !isAssociatedWithActivity()) {
        if (!isOverlayWithActivityAssociation()) {
            return;
        }
        if (mAssociatedActivityToken == activityToken) {
@@ -500,7 +500,7 @@ class TaskFragmentContainer {
        // sure the controller considers this container as the one containing the activity.
        // This is needed when the activity is added as pending appeared activity to one
        // TaskFragment while it is also an appeared activity in another.
        return mController.getContainerWithActivity(activityToken) == this;
        return mTaskContainer.getContainerWithActivity(activityToken) == this;
    }

    /** Whether this activity has appeared in the TaskFragment on the server side. */
@@ -1019,16 +1019,16 @@ class TaskFragmentContainer {
        return mAssociatedActivityToken;
    }

    boolean isAssociatedWithActivity() {
        return mAssociatedActivityToken != null;
    }

    /**
     * Returns {@code true} if the overlay container should be always on top, which should be
     * a non-fill-parent overlay without activity association.
     */
    boolean isAlwaysOnTopOverlay() {
        return isOverlay() && !isAssociatedWithActivity();
        return isOverlay() && mAssociatedActivityToken == null;
    }

    boolean isOverlayWithActivityAssociation() {
        return isOverlay() && mAssociatedActivityToken != null;
    }

    @Override
@@ -1050,7 +1050,7 @@ class TaskFragmentContainer {
                + " runningActivityCount=" + getRunningActivityCount()
                + " isFinished=" + mIsFinished
                + " overlayTag=" + mOverlayTag
                + " associatedActivity" + mAssociatedActivityToken
                + " associatedActivityToken=" + mAssociatedActivityToken
                + " lastRequestedBounds=" + mLastRequestedBounds
                + " pendingAppearedActivities=" + mPendingAppearedActivities
                + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+132 −11
Original line number Diff line number Diff line
@@ -21,11 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;

import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
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.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -85,6 +89,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
@@ -102,6 +107,9 @@ public class OverlayPresentationTest {
    @Rule
    public MockitoRule rule = MockitoJUnit.rule();

    private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
            new ComponentName("test", "placeholder"));

    @Rule
    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();

@@ -259,8 +267,7 @@ public class OverlayPresentationTest {
        mSplitController.setActivityStackAttributesCalculator(params ->
                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
                mOverlayContainer1.getOverlayTag(),
                mOverlayContainer1.getTopNonFinishingActivity());
                mOverlayContainer1.getOverlayTag());

        assertWithMessage("overlayContainer1 must be updated since the new overlay container"
                + " is launched with the same tag and task")
@@ -280,12 +287,13 @@ public class OverlayPresentationTest {
        mSplitController.setActivityStackAttributesCalculator(params ->
                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
                mOverlayContainer1.getOverlayTag(), mActivity);
                mOverlayContainer1.getOverlayTag(), createMockActivity());

        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
                + " is associated with different launching activity")
                .that(mSplitController.getAllNonFinishingOverlayContainers())
                .containsExactly(mOverlayContainer2, overlayContainer);
        assertThat(overlayContainer).isNotEqualTo(mOverlayContainer1);
    }

    @Test
@@ -323,7 +331,8 @@ public class OverlayPresentationTest {
    }

    private void createExistingOverlayContainers(boolean visible) {
        mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible);
        mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible,
                true /* associatedLaunchingActivity */, mActivity);
        mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
        List<TaskFragmentContainer> overlayContainers = mSplitController
                .getAllNonFinishingOverlayContainers();
@@ -335,17 +344,49 @@ public class OverlayPresentationTest {
        mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
                MinimumDimensionActivity.class));
        final Rect bounds = new Rect(0, 0, 100, 100);
        final TaskFragmentContainer overlayContainer =
                createTestOverlayContainer(TASK_ID, "test1");

        SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
                TASK_BOUNDS);
        assertThat(sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
                overlayContainer).isEmpty()).isTrue();
    }

    @Test
    public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
        final Rect bounds = new Rect(TASK_BOUNDS);
        bounds.offset(10, 10);
        final TaskFragmentContainer overlayContainer =
                createTestOverlayContainer(TASK_ID, "test1");

        assertThat(sanitizeBounds(bounds, null, overlayContainer)
                .isEmpty()).isTrue();
    }

    @Test
    public void testSanitizeBounds_visibleSplit_expandOverlay() {
        // Launch a visible split
        final Activity primaryActivity = createMockActivity();
        final Activity secondaryActivity = createMockActivity();
        final TaskFragmentContainer primaryContainer =
                createMockTaskFragmentContainer(primaryActivity, true /* isVisible */);
        final TaskFragmentContainer secondaryContainer =
                createMockTaskFragmentContainer(secondaryActivity, true /* isVisible */);

        final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
                activityActivityPair -> true /* activityPairPredicate */,
                activityIntentPair -> true  /* activityIntentPairPredicate */,
                parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
                .build();
        mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
                secondaryContainer, splitPairRule,  splitPairRule.getDefaultSplitAttributes());

        final Rect bounds = new Rect(0, 0, 100, 100);
        final TaskFragmentContainer overlayContainer =
                createTestOverlayContainer(TASK_ID, "test1", true /* isVisible */,
                        true /* associatedLaunchingActivity */, secondaryActivity);

        SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
        assertThat(sanitizeBounds(bounds, null, overlayContainer)
                .isEmpty()).isTrue();
    }

    @Test
@@ -701,6 +742,70 @@ public class OverlayPresentationTest {
                .doesNotContain(overlayWithAssociation);
    }

    @Test
    public void testLaunchPlaceholderIfNecessary_skipIfActivityAssociateOverlay() {
        setupPlaceholderRule(mActivity);
        createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
                true /* associateLaunchingActivity */, mActivity);


        mSplitController.mTransactionManager.startNewTransaction();
        assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
                false /* isOnCreated */)).isFalse();

        verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
    }

    @Test
    public void testLaunchPlaceholderIfNecessary_skipIfActivityInOverlay() {
        setupPlaceholderRule(mActivity);
        createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);

        mSplitController.mTransactionManager.startNewTransaction();
        assertThat(mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
                false /* isOnCreated */)).isFalse();

        verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
    }

    /** Setups a rule to launch placeholder for the given activity. */
    private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
        final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT,
                primaryActivity::equals, i -> false, w -> true)
                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                .build();
        mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
    }

    @Test
    public void testResolveStartActivityIntent_skipIfAssociateOverlay() {
        final Intent intent = new Intent();
        mSplitController.setEmbeddingRules(Collections.singleton(
                createSplitRule(mActivity, intent)));
        createTestOverlayContainer(TASK_ID, "test", true /* isVisible */,
                true /* associateLaunchingActivity */, mActivity);
        final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
                mTransaction, TASK_ID, intent, mActivity);

        assertThat(container).isNull();
        verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
                any());
    }

    @Test
    public void testResolveStartActivityIntent_skipIfLaunchingActivityInOverlay() {
        final Intent intent = new Intent();
        mSplitController.setEmbeddingRules(Collections.singleton(
                createSplitRule(mActivity, intent)));
        createOrUpdateOverlayTaskFragmentIfNeeded("test1", mActivity);
        final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
                mTransaction, TASK_ID, intent, mActivity);

        assertThat(container).isNull();
        verify(mSplitController, never()).resolveStartActivityIntentByRule(any(), anyInt(), any(),
                any());
    }

    /**
     * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
     */
@@ -726,9 +831,16 @@ public class OverlayPresentationTest {
    /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
    @NonNull
    private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
        return createMockTaskFragmentContainer(activity, false /* isVisible */);
    }

    /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
    @NonNull
    private TaskFragmentContainer createMockTaskFragmentContainer(
            @NonNull Activity activity, boolean isVisible) {
        final TaskFragmentContainer container = mSplitController.newContainer(activity,
                activity.getTaskId());
        setupTaskFragmentInfo(container, activity, false /* isVisible */);
        setupTaskFragmentInfo(container, activity, isVisible);
        return container;
    }

@@ -745,17 +857,26 @@ public class OverlayPresentationTest {
                true /* associateLaunchingActivity */);
    }

    @NonNull
    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
                boolean isVisible, boolean associateLaunchingActivity) {
        return createTestOverlayContainer(taskId, tag, isVisible, associateLaunchingActivity,
                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) {
        Activity activity = createMockActivity();
            boolean isVisible, boolean associateLaunchingActivity,
            @Nullable Activity launchingActivity) {
        final Activity activity = launchingActivity != null
                ? launchingActivity : createMockActivity();
        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
                null /* pendingAppearedActivity */, mIntent, activity, taskId,
                null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
                associateLaunchingActivity);
        setupTaskFragmentInfo(overlayContainer, activity, isVisible);
        setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible);
        return overlayContainer;
    }

Loading