From f1bf8d6d9d992d5f847630b7929e415c6df9de5c Mon Sep 17 00:00:00 2001 From: Chris Li Date: Tue, 30 Aug 2022 16:17:24 +0800 Subject: [PATCH] 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 --- core/java/android/app/ActivityThread.java | 9 +++-- .../extensions/embedding/SplitController.java | 17 +++++---- .../extensions/embedding/TaskContainer.java | 7 ++++ .../embedding/TaskFragmentContainer.java | 35 +++++++++++++++++++ .../embedding/SplitControllerTest.java | 3 +- .../embedding/TaskFragmentContainerTest.java | 19 ++++++++++ 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a70a1a8d51de9..51efdbac00cc0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -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 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(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 4102732fd80eb..c9649e78ea644 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -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) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 45645b2018c34..b5636777568ec 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -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) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 344ffc759c46a..8e44ac35662a6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -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 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 diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index f7436108d3e3b..506780f8acad6 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -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, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 1bc81ee3dcc71..082774e048a9f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -316,6 +316,25 @@ public class TaskFragmentContainerTest { assertEquals(activity, container.getBottomMostActivity()); } + @Test + public void testOnActivityDestroyed() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + container.addPendingAppearedActivity(mActivity); + final List activities = new ArrayList<>(); + activities.add(mActivity.getActivityToken()); + doReturn(activities).when(mInfo).getActivities(); + container.setInfo(mTransaction, mInfo); + + assertTrue(container.hasActivity(mActivity.getActivityToken())); + + taskContainer.onActivityDestroyed(mActivity); + + // It should not contain the destroyed Activity. + assertFalse(container.hasActivity(mActivity.getActivityToken())); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class);