Merge "Fix ActivityEmbedding issues with REORDER_TO_FRONT" into tm-qpr-dev am: aebafa287e

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

Change-Id: Ie29d9527f70dc18ccf68dfa701994ab9a413e7cd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chris Li
2023-01-10 04:24:39 +00:00
committed by Automerger Merge Worker
8 changed files with 223 additions and 13 deletions

View File

@@ -71,20 +71,42 @@ public final class TaskFragmentCreationParams implements Parcelable {
* *
* This is needed in case we need to launch a placeholder Activity to split below a transparent * This is needed in case we need to launch a placeholder Activity to split below a transparent
* always-expand Activity. * always-expand Activity.
*
* This should not be used with {@link #mPairedActivityToken}.
*/ */
@Nullable @Nullable
private final IBinder mPairedPrimaryFragmentToken; private final IBinder mPairedPrimaryFragmentToken;
/**
* The Activity token to place the new TaskFragment on top of.
* When it is set, the new TaskFragment will be positioned right above the target Activity.
* Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
*
* This is needed in case we need to place an Activity into TaskFragment to launch placeholder
* below a transparent always-expand Activity, or when there is another Intent being started in
* a TaskFragment above.
*
* This should not be used with {@link #mPairedPrimaryFragmentToken}.
*/
@Nullable
private final IBinder mPairedActivityToken;
private TaskFragmentCreationParams( private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken, @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialBounds, @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) { @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
@Nullable IBinder pairedActivityToken) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
}
mOrganizer = organizer; mOrganizer = organizer;
mFragmentToken = fragmentToken; mFragmentToken = fragmentToken;
mOwnerToken = ownerToken; mOwnerToken = ownerToken;
mInitialBounds.set(initialBounds); mInitialBounds.set(initialBounds);
mWindowingMode = windowingMode; mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken; mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
} }
@NonNull @NonNull
@@ -121,6 +143,15 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mPairedPrimaryFragmentToken; return mPairedPrimaryFragmentToken;
} }
/**
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@Nullable
public IBinder getPairedActivityToken() {
return mPairedActivityToken;
}
private TaskFragmentCreationParams(Parcel in) { private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in); mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder(); mFragmentToken = in.readStrongBinder();
@@ -128,6 +159,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mInitialBounds.readFromParcel(in); mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt(); mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder(); mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
} }
/** @hide */ /** @hide */
@@ -139,6 +171,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mInitialBounds.writeToParcel(dest, flags); mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode); dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken); dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
} }
@NonNull @NonNull
@@ -164,6 +197,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " initialBounds=" + mInitialBounds + " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode + " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ "}"; + "}";
} }
@@ -194,6 +228,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
@Nullable @Nullable
private IBinder mPairedPrimaryFragmentToken; private IBinder mPairedPrimaryFragmentToken;
@Nullable
private IBinder mPairedActivityToken;
public Builder(@NonNull TaskFragmentOrganizerToken organizer, public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer; mOrganizer = organizer;
@@ -224,6 +261,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
* This is needed in case we need to launch a placeholder Activity to split below a * This is needed in case we need to launch a placeholder Activity to split below a
* transparent always-expand Activity. * transparent always-expand Activity.
* *
* This should not be used with {@link #setPairedActivityToken}.
*
* TODO(b/232476698): remove the hide with adding CTS for this in next release. * TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide * @hide
*/ */
@@ -233,11 +272,32 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this; return this;
} }
/**
* Sets the Activity token to place the new TaskFragment on top of.
* When it is set, the new TaskFragment will be positioned right above the target Activity.
* Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
*
* This is needed in case we need to place an Activity into TaskFragment to launch
* placeholder below a transparent always-expand Activity, or when there is another Intent
* being started in a TaskFragment above.
*
* This should not be used with {@link #setPairedPrimaryFragmentToken}.
*
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@NonNull
public Builder setPairedActivityToken(@Nullable IBinder activityToken) {
mPairedActivityToken = activityToken;
return this;
}
/** Constructs the options to create TaskFragment with. */ /** Constructs the options to create TaskFragment with. */
@NonNull @NonNull
public TaskFragmentCreationParams build() { public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken, return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken); mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
mPairedActivityToken);
} }
} }
} }

View File

@@ -191,10 +191,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
*/ */
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
null /* pairedActivityToken */);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
* @param pairedActivityToken The token of the activity that will be reparented to this task
* fragment. When it is not {@code null}, the task fragment will be
* positioned right above it.
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
@Nullable IBinder pairedActivityToken) {
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder( final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), fragmentToken, ownerToken) getOrganizerToken(), fragmentToken, ownerToken)
.setInitialBounds(bounds) .setInitialBounds(bounds)
.setWindowingMode(windowingMode) .setWindowingMode(windowingMode)
.setPairedActivityToken(pairedActivityToken)
.build(); .build();
createTaskFragment(wct, fragmentOptions); createTaskFragment(wct, fragmentOptions);
} }
@@ -216,8 +231,10 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct, private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
@WindowingMode int windowingMode, @NonNull Activity activity) { @WindowingMode int windowingMode, @NonNull Activity activity) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); final IBinder reparentActivityToken = activity.getActivityToken();
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken()); createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
reparentActivityToken);
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
} }
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,

View File

@@ -1499,7 +1499,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns the active split that has the provided containers as primary and secondary or as * Returns the active split that has the provided containers as primary and secondary or as
* secondary and primary, if available. * secondary and primary, if available.
*/ */
@VisibleForTesting @GuardedBy("mLock")
@Nullable @Nullable
SplitContainer getActiveSplitForContainers( SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer firstContainer,

View File

@@ -268,10 +268,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
container = mController.newContainer(activity, taskId); container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId) final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(bounds); .getWindowingModeForSplitTaskFragment(bounds);
createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(), final IBinder reparentActivityToken = activity.getActivityToken();
bounds, windowingMode); createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
bounds, windowingMode, reparentActivityToken);
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
activity.getActivityToken()); reparentActivityToken);
} else { } else {
resizeTaskFragmentIfRegistered(wct, container, bounds); resizeTaskFragmentIfRegistered(wct, container, bounds);
final int windowingMode = mController.getTaskContainer(taskId) final int windowingMode = mController.getTaskContainer(taskId)

View File

@@ -141,12 +141,26 @@ class TaskFragmentContainer {
mToken = new Binder("TaskFragmentContainer"); mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer; mTaskContainer = taskContainer;
if (pairedPrimaryContainer != null) { if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) { if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task"); "pairedPrimaryContainer must be in the same Task");
} }
final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer); final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
taskContainer.mContainers.add(primaryIndex + 1, this); taskContainer.mContainers.add(primaryIndex + 1, this);
} else if (pendingAppearedActivity != null) {
// The TaskFragment will be positioned right above the pending appeared Activity. If any
// existing TaskFragment is empty with pending Intent, it is likely that the Activity of
// the pending Intent hasn't been created yet, so the new Activity should be below the
// empty TaskFragment.
int i = taskContainer.mContainers.size() - 1;
for (; i >= 0; i--) {
final TaskFragmentContainer container = taskContainer.mContainers.get(i);
if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
break;
}
}
taskContainer.mContainers.add(i + 1, this);
} else { } else {
taskContainer.mContainers.add(this); taskContainer.mContainers.add(this);
} }
@@ -500,6 +514,8 @@ class TaskFragmentContainer {
} }
if (!shouldFinishDependent) { if (!shouldFinishDependent) {
// Always finish the placeholder when the primary is finished.
finishPlaceholderIfAny(wct, presenter);
return; return;
} }
@@ -526,6 +542,28 @@ class TaskFragmentContainer {
mActivitiesToFinishOnExit.clear(); mActivitiesToFinishOnExit.clear();
} }
@GuardedBy("mController.mLock")
private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
@NonNull SplitPresenter presenter) {
final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
for (TaskFragmentContainer container : mContainersToFinishOnExit) {
if (container.mIsFinished) {
continue;
}
final SplitContainer splitContainer = mController.getActiveSplitForContainers(
this, container);
if (splitContainer != null && splitContainer.isPlaceholderContainer()
&& splitContainer.getSecondaryContainer() == container) {
// Remove the placeholder secondary TaskFragment.
containersToRemove.add(container);
}
}
mContainersToFinishOnExit.removeAll(containersToRemove);
for (TaskFragmentContainer container : containersToRemove) {
container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
}
}
boolean isFinished() { boolean isFinished() {
return mIsFinished; return mIsFinished;
} }

View File

@@ -154,16 +154,51 @@ public class TaskFragmentContainerTest {
null /* pendingAppearedIntent */, taskContainer, mController, null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */); null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity); doReturn(container1).when(mController).getContainerWithActivity(mActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction();
// The activity is requested to be reparented, so don't finish it. // The activity is requested to be reparented, so don't finish it.
container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); container0.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
verify(mTransaction, never()).finishActivity(any()); verify(mTransaction, never()).finishActivity(any());
verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken()); verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
verify(mController).removeContainer(container0); verify(mController).removeContainer(container0);
} }
@Test
public void testFinish_alwaysFinishPlaceholder() {
// Register container1 as a placeholder
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity);
container0.setInfo(mTransaction, info0);
final Activity placeholderActivity = createMockActivity();
final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity,
null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity);
container1.setInfo(mTransaction, info1);
final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(new Intent(),
mActivity::equals, (java.util.function.Predicate) i -> false,
(java.util.function.Predicate) w -> true)
.setDefaultSplitAttributes(splitAttributes)
.build();
mController.registerSplit(mTransaction, container0, mActivity, container1, rule,
splitAttributes);
// The placeholder TaskFragment should be finished even if the primary is finished with
// shouldFinishDependent = false.
container0.finish(false /* shouldFinishDependent */, mPresenter, mTransaction, mController);
assertTrue(container0.isFinished());
assertTrue(container1.isFinished());
verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
verify(mPresenter).deleteTaskFragment(mTransaction, container1.getTaskFragmentToken());
verify(mController).removeContainer(container0);
verify(mController).removeContainer(container1);
}
@Test @Test
public void testSetInfo() { public void testSetInfo() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
@@ -493,8 +528,6 @@ public class TaskFragmentContainerTest {
final TaskFragmentContainer tf1 = new TaskFragmentContainer( final TaskFragmentContainer tf1 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
null /* pairedPrimaryTaskFragment */); null /* pairedPrimaryTaskFragment */);
taskContainer.mContainers.add(tf0);
taskContainer.mContainers.add(tf1);
// When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
// right above tf0. // right above tf0.
@@ -505,6 +538,26 @@ public class TaskFragmentContainerTest {
assertEquals(2, taskContainer.indexOf(tf1)); assertEquals(2, taskContainer.indexOf(tf1));
} }
@Test
public void testNewContainerWithPairedPendingAppearedActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer tf0 = new TaskFragmentContainer(
createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
final TaskFragmentContainer tf1 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
// When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any
// TaskFragment without any Activity.
final TaskFragmentContainer tf2 = new TaskFragmentContainer(
createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
assertEquals(0, taskContainer.indexOf(tf0));
assertEquals(1, taskContainer.indexOf(tf2));
assertEquals(2, taskContainer.indexOf(tf1));
}
@Test @Test
public void testIsVisible() { public void testIsVisible() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();

View File

@@ -1949,6 +1949,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
creationParams.getPairedPrimaryFragmentToken()); creationParams.getPairedPrimaryFragmentToken());
final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment); final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP; position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
} else if (creationParams.getPairedActivityToken() != null) {
// When there is a paired Activity, we want to place the new TaskFragment right above
// the paired Activity to make sure the Activity position is not changed after reparent.
final ActivityRecord pairedActivity = ActivityRecord.forTokenLocked(
creationParams.getPairedActivityToken());
final int pairedPosition = ownerTask.mChildren.indexOf(pairedActivity);
position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
} else { } else {
position = POSITION_TOP; position = POSITION_TOP;
} }

View File

@@ -795,6 +795,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
task.mChildren.indexOf(activityOnTop)); task.mChildren.indexOf(activityOnTop));
} }
@Test
public void testApplyTransaction_createTaskFragment_withPairedActivityToken() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activityAtBottom = createActivityRecord(task);
final int uid = Binder.getCallingUid();
activityAtBottom.info.applicationInfo.uid = uid;
activityAtBottom.getTask().effectiveUid = uid;
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
.createActivityCount(1)
.build();
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
final IBinder fragmentToken1 = new Binder();
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken1, activityAtBottom.token)
.setPairedActivityToken(activityAtBottom.token)
.build();
mTransaction.setTaskFragmentOrganizer(mIOrganizer);
mTransaction.createTaskFragment(params);
assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment.
final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
fragmentToken1);
assertNotNull(taskFragment);
// The new TaskFragment should be positioned right above the paired activity.
assertEquals(task.mChildren.indexOf(activityAtBottom) + 1,
task.mChildren.indexOf(taskFragment));
// The top TaskFragment should remain on top.
assertEquals(task.mChildren.indexOf(taskFragment) + 1,
task.mChildren.indexOf(mTaskFragment));
}
@Test @Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() { public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached(); doReturn(true).when(mTaskFragment).isAttached();