Merge "Fix ActivityEmbedding placeholder launching above fullscreen activity" into tm-qpr-dev am: 4706a5c650

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

Change-Id: I8e51bd63e2b25065d0f98eaa1ae176cd99858be7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chris Li
2022-12-09 00:30:42 +00:00
committed by Automerger Merge Worker
11 changed files with 226 additions and 89 deletions

View File

@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode; import static android.app.WindowConfiguration.WindowingMode;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi; import android.annotation.TestApi;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.IBinder; import android.os.IBinder;
@@ -57,14 +58,33 @@ public final class TaskFragmentCreationParams implements Parcelable {
/** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */ /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
@WindowingMode @WindowingMode
private int mWindowingMode = WINDOWING_MODE_UNDEFINED; private final int mWindowingMode;
/**
* The fragment token of the paired primary TaskFragment.
* When it is set, the new TaskFragment will be positioned right above the paired TaskFragment.
* Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
*
* This is different from {@link WindowContainerTransaction#setAdjacentTaskFragments} as we may
* set this when the pair of TaskFragments are stacked, while adjacent is only set on the pair
* of TaskFragments that are in split.
*
* This is needed in case we need to launch a placeholder Activity to split below a transparent
* always-expand Activity.
*/
@Nullable
private final IBinder mPairedPrimaryFragmentToken;
private TaskFragmentCreationParams( private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
mOrganizer = organizer; mOrganizer = organizer;
mFragmentToken = fragmentToken; mFragmentToken = fragmentToken;
mOwnerToken = ownerToken; mOwnerToken = ownerToken;
mInitialBounds.set(initialBounds);
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
} }
@NonNull @NonNull
@@ -92,12 +112,22 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mWindowingMode; return mWindowingMode;
} }
/**
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@Nullable
public IBinder getPairedPrimaryFragmentToken() {
return mPairedPrimaryFragmentToken;
}
private TaskFragmentCreationParams(Parcel in) { private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in); mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder(); mFragmentToken = in.readStrongBinder();
mOwnerToken = in.readStrongBinder(); mOwnerToken = in.readStrongBinder();
mInitialBounds.readFromParcel(in); mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt(); mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
} }
/** @hide */ /** @hide */
@@ -108,6 +138,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeStrongBinder(mOwnerToken); dest.writeStrongBinder(mOwnerToken);
mInitialBounds.writeToParcel(dest, flags); mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode); dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
} }
@NonNull @NonNull
@@ -132,6 +163,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " ownerToken=" + mOwnerToken + " ownerToken=" + mOwnerToken
+ " initialBounds=" + mInitialBounds + " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode + " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ "}"; + "}";
} }
@@ -159,6 +191,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
@WindowingMode @WindowingMode
private int mWindowingMode = WINDOWING_MODE_UNDEFINED; private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
@Nullable
private IBinder mPairedPrimaryFragmentToken;
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;
@@ -180,14 +215,29 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this; return this;
} }
/**
* Sets the fragment token of the paired primary TaskFragment.
* When it is set, the new TaskFragment will be positioned right above the paired
* TaskFragment. Otherwise, the new TaskFragment will be positioned on the top of the Task
* by default.
*
* This is needed in case we need to launch a placeholder Activity to split below a
* transparent always-expand Activity.
*
* TODO(b/232476698): remove the hide with adding CTS for this in next release.
* @hide
*/
@NonNull
public Builder setPairedPrimaryFragmentToken(@Nullable IBinder fragmentToken) {
mPairedPrimaryFragmentToken = fragmentToken;
return this;
}
/** Constructs the options to create TaskFragment with. */ /** Constructs the options to create TaskFragment with. */
@NonNull @NonNull
public TaskFragmentCreationParams build() { public TaskFragmentCreationParams build() {
final TaskFragmentCreationParams result = new TaskFragmentCreationParams( return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mOrganizer, mFragmentToken, mOwnerToken); mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
result.mInitialBounds.set(mInitialBounds);
result.mWindowingMode = mWindowingMode;
return result;
} }
} }
} }

View File

@@ -133,8 +133,18 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
} }
// Create a TaskFragment for the secondary activity. // Create a TaskFragment for the secondary activity.
createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
secondaryFragmentBounds, windowingMode, activityIntent, getOrganizerToken(), secondaryFragmentToken, ownerToken)
.setInitialBounds(secondaryFragmentBounds)
.setWindowingMode(windowingMode)
// Make sure to set the paired fragment token so that the new TaskFragment will be
// positioned right above the paired TaskFragment.
// This is needed in case we need to launch a placeholder Activity to split below a
// transparent always-expand Activity.
.setPairedPrimaryFragmentToken(launchingFragmentToken)
.build();
createTaskFragment(wct, fragmentOptions);
wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions); activityOptions);
// Set adjacent to each other so that the containers below will be invisible. // Set adjacent to each other so that the containers below will be invisible.
@@ -173,8 +183,21 @@ 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) {
final TaskFragmentCreationParams fragmentOptions = final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode); getOrganizerToken(), fragmentToken, ownerToken)
.setInitialBounds(bounds)
.setWindowingMode(windowingMode)
.build();
createTaskFragment(wct, fragmentOptions);
}
void createTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentCreationParams fragmentOptions) {
if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken="
+ fragmentOptions.getFragmentToken());
}
wct.createTaskFragment(fragmentOptions); wct.createTaskFragment(fragmentOptions);
} }
@@ -189,18 +212,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken()); wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
} }
/**
* @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.
*/
private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
@WindowingMode int windowingMode, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
}
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) { @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null; WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
@@ -238,22 +249,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null); wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
} }
TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken=" + fragmentToken);
}
return new TaskFragmentCreationParams.Builder(
getOrganizerToken(),
fragmentToken,
ownerToken)
.setInitialBounds(bounds)
.setWindowingMode(windowingMode)
.build();
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) { @Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) { if (!mFragmentInfos.containsKey(fragmentToken)) {

View File

@@ -1218,14 +1218,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) { @NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
activityInTask, taskId); activityInTask, taskId, null /* pairedPrimaryContainer */);
} }
@GuardedBy("mLock") @GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) { @NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId); activityInTask, taskId, null /* pairedPrimaryContainer */);
} }
/** /**
@@ -1237,10 +1237,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param activityInTask activity in the same Task so that we can get the Task bounds * @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed. * if needed.
* @param taskId parent Task of the new TaskFragment. * @param taskId parent Task of the new TaskFragment.
* @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
*/ */
@GuardedBy("mLock") @GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
if (activityInTask == null) { if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,"); throw new IllegalArgumentException("activityInTask must not be null,");
} }
@@ -1249,7 +1252,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
pendingAppearedIntent, taskContainer, this); pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
return container; return container;
} }

View File

@@ -36,6 +36,7 @@ import android.util.Size;
import android.view.View; import android.view.View;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowMetrics; import android.view.WindowMetrics;
import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
@@ -307,10 +308,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} }
final int taskId = primaryContainer.getTaskId(); final int taskId = primaryContainer.getTaskId();
final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, final TaskFragmentContainer secondaryContainer = mController.newContainer(
launchingActivity, taskId); null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
final int windowingMode = mController.getTaskContainer(taskId) // Pass in the primary container to make sure it is added right above the primary.
.getWindowingModeForSplitTaskFragment(primaryRectBounds); primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes); rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -412,17 +416,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} }
@Override @Override
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, void createTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { @NonNull TaskFragmentCreationParams fragmentOptions) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken); final TaskFragmentContainer container = mController.getContainer(
fragmentOptions.getFragmentToken());
if (container == null) { if (container == null) {
throw new IllegalStateException( throw new IllegalStateException(
"Creating a task fragment that is not registered with controller."); "Creating a task fragment that is not registered with controller.");
} }
container.setLastRequestedBounds(bounds); container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
container.setLastRequestedWindowingMode(windowingMode); container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); super.createTaskFragment(wct, fragmentOptions);
} }
@Override @Override

View File

@@ -118,10 +118,12 @@ class TaskFragmentContainer {
/** /**
* Creates a container with an existing activity that will be re-parented to it in a window * Creates a container with an existing activity that will be re-parented to it in a window
* container transaction. * container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
*/ */
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller) { @NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null) if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) { || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -130,7 +132,16 @@ class TaskFragmentContainer {
mController = controller; mController = controller;
mToken = new Binder("TaskFragmentContainer"); mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer; mTaskContainer = taskContainer;
if (pairedPrimaryContainer != null) {
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 {
taskContainer.mContainers.add(this); taskContainer.mContainers.add(this);
}
if (pendingAppearedActivity != null) { if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity); addPendingAppearedActivity(pendingAppearedActivity);
} }

View File

@@ -102,7 +102,7 @@ public class JetpackTaskFragmentOrganizerTest {
public void testExpandTaskFragment() { public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mSplitController); new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockInfo(container); final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
container.setInfo(mTransaction, info); container.setInfo(mTransaction, info);

View File

@@ -169,7 +169,7 @@ public class SplitControllerTest {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
// tf1 has no running activity so is not active. // tf1 has no running activity so is not active.
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mSplitController); new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
// tf2 has running activity so is active. // tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount(); doReturn(1).when(tf2).getRunningActivityCount();
@@ -375,7 +375,7 @@ public class SplitControllerTest {
final Intent intent = new Intent(); final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
intent, taskContainer, mSplitController); intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final SplitController.ActivityStartMonitor monitor = final SplitController.ActivityStartMonitor monitor =
mSplitController.getActivityStartMonitor(); mSplitController.getActivityStartMonitor();
@@ -609,7 +609,7 @@ public class SplitControllerTest {
false /* isOnReparent */); false /* isOnReparent */);
assertFalse(result); assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
} }
@Test @Test
@@ -771,7 +771,7 @@ public class SplitControllerTest {
false /* isOnReparent */); false /* isOnReparent */);
assertTrue(result); assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
} }
@@ -813,7 +813,7 @@ public class SplitControllerTest {
false /* isOnReparent */); false /* isOnReparent */);
assertTrue(result); assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
} }

View File

@@ -122,7 +122,7 @@ public class TaskContainerTest {
assertTrue(taskContainer.isEmpty()); assertTrue(taskContainer.isEmpty());
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController); new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertFalse(taskContainer.isEmpty()); assertFalse(taskContainer.isEmpty());
@@ -138,11 +138,11 @@ public class TaskContainerTest {
assertNull(taskContainer.getTopTaskFragmentContainer()); assertNull(taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController); new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer()); assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController); new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer()); assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
} }

View File

@@ -94,18 +94,21 @@ public class TaskFragmentContainerTest {
// One of the activity and the intent must be non-null // One of the activity and the intent must be non-null
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
() -> new TaskFragmentContainer(null, null, taskContainer, mController)); () -> new TaskFragmentContainer(null, null, taskContainer, mController,
null /* pairedPrimaryContainer */));
// One of the activity and the intent must be null. // One of the activity and the intent must be null.
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
() -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController)); () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController,
null /* pairedPrimaryContainer */));
} }
@Test @Test
public void testFinish() { public void testFinish() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController); null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
doReturn(container).when(mController).getContainerWithActivity(mActivity); doReturn(container).when(mController).getContainerWithActivity(mActivity);
// Only remove the activity, but not clear the reference until appeared. // Only remove the activity, but not clear the reference until appeared.
@@ -137,12 +140,14 @@ public class TaskFragmentContainerTest {
public void testFinish_notFinishActivityThatIsReparenting() { public void testFinish_notFinishActivityThatIsReparenting() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController); null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
container0.setInfo(mTransaction, info); container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment. // Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity, final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController); null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity); doReturn(container1).when(mController).getContainerWithActivity(mActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -159,7 +164,8 @@ public class TaskFragmentContainerTest {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side. // Pending activity should be cleared when it has appeared on server side.
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity, final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController); null /* pendingAppearedIntent */, taskContainer, mController,
null /* pairedPrimaryContainer */);
assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains( assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
mActivity.getActivityToken())); mActivity.getActivityToken()));
@@ -172,7 +178,8 @@ public class TaskFragmentContainerTest {
// Pending intent should be cleared when the container becomes non-empty. // Pending intent should be cleared when the container becomes non-empty.
final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer( final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
null /* pendingAppearedActivity */, mIntent, taskContainer, mController); null /* pendingAppearedActivity */, mIntent, taskContainer, mController,
null /* pairedPrimaryContainer */);
assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent()); assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
@@ -187,7 +194,7 @@ public class TaskFragmentContainerTest {
public void testIsWaitingActivityAppear() { public void testIsWaitingActivityAppear() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container.isWaitingActivityAppear()); assertTrue(container.isWaitingActivityAppear());
@@ -209,7 +216,7 @@ public class TaskFragmentContainerTest {
doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertNull(container.mAppearEmptyTimeout); assertNull(container.mAppearEmptyTimeout);
@@ -249,7 +256,7 @@ public class TaskFragmentContainerTest {
public void testCollectNonFinishingActivities() { public void testCollectNonFinishingActivities() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
List<Activity> activities = container.collectNonFinishingActivities(); List<Activity> activities = container.collectNonFinishingActivities();
assertTrue(activities.isEmpty()); assertTrue(activities.isEmpty());
@@ -277,7 +284,7 @@ public class TaskFragmentContainerTest {
public void testAddPendingActivity() { public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
assertEquals(1, container.collectNonFinishingActivities().size()); assertEquals(1, container.collectNonFinishingActivities().size());
@@ -291,9 +298,9 @@ public class TaskFragmentContainerTest {
public void testIsAbove() { public void testIsAbove() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container1.isAbove(container0)); assertTrue(container1.isAbove(container0));
assertFalse(container0.isAbove(container1)); assertFalse(container0.isAbove(container1));
@@ -303,7 +310,7 @@ public class TaskFragmentContainerTest {
public void testGetBottomMostActivity() { public void testGetBottomMostActivity() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
assertEquals(mActivity, container.getBottomMostActivity()); assertEquals(mActivity, container.getBottomMostActivity());
@@ -320,7 +327,7 @@ public class TaskFragmentContainerTest {
public void testOnActivityDestroyed() { public void testOnActivityDestroyed() {
final TaskContainer taskContainer = createTestTaskContainer(mController); final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
final List<IBinder> activities = new ArrayList<>(); final List<IBinder> activities = new ArrayList<>();
activities.add(mActivity.getActivityToken()); activities.add(mActivity.getActivityToken());
@@ -340,7 +347,7 @@ public class TaskFragmentContainerTest {
// True if no info set. // True if no info set.
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
spyOn(taskContainer); spyOn(taskContainer);
doReturn(true).when(taskContainer).isVisible(); doReturn(true).when(taskContainer).isVisible();
@@ -403,7 +410,7 @@ public class TaskFragmentContainerTest {
public void testHasAppearedActivity() { public void testHasAppearedActivity() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
assertFalse(container.hasAppearedActivity(mActivity.getActivityToken())); assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
@@ -420,7 +427,7 @@ public class TaskFragmentContainerTest {
public void testHasPendingAppearedActivity() { public void testHasPendingAppearedActivity() {
final TaskContainer taskContainer = createTestTaskContainer(); final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken())); assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
@@ -437,9 +444,9 @@ public class TaskFragmentContainerTest {
public void testHasActivity() { public void testHasActivity() {
final TaskContainer taskContainer = createTestTaskContainer(mController); final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */, final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController); mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
// Activity is pending appeared on container2. // Activity is pending appeared on container2.
container2.addPendingAppearedActivity(mActivity); container2.addPendingAppearedActivity(mActivity);
@@ -472,6 +479,27 @@ public class TaskFragmentContainerTest {
assertTrue(container2.hasActivity(mActivity.getActivityToken())); assertTrue(container2.hasActivity(mActivity.getActivityToken()));
} }
@Test
public void testNewContainerWithPairedPrimaryContainer() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer tf0 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
null /* pairedPrimaryTaskFragment */);
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.
final TaskFragmentContainer tf2 = new TaskFragmentContainer(
null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0);
assertEquals(0, taskContainer.indexOf(tf0));
assertEquals(1, taskContainer.indexOf(tf2));
assertEquals(2, taskContainer.indexOf(tf1));
}
/** 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);

View File

@@ -1891,7 +1891,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// actions. // actions.
taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
ownerActivity.getUid(), ownerActivity.info.processName); ownerActivity.getUid(), ownerActivity.info.processName);
ownerTask.addChild(taskFragment, POSITION_TOP); final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
// right above the paired one to make sure there is no other window in between.
final TaskFragment pairedPrimaryTaskFragment = getTaskFragment(
creationParams.getPairedPrimaryFragmentToken());
final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
} else {
position = POSITION_TOP;
}
ownerTask.addChild(taskFragment, position);
taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setWindowingMode(creationParams.getWindowingMode());
taskFragment.setBounds(creationParams.getInitialBounds()); taskFragment.setBounds(creationParams.getInitialBounds());
mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);

View File

@@ -552,10 +552,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test @Test
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() { public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent); final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken); createTaskFragmentFromOrganizer(mTransaction, ownerActivity, mFragmentToken);
mTransaction.startActivityInTaskFragment( mTransaction.startActivityInTaskFragment(
mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */); mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -564,7 +563,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertApplyTransactionAllowed(mTransaction); assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment. // Successfully created a TaskFragment.
final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken); final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
mFragmentToken);
assertNotNull(taskFragment); assertNotNull(taskFragment);
assertEquals(ownerActivity.getTask(), taskFragment.getTask()); assertEquals(ownerActivity.getTask(), taskFragment.getTask());
} }
@@ -702,6 +702,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
} }
@Test
public void testApplyTransaction_createTaskFragment_withPairedPrimaryFragmentToken() {
final Task task = createTask(mDisplayContent);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
.createActivityCount(1)
.build();
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
final ActivityRecord activityOnTop = createActivityRecord(task);
final int uid = Binder.getCallingUid();
activityOnTop.info.applicationInfo.uid = uid;
activityOnTop.getTask().effectiveUid = uid;
final IBinder fragmentToken1 = new Binder();
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken1, activityOnTop.token)
.setPairedPrimaryFragmentToken(mFragmentToken)
.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 TaskFragment.
assertEquals(task.mChildren.indexOf(mTaskFragment) + 1,
task.mChildren.indexOf(taskFragment));
// The top activity should remain on top.
assertEquals(task.mChildren.indexOf(taskFragment) + 1,
task.mChildren.indexOf(activityOnTop));
}
@Test @Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() { public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached(); doReturn(true).when(mTaskFragment).isAttached();