Merge "Handle onActivityReparentToTask for activity in other process" into tm-dev am: 9b36cc92eb am: 3829254960 am: e5d99bf4e6
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17950644 Change-Id: I2bb8e5173f9bbb8ec98ac3a580e8344a2d6f3f13 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -234,13 +234,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
|
||||
@NonNull IBinder activityToken) {
|
||||
// If the activity belongs to the current app process, we treat it as a new activity launch.
|
||||
final Activity activity = ActivityThread.currentActivityThread().getActivity(activityToken);
|
||||
final Activity activity = getActivity(activityToken);
|
||||
if (activity != null) {
|
||||
onActivityCreated(activity);
|
||||
updateCallbackIfNecessary();
|
||||
return;
|
||||
}
|
||||
// TODO: handle for activity in other process.
|
||||
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.isInPictureInPicture()) {
|
||||
// We don't embed activity when it is in PIP.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the activity belongs to a different app process, we treat it as starting new intent,
|
||||
// since both actions might result in a new activity that should appear in an organized
|
||||
// TaskFragment.
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
|
||||
activityIntent, null /* launchingActivity */);
|
||||
if (targetContainer == null) {
|
||||
// When there is no split rule matched, try to place it in the top container like a
|
||||
// normal launch.
|
||||
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
}
|
||||
if (targetContainer == null) {
|
||||
return;
|
||||
}
|
||||
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken);
|
||||
mPresenter.applyTransaction(wct);
|
||||
// Because the activity does not belong to the organizer process, we wait until
|
||||
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
|
||||
}
|
||||
|
||||
/** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
|
||||
@@ -327,16 +350,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
*/
|
||||
// TODO(b/190433398): Break down into smaller functions.
|
||||
void handleActivityCreated(@NonNull Activity launchedActivity) {
|
||||
if (isInPictureInPicture(launchedActivity)) {
|
||||
// We don't embed activity when it is in PIP.
|
||||
if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) {
|
||||
// We don't embed activity when it is in PIP, or finishing.
|
||||
return;
|
||||
}
|
||||
final List<EmbeddingRule> splitRules = getSplitRules();
|
||||
final TaskFragmentContainer currentContainer = getContainerWithActivity(
|
||||
launchedActivity.getActivityToken());
|
||||
final TaskFragmentContainer currentContainer = getContainerWithActivity(launchedActivity);
|
||||
|
||||
// Check if the activity is configured to always be expanded.
|
||||
if (shouldExpand(launchedActivity, null, splitRules)) {
|
||||
if (shouldExpand(launchedActivity, null /* intent */)) {
|
||||
if (shouldContainerBeExpanded(currentContainer)) {
|
||||
// Make sure that the existing container is expanded
|
||||
mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
|
||||
@@ -375,7 +396,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
|
||||
launchedActivity.getActivityToken());
|
||||
if (belowToken != null) {
|
||||
activityBelow = ActivityThread.currentActivityThread().getActivity(belowToken);
|
||||
activityBelow = getActivity(belowToken);
|
||||
}
|
||||
}
|
||||
if (activityBelow == null) {
|
||||
@@ -384,7 +405,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
|
||||
// Check if the split is already set.
|
||||
final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
|
||||
activityBelow.getActivityToken());
|
||||
activityBelow);
|
||||
if (currentContainer != null && activityBelowContainer != null) {
|
||||
final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer,
|
||||
activityBelowContainer);
|
||||
@@ -394,8 +415,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
}
|
||||
|
||||
final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity,
|
||||
splitRules);
|
||||
final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity);
|
||||
if (splitPairRule == null) {
|
||||
return;
|
||||
}
|
||||
@@ -409,8 +429,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
// We don't embed activity when it is in PIP.
|
||||
return;
|
||||
}
|
||||
final TaskFragmentContainer currentContainer = getContainerWithActivity(
|
||||
activity.getActivityToken());
|
||||
final TaskFragmentContainer currentContainer = getContainerWithActivity(activity);
|
||||
|
||||
if (currentContainer != null) {
|
||||
// Changes to activities in controllers are handled in
|
||||
@@ -442,12 +461,144 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
|
||||
}
|
||||
|
||||
/**
|
||||
* When we are trying to handle a new activity Intent, returns the {@link TaskFragmentContainer}
|
||||
* that we should reparent the new activity to if there is any embedding rule matched.
|
||||
*
|
||||
* @param wct {@link WindowContainerTransaction} including all the window change
|
||||
* requests. The caller is responsible to call
|
||||
* {@link android.window.TaskFragmentOrganizer#applyTransaction}.
|
||||
* @param taskId The Task to start the activity in.
|
||||
* @param intent The {@link Intent} for starting the new launched activity.
|
||||
* @param launchingActivity The {@link Activity} that starts the new activity. We will
|
||||
* prioritize to split the new activity with it if it is not
|
||||
* {@code null}.
|
||||
* @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
|
||||
* is no embedding rule matched.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
|
||||
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
|
||||
/*
|
||||
* We will check the following to see if there is any embedding rule matched:
|
||||
* 1. Whether the new activity intent should always expand.
|
||||
* 2. Whether the launching activity (if set) should be split with the new activity intent.
|
||||
* 3. Whether the top activity (if any) should be split with the new activity intent.
|
||||
* 4. Whether the top activity (if any) in other split should be split with the new
|
||||
* activity intent.
|
||||
*/
|
||||
|
||||
// 1. Whether the new activity intent should always expand.
|
||||
if (shouldExpand(null /* activity */, intent)) {
|
||||
return createEmptyExpandedContainer(wct, taskId, launchingActivity);
|
||||
}
|
||||
|
||||
// 2. Whether the launching activity (if set) should be split with the new activity intent.
|
||||
if (launchingActivity != null) {
|
||||
final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
|
||||
launchingActivity, intent, true /* respectClearTop */);
|
||||
if (container != null) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Whether the top activity (if any) should be split with the new activity intent.
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
|
||||
// There is no other activity in the Task to check split with.
|
||||
return null;
|
||||
}
|
||||
final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
final Activity topActivity = topContainer.getTopNonFinishingActivity();
|
||||
if (topActivity != null && topActivity != launchingActivity) {
|
||||
final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
|
||||
topActivity, intent, false /* respectClearTop */);
|
||||
if (container != null) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Whether the top activity (if any) in other split should be split with the new
|
||||
// activity intent.
|
||||
final SplitContainer topSplit = getActiveSplitForContainer(topContainer);
|
||||
if (topSplit == null) {
|
||||
return null;
|
||||
}
|
||||
final TaskFragmentContainer otherTopContainer =
|
||||
topSplit.getPrimaryContainer() == topContainer
|
||||
? topSplit.getSecondaryContainer()
|
||||
: topSplit.getPrimaryContainer();
|
||||
final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
|
||||
if (otherTopActivity != null && otherTopActivity != launchingActivity) {
|
||||
return getSecondaryContainerForSplitIfAny(wct, otherTopActivity, intent,
|
||||
false /* respectClearTop */);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
|
||||
*/
|
||||
@Nullable
|
||||
private TaskFragmentContainer createEmptyExpandedContainer(
|
||||
@NonNull WindowContainerTransaction wct, int taskId,
|
||||
@Nullable Activity launchingActivity) {
|
||||
// We need an activity in the organizer process in the same Task to use as the owner
|
||||
// activity, as well as to get the Task window info.
|
||||
final Activity activityInTask;
|
||||
if (launchingActivity != null) {
|
||||
activityInTask = launchingActivity;
|
||||
} else {
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
activityInTask = taskContainer != null
|
||||
? taskContainer.getTopNonFinishingActivity()
|
||||
: null;
|
||||
}
|
||||
if (activityInTask == null) {
|
||||
// Can't find any activity in the Task that we can use as the owner activity.
|
||||
return null;
|
||||
}
|
||||
final TaskFragmentContainer expandedContainer = newContainer(null /* activity */,
|
||||
activityInTask, taskId);
|
||||
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
|
||||
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
|
||||
return expandedContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container for the new activity intent to launch into as splitting with the primary
|
||||
* activity.
|
||||
*/
|
||||
@Nullable
|
||||
private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
|
||||
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
|
||||
@NonNull Intent intent, boolean respectClearTop) {
|
||||
final SplitPairRule splitRule = getSplitRule(primaryActivity, intent);
|
||||
if (splitRule == null) {
|
||||
return null;
|
||||
}
|
||||
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
|
||||
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
|
||||
&& (canReuseContainer(splitRule, splitContainer.getSplitRule())
|
||||
// TODO(b/231845476) we should always respect clearTop.
|
||||
|| !respectClearTop)) {
|
||||
// Can launch in the existing secondary container if the rules share the same
|
||||
// presentation.
|
||||
return splitContainer.getSecondaryContainer();
|
||||
}
|
||||
// Create a new TaskFragment to split with the primary activity for the new activity.
|
||||
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container that this activity is registered with. An activity can only belong to one
|
||||
* container, or no container at all.
|
||||
*/
|
||||
@Nullable
|
||||
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
|
||||
TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
|
||||
final IBinder activityToken = activity.getActivityToken();
|
||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|
||||
for (TaskFragmentContainer container : containers) {
|
||||
@@ -668,7 +819,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Returns the top active split container that has the provided container, if available.
|
||||
*/
|
||||
@Nullable
|
||||
private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
|
||||
private SplitContainer getActiveSplitForContainer(@Nullable TaskFragmentContainer container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
|
||||
if (splitContainers.isEmpty()) {
|
||||
return null;
|
||||
@@ -687,8 +841,9 @@ 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
|
||||
@Nullable
|
||||
private SplitContainer getActiveSplitForContainers(
|
||||
SplitContainer getActiveSplitForContainers(
|
||||
@NonNull TaskFragmentContainer firstContainer,
|
||||
@NonNull TaskFragmentContainer secondContainer) {
|
||||
final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
|
||||
@@ -718,15 +873,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
|
||||
boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
|
||||
final TaskFragmentContainer container = getContainerWithActivity(
|
||||
activity.getActivityToken());
|
||||
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||
// Don't launch placeholder if the container is occluded.
|
||||
if (container != null && container != getTopActiveContainer(container.getTaskId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SplitContainer splitContainer = container != null ? getActiveSplitForContainer(container)
|
||||
: null;
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(container);
|
||||
if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
|
||||
// Don't launch placeholder in primary split container
|
||||
return false;
|
||||
@@ -876,9 +1029,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* if available.
|
||||
*/
|
||||
@Nullable
|
||||
private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryActivityIntent, @NonNull List<EmbeddingRule> splitRules) {
|
||||
for (EmbeddingRule rule : splitRules) {
|
||||
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryActivityIntent) {
|
||||
for (EmbeddingRule rule : mSplitRules) {
|
||||
if (!(rule instanceof SplitPairRule)) {
|
||||
continue;
|
||||
}
|
||||
@@ -894,9 +1047,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Returns a split rule for the provided pair of primary and secondary activities if available.
|
||||
*/
|
||||
@Nullable
|
||||
private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Activity secondaryActivity, @NonNull List<EmbeddingRule> splitRules) {
|
||||
for (EmbeddingRule rule : splitRules) {
|
||||
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Activity secondaryActivity) {
|
||||
for (EmbeddingRule rule : mSplitRules) {
|
||||
if (!(rule instanceof SplitPairRule)) {
|
||||
continue;
|
||||
}
|
||||
@@ -933,16 +1086,24 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
int getTaskId(@NonNull Activity activity) {
|
||||
// Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an
|
||||
// IPC call.
|
||||
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||
return container != null ? container.getTaskId() : activity.getTaskId();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Activity getActivity(@NonNull IBinder activityToken) {
|
||||
return ActivityThread.currentActivityThread().getActivity(activityToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if an Activity with the provided component name should always be
|
||||
* expanded to occupy full task bounds. Such activity must not be put in a split.
|
||||
*/
|
||||
private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent,
|
||||
List<EmbeddingRule> splitRules) {
|
||||
if (splitRules == null) {
|
||||
return false;
|
||||
}
|
||||
for (EmbeddingRule rule : splitRules) {
|
||||
private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
|
||||
for (EmbeddingRule rule : mSplitRules) {
|
||||
if (!(rule instanceof ActivityRule)) {
|
||||
continue;
|
||||
}
|
||||
@@ -996,8 +1157,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
*/
|
||||
boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
|
||||
@NonNull Activity associatedActivity) {
|
||||
TaskFragmentContainer associatedContainer = getContainerWithActivity(
|
||||
associatedActivity.getActivityToken());
|
||||
final TaskFragmentContainer associatedContainer = getContainerWithActivity(
|
||||
associatedActivity);
|
||||
if (associatedContainer == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -1085,130 +1246,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return super.onStartActivity(who, intent, options);
|
||||
}
|
||||
|
||||
if (shouldExpand(null, intent, getSplitRules())) {
|
||||
setLaunchingInExpandedContainer(launchingActivity, options);
|
||||
} else if (!splitWithLaunchingActivity(launchingActivity, intent, options)) {
|
||||
setLaunchingInSameSideContainer(launchingActivity, intent, options);
|
||||
final int taskId = getTaskId(launchingActivity);
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
|
||||
taskId, intent, launchingActivity);
|
||||
if (launchedInTaskFragment != null) {
|
||||
mPresenter.applyTransaction(wct);
|
||||
// Amend the request to let the WM know that the activity should be placed in the
|
||||
// dedicated container.
|
||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||
launchedInTaskFragment.getTaskFragmentToken());
|
||||
}
|
||||
|
||||
return super.onStartActivity(who, intent, options);
|
||||
}
|
||||
|
||||
private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) {
|
||||
TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer(
|
||||
launchingActivity);
|
||||
|
||||
// Amend the request to let the WM know that the activity should be placed in the
|
||||
// dedicated container.
|
||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||
newContainer.getTaskFragmentToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the activity that is going to be started via the
|
||||
* {@code intent} should be paired with the {@code launchingActivity} and is set to be
|
||||
* launched in the side container.
|
||||
*/
|
||||
private boolean splitWithLaunchingActivity(Activity launchingActivity, Intent intent,
|
||||
Bundle options) {
|
||||
final SplitPairRule splitPairRule = getSplitRule(launchingActivity, intent,
|
||||
getSplitRules());
|
||||
if (splitPairRule == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is any existing side container to launch into.
|
||||
TaskFragmentContainer secondaryContainer = findSideContainerForNewLaunch(
|
||||
launchingActivity, splitPairRule);
|
||||
if (secondaryContainer == null) {
|
||||
// Create a new split with an empty side container.
|
||||
secondaryContainer = mPresenter
|
||||
.createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
|
||||
}
|
||||
|
||||
// Amend the request to let the WM know that the activity should be placed in the
|
||||
// dedicated container.
|
||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||
secondaryContainer.getTaskFragmentToken());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds if there is an existing split side {@link TaskFragmentContainer} that can be used
|
||||
* for the new rule.
|
||||
*/
|
||||
@Nullable
|
||||
private TaskFragmentContainer findSideContainerForNewLaunch(Activity launchingActivity,
|
||||
SplitPairRule splitPairRule) {
|
||||
final TaskFragmentContainer launchingContainer = getContainerWithActivity(
|
||||
launchingActivity.getActivityToken());
|
||||
if (launchingContainer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We only check if the launching activity is the primary of the split. We will check
|
||||
// if the launching activity is the secondary in #setLaunchingInSameSideContainer.
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer);
|
||||
if (splitContainer == null
|
||||
|| splitContainer.getPrimaryContainer() != launchingContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (canReuseContainer(splitPairRule, splitContainer.getSplitRule())) {
|
||||
return splitContainer.getSecondaryContainer();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the activity that is going to be started via the {@code intent} should be
|
||||
* paired with the existing top activity which is currently paired with the
|
||||
* {@code launchingActivity}. If so, set the activity to be launched in the same side
|
||||
* container of the {@code launchingActivity}.
|
||||
*/
|
||||
private void setLaunchingInSameSideContainer(Activity launchingActivity, Intent intent,
|
||||
Bundle options) {
|
||||
final TaskFragmentContainer launchingContainer = getContainerWithActivity(
|
||||
launchingActivity.getActivityToken());
|
||||
if (launchingContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer);
|
||||
if (splitContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (splitContainer.getSecondaryContainer() != launchingContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The launching activity is on the secondary container. Retrieve the primary
|
||||
// activity from the other container.
|
||||
Activity primaryActivity =
|
||||
splitContainer.getPrimaryContainer().getTopNonFinishingActivity();
|
||||
if (primaryActivity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SplitPairRule splitPairRule = getSplitRule(primaryActivity, intent,
|
||||
getSplitRules());
|
||||
if (splitPairRule == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Can only launch in the same container if the rules share the same presentation.
|
||||
if (!canReuseContainer(splitPairRule, splitContainer.getSplitRule())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Amend the request to let the WM know that the activity should be placed in the
|
||||
// dedicated container. This is necessary for the case that the activity is started
|
||||
// into a new Task, or new Task will be escaped from the current host Task and be
|
||||
// displayed in fullscreen.
|
||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||
launchingContainer.getTaskFragmentToken());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1228,8 +1279,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
|
||||
return false;
|
||||
}
|
||||
return rule1.getSplitRatio() == rule2.getSplitRatio()
|
||||
&& rule1.getLayoutDirection() == rule2.getLayoutDirection();
|
||||
final SplitPairRule pairRule1 = (SplitPairRule) rule1;
|
||||
final SplitPairRule pairRule2 = (SplitPairRule) rule2;
|
||||
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
|
||||
return pairRule1.getSplitRatio() == pairRule2.getSplitRatio()
|
||||
&& pairRule1.getLayoutDirection() == pairRule2.getLayoutDirection()
|
||||
&& pairRule1.getFinishPrimaryWithSecondary()
|
||||
== pairRule2.getFinishPrimaryWithSecondary()
|
||||
&& pairRule1.getFinishSecondaryWithPrimary()
|
||||
== pairRule2.getFinishSecondaryWithPrimary();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package androidx.window.extensions.embedding;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.app.WindowConfiguration.WindowingMode;
|
||||
@@ -100,10 +98,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* Creates a new split with the primary activity and an empty secondary container.
|
||||
* @return The newly created secondary container.
|
||||
*/
|
||||
TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity,
|
||||
@NonNull
|
||||
TaskFragmentContainer createNewSplitWithEmptySideContainer(
|
||||
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
|
||||
@NonNull SplitPairRule rule) {
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
|
||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||
isLtr(primaryActivity, rule));
|
||||
@@ -127,8 +125,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
|
||||
|
||||
applyTransaction(wct);
|
||||
|
||||
return secondaryContainer;
|
||||
}
|
||||
|
||||
@@ -155,8 +151,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
||||
isLtr(primaryActivity, rule));
|
||||
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
|
||||
secondaryActivity);
|
||||
TaskFragmentContainer containerToAvoid = primaryContainer;
|
||||
if (rule.shouldClearTop() && curSecondaryContainer != null) {
|
||||
// Do not reuse the current TaskFragment if the rule is to clear top.
|
||||
containerToAvoid = curSecondaryContainer;
|
||||
}
|
||||
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
|
||||
secondaryActivity, secondaryRectBounds, primaryContainer);
|
||||
secondaryActivity, secondaryRectBounds, containerToAvoid);
|
||||
|
||||
// Set adjacent to each other so that the containers below will be invisible.
|
||||
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
|
||||
@@ -166,21 +169,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
applyTransaction(wct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expanded container.
|
||||
*/
|
||||
TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
|
||||
final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */,
|
||||
launchingActivity, launchingActivity.getTaskId());
|
||||
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
createTaskFragment(wct, newContainer.getTaskFragmentToken(),
|
||||
launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
|
||||
|
||||
applyTransaction(wct);
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new container or resizes an existing container for activity to the provided bounds.
|
||||
* @param activity The activity to be re-parented to the container if necessary.
|
||||
@@ -189,8 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
private TaskFragmentContainer prepareContainerForActivity(
|
||||
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
|
||||
@NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
|
||||
TaskFragmentContainer container = mController.getContainerWithActivity(
|
||||
activity.getActivityToken());
|
||||
TaskFragmentContainer container = mController.getContainerWithActivity(activity);
|
||||
final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
|
||||
if (container == null || container == containerToAvoid) {
|
||||
container = mController.newContainer(activity, taskId);
|
||||
@@ -230,7 +217,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
isLtr(launchingActivity, rule));
|
||||
|
||||
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
|
||||
launchingActivity.getActivityToken());
|
||||
launchingActivity);
|
||||
if (primaryContainer == null) {
|
||||
primaryContainer = mController.newContainer(launchingActivity,
|
||||
launchingActivity.getTaskId());
|
||||
@@ -460,8 +447,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
@NonNull
|
||||
Rect getParentContainerBounds(@NonNull Activity activity) {
|
||||
final TaskFragmentContainer container = mController.getContainerWithActivity(
|
||||
activity.getActivityToken());
|
||||
final TaskFragmentContainer container = mController.getContainerWithActivity(activity);
|
||||
if (container != null) {
|
||||
return getParentContainerBounds(container);
|
||||
}
|
||||
|
||||
@@ -142,4 +142,23 @@ class TaskContainer {
|
||||
container.removePendingAppearedActivity(pendingAppearedActivity);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
TaskFragmentContainer getTopTaskFragmentContainer() {
|
||||
if (mContainers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return mContainers.get(mContainers.size() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Activity getTopNonFinishingActivity() {
|
||||
for (int i = mContainers.size() - 1; i >= 0; i--) {
|
||||
final Activity activity = mContainers.get(i).getTopNonFinishingActivity();
|
||||
if (activity != null) {
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.WindowConfiguration.WindowingMode;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
@@ -133,9 +132,8 @@ class TaskFragmentContainer {
|
||||
if (mInfo == null) {
|
||||
return allActivities;
|
||||
}
|
||||
ActivityThread activityThread = ActivityThread.currentActivityThread();
|
||||
for (IBinder token : mInfo.getActivities()) {
|
||||
Activity activity = activityThread.getActivity(token);
|
||||
Activity activity = mController.getActivity(token);
|
||||
if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) {
|
||||
allActivities.add(activity);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package androidx.window.extensions.embedding;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
|
||||
@@ -24,23 +27,31 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.Pair;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -53,6 +64,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -67,8 +79,8 @@ import java.util.List;
|
||||
public class SplitControllerTest {
|
||||
private static final int TASK_ID = 10;
|
||||
private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
|
||||
private static final float SPLIT_RATIO = 0.5f;
|
||||
|
||||
@Mock
|
||||
private Activity mActivity;
|
||||
@Mock
|
||||
private Resources mActivityResources;
|
||||
@@ -92,9 +104,9 @@ public class SplitControllerTest {
|
||||
final Configuration activityConfig = new Configuration();
|
||||
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
|
||||
activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
|
||||
doReturn(mActivityResources).when(mActivity).getResources();
|
||||
doReturn(activityConfig).when(mActivityResources).getConfiguration();
|
||||
doReturn(mHandler).when(mSplitController).getHandler();
|
||||
mActivity = createMockActivity();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -260,6 +272,246 @@ public class SplitControllerTest {
|
||||
|
||||
mSplitController.updateContainer(mTransaction, tf);
|
||||
|
||||
verify(mSplitPresenter).updateSplitContainer(eq(splitContainer), eq(tf), eq(mTransaction));
|
||||
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnActivityReparentToTask_sameProcess() {
|
||||
mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
|
||||
mActivity.getActivityToken());
|
||||
|
||||
// Treated as on activity created.
|
||||
verify(mSplitController).onActivityCreated(mActivity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnActivityReparentToTask_diffProcess() {
|
||||
// Create an empty TaskFragment to initialize for the Task.
|
||||
mSplitController.newContainer(null, mActivity, TASK_ID);
|
||||
final IBinder activityToken = new Binder();
|
||||
final Intent intent = new Intent();
|
||||
|
||||
mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
|
||||
|
||||
// Treated as starting new intent
|
||||
verify(mSplitController, never()).onActivityCreated(mActivity);
|
||||
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
|
||||
isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_withoutLaunchingActivity() {
|
||||
final Intent intent = new Intent();
|
||||
final ActivityRule expandRule = new ActivityRule.Builder(r -> false, i -> i == intent)
|
||||
.setShouldAlwaysExpand(true)
|
||||
.build();
|
||||
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
|
||||
|
||||
// No other activity available in the Task.
|
||||
TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(mTransaction,
|
||||
TASK_ID, intent, null /* launchingActivity */);
|
||||
assertNull(container);
|
||||
|
||||
// Task contains another activity that can be used as owner activity.
|
||||
createMockTaskFragmentContainer(mActivity);
|
||||
container = mSplitController.resolveStartActivityIntent(mTransaction,
|
||||
TASK_ID, intent, null /* launchingActivity */);
|
||||
assertNotNull(container);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_shouldExpand() {
|
||||
final Intent intent = new Intent();
|
||||
setupExpandRule(intent);
|
||||
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
|
||||
mTransaction, TASK_ID, intent, mActivity);
|
||||
|
||||
assertNotNull(container);
|
||||
assertTrue(container.areLastRequestedBoundsEqual(null));
|
||||
assertTrue(container.isLastRequestedWindowingModeEqual(WINDOWING_MODE_UNDEFINED));
|
||||
assertFalse(container.hasActivity(mActivity.getActivityToken()));
|
||||
verify(mSplitPresenter).createTaskFragment(mTransaction, container.getTaskFragmentToken(),
|
||||
mActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_shouldSplitWithLaunchingActivity() {
|
||||
final Intent intent = new Intent();
|
||||
setupSplitRule(mActivity, intent);
|
||||
|
||||
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
|
||||
mTransaction, TASK_ID, intent, mActivity);
|
||||
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||
mActivity);
|
||||
|
||||
assertSplitPair(primaryContainer, container);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_shouldSplitWithTopExpandActivity() {
|
||||
final Intent intent = new Intent();
|
||||
setupSplitRule(mActivity, intent);
|
||||
createMockTaskFragmentContainer(mActivity);
|
||||
|
||||
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
|
||||
mTransaction, TASK_ID, intent, null /* launchingActivity */);
|
||||
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||
mActivity);
|
||||
|
||||
assertSplitPair(primaryContainer, container);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_shouldSplitWithTopSecondaryActivity() {
|
||||
final Intent intent = new Intent();
|
||||
setupSplitRule(mActivity, intent);
|
||||
final Activity primaryActivity = createMockActivity();
|
||||
addSplitTaskFragments(primaryActivity, mActivity);
|
||||
|
||||
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
|
||||
mTransaction, TASK_ID, intent, null /* launchingActivity */);
|
||||
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||
mActivity);
|
||||
|
||||
assertSplitPair(primaryContainer, container);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveStartActivityIntent_shouldSplitWithTopPrimaryActivity() {
|
||||
final Intent intent = new Intent();
|
||||
setupSplitRule(mActivity, intent);
|
||||
final Activity secondaryActivity = createMockActivity();
|
||||
addSplitTaskFragments(mActivity, secondaryActivity);
|
||||
|
||||
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
|
||||
mTransaction, TASK_ID, intent, null /* launchingActivity */);
|
||||
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||
mActivity);
|
||||
|
||||
assertSplitPair(primaryContainer, container);
|
||||
}
|
||||
|
||||
/** Creates a mock activity in the organizer process. */
|
||||
private Activity createMockActivity() {
|
||||
final Activity activity = mock(Activity.class);
|
||||
doReturn(mActivityResources).when(activity).getResources();
|
||||
final IBinder activityToken = new Binder();
|
||||
doReturn(activityToken).when(activity).getActivityToken();
|
||||
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
||||
return activity;
|
||||
}
|
||||
|
||||
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
|
||||
private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
|
||||
@NonNull Activity activity) {
|
||||
return new TaskFragmentInfo(container.getTaskFragmentToken(),
|
||||
mock(WindowContainerToken.class),
|
||||
new Configuration(),
|
||||
1,
|
||||
true /* isVisible */,
|
||||
Collections.singletonList(activity.getActivityToken()),
|
||||
new Point(),
|
||||
false /* isTaskClearedForReuse */,
|
||||
false /* isTaskFragmentClearedForPip */);
|
||||
}
|
||||
|
||||
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
||||
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
|
||||
final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
|
||||
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
|
||||
container.setInfo(createMockTaskFragmentInfo(container, activity));
|
||||
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
|
||||
return container;
|
||||
}
|
||||
|
||||
/** Setups a rule to always expand the given intent. */
|
||||
private void setupExpandRule(@NonNull Intent expandIntent) {
|
||||
final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals)
|
||||
.setShouldAlwaysExpand(true)
|
||||
.build();
|
||||
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
|
||||
}
|
||||
|
||||
/** Setups a rule to always split the given activities. */
|
||||
private void setupSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryIntent) {
|
||||
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent);
|
||||
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
|
||||
}
|
||||
|
||||
/** Creates a rule to always split the given activity and the given intent. */
|
||||
private SplitRule createSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryIntent) {
|
||||
final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
|
||||
return new SplitPairRule.Builder(
|
||||
activityPair -> false,
|
||||
targetPair::equals,
|
||||
w -> true)
|
||||
.setSplitRatio(SPLIT_RATIO)
|
||||
.setShouldClearTop(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Creates a rule to always split the given activities. */
|
||||
private SplitRule createSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Activity secondaryActivity) {
|
||||
final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
|
||||
return new SplitPairRule.Builder(
|
||||
targetPair::equals,
|
||||
activityIntentPair -> false,
|
||||
w -> true)
|
||||
.setSplitRatio(SPLIT_RATIO)
|
||||
.setShouldClearTop(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Adds a pair of TaskFragments as split for the given activities. */
|
||||
private void addSplitTaskFragments(@NonNull Activity primaryActivity,
|
||||
@NonNull Activity secondaryActivity) {
|
||||
final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer(
|
||||
primaryActivity);
|
||||
final TaskFragmentContainer secondaryContainer = createMockTaskFragmentContainer(
|
||||
secondaryActivity);
|
||||
mSplitController.registerSplit(
|
||||
mock(WindowContainerTransaction.class),
|
||||
primaryContainer,
|
||||
primaryActivity,
|
||||
secondaryContainer,
|
||||
createSplitRule(primaryActivity, secondaryActivity));
|
||||
|
||||
// We need to set those in case we are not respecting clear top.
|
||||
// TODO(b/231845476) we should always respect clearTop.
|
||||
final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
|
||||
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
|
||||
primaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||
primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */));
|
||||
secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */));
|
||||
}
|
||||
|
||||
/** Gets the bounds of a TaskFragment that is in split. */
|
||||
private Rect getSplitBounds(boolean isPrimary) {
|
||||
final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
|
||||
return isPrimary
|
||||
? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
|
||||
TASK_BOUNDS.bottom)
|
||||
: new Rect(TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.bottom);
|
||||
}
|
||||
|
||||
/** Asserts that the two given TaskFragments are in split. */
|
||||
private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull TaskFragmentContainer secondaryContainer) {
|
||||
assertNotNull(primaryContainer);
|
||||
assertNotNull(secondaryContainer);
|
||||
assertTrue(primaryContainer.areLastRequestedBoundsEqual(
|
||||
getSplitBounds(true /* isPrimary */)));
|
||||
assertTrue(secondaryContainer.areLastRequestedBoundsEqual(
|
||||
getSplitBounds(false /* isPrimary */)));
|
||||
assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(WINDOWING_MODE_MULTI_WINDOW));
|
||||
assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(
|
||||
WINDOWING_MODE_MULTI_WINDOW));
|
||||
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer,
|
||||
secondaryContainer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
@@ -147,4 +151,38 @@ public class TaskContainerTest {
|
||||
|
||||
assertFalse(taskContainer.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopTaskFragmentContainer() {
|
||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||
assertNull(taskContainer.getTopTaskFragmentContainer());
|
||||
|
||||
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
|
||||
taskContainer, mController);
|
||||
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
|
||||
|
||||
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
||||
taskContainer, mController);
|
||||
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopNonFinishingActivity() {
|
||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||
assertNull(taskContainer.getTopNonFinishingActivity());
|
||||
|
||||
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
|
||||
taskContainer.mContainers.add(tf0);
|
||||
final Activity activity0 = mock(Activity.class);
|
||||
doReturn(activity0).when(tf0).getTopNonFinishingActivity();
|
||||
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
|
||||
|
||||
final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
|
||||
taskContainer.mContainers.add(tf1);
|
||||
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
|
||||
|
||||
final Activity activity1 = mock(Activity.class);
|
||||
doReturn(activity1).when(tf1).getTopNonFinishingActivity();
|
||||
assertEquals(activity1, taskContainer.getTopNonFinishingActivity());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user