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:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user