Merge "Update TaskFragmentToken for Activity relaunch" into tm-qpr-dev am: 379c38b1ae

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19707668

Change-Id: Ief7239f819fc1babade677aca230c2df93ad5a72
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-09-01 22:17:20 +00:00
committed by Automerger Merge Worker
6 changed files with 77 additions and 13 deletions

View File

@@ -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 // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
// used without security checks // used without security checks
public IBinder shareableActivityToken; public IBinder shareableActivityToken;
// The token of the initial TaskFragment that embedded this activity. Do not rely on it // The token of the TaskFragment that embedded this activity.
// after creation because the activity could be reparented. @Nullable public IBinder mTaskFragmentToken;
@Nullable public IBinder mInitialTaskFragmentToken;
int ident; int ident;
@UnsupportedAppUsage @UnsupportedAppUsage
Intent intent; Intent intent;
@@ -620,7 +619,7 @@ public final class ActivityThread extends ClientTransactionHandler
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions, List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client, boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble, IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
IBinder initialTaskFragmentToken) { IBinder taskFragmentToken) {
this.token = token; this.token = token;
this.assistToken = assistToken; this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken; this.shareableActivityToken = shareableActivityToken;
@@ -641,7 +640,7 @@ public final class ActivityThread extends ClientTransactionHandler
compatInfo); compatInfo);
mActivityOptions = activityOptions; mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble; mLaunchedFromBubble = launchedFromBubble;
mInitialTaskFragmentToken = initialTaskFragmentToken; mTaskFragmentToken = taskFragmentToken;
init(); init();
} }

View File

@@ -583,7 +583,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
if (!isOnReparent && getContainerWithActivity(activity) == null 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 // 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: // currently placed in an embedded TaskFragment. This can happen in two cases:
// 1. the activity is embedded in another app. // 1. the activity is embedded in another app.
@@ -866,11 +866,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
@VisibleForTesting @VisibleForTesting
@GuardedBy("mLock")
void onActivityDestroyed(@NonNull Activity activity) { void onActivityDestroyed(@NonNull Activity activity) {
// Remove any pending appeared activity, as the server won't send finished activity to the // Remove any pending appeared activity, as the server won't send finished activity to the
// organizer. // organizer.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 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 // We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed. // again after the pending is removed.
@@ -1605,15 +1606,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
/** /**
* Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it * Gets the token of the TaskFragment that embedded this activity. It is available as soon as
* after creation because the activity could be reparented. * 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 @VisibleForTesting
@Nullable @Nullable
IBinder getInitialTaskFragmentToken(@NonNull Activity activity) { IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken()); .getActivityClient(activity.getActivityToken());
return record != null ? record.mInitialTaskFragmentToken : null; return record != null ? record.mTaskFragmentToken : null;
} }
/** /**
@@ -1691,7 +1693,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
synchronized (mLock) { synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken(); 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 // If the activity is not embedded, then it will not have an initial task fragment
// token so no further action is needed. // token so no further action is needed.
if (initialTaskFragmentToken == null) { if (initialTaskFragmentToken == null) {

View File

@@ -137,6 +137,13 @@ class TaskContainer {
return mContainers.isEmpty() && mFinishedContainer.isEmpty(); 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. */ /** Removes the pending appeared activity from all TaskFragments in this Task. */
void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) { void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
for (TaskFragmentContainer container : mContainers) { for (TaskFragmentContainer container : mContainers) {

View File

@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode; import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent; import android.content.Intent;
import android.graphics.Rect; import android.graphics.Rect;
@@ -190,6 +191,19 @@ class TaskFragmentContainer {
// Remove the pending activity from other TaskFragments. // Remove the pending activity from other TaskFragments.
mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity); mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
mPendingAppearedActivities.add(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) { void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
@@ -197,8 +211,29 @@ class TaskFragmentContainer {
} }
void clearPendingAppearedActivities() { void clearPendingAppearedActivities() {
final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
// Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
// current TaskFragment.
mPendingAppearedActivities.clear(); mPendingAppearedActivities.clear();
mPendingAppearedIntent = null; 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 @Nullable

View File

@@ -930,7 +930,8 @@ public class SplitControllerTest {
@Test @Test
public void testResolveActivityToContainer_inUnknownTaskFragment() { 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. // No need to handle when the new launched activity is in an unknown TaskFragment.
assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity, assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,

View File

@@ -316,6 +316,25 @@ public class TaskFragmentContainerTest {
assertEquals(activity, container.getBottomMostActivity()); 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<IBinder> 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. */ /** Creates a mock activity in the organizer process. */
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);