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:
@@ -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
|
||||
* always-expand Activity.
|
||||
*
|
||||
* This should not be used with {@link #mPairedActivityToken}.
|
||||
*/
|
||||
@Nullable
|
||||
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(
|
||||
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
|
||||
@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;
|
||||
mFragmentToken = fragmentToken;
|
||||
mOwnerToken = ownerToken;
|
||||
mInitialBounds.set(initialBounds);
|
||||
mWindowingMode = windowingMode;
|
||||
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
|
||||
mPairedActivityToken = pairedActivityToken;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -121,6 +143,15 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
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) {
|
||||
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
|
||||
mFragmentToken = in.readStrongBinder();
|
||||
@@ -128,6 +159,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
mInitialBounds.readFromParcel(in);
|
||||
mWindowingMode = in.readInt();
|
||||
mPairedPrimaryFragmentToken = in.readStrongBinder();
|
||||
mPairedActivityToken = in.readStrongBinder();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -139,6 +171,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
mInitialBounds.writeToParcel(dest, flags);
|
||||
dest.writeInt(mWindowingMode);
|
||||
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
|
||||
dest.writeStrongBinder(mPairedActivityToken);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -164,6 +197,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
+ " initialBounds=" + mInitialBounds
|
||||
+ " windowingMode=" + mWindowingMode
|
||||
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
|
||||
+ " pairedActivityToken=" + mPairedActivityToken
|
||||
+ "}";
|
||||
}
|
||||
|
||||
@@ -194,6 +228,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
@Nullable
|
||||
private IBinder mPairedPrimaryFragmentToken;
|
||||
|
||||
@Nullable
|
||||
private IBinder mPairedActivityToken;
|
||||
|
||||
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
|
||||
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
|
||||
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
|
||||
* 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.
|
||||
* @hide
|
||||
*/
|
||||
@@ -233,11 +272,32 @@ public final class TaskFragmentCreationParams implements Parcelable {
|
||||
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. */
|
||||
@NonNull
|
||||
public TaskFragmentCreationParams build() {
|
||||
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
|
||||
mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
|
||||
mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
|
||||
mPairedActivityToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,10 +191,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
*/
|
||||
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
|
||||
@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(
|
||||
getOrganizerToken(), fragmentToken, ownerToken)
|
||||
.setInitialBounds(bounds)
|
||||
.setWindowingMode(windowingMode)
|
||||
.setPairedActivityToken(pairedActivityToken)
|
||||
.build();
|
||||
createTaskFragment(wct, fragmentOptions);
|
||||
}
|
||||
@@ -216,8 +231,10 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
|
||||
@WindowingMode int windowingMode, @NonNull Activity activity) {
|
||||
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
|
||||
final IBinder reparentActivityToken = activity.getActivityToken();
|
||||
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
|
||||
reparentActivityToken);
|
||||
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
|
||||
}
|
||||
|
||||
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
|
||||
|
||||
@@ -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
|
||||
* secondary and primary, if available.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
SplitContainer getActiveSplitForContainers(
|
||||
@NonNull TaskFragmentContainer firstContainer,
|
||||
|
||||
@@ -268,10 +268,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
container = mController.newContainer(activity, taskId);
|
||||
final int windowingMode = mController.getTaskContainer(taskId)
|
||||
.getWindowingModeForSplitTaskFragment(bounds);
|
||||
createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(),
|
||||
bounds, windowingMode);
|
||||
final IBinder reparentActivityToken = activity.getActivityToken();
|
||||
createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
|
||||
bounds, windowingMode, reparentActivityToken);
|
||||
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
|
||||
activity.getActivityToken());
|
||||
reparentActivityToken);
|
||||
} else {
|
||||
resizeTaskFragmentIfRegistered(wct, container, bounds);
|
||||
final int windowingMode = mController.getTaskContainer(taskId)
|
||||
|
||||
@@ -141,12 +141,26 @@ class TaskFragmentContainer {
|
||||
mToken = new Binder("TaskFragmentContainer");
|
||||
mTaskContainer = taskContainer;
|
||||
if (pairedPrimaryContainer != null) {
|
||||
// The TaskFragment will be positioned right above the paired container.
|
||||
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
|
||||
throw new IllegalArgumentException(
|
||||
"pairedPrimaryContainer must be in the same Task");
|
||||
}
|
||||
final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
|
||||
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 {
|
||||
taskContainer.mContainers.add(this);
|
||||
}
|
||||
@@ -500,6 +514,8 @@ class TaskFragmentContainer {
|
||||
}
|
||||
|
||||
if (!shouldFinishDependent) {
|
||||
// Always finish the placeholder when the primary is finished.
|
||||
finishPlaceholderIfAny(wct, presenter);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -526,6 +542,28 @@ class TaskFragmentContainer {
|
||||
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() {
|
||||
return mIsFinished;
|
||||
}
|
||||
|
||||
@@ -154,16 +154,51 @@ public class TaskFragmentContainerTest {
|
||||
null /* pendingAppearedIntent */, taskContainer, mController,
|
||||
null /* pairedPrimaryContainer */);
|
||||
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
|
||||
// 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(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
|
||||
verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
|
||||
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
|
||||
public void testSetInfo() {
|
||||
final TaskContainer taskContainer = createTestTaskContainer();
|
||||
@@ -493,8 +528,6 @@ public class TaskFragmentContainerTest {
|
||||
final TaskFragmentContainer tf1 = new TaskFragmentContainer(
|
||||
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
|
||||
null /* pairedPrimaryTaskFragment */);
|
||||
taskContainer.mContainers.add(tf0);
|
||||
taskContainer.mContainers.add(tf1);
|
||||
|
||||
// When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
|
||||
// right above tf0.
|
||||
@@ -505,6 +538,26 @@ public class TaskFragmentContainerTest {
|
||||
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
|
||||
public void testIsVisible() {
|
||||
final TaskContainer taskContainer = createTestTaskContainer();
|
||||
|
||||
@@ -1949,6 +1949,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
|
||||
creationParams.getPairedPrimaryFragmentToken());
|
||||
final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
|
||||
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 {
|
||||
position = POSITION_TOP;
|
||||
}
|
||||
|
||||
@@ -795,6 +795,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
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
|
||||
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
|
||||
doReturn(true).when(mTaskFragment).isAttached();
|
||||
|
||||
Reference in New Issue
Block a user