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

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

Update TaskFragmentToken for Activity relaunch

Before, we store the TaskFragmentToken in ActivityClientRecord from
LaunchActivityItem, which won't update if the Activity is relaunched.
Now, we update the token when the activity is reparented to make sure
the token is up-to-date when relaunched.

Also make sure that when activity is destroyed, it is no longer
available using SplitController#getContainerWithActivity(Activity).

Bug: 243330085
Test: atest WMJetpackUnitTests:TaskFragmentContainerTest
Change-Id: I37f1a442c6f1bfb40f04f61eb4f9a3d8d09733ee
parent 907c1ad8
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -534,9 +534,8 @@ public final class ActivityThread extends ClientTransactionHandler
        // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
        // used without security checks
        public IBinder shareableActivityToken;
        // The token of the initial TaskFragment that embedded this activity. Do not rely on it
        // after creation because the activity could be reparented.
        @Nullable public IBinder mInitialTaskFragmentToken;
        // The token of the TaskFragment that embedded this activity.
        @Nullable public IBinder mTaskFragmentToken;
        int ident;
        @UnsupportedAppUsage
        Intent intent;
@@ -620,7 +619,7 @@ public final class ActivityThread extends ClientTransactionHandler
                List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
                boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
                IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
                IBinder initialTaskFragmentToken) {
                IBinder taskFragmentToken) {
            this.token = token;
            this.assistToken = assistToken;
            this.shareableActivityToken = shareableActivityToken;
@@ -641,7 +640,7 @@ public final class ActivityThread extends ClientTransactionHandler
                    compatInfo);
            mActivityOptions = activityOptions;
            mLaunchedFromBubble = launchedFromBubble;
            mInitialTaskFragmentToken = initialTaskFragmentToken;
            mTaskFragmentToken = taskFragmentToken;
            init();
        }

+10 −7
Original line number Diff line number Diff line
@@ -571,7 +571,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }

        if (!isOnReparent && getContainerWithActivity(activity) == null
                && getInitialTaskFragmentToken(activity) != null) {
                && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
            // We can't find the new launched activity in any recorded container, but it is
            // currently placed in an embedded TaskFragment. This can happen in two cases:
            // 1. the activity is embedded in another app.
@@ -854,11 +854,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    void onActivityDestroyed(@NonNull Activity activity) {
        // Remove any pending appeared activity, as the server won't send finished activity to the
        // organizer.
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            mTaskContainers.valueAt(i).cleanupPendingAppearedActivity(activity);
            mTaskContainers.valueAt(i).onActivityDestroyed(activity);
        }
        // We didn't trigger the callback if there were any pending appeared activities, so check
        // again after the pending is removed.
@@ -1593,15 +1594,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    }

    /**
     * Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
     * after creation because the activity could be reparented.
     * Gets the token of the TaskFragment that embedded this activity. It is available as soon as
     * the activity is created and attached, so it can be used during {@link #onActivityCreated}
     * before the server notifies the organizer to avoid racing condition.
     */
    @VisibleForTesting
    @Nullable
    IBinder getInitialTaskFragmentToken(@NonNull Activity activity) {
    IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
        final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
                .getActivityClient(activity.getActivityToken());
        return record != null ? record.mInitialTaskFragmentToken : null;
        return record != null ? record.mTaskFragmentToken : null;
    }

    /**
@@ -1679,7 +1681,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                @Nullable Bundle savedInstanceState) {
            synchronized (mLock) {
                final IBinder activityToken = activity.getActivityToken();
                final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
                final IBinder initialTaskFragmentToken =
                        getTaskFragmentTokenFromActivityClientRecord(activity);
                // If the activity is not embedded, then it will not have an initial task fragment
                // token so no further action is needed.
                if (initialTaskFragmentToken == null) {
+7 −0
Original line number Diff line number Diff line
@@ -137,6 +137,13 @@ class TaskContainer {
        return mContainers.isEmpty() && mFinishedContainer.isEmpty();
    }

    /** Called when the activity is destroyed. */
    void onActivityDestroyed(@NonNull Activity activity) {
        for (TaskFragmentContainer container : mContainers) {
            container.onActivityDestroyed(activity);
        }
    }

    /** Removes the pending appeared activity from all TaskFragments in this Task. */
    void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
        for (TaskFragmentContainer container : mContainers) {
+35 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
@@ -189,6 +190,19 @@ class TaskFragmentContainer {
        // Remove the pending activity from other TaskFragments.
        mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
        mPendingAppearedActivities.add(pendingAppearedActivity);
        updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
    }

    /**
     * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
     * activity. This makes sure the token is up-to-date if the activity is relaunched later.
     */
    private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
        final ActivityThread.ActivityClientRecord record = ActivityThread
                .currentActivityThread().getActivityClient(activity.getActivityToken());
        if (record != null) {
            record.mTaskFragmentToken = mToken;
        }
    }

    void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
@@ -196,8 +210,29 @@ class TaskFragmentContainer {
    }

    void clearPendingAppearedActivities() {
        final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
        // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
        // current TaskFragment.
        mPendingAppearedActivities.clear();
        mPendingAppearedIntent = null;

        // For removed pending activities, we need to update the them to their previous containers.
        for (Activity activity : cleanupActivities) {
            final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
                    activity);
            if (curContainer != null) {
                curContainer.updateActivityClientRecordTaskFragmentToken(activity);
            }
        }
    }

    /** Called when the activity is destroyed. */
    void onActivityDestroyed(@NonNull Activity activity) {
        removePendingAppearedActivity(activity);
        if (mInfo != null) {
            // Remove the activity now because there can be a delay before the server callback.
            mInfo.getActivities().remove(activity.getActivityToken());
        }
    }

    @Nullable
+2 −1
Original line number Diff line number Diff line
@@ -930,7 +930,8 @@ public class SplitControllerTest {

    @Test
    public void testResolveActivityToContainer_inUnknownTaskFragment() {
        doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
        doReturn(new Binder()).when(mSplitController)
                .getTaskFragmentTokenFromActivityClientRecord(mActivity);

        // No need to handle when the new launched activity is in an unknown TaskFragment.
        assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
Loading