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

Commit b3410923 authored by Chris Li's avatar Chris Li
Browse files

Group ActivityEmbedding split by Task id

When the app is the host of multiple Tasks, there can be split pairs in
each Task.

Before, we treated all split as in one Task, which can sometimes cause
issue. Now, we group them by Task id, so that they won't affect each
other.

Bug: 207720388
Test: test with Settings deep link
Change-Id: Iaa9865461d984df10c6db237a87ee0903b70e7da
parent 2907ec82
Loading
Loading
Loading
Loading
+116 −52
Original line number Original line Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction;


@@ -58,14 +59,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen


    // Currently applied split configuration.
    // Currently applied split configuration.
    private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
    private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
    private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
    /**
    private final List<SplitContainer> mSplitContainers = new ArrayList<>();
     * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
     * below it.
     * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
     * organizer.
     */
    private final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();


    // Callback to Jetpack to notify about changes to split states.
    // Callback to Jetpack to notify about changes to split states.
    private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
    private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
    private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
    private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();


    // We currently only support split activity embedding within the one root Task.
    // We currently only support split activity embedding within the one root Task.
    // TODO(b/207720388): move to TaskContainer
    private final Rect mParentBounds = new Rect();
    private final Rect mParentBounds = new Rect();


    public SplitController() {
    public SplitController() {
@@ -244,7 +251,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
                mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
            } else {
            } else {
                // Put activity into a new expanded container
                // Put activity into a new expanded container
                final TaskFragmentContainer newContainer = newContainer(launchedActivity);
                final TaskFragmentContainer newContainer = newContainer(launchedActivity,
                        launchedActivity.getTaskId());
                mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
                mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
                        launchedActivity);
                        launchedActivity);
            }
            }
@@ -327,12 +335,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
     */
    @Nullable
    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
        for (TaskFragmentContainer container : mContainers) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            for (TaskFragmentContainer container : containers) {
                if (container.hasActivity(activityToken)) {
                if (container.hasActivity(activityToken)) {
                    return container;
                    return container;
                }
                }
            }
            }

        }
        return null;
        return null;
    }
    }


@@ -340,9 +350,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Creates and registers a new organized container with an optional activity that will be
     * Creates and registers a new organized container with an optional activity that will be
     * re-parented to it in a WCT.
     * re-parented to it in a WCT.
     */
     */
    TaskFragmentContainer newContainer(@Nullable Activity activity) {
    TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
        TaskFragmentContainer container = new TaskFragmentContainer(activity);
        final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
        mContainers.add(container);
        if (!mTaskContainers.contains(taskId)) {
            mTaskContainers.put(taskId, new TaskContainer());
        }
        mTaskContainers.get(taskId).mContainers.add(container);
        return container;
        return container;
    }
    }


@@ -354,13 +367,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
            @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
            @NonNull TaskFragmentContainer secondaryContainer,
            @NonNull TaskFragmentContainer secondaryContainer,
            @NonNull SplitRule splitRule) {
            @NonNull SplitRule splitRule) {
        SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
        final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
                secondaryContainer, splitRule);
                secondaryContainer, splitRule);
        // Remove container later to prevent pinning escaping toast showing in lock task mode.
        // Remove container later to prevent pinning escaping toast showing in lock task mode.
        if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
        if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
            removeExistingSecondaryContainers(wct, primaryContainer);
            removeExistingSecondaryContainers(wct, primaryContainer);
        }
        }
        mSplitContainers.add(splitContainer);
        mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer);
    }
    }


    /**
    /**
@@ -368,15 +381,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
     */
    void removeContainer(@NonNull TaskFragmentContainer container) {
    void removeContainer(@NonNull TaskFragmentContainer container) {
        // Remove all split containers that included this one
        // Remove all split containers that included this one
        mContainers.remove(container);
        final int taskId = container.getTaskId();
        List<SplitContainer> containersToRemove = new ArrayList<>();
        final TaskContainer taskContainer = mTaskContainers.get(taskId);
        for (SplitContainer splitContainer : mSplitContainers) {
        if (taskContainer == null) {
            return;
        }
        taskContainer.mContainers.remove(container);
        if (taskContainer.mContainers.isEmpty()) {
            mTaskContainers.remove(taskId);
            // No more TaskFragment in this Task, so no need to check split container.
            return;
        }

        final List<SplitContainer> containersToRemove = new ArrayList<>();
        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
            if (container.equals(splitContainer.getSecondaryContainer())
            if (container.equals(splitContainer.getSecondaryContainer())
                    || container.equals(splitContainer.getPrimaryContainer())) {
                    || container.equals(splitContainer.getPrimaryContainer())) {
                containersToRemove.add(splitContainer);
                containersToRemove.add(splitContainer);
            }
            }
        }
        }
        mSplitContainers.removeAll(containersToRemove);
        taskContainer.mSplitContainers.removeAll(containersToRemove);
    }
    }


    /**
    /**
@@ -399,12 +423,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }
    }


    /**
    /**
     * Returns the topmost not finished container.
     * Returns the topmost not finished container in Task of given task id.
     */
     */
    @Nullable
    @Nullable
    TaskFragmentContainer getTopActiveContainer() {
    TaskFragmentContainer getTopActiveContainer(int taskId) {
        for (int i = mContainers.size() - 1; i >= 0; i--) {
        final TaskContainer taskContainer = mTaskContainers.get(taskId);
            TaskFragmentContainer container = mContainers.get(i);
        if (taskContainer == null) {
            return null;
        }
        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
            final TaskFragmentContainer container = taskContainer.mContainers.get(i);
            if (!container.isFinished() && container.getRunningActivityCount() > 0) {
            if (!container.isFinished() && container.getRunningActivityCount() > 0) {
                return container;
                return container;
            }
            }
@@ -434,7 +462,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (splitContainer == null) {
        if (splitContainer == null) {
            return;
            return;
        }
        }
        if (splitContainer != mSplitContainers.get(mSplitContainers.size() - 1)) {
        final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
                .mSplitContainers;
        if (splitContainers == null
                || splitContainer != splitContainers.get(splitContainers.size() - 1)) {
            // Skip position update - it isn't the topmost split.
            // Skip position update - it isn't the topmost split.
            return;
            return;
        }
        }
@@ -455,8 +486,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
     */
    @Nullable
    @Nullable
    private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
    private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
        for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
        final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
            SplitContainer splitContainer = mSplitContainers.get(i);
                .mSplitContainers;
        if (splitContainers == null) {
            return null;
        }
        for (int i = splitContainers.size() - 1; i >= 0; i--) {
            final SplitContainer splitContainer = splitContainers.get(i);
            if (container.equals(splitContainer.getSecondaryContainer())
            if (container.equals(splitContainer.getSecondaryContainer())
                    || container.equals(splitContainer.getPrimaryContainer())) {
                    || container.equals(splitContainer.getPrimaryContainer())) {
                return splitContainer;
                return splitContainer;
@@ -473,8 +509,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    private SplitContainer getActiveSplitForContainers(
    private SplitContainer getActiveSplitForContainers(
            @NonNull TaskFragmentContainer firstContainer,
            @NonNull TaskFragmentContainer firstContainer,
            @NonNull TaskFragmentContainer secondContainer) {
            @NonNull TaskFragmentContainer secondContainer) {
        for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
        final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId())
            SplitContainer splitContainer = mSplitContainers.get(i);
                .mSplitContainers;
        if (splitContainers == null) {
            return null;
        }
        for (int i = splitContainers.size() - 1; i >= 0; i--) {
            final SplitContainer splitContainer = splitContainers.get(i);
            final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
            final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
            final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
            final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
            if ((firstContainer == secondary && secondContainer == primary)
            if ((firstContainer == secondary && secondContainer == primary)
@@ -501,7 +542,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        final  TaskFragmentContainer container = getContainerWithActivity(
        final  TaskFragmentContainer container = getContainerWithActivity(
                activity.getActivityToken());
                activity.getActivityToken());
        // Don't launch placeholder if the container is occluded.
        // Don't launch placeholder if the container is occluded.
        if (container != null && container != getTopActiveContainer()) {
        if (container != null && container != getTopActiveContainer(container.getTaskId())) {
            return false;
            return false;
        }
        }


@@ -588,17 +629,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @Nullable
    @Nullable
    private List<SplitInfo> getActiveSplitStates() {
    private List<SplitInfo> getActiveSplitStates() {
        List<SplitInfo> splitStates = new ArrayList<>();
        List<SplitInfo> splitStates = new ArrayList<>();
        for (SplitContainer container : mSplitContainers) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
                    .mSplitContainers;
            for (SplitContainer container : splitContainers) {
                if (container.getPrimaryContainer().isEmpty()
                if (container.getPrimaryContainer().isEmpty()
                        || container.getSecondaryContainer().isEmpty()) {
                        || container.getSecondaryContainer().isEmpty()) {
                // We are in an intermediate state because either the split container is about to be
                    // We are in an intermediate state because either the split container is about
                // removed or the primary or secondary container are about to receive an activity.
                    // to be removed or the primary or secondary container are about to receive an
                    // activity.
                    return null;
                    return null;
                }
                }
            ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
                final ActivityStack primaryContainer = container.getPrimaryContainer()
            ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
                        .toActivityStack();
            SplitInfo splitState = new SplitInfo(primaryContainer,
                final ActivityStack secondaryContainer = container.getSecondaryContainer()
                    secondaryContainer,
                        .toActivityStack();
                final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
                        // Splits that are not showing side-by-side are reported as having 0 split
                        // Splits that are not showing side-by-side are reported as having 0 split
                        // ratio, since by definition in the API the primary container occupies no
                        // ratio, since by definition in the API the primary container occupies no
                        // width of the split when covered by the secondary.
                        // width of the split when covered by the secondary.
@@ -607,6 +653,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                                : 0.0f);
                                : 0.0f);
                splitStates.add(splitState);
                splitStates.add(splitState);
            }
            }
        }
        return splitStates;
        return splitStates;
    }
    }


@@ -615,13 +662,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * the client.
     * the client.
     */
     */
    private boolean allActivitiesCreated() {
    private boolean allActivitiesCreated() {
        for (TaskFragmentContainer container : mContainers) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            for (TaskFragmentContainer container : containers) {
                if (container.getInfo() == null
                if (container.getInfo() == null
                        || container.getInfo().getActivities().size()
                        || container.getInfo().getActivities().size()
                        != container.collectActivities().size()) {
                        != container.collectActivities().size()) {
                    return false;
                    return false;
                }
                }
            }
            }
        }
        return true;
        return true;
    }
    }


@@ -633,7 +683,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (container == null) {
        if (container == null) {
            return false;
            return false;
        }
        }
        for (SplitContainer splitContainer : mSplitContainers) {
        final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
                .mSplitContainers;
        if (splitContainers == null) {
            return true;
        }
        for (SplitContainer splitContainer : splitContainers) {
            if (container.equals(splitContainer.getPrimaryContainer())
            if (container.equals(splitContainer.getPrimaryContainer())
                    || container.equals(splitContainer.getSecondaryContainer())) {
                    || container.equals(splitContainer.getSecondaryContainer())) {
                return false;
                return false;
@@ -684,11 +739,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen


    @Nullable
    @Nullable
    TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
    TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
        for (TaskFragmentContainer container : mContainers) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            for (TaskFragmentContainer container : containers) {
                if (container.getTaskFragmentToken().equals(fragmentToken)) {
                if (container.getTaskFragmentToken().equals(fragmentToken)) {
                    return container;
                    return container;
                }
                }
            }
            }
        }
        return null;
        return null;
    }
    }


@@ -969,4 +1027,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // Not reuse if it needs to destroy the existing.
        // Not reuse if it needs to destroy the existing.
        return !pairRule.shouldClearTop();
        return !pairRule.shouldClearTop();
    }
    }

    /** Represents TaskFragments and split pairs below a Task. */
    private static class TaskContainer {
        final List<TaskFragmentContainer> mContainers = new ArrayList<>();
        final List<SplitContainer> mSplitContainers = new ArrayList<>();
    }
}
}
+11 −6
Original line number Original line Diff line number Diff line
@@ -80,7 +80,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {


        container.finish(shouldFinishDependent, this, wct, mController);
        container.finish(shouldFinishDependent, this, wct, mController);


        final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer();
        final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
                container.getTaskId());
        if (newTopContainer != null) {
        if (newTopContainer != null) {
            mController.updateContainer(wct, newTopContainer);
            mController.updateContainer(wct, newTopContainer);
        }
        }
@@ -103,7 +104,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
                primaryActivity, primaryRectBounds, null);
                primaryActivity, primaryRectBounds, null);


        // Create new empty task fragment
        // Create new empty task fragment
        final TaskFragmentContainer secondaryContainer = mController.newContainer(null);
        final TaskFragmentContainer secondaryContainer = mController.newContainer(null,
                primaryContainer.getTaskId());
        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
                rule, isLtr(primaryActivity, rule));
                rule, isLtr(primaryActivity, rule));
        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -159,7 +161,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
     * Creates a new expanded container.
     * Creates a new expanded container.
     */
     */
    TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
    TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
        final TaskFragmentContainer newContainer = mController.newContainer(null);
        final TaskFragmentContainer newContainer = mController.newContainer(null,
                launchingActivity.getTaskId());


        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        createTaskFragment(wct, newContainer.getTaskFragmentToken(),
        createTaskFragment(wct, newContainer.getTaskFragmentToken(),
@@ -180,7 +183,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        TaskFragmentContainer container = mController.getContainerWithActivity(
        TaskFragmentContainer container = mController.getContainerWithActivity(
                activity.getActivityToken());
                activity.getActivityToken());
        if (container == null || container == containerToAvoid) {
        if (container == null || container == containerToAvoid) {
            container = mController.newContainer(activity);
            container = mController.newContainer(activity, activity.getTaskId());


            final TaskFragmentCreationParams fragmentOptions =
            final TaskFragmentCreationParams fragmentOptions =
                    createFragmentOptions(
                    createFragmentOptions(
@@ -222,10 +225,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
        TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
                launchingActivity.getActivityToken());
                launchingActivity.getActivityToken());
        if (primaryContainer == null) {
        if (primaryContainer == null) {
            primaryContainer = mController.newContainer(launchingActivity);
            primaryContainer = mController.newContainer(launchingActivity,
                    launchingActivity.getTaskId());
        }
        }


        TaskFragmentContainer secondaryContainer = mController.newContainer(null);
        TaskFragmentContainer secondaryContainer = mController.newContainer(null,
                primaryContainer.getTaskId());
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
        mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                rule);
                rule);
+15 −1
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package androidx.window.extensions.embedding;
package androidx.window.extensions.embedding;


import static android.app.ActivityTaskManager.INVALID_TASK_ID;

import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Activity;
@@ -41,6 +43,9 @@ class TaskFragmentContainer {
    @NonNull
    @NonNull
    private final IBinder mToken;
    private final IBinder mToken;


    /** Parent leaf Task id. */
    private final int mTaskId;

    /**
    /**
     * Server-provided task fragment information.
     * Server-provided task fragment information.
     */
     */
@@ -71,8 +76,12 @@ class TaskFragmentContainer {
     * Creates a container with an existing activity that will be re-parented to it in a window
     * Creates a container with an existing activity that will be re-parented to it in a window
     * container transaction.
     * container transaction.
     */
     */
    TaskFragmentContainer(@Nullable Activity activity) {
    TaskFragmentContainer(@Nullable Activity activity, int taskId) {
        mToken = new Binder("TaskFragmentContainer");
        mToken = new Binder("TaskFragmentContainer");
        if (taskId == INVALID_TASK_ID) {
            throw new IllegalArgumentException("Invalid Task id");
        }
        mTaskId = taskId;
        if (activity != null) {
        if (activity != null) {
            addPendingAppearedActivity(activity);
            addPendingAppearedActivity(activity);
        }
        }
@@ -275,6 +284,11 @@ class TaskFragmentContainer {
        }
        }
    }
    }


    /** Gets the parent leaf Task id. */
    int getTaskId() {
        return mTaskId;
    }

    @Override
    @Override
    public String toString() {
    public String toString() {
        return toString(true /* includeContainersToFinishOnExit */);
        return toString(true /* includeContainersToFinishOnExit */);