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

Commit 722ec8b7 authored by Chris Li's avatar Chris Li
Browse files

Fix ActivityEmbedding issues with REORDER_TO_FRONT

1. When the primary TaskFragment is finished with shouldFinishDependent
   = false, it should still finish the placeholder TaskFragment.
   Otherwise it may leave the placeholder there forever.
2. In case the app starts two activities one after another, if the first
   Intent is not handled, but the second is, when the first activity
   onCreate reach the organizer, it may be reparented to a new
   TaskFragment that is on top of the second activity. We want to make
   sure that the TaskFragment is created at the same position as the
   reparent Activity.

Bug: 255628567
Test: manually verify with the test app in the bug.
Test: atest WMJetpackUnitTests:TaskFragmentContainerTest
Test: atest WmTests:TaskFragmentOrganizerControllerTest
Change-Id: Ie48d7e46786cabcf3a1ededa9275f0223e2f477f
parent 6b14088d
Loading
Loading
Loading
Loading
+62 −2
Original line number Diff line number Diff line
@@ -71,20 +71,42 @@ public final class TaskFragmentCreationParams implements Parcelable {
     *
     * This is needed in case we need to launch a placeholder Activity to split below a transparent
     * always-expand Activity.
     *
     * This should not be used with {@link #mPairedActivityToken}.
     */
    @Nullable
    private final IBinder mPairedPrimaryFragmentToken;

    /**
     * The Activity token to place the new TaskFragment on top of.
     * When it is set, the new TaskFragment will be positioned right above the target Activity.
     * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
     *
     * This is needed in case we need to place an Activity into TaskFragment to launch placeholder
     * below a transparent always-expand Activity, or when there is another Intent being started in
     * a TaskFragment above.
     *
     * This should not be used with {@link #mPairedPrimaryFragmentToken}.
     */
    @Nullable
    private final IBinder mPairedActivityToken;

    private TaskFragmentCreationParams(
            @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
            @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
            @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
            @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
            @Nullable IBinder pairedActivityToken) {
        if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
            throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
                    + " pairedActivityToken should not be set at the same time.");
        }
        mOrganizer = organizer;
        mFragmentToken = fragmentToken;
        mOwnerToken = ownerToken;
        mInitialBounds.set(initialBounds);
        mWindowingMode = windowingMode;
        mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
        mPairedActivityToken = pairedActivityToken;
    }

    @NonNull
@@ -121,6 +143,15 @@ public final class TaskFragmentCreationParams implements Parcelable {
        return mPairedPrimaryFragmentToken;
    }

    /**
     * TODO(b/232476698): remove the hide with adding CTS for this in next release.
     * @hide
     */
    @Nullable
    public IBinder getPairedActivityToken() {
        return mPairedActivityToken;
    }

    private TaskFragmentCreationParams(Parcel in) {
        mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
        mFragmentToken = in.readStrongBinder();
@@ -128,6 +159,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
        mInitialBounds.readFromParcel(in);
        mWindowingMode = in.readInt();
        mPairedPrimaryFragmentToken = in.readStrongBinder();
        mPairedActivityToken = in.readStrongBinder();
    }

    /** @hide */
@@ -139,6 +171,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
        mInitialBounds.writeToParcel(dest, flags);
        dest.writeInt(mWindowingMode);
        dest.writeStrongBinder(mPairedPrimaryFragmentToken);
        dest.writeStrongBinder(mPairedActivityToken);
    }

    @NonNull
@@ -164,6 +197,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
                + " initialBounds=" + mInitialBounds
                + " windowingMode=" + mWindowingMode
                + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
                + " pairedActivityToken=" + mPairedActivityToken
                + "}";
    }

@@ -194,6 +228,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
        @Nullable
        private IBinder mPairedPrimaryFragmentToken;

        @Nullable
        private IBinder mPairedActivityToken;

        public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
            mOrganizer = organizer;
@@ -224,6 +261,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
         * This is needed in case we need to launch a placeholder Activity to split below a
         * transparent always-expand Activity.
         *
         * This should not be used with {@link #setPairedActivityToken}.
         *
         * TODO(b/232476698): remove the hide with adding CTS for this in next release.
         * @hide
         */
@@ -233,11 +272,32 @@ public final class TaskFragmentCreationParams implements Parcelable {
            return this;
        }

        /**
         * Sets the Activity token to place the new TaskFragment on top of.
         * When it is set, the new TaskFragment will be positioned right above the target Activity.
         * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
         *
         * This is needed in case we need to place an Activity into TaskFragment to launch
         * placeholder below a transparent always-expand Activity, or when there is another Intent
         * being started in a TaskFragment above.
         *
         * This should not be used with {@link #setPairedPrimaryFragmentToken}.
         *
         * TODO(b/232476698): remove the hide with adding CTS for this in next release.
         * @hide
         */
        @NonNull
        public Builder setPairedActivityToken(@Nullable IBinder activityToken) {
            mPairedActivityToken = activityToken;
            return this;
        }

        /** Constructs the options to create TaskFragment with. */
        @NonNull
        public TaskFragmentCreationParams build() {
            return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
                    mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
                    mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
                    mPairedActivityToken);
        }
    }
}
+19 −2
Original line number Diff line number Diff line
@@ -191,10 +191,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
     */
    void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
                null /* pairedActivityToken */);
    }

    /**
     * @param ownerToken The token of the activity that creates this task fragment. It does not
     *                   have to be a child of this task fragment, but must belong to the same task.
     * @param pairedActivityToken The token of the activity that will be reparented to this task
     *                            fragment. When it is not {@code null}, the task fragment will be
     *                            positioned right above it.
     */
    void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
            @Nullable IBinder pairedActivityToken) {
        final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
                getOrganizerToken(), fragmentToken, ownerToken)
                .setInitialBounds(bounds)
                .setWindowingMode(windowingMode)
                .setPairedActivityToken(pairedActivityToken)
                .build();
        createTaskFragment(wct, fragmentOptions);
    }
@@ -216,8 +231,10 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
    private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
            @WindowingMode int windowingMode, @NonNull Activity activity) {
        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
        wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
        final IBinder reparentActivityToken = activity.getActivityToken();
        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
                reparentActivityToken);
        wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
    }

    void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+1 −1
Original line number Diff line number Diff line
@@ -1481,7 +1481,7 @@ 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
    @GuardedBy("mLock")
    @Nullable
    SplitContainer getActiveSplitForContainers(
            @NonNull TaskFragmentContainer firstContainer,
+4 −3
Original line number Diff line number Diff line
@@ -268,10 +268,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            container = mController.newContainer(activity, taskId);
            final int windowingMode = mController.getTaskContainer(taskId)
                    .getWindowingModeForSplitTaskFragment(bounds);
            createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(),
                    bounds, windowingMode);
            final IBinder reparentActivityToken = activity.getActivityToken();
            createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
                    bounds, windowingMode, reparentActivityToken);
            wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
                    activity.getActivityToken());
                    reparentActivityToken);
        } else {
            resizeTaskFragmentIfRegistered(wct, container, bounds);
            final int windowingMode = mController.getTaskContainer(taskId)
+38 −0
Original line number Diff line number Diff line
@@ -141,12 +141,26 @@ class TaskFragmentContainer {
        mToken = new Binder("TaskFragmentContainer");
        mTaskContainer = taskContainer;
        if (pairedPrimaryContainer != null) {
            // The TaskFragment will be positioned right above the paired container.
            if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
                throw new IllegalArgumentException(
                        "pairedPrimaryContainer must be in the same Task");
            }
            final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
            taskContainer.mContainers.add(primaryIndex + 1, this);
        } else if (pendingAppearedActivity != null) {
            // The TaskFragment will be positioned right above the pending appeared Activity. If any
            // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
            // the pending Intent hasn't been created yet, so the new Activity should be below the
            // empty TaskFragment.
            int i = taskContainer.mContainers.size() - 1;
            for (; i >= 0; i--) {
                final TaskFragmentContainer container = taskContainer.mContainers.get(i);
                if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
                    break;
                }
            }
            taskContainer.mContainers.add(i + 1, this);
        } else {
            taskContainer.mContainers.add(this);
        }
@@ -500,6 +514,8 @@ class TaskFragmentContainer {
        }

        if (!shouldFinishDependent) {
            // Always finish the placeholder when the primary is finished.
            finishPlaceholderIfAny(wct, presenter);
            return;
        }

@@ -526,6 +542,28 @@ class TaskFragmentContainer {
        mActivitiesToFinishOnExit.clear();
    }

    @GuardedBy("mController.mLock")
    private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
            @NonNull SplitPresenter presenter) {
        final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
        for (TaskFragmentContainer container : mContainersToFinishOnExit) {
            if (container.mIsFinished) {
                continue;
            }
            final SplitContainer splitContainer = mController.getActiveSplitForContainers(
                    this, container);
            if (splitContainer != null && splitContainer.isPlaceholderContainer()
                    && splitContainer.getSecondaryContainer() == container) {
                // Remove the placeholder secondary TaskFragment.
                containersToRemove.add(container);
            }
        }
        mContainersToFinishOnExit.removeAll(containersToRemove);
        for (TaskFragmentContainer container : containersToRemove) {
            container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
        }
    }

    boolean isFinished() {
        return mIsFinished;
    }
Loading