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

Commit f5bc4291 authored by Charles Chen's avatar Charles Chen
Browse files

Implement ActivityStack operations

Implements new WM Extensions APIs for the overlay feature.
They are:
- #getActivityStackToken by tag
- #getParentContainerInfo by ActivityStack token
- #(un)registerActivityStackCallbacks

This CL also calls to ActivityStackAttributesCalculator
to update the overlay container bounds.

Test: atest OverlayPresentationTest
Bug: 295804279
Bug: 295803338
Bug: 270492365

Change-Id: Ib3fb27e74b87220da5057fd97594fed8fd0c6693
parent 219afd3b
Loading
Loading
Loading
Loading
+314 −114

File changed.

Preview size limit exceeded, changes collapsed.

+23 −10
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.PackageManager.MATCH_ALL;

import android.app.Activity;
@@ -187,7 +189,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
                splitAttributes);
        final int windowingMode = mController.getTaskContainer(taskId)
                .getWindowingModeForSplitTaskFragment(secondaryRelBounds);
                .getWindowingModeForTaskFragment(secondaryRelBounds);
        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
                primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode);
        updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
@@ -259,7 +261,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        if (container == null || container == containerToAvoid) {
            container = mController.newContainer(activity, taskId);
            final int windowingMode = mController.getTaskContainer(taskId)
                    .getWindowingModeForSplitTaskFragment(relBounds);
                    .getWindowingModeForTaskFragment(relBounds);
            final IBinder reparentActivityToken = activity.getActivityToken();
            createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
                    relBounds, windowingMode, reparentActivityToken);
@@ -268,7 +270,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        } else {
            resizeTaskFragmentIfRegistered(wct, container, relBounds);
            final int windowingMode = mController.getTaskContainer(taskId)
                    .getWindowingModeForSplitTaskFragment(relBounds);
                    .getWindowingModeForTaskFragment(relBounds);
            updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
        }
        updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
@@ -310,7 +312,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                // Pass in the primary container to make sure it is added right above the primary.
                primaryContainer);
        final TaskContainer taskContainer = mController.getTaskContainer(taskId);
        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
        final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
                primaryRelBounds);
        mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                rule, splitAttributes);
@@ -347,6 +349,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                && secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
                && !secondaryRelBounds.isEmpty();

        // TODO(b/243518738): remove usages of XXXIfRegistered.
        // If the task fragments are not registered yet, the positions will be updated after they
        // are created again.
        resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds);
@@ -357,7 +360,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            // When placeholder is shown in split, we should keep the focus on the primary.
            wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
        }
        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
        final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
                primaryRelBounds);
        updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
        updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
@@ -398,13 +401,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
     */
    void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
                                           @NonNull TaskFragmentContainer taskFragmentContainer,
                                           @NonNull TaskFragmentContainer container,
                                           boolean isolatedNavigationEnabled) {
        if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
        if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
            return;
        }
        taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
        setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
        container.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
        setTaskFragmentIsolatedNavigation(wct, container.getTaskFragmentToken(),
                isolatedNavigationEnabled);
    }

@@ -566,6 +569,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        super.setCompanionTaskFragment(wct, primary, secondary);
    }

    void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
        final Rect bounds = attributes.getRelativeBounds();

        resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
        updateWindowingMode(wct, container.getTaskFragmentToken(),
                bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
    }

    /**
     * Expands the split container if the current split bounds are smaller than the Activity or
     * Intent that is added to the container.
@@ -1086,7 +1098,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
    }

    @NonNull
    ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
    ParentContainerInfo createParentContainerInfoFromTaskProperties(
            @NonNull TaskProperties taskProperties) {
        final Configuration configuration = taskProperties.getConfiguration();
        final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
                .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+21 −1
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ class TaskContainer {
     *                              the pair of TaskFragments are stacked due to the limited space.
     */
    @WindowingMode
    int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
    int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) {
        // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
        // will be set to UNDEFINED which will then inherit the Task windowing mode.
        if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) {
@@ -443,6 +443,26 @@ class TaskContainer {
        return splitStates;
    }

    // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable.
    /**
     * Returns a list of currently active {@link ActivityStack activityStacks}.
     *
     * @return a list of {@link ActivityStack activityStacks} if all the containers are in
     * a stable state, or {@code null} otherwise.
     */
    @Nullable
    List<ActivityStack> getActivityStacksIfStable() {
        final List<ActivityStack> activityStacks = new ArrayList<>();
        for (TaskFragmentContainer container : mContainers) {
            final ActivityStack activityStack = container.toActivityStackIfStable();
            if (activityStack == null) {
                return null;
            }
            activityStacks.add(activityStack);
        }
        return activityStacks;
    }

    /** A wrapper class which contains the information of {@link TaskContainer} */
    static final class TaskProperties {
        private final int mDisplayId;
+18 −5
Original line number Diff line number Diff line
@@ -107,11 +107,11 @@ class TaskFragmentContainer {
    private final String mOverlayTag;

    /**
     * The launch options that was used to create this container. Must not be {@code null} for
     * {@link #isOverlay()} container.
     * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
     * for {@link #isOverlay()} container.
     */
    @Nullable
    private final Bundle mLaunchOptions;
    @NonNull
    private final Bundle mLaunchOptions = new Bundle();

    /** Indicates whether the container was cleaned up after the last activity was removed. */
    private boolean mIsFinished;
@@ -210,7 +210,9 @@ class TaskFragmentContainer {
        if (overlayTag != null) {
            Objects.requireNonNull(launchOptions);
        }
        mLaunchOptions = launchOptions;
        if (launchOptions != null) {
            mLaunchOptions.putAll(launchOptions);
        }

        if (pairedPrimaryContainer != null) {
            // The TaskFragment will be positioned right above the paired container.
@@ -927,6 +929,17 @@ class TaskFragmentContainer {
        return mOverlayTag;
    }

    /**
     * Returns the options that was used to launch this {@link TaskFragmentContainer}.
     * {@link Bundle#isEmpty()} means there's no launch option for this container.
     * <p>
     * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object.
     */
    @NonNull
    Bundle getLaunchOptions() {
        return mLaunchOptions;
    }

    @Override
    public String toString() {
        return toString(true /* includeContainersToFinishOnExit */);
+52 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package androidx.window.extensions.embedding;

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;
@@ -287,10 +288,10 @@ public class OverlayPresentationTest {
        createOrUpdateOverlayTaskFragmentIfNeeded("test");

        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
        verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
                WINDOWING_MODE_UNDEFINED);
        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
                false);
        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                .containsExactly(overlayContainer);
    }

    @Test
@@ -315,8 +316,10 @@ public class OverlayPresentationTest {
        createOrUpdateOverlayTaskFragmentIfNeeded("test");

        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
                false);
        verify(mSplitPresenter).updateWindowingMode(mTransaction,
                overlayToken, WINDOWING_MODE_UNDEFINED);
        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
                overlayContainer, false);
        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                .containsExactly(overlayContainer);
    }
@@ -425,6 +428,50 @@ public class OverlayPresentationTest {
                .that(taskContainer.getTaskFragmentContainers()).isEmpty();
    }

    @Test
    public void testUpdateActivityStackAttributes_nullParams_throwException() {
        assertThrows(NullPointerException.class, () ->
                mSplitController.updateActivityStackAttributes(null,
                        new ActivityStackAttributes.Builder().build()));

        assertThrows(NullPointerException.class, () ->
                mSplitController.updateActivityStackAttributes(new Binder(), null));

        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
    }

    @Test
    public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() {
        final TaskFragmentContainer container = mSplitController.newContainer(mActivity,
                mActivity.getTaskId());
        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
                new ActivityStackAttributes.Builder().build());

        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
    }

    @Test
    public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() {
        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);

        mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
                new ActivityStackAttributes.Builder().build());

        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
    }

    @Test
    public void testUpdateActivityStackAttributes() {
        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
        doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
        final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
        final IBinder token = container.getTaskFragmentToken();

        mSplitController.updateActivityStackAttributes(token, attrs);

        verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
    }

    /**
     * A simplified version of {@link SplitController.ActivityStartMonitor
     * #createOrUpdateOverlayTaskFragmentIfNeeded}
Loading