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

Commit ddcd8e5e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Reland "Allow activity in ActivityEmbedding to get Task windowing mode"" into tm-dev

parents b77a6de7 10e4a19e
Loading
Loading
Loading
Loading
+19 −5
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.app.Activity;
@@ -49,7 +48,8 @@ import java.util.concurrent.Executor;
class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {

    /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
    private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
    @VisibleForTesting
    final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();

    /**
     * Mapping from the client assigned unique token to the TaskFragment parent
@@ -120,25 +120,29 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
     * @param secondaryFragmentBounds   the initial bounds for the secondary TaskFragment
     * @param activityIntent    Intent to start the secondary Activity with.
     * @param activityOptions   ActivityOptions to start the secondary Activity with.
     * @param windowingMode     the windowing mode to set for the TaskFragments.
     */
    void startActivityToSide(@NonNull WindowContainerTransaction wct,
            @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
            @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
            @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
            @Nullable Bundle activityOptions, @NonNull SplitRule rule) {
            @Nullable Bundle activityOptions, @NonNull SplitRule rule,
            @WindowingMode int windowingMode) {
        final IBinder ownerToken = launchingActivity.getActivityToken();

        // Create or resize the launching TaskFragment.
        if (mFragmentInfos.containsKey(launchingFragmentToken)) {
            resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
            wct.setWindowingMode(mFragmentInfos.get(launchingFragmentToken).getToken(),
                    windowingMode);
        } else {
            createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
                    launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity);
                    launchingFragmentBounds, windowingMode, launchingActivity);
        }

        // Create a TaskFragment for the secondary activity.
        createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
                secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent,
                secondaryFragmentBounds, windowingMode, activityIntent,
                activityOptions);

        // Set adjacent to each other so that the containers below will be invisible.
@@ -153,6 +157,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
    void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
        resizeTaskFragment(wct, fragmentToken, new Rect());
        setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
        setWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
    }

    /**
@@ -255,6 +260,15 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
        wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
    }

    private void setWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
            @WindowingMode int windowingMode) {
        if (!mFragmentInfos.containsKey(fragmentToken)) {
            throw new IllegalArgumentException(
                    "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
        }
        wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
    }

    void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
        if (!mFragmentInfos.containsKey(fragmentToken)) {
            throw new IllegalArgumentException(
+11 −6
Original line number Diff line number Diff line
@@ -257,9 +257,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (taskContainer == null) {
            return;
        }
        final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration());
        final boolean wasInPip = taskContainer.isInPictureInPicture();
        final boolean isInPIp = isInPictureInPicture(config);
        taskContainer.setConfiguration(config);
        taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode());

        // We need to check the animation override when enter/exit PIP or has bounds changed.
        boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
@@ -278,8 +278,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * bounds is large enough for at least one split rule.
     */
    private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
        if (!taskContainer.isTaskBoundsInitialized()) {
            // We don't know about the Task bounds yet.
        if (!taskContainer.isTaskBoundsInitialized()
                || !taskContainer.isWindowingModeInitialized()) {
            // We don't know about the Task bounds/windowingMode yet.
            return;
        }

@@ -293,7 +294,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

    private boolean supportSplit(@NonNull TaskContainer taskContainer) {
        // No split inside PIP.
        if (isInPictureInPicture(taskContainer.getConfiguration())) {
        if (taskContainer.isInPictureInPicture()) {
            return false;
        }
        // Check if the parent container bounds can support any split rule.
@@ -461,8 +462,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            if (!taskContainer.setTaskBounds(taskBounds)) {
                Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
            }
            updateAnimationOverride(taskContainer);
        }
        if (!taskContainer.isWindowingModeInitialized()) {
            taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration()
                    .windowConfiguration.getWindowingMode());
        }
        updateAnimationOverride(taskContainer);
        return container;
    }

+36 −9
Original line number Diff line number Diff line
@@ -16,10 +16,11 @@

package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@@ -111,13 +112,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                primaryActivity, primaryRectBounds, null);

        // Create new empty task fragment
        final int taskId = primaryContainer.getTaskId();
        final TaskFragmentContainer secondaryContainer = mController.newContainer(
                null /* activity */, primaryActivity, primaryContainer.getTaskId());
                null /* activity */, primaryActivity, taskId);
        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
                rule, isLtr(primaryActivity, rule));
        final int windowingMode = mController.getTaskContainer(taskId)
                .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
                primaryActivity.getActivityToken(), secondaryRectBounds,
                WINDOWING_MODE_MULTI_WINDOW);
                windowingMode);
        secondaryContainer.setLastRequestedBounds(secondaryRectBounds);

        // Set adjacent to each other so that the containers below will be invisible.
@@ -173,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {

        final WindowContainerTransaction wct = new WindowContainerTransaction();
        createTaskFragment(wct, newContainer.getTaskFragmentToken(),
                launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW);
                launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);

        applyTransaction(wct);
        return newContainer;
@@ -189,15 +193,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
        TaskFragmentContainer container = mController.getContainerWithActivity(
                activity.getActivityToken());
        final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
        if (container == null || container == containerToAvoid) {
            container = mController.newContainer(activity, activity.getTaskId());

            container = mController.newContainer(activity, taskId);
            final int windowingMode = mController.getTaskContainer(taskId)
                    .getWindowingModeForSplitTaskFragment(bounds);
            final TaskFragmentCreationParams fragmentOptions =
                    createFragmentOptions(
                            container.getTaskFragmentToken(),
                            activity.getActivityToken(),
                            bounds,
                            WINDOWING_MODE_MULTI_WINDOW);
                            windowingMode);
            wct.createTaskFragment(fragmentOptions);

            wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
@@ -206,6 +212,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            container.setLastRequestedBounds(bounds);
        } else {
            resizeTaskFragmentIfRegistered(wct, container, bounds);
            final int windowingMode = mController.getTaskContainer(taskId)
                    .getWindowingModeForSplitTaskFragment(bounds);
            updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
        }

        return container;
@@ -237,14 +246,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                    launchingActivity.getTaskId());
        }

        final int taskId = primaryContainer.getTaskId();
        TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */,
                launchingActivity, primaryContainer.getTaskId());
                launchingActivity, taskId);
        final int windowingMode = mController.getTaskContainer(taskId)
                .getWindowingModeForSplitTaskFragment(primaryRectBounds);
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                rule);
        startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
                launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
                activityIntent, activityOptions, rule);
                activityIntent, activityOptions, rule, windowingMode);
        if (isPlaceholder) {
            // When placeholder is launched in split, we should keep the focus on the primary.
            wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -292,6 +304,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            // When placeholder is shown in split, we should keep the focus on the primary.
            wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
        }
        final TaskContainer taskContainer = mController.getTaskContainer(
                updatedContainer.getTaskId());
        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
                primaryRectBounds);
        updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
        updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
    }

    private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -323,6 +341,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
    }

    private void updateTaskFragmentWindowingModeIfRegistered(
            @NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container,
            @WindowingMode int windowingMode) {
        if (container.getInfo() != null) {
            wct.setWindowingMode(container.getInfo().getToken(), windowingMode);
        }
    }

    @Override
    void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
            @Nullable Rect bounds) {
+43 −9
Original line number Diff line number Diff line
@@ -16,9 +16,14 @@

package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
@@ -37,9 +42,9 @@ class TaskContainer {
    /** Available window bounds of this Task. */
    private final Rect mTaskBounds = new Rect();

    /** Configuration of the Task. */
    @Nullable
    private Configuration mConfiguration;
    /** Windowing mode of this Task. */
    @WindowingMode
    private int mWindowingMode = WINDOWING_MODE_UNDEFINED;

    /** Active TaskFragments in this Task. */
    final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -81,13 +86,42 @@ class TaskContainer {
        return !mTaskBounds.isEmpty();
    }

    @Nullable
    Configuration getConfiguration() {
        return mConfiguration;
    void setWindowingMode(int windowingMode) {
        mWindowingMode = windowingMode;
    }

    /** Whether the Task windowing mode has been initialized. */
    boolean isWindowingModeInitialized() {
        return mWindowingMode != WINDOWING_MODE_UNDEFINED;
    }

    /**
     * Returns the windowing mode for the TaskFragments below this Task, which should be split with
     * other TaskFragments.
     *
     * @param taskFragmentBounds    Requested bounds for the TaskFragment. It will be empty when
     *                              the pair of TaskFragments are stacked due to the limited space.
     */
    @WindowingMode
    int getWindowingModeForSplitTaskFragment(@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()) {
            return WINDOWING_MODE_UNDEFINED;
        }
        // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen.
        // However, when the Task is in other multi windowing mode, such as Freeform, we need to
        // have the activity windowing mode to match the Task, otherwise things like
        // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
        // Task windowing mode if the Task is in multi window.
        // TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
        return WindowConfiguration.inMultiWindowMode(mWindowingMode)
                ? mWindowingMode
                : WINDOWING_MODE_MULTI_WINDOW;
    }

    void setConfiguration(@Nullable Configuration configuration) {
        mConfiguration = configuration;
    boolean isInPictureInPicture() {
        return mWindowingMode == WINDOWING_MODE_PINNED;
    }

    /** Whether there is any {@link TaskFragmentContainer} below this Task. */
+32 −0
Original line number Diff line number Diff line
@@ -16,15 +16,23 @@

package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;

import android.content.res.Configuration;
import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -35,6 +43,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;

/**
 * Test class for {@link JetpackTaskFragmentOrganizer}.
 *
@@ -47,6 +57,8 @@ import org.mockito.MockitoAnnotations;
public class JetpackTaskFragmentOrganizerTest {
    private static final int TASK_ID = 10;

    @Mock
    private WindowContainerTransaction mTransaction;
    @Mock
    private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
    private JetpackTaskFragmentOrganizer mOrganizer;
@@ -91,4 +103,24 @@ public class JetpackTaskFragmentOrganizerTest {

        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
    }

    @Test
    public void testExpandTaskFragment() {
        final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID);
        final TaskFragmentInfo info = createMockInfo(container);
        mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
        container.setInfo(info);

        mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());

        verify(mTransaction).setWindowingMode(container.getInfo().getToken(),
                WINDOWING_MODE_UNDEFINED);
    }

    private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
        return new TaskFragmentInfo(container.getTaskFragmentToken(),
                mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
                false /* isVisible */, new ArrayList<>(), new Point(),
                false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */);
    }
}
Loading