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

Commit 9b36cc92 authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Handle onActivityReparentToTask for activity in other process" into tm-dev

parents e41f27bf 238e0b32
Loading
Loading
Loading
Loading
+216 −158
Original line number Diff line number Diff line
@@ -234,13 +234,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
            @NonNull IBinder activityToken) {
        // If the activity belongs to the current app process, we treat it as a new activity launch.
        final Activity activity = ActivityThread.currentActivityThread().getActivity(activityToken);
        final Activity activity = getActivity(activityToken);
        if (activity != null) {
            onActivityCreated(activity);
            updateCallbackIfNecessary();
            return;
        }
        // TODO: handle for activity in other process.

        final TaskContainer taskContainer = getTaskContainer(taskId);
        if (taskContainer == null || taskContainer.isInPictureInPicture()) {
            // We don't embed activity when it is in PIP.
            return;
        }

        // If the activity belongs to a different app process, we treat it as starting new intent,
        // since both actions might result in a new activity that should appear in an organized
        // TaskFragment.
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
                activityIntent, null /* launchingActivity */);
        if (targetContainer == null) {
            // When there is no split rule matched, try to place it in the top container like a
            // normal launch.
            targetContainer = taskContainer.getTopTaskFragmentContainer();
        }
        if (targetContainer == null) {
            return;
        }
        wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken);
        mPresenter.applyTransaction(wct);
        // Because the activity does not belong to the organizer process, we wait until
        // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
    }

    /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
@@ -327,16 +350,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
    // TODO(b/190433398): Break down into smaller functions.
    void handleActivityCreated(@NonNull Activity launchedActivity) {
        if (isInPictureInPicture(launchedActivity)) {
            // We don't embed activity when it is in PIP.
        if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) {
            // We don't embed activity when it is in PIP, or finishing.
            return;
        }
        final List<EmbeddingRule> splitRules = getSplitRules();
        final TaskFragmentContainer currentContainer = getContainerWithActivity(
                launchedActivity.getActivityToken());
        final TaskFragmentContainer currentContainer = getContainerWithActivity(launchedActivity);

        // Check if the activity is configured to always be expanded.
        if (shouldExpand(launchedActivity, null, splitRules)) {
        if (shouldExpand(launchedActivity, null /* intent */)) {
            if (shouldContainerBeExpanded(currentContainer)) {
                // Make sure that the existing container is expanded
                mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
@@ -375,7 +396,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
                    launchedActivity.getActivityToken());
            if (belowToken != null) {
                activityBelow = ActivityThread.currentActivityThread().getActivity(belowToken);
                activityBelow = getActivity(belowToken);
            }
        }
        if (activityBelow == null) {
@@ -384,7 +405,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

        // Check if the split is already set.
        final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
                activityBelow.getActivityToken());
                activityBelow);
        if (currentContainer != null && activityBelowContainer != null) {
            final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer,
                    activityBelowContainer);
@@ -394,8 +415,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
        }

        final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity,
                splitRules);
        final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity);
        if (splitPairRule == null) {
            return;
        }
@@ -409,8 +429,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // We don't embed activity when it is in PIP.
            return;
        }
        final TaskFragmentContainer currentContainer = getContainerWithActivity(
                activity.getActivityToken());
        final TaskFragmentContainer currentContainer = getContainerWithActivity(activity);

        if (currentContainer != null) {
            // Changes to activities in controllers are handled in
@@ -442,12 +461,144 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
    }

    /**
     * When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer}
     * that we should reparent the new activity to if there is any embedding rule matched.
     *
     * @param wct               {@link WindowContainerTransaction} including all the window change
     *                          requests. The caller is responsible to call
     *                          {@link android.window.TaskFragmentOrganizer#applyTransaction}.
     * @param taskId            The Task to start the activity in.
     * @param intent            The {@link Intent} for starting the new launched activity.
     * @param launchingActivity The {@link Activity} that starts the new activity. We will
     *                          prioritize to split the new activity with it if it is not
     *                          {@code null}.
     * @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
     *         is no embedding rule matched.
     */
    @VisibleForTesting
    @Nullable
    TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
        /*
         * We will check the following to see if there is any embedding rule matched:
         * 1. Whether the new activity intent should always expand.
         * 2. Whether the launching activity (if set) should be split with the new activity intent.
         * 3. Whether the top activity (if any) should be split with the new activity intent.
         * 4. Whether the top activity (if any) in other split should be split with the new
         *    activity intent.
         */

        // 1. Whether the new activity intent should always expand.
        if (shouldExpand(null /* activity */, intent)) {
            return createEmptyExpandedContainer(wct, taskId, launchingActivity);
        }

        // 2. Whether the launching activity (if set) should be split with the new activity intent.
        if (launchingActivity != null) {
            final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
                    launchingActivity, intent, true /* respectClearTop */);
            if (container != null) {
                return container;
            }
        }

        // 3. Whether the top activity (if any) should be split with the new activity intent.
        final TaskContainer taskContainer = getTaskContainer(taskId);
        if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
            // There is no other activity in the Task to check split with.
            return null;
        }
        final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
        final Activity topActivity = topContainer.getTopNonFinishingActivity();
        if (topActivity != null && topActivity != launchingActivity) {
            final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
                    topActivity, intent, false /* respectClearTop */);
            if (container != null) {
                return container;
            }
        }

        // 4. Whether the top activity (if any) in other split should be split with the new
        //    activity intent.
        final SplitContainer topSplit = getActiveSplitForContainer(topContainer);
        if (topSplit == null) {
            return null;
        }
        final TaskFragmentContainer otherTopContainer =
                topSplit.getPrimaryContainer() == topContainer
                        ? topSplit.getSecondaryContainer()
                        : topSplit.getPrimaryContainer();
        final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
        if (otherTopActivity != null && otherTopActivity != launchingActivity) {
            return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent,
                    false /* respectClearTop */);
        }
        return null;
    }

    /**
     * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
     */
    @Nullable
    private TaskFragmentContainer createEmptyExpandedContainer(
            @NonNull WindowContainerTransaction wct, int taskId,
            @Nullable Activity launchingActivity) {
        // We need an activity in the organizer process in the same Task to use as the owner
        // activity, as well as to get the Task window info.
        final Activity activityInTask;
        if (launchingActivity != null) {
            activityInTask = launchingActivity;
        } else {
            final TaskContainer taskContainer = getTaskContainer(taskId);
            activityInTask = taskContainer != null
                    ? taskContainer.getTopNonFinishingActivity()
                    : null;
        }
        if (activityInTask == null) {
            // Can't find any activity in the Task that we can use as the owner activity.
            return null;
        }
        final TaskFragmentContainer expandedContainer = newContainer(null /* activity */,
                activityInTask, taskId);
        mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
                activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
        return expandedContainer;
    }

    /**
     * Returns a container for the new activity intent to launch into as splitting with the primary
     * activity.
     */
    @Nullable
    private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
            @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
            @NonNull Intent intent, boolean respectClearTop) {
        final SplitPairRule splitRule = getSplitRule(primaryActivity, intent);
        if (splitRule == null) {
            return null;
        }
        final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
        final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
        if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
                && (canReuseContainer(splitRule, splitContainer.getSplitRule())
                // TODO(b/231845476) we should always respect clearTop.
                || !respectClearTop)) {
            // Can launch in the existing secondary container if the rules share the same
            // presentation.
            return splitContainer.getSecondaryContainer();
        }
        // Create a new TaskFragment to split with the primary activity for the new activity.
        return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule);
    }

    /**
     * Returns a container that this activity is registered with. An activity can only belong to one
     * container, or no container at all.
     */
    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
    TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
        final IBinder activityToken = activity.getActivityToken();
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            for (TaskFragmentContainer container : containers) {
@@ -668,7 +819,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Returns the top active split container that has the provided container, if available.
     */
    @Nullable
    private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
    private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
        if (container == null) {
            return null;
        }
        final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
        if (splitContainers.isEmpty()) {
            return null;
@@ -687,8 +841,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Returns the active split that has the provided containers as primary and secondary or as
     * secondary and primary, if available.
     */
    @VisibleForTesting
    @Nullable
    private SplitContainer getActiveSplitForContainers(
    SplitContainer getActiveSplitForContainers(
            @NonNull TaskFragmentContainer firstContainer,
            @NonNull TaskFragmentContainer secondContainer) {
        final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
@@ -718,15 +873,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }

    boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
        final TaskFragmentContainer container = getContainerWithActivity(
                activity.getActivityToken());
        final TaskFragmentContainer container = getContainerWithActivity(activity);
        // Don't launch placeholder if the container is occluded.
        if (container != null && container != getTopActiveContainer(container.getTaskId())) {
            return false;
        }

        SplitContainer splitContainer = container != null ? getActiveSplitForContainer(container)
                : null;
        final SplitContainer splitContainer = getActiveSplitForContainer(container);
        if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
            // Don't launch placeholder in primary split container
            return false;
@@ -876,9 +1029,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * if available.
     */
    @Nullable
    private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
            @NonNull Intent secondaryActivityIntent, @NonNull List<EmbeddingRule> splitRules) {
        for (EmbeddingRule rule : splitRules) {
    private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
            @NonNull Intent secondaryActivityIntent) {
        for (EmbeddingRule rule : mSplitRules) {
            if (!(rule instanceof SplitPairRule)) {
                continue;
            }
@@ -894,9 +1047,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Returns a split rule for the provided pair of primary and secondary activities if available.
     */
    @Nullable
    private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
            @NonNull Activity secondaryActivity, @NonNull List<EmbeddingRule> splitRules) {
        for (EmbeddingRule rule : splitRules) {
    private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
            @NonNull Activity secondaryActivity) {
        for (EmbeddingRule rule : mSplitRules) {
            if (!(rule instanceof SplitPairRule)) {
                continue;
            }
@@ -933,16 +1086,24 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return mHandler;
    }

    int getTaskId(@NonNull Activity activity) {
        // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an
        // IPC call.
        final TaskFragmentContainer container = getContainerWithActivity(activity);
        return container != null ? container.getTaskId() : activity.getTaskId();
    }

    @Nullable
    Activity getActivity(@NonNull IBinder activityToken) {
        return ActivityThread.currentActivityThread().getActivity(activityToken);
    }

    /**
     * Returns {@code true} if an Activity with the provided component name should always be
     * expanded to occupy full task bounds. Such activity must not be put in a split.
     */
    private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent,
            List<EmbeddingRule> splitRules) {
        if (splitRules == null) {
            return false;
        }
        for (EmbeddingRule rule : splitRules) {
    private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
        for (EmbeddingRule rule : mSplitRules) {
            if (!(rule instanceof ActivityRule)) {
                continue;
            }
@@ -996,8 +1157,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
    boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
            @NonNull Activity associatedActivity) {
        TaskFragmentContainer associatedContainer = getContainerWithActivity(
                associatedActivity.getActivityToken());
        final TaskFragmentContainer associatedContainer = getContainerWithActivity(
                associatedActivity);
        if (associatedContainer == null) {
            return false;
        }
@@ -1085,129 +1246,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                return super.onStartActivity(who, intent, options);
            }

            if (shouldExpand(null, intent, getSplitRules())) {
                setLaunchingInExpandedContainer(launchingActivity, options);
            } else if (!splitWithLaunchingActivity(launchingActivity, intent, options)) {
                setLaunchingInSameSideContainer(launchingActivity, intent, options);
            }

            return super.onStartActivity(who, intent, options);
        }

        private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) {
            TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer(
                    launchingActivity);

            // Amend the request to let the WM know that the activity should be placed in the
            // dedicated container.
            options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                    newContainer.getTaskFragmentToken());
        }

        /**
         * Returns {@code true} if the activity that is going to be started via the
         * {@code intent} should be paired with the {@code launchingActivity} and is set to be
         * launched in the side container.
         */
        private boolean splitWithLaunchingActivity(Activity launchingActivity, Intent intent,
                Bundle options) {
            final SplitPairRule splitPairRule = getSplitRule(launchingActivity, intent,
                    getSplitRules());
            if (splitPairRule == null) {
                return false;
            }

            // Check if there is any existing side container to launch into.
            TaskFragmentContainer secondaryContainer = findSideContainerForNewLaunch(
                    launchingActivity, splitPairRule);
            if (secondaryContainer == null) {
                // Create a new split with an empty side container.
                secondaryContainer = mPresenter
                        .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
            }

            final int taskId = getTaskId(launchingActivity);
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
                    taskId, intent, launchingActivity);
            if (launchedInTaskFragment != null) {
                mPresenter.applyTransaction(wct);
                // Amend the request to let the WM know that the activity should be placed in the
                // dedicated container.
                options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                    secondaryContainer.getTaskFragmentToken());
            return true;
        }

        /**
         * Finds if there is an existing split side {@link TaskFragmentContainer} that can be used
         * for the new rule.
         */
        @Nullable
        private TaskFragmentContainer findSideContainerForNewLaunch(Activity launchingActivity,
                SplitPairRule splitPairRule) {
            final TaskFragmentContainer launchingContainer = getContainerWithActivity(
                    launchingActivity.getActivityToken());
            if (launchingContainer == null) {
                return null;
            }

            // We only check if the launching activity is the primary of the split. We will check
            // if the launching activity is the secondary in #setLaunchingInSameSideContainer.
            final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer);
            if (splitContainer == null
                    || splitContainer.getPrimaryContainer() != launchingContainer) {
                return null;
            }

            if (canReuseContainer(splitPairRule, splitContainer.getSplitRule())) {
                return splitContainer.getSecondaryContainer();
            }
            return null;
        }

        /**
         * Checks if the activity that is going to be started via the {@code intent} should be
         * paired with the existing top activity which is currently paired with the
         * {@code launchingActivity}. If so, set the activity to be launched in the same side
         * container of the {@code launchingActivity}.
         */
        private void setLaunchingInSameSideContainer(Activity launchingActivity, Intent intent,
                Bundle options) {
            final TaskFragmentContainer launchingContainer = getContainerWithActivity(
                    launchingActivity.getActivityToken());
            if (launchingContainer == null) {
                return;
            }

            final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer);
            if (splitContainer == null) {
                return;
                        launchedInTaskFragment.getTaskFragmentToken());
            }

            if (splitContainer.getSecondaryContainer() != launchingContainer) {
                return;
            }

            // The launching activity is on the secondary container. Retrieve the primary
            // activity from the other container.
            Activity primaryActivity =
                    splitContainer.getPrimaryContainer().getTopNonFinishingActivity();
            if (primaryActivity == null) {
                return;
            }

            final SplitPairRule splitPairRule = getSplitRule(primaryActivity, intent,
                    getSplitRules());
            if (splitPairRule == null) {
                return;
            }

            // Can only launch in the same container if the rules share the same presentation.
            if (!canReuseContainer(splitPairRule, splitContainer.getSplitRule())) {
                return;
            }

            // Amend the request to let the WM know that the activity should be placed in the
            // dedicated container. This is necessary for the case that the activity is started
            // into a new Task, or new Task will be escaped from the current host Task and be
            // displayed in fullscreen.
            options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                    launchingContainer.getTaskFragmentToken());
            return super.onStartActivity(who, intent, options);
        }
    }

@@ -1228,8 +1279,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
            return false;
        }
        return rule1.getSplitRatio() == rule2.getSplitRatio()
                && rule1.getLayoutDirection() == rule2.getLayoutDirection();
        final SplitPairRule pairRule1 = (SplitPairRule) rule1;
        final SplitPairRule pairRule2 = (SplitPairRule) rule2;
        // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
        return pairRule1.getSplitRatio() == pairRule2.getSplitRatio()
                && pairRule1.getLayoutDirection() == pairRule2.getLayoutDirection()
                && pairRule1.getFinishPrimaryWithSecondary()
                == pairRule2.getFinishPrimaryWithSecondary()
                && pairRule1.getFinishSecondaryWithPrimary()
                == pairRule2.getFinishSecondaryWithPrimary();
    }

    /**
+14 −28

File changed.

Preview size limit exceeded, changes collapsed.

+19 −0
Original line number Diff line number Diff line
@@ -142,4 +142,23 @@ class TaskContainer {
            container.removePendingAppearedActivity(pendingAppearedActivity);
        }
    }

    @Nullable
    TaskFragmentContainer getTopTaskFragmentContainer() {
        if (mContainers.isEmpty()) {
            return null;
        }
        return mContainers.get(mContainers.size() - 1);
    }

    @Nullable
    Activity getTopNonFinishingActivity() {
        for (int i = mContainers.size() - 1; i >= 0; i--) {
            final Activity activity = mContainers.get(i).getTopNonFinishingActivity();
            if (activity != null) {
                return activity;
            }
        }
        return null;
    }
}
+1 −3

File changed.

Preview size limit exceeded, changes collapsed.

+255 −3

File changed.

Preview size limit exceeded, changes collapsed.

Loading