Merge "Update TaskFragmentToken for Activity relaunch" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-09-01 21:51:47 +00:00
committed by Android (Google) Code Review
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
// 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();
}

View File

@@ -583,7 +583,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.
@@ -866,11 +866,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.
@@ -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
* 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;
}
/**
@@ -1691,7 +1693,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) {

View File

@@ -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) {

View File

@@ -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;
@@ -190,6 +191,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) {
@@ -197,8 +211,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

View File

@@ -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,

View File

@@ -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<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. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);