Merge "Cleanup handleActivityCreated" into tm-dev am: 5284c9b855
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18179569 Change-Id: Id88670730231e0999e67b90cee6f93ec8c7687ce Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import android.app.ActivityClient;
|
|||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@@ -236,7 +237,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
// If the activity belongs to the current app process, we treat it as a new activity launch.
|
// If the activity belongs to the current app process, we treat it as a new activity launch.
|
||||||
final Activity activity = getActivity(activityToken);
|
final Activity activity = getActivity(activityToken);
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
onActivityCreated(activity);
|
// We don't allow split as primary for new launch because we currently only support
|
||||||
|
// launching to top. We allow split as primary for activity reparent because the
|
||||||
|
// activity may be split as primary before it is reparented out. In that case, we want
|
||||||
|
// to show it as primary again when it is reparented back.
|
||||||
|
if (!resolveActivityToContainer(activity, true /* canSplitAsPrimary */)) {
|
||||||
|
// When there is no embedding rule matched, try to place it in the top container
|
||||||
|
// like a normal launch.
|
||||||
|
placeActivityInTopContainer(activity);
|
||||||
|
}
|
||||||
|
updateCallbackIfNecessary();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +263,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
|
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
|
||||||
activityIntent, null /* launchingActivity */);
|
activityIntent, null /* launchingActivity */);
|
||||||
if (targetContainer == null) {
|
if (targetContainer == null) {
|
||||||
// When there is no split rule matched, try to place it in the top container like a
|
// When there is no embedding rule matched, try to place it in the top container like a
|
||||||
// normal launch.
|
// normal launch.
|
||||||
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||||
}
|
}
|
||||||
@@ -339,89 +349,244 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
void onActivityCreated(@NonNull Activity launchedActivity) {
|
void onActivityCreated(@NonNull Activity launchedActivity) {
|
||||||
handleActivityCreated(launchedActivity);
|
// TODO(b/229680885): we don't support launching into primary yet because we want to always
|
||||||
|
// launch the new activity on top.
|
||||||
|
resolveActivityToContainer(launchedActivity, false /* canSplitAsPrimary */);
|
||||||
updateCallbackIfNecessary();
|
updateCallbackIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the activity start should be routed to a particular container. It can create a new
|
* Checks if the new added activity should be routed to a particular container. It can create a
|
||||||
* container for the activity and a new split container if necessary.
|
* new container for the activity and a new split container if necessary.
|
||||||
|
* @param launchedActivity the new launched activity.
|
||||||
|
* @param canSplitAsPrimary whether we can put the new launched activity into primary split.
|
||||||
|
* @return {@code true} if the activity was placed in TaskFragment container.
|
||||||
*/
|
*/
|
||||||
// TODO(b/190433398): Break down into smaller functions.
|
@VisibleForTesting
|
||||||
void handleActivityCreated(@NonNull Activity launchedActivity) {
|
boolean resolveActivityToContainer(@NonNull Activity launchedActivity,
|
||||||
|
boolean canSplitAsPrimary) {
|
||||||
if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) {
|
if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) {
|
||||||
// We don't embed activity when it is in PIP, or finishing.
|
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
|
||||||
return;
|
// want any extra handling.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
final TaskFragmentContainer currentContainer = getContainerWithActivity(launchedActivity);
|
|
||||||
|
|
||||||
// Check if the activity is configured to always be expanded.
|
/*
|
||||||
|
* We will check the following to see if there is any embedding rule matched:
|
||||||
|
* 1. Whether the new launched activity should always expand.
|
||||||
|
* 2. Whether the new launched activity should launch a placeholder.
|
||||||
|
* 3. Whether the new launched activity has already been in a split with a rule matched
|
||||||
|
* (likely done in #onStartActivity).
|
||||||
|
* 4. Whether the activity below (if any) should be split with the new launched activity.
|
||||||
|
* 5. Whether the activity split with the activity below (if any) should be split with the
|
||||||
|
* new launched activity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1. Whether the new launched activity should always expand.
|
||||||
if (shouldExpand(launchedActivity, null /* intent */)) {
|
if (shouldExpand(launchedActivity, null /* intent */)) {
|
||||||
if (shouldContainerBeExpanded(currentContainer)) {
|
expandActivity(launchedActivity);
|
||||||
// Make sure that the existing container is expanded
|
return true;
|
||||||
mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
|
|
||||||
} else {
|
|
||||||
// Put activity into a new expanded container
|
|
||||||
final TaskFragmentContainer newContainer = newContainer(launchedActivity,
|
|
||||||
launchedActivity.getTaskId());
|
|
||||||
mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
|
|
||||||
launchedActivity);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if activity requires a placeholder
|
// 2. Whether the new launched activity should launch a placeholder.
|
||||||
if (launchPlaceholderIfNecessary(launchedActivity)) {
|
if (launchPlaceholderIfNecessary(launchedActivity)) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/190433398): Check if it is a placeholder and there is already another split
|
// 3. Whether the new launched activity has already been in a split with a rule matched.
|
||||||
// created by the primary activity. This is necessary for the case when the primary activity
|
if (isNewActivityInSplitWithRuleMatched(launchedActivity)) {
|
||||||
// launched another secondary in the split, but the placeholder was still launched by the
|
return true;
|
||||||
// logic above. We didn't prevent the placeholder launcher because we didn't know that
|
}
|
||||||
// another secondary activity is coming up.
|
|
||||||
|
|
||||||
// Check if the activity should form a split with the activity below in the same task
|
// 4. Whether the activity below (if any) should be split with the new launched activity.
|
||||||
// fragment.
|
final Activity activityBelow = findActivityBelow(launchedActivity);
|
||||||
|
if (activityBelow == null) {
|
||||||
|
// Can't find any activity below.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (putActivitiesIntoSplitIfNecessary(activityBelow, launchedActivity)) {
|
||||||
|
// Have split rule of [ activityBelow | launchedActivity ].
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canSplitAsPrimary
|
||||||
|
&& putActivitiesIntoSplitIfNecessary(launchedActivity, activityBelow)) {
|
||||||
|
// Have split rule of [ launchedActivity | activityBelow].
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Whether the activity split with the activity below (if any) should be split with the
|
||||||
|
// new launched activity.
|
||||||
|
final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
|
||||||
|
activityBelow);
|
||||||
|
final SplitContainer topSplit = getActiveSplitForContainer(activityBelowContainer);
|
||||||
|
if (topSplit == null || !isTopMostSplit(topSplit)) {
|
||||||
|
// Skip if it is not the topmost split.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TaskFragmentContainer otherTopContainer =
|
||||||
|
topSplit.getPrimaryContainer() == activityBelowContainer
|
||||||
|
? topSplit.getSecondaryContainer()
|
||||||
|
: topSplit.getPrimaryContainer();
|
||||||
|
final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
|
||||||
|
if (otherTopActivity == null || otherTopActivity == launchedActivity) {
|
||||||
|
// Can't find the top activity on the other split TaskFragment.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (putActivitiesIntoSplitIfNecessary(otherTopActivity, launchedActivity)) {
|
||||||
|
// Have split rule of [ otherTopActivity | launchedActivity ].
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Have split rule of [ launchedActivity | otherTopActivity].
|
||||||
|
return canSplitAsPrimary
|
||||||
|
&& putActivitiesIntoSplitIfNecessary(launchedActivity, otherTopActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places the given activity to the top most TaskFragment in the task if there is any.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
void placeActivityInTopContainer(@NonNull Activity activity) {
|
||||||
|
if (getContainerWithActivity(activity) != null) {
|
||||||
|
// The activity has already been put in a TaskFragment. This is likely to be done by
|
||||||
|
// the server when the activity is started.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int taskId = getTaskId(activity);
|
||||||
|
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||||
|
if (taskContainer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||||
|
if (targetContainer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetContainer.addPendingAppearedActivity(activity);
|
||||||
|
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||||
|
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
|
||||||
|
activity.getActivityToken());
|
||||||
|
mPresenter.applyTransaction(wct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands the given activity by either expanding the TaskFragment it is currently in or putting
|
||||||
|
* it into a new expanded TaskFragment.
|
||||||
|
*/
|
||||||
|
private void expandActivity(@NonNull Activity activity) {
|
||||||
|
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||||
|
if (shouldContainerBeExpanded(container)) {
|
||||||
|
// Make sure that the existing container is expanded.
|
||||||
|
mPresenter.expandTaskFragment(container.getTaskFragmentToken());
|
||||||
|
} else {
|
||||||
|
// Put activity into a new expanded container.
|
||||||
|
final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
|
||||||
|
mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the given new launched activity is in a split with a rule matched. */
|
||||||
|
private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
|
||||||
|
final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
|
||||||
|
final SplitContainer splitContainer = getActiveSplitForContainer(container);
|
||||||
|
if (splitContainer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container == splitContainer.getPrimaryContainer()) {
|
||||||
|
// The new launched can be in the primary container when it is starting a new activity
|
||||||
|
// onCreate, thus the secondary may still be empty.
|
||||||
|
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||||
|
final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity();
|
||||||
|
return secondaryActivity == null
|
||||||
|
|| getSplitRule(launchedActivity, secondaryActivity) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the new launched activity is a placeholder.
|
||||||
|
if (splitContainer.getSplitRule() instanceof SplitPlaceholderRule) {
|
||||||
|
final SplitPlaceholderRule placeholderRule =
|
||||||
|
(SplitPlaceholderRule) splitContainer.getSplitRule();
|
||||||
|
final ComponentName placeholderName = placeholderRule.getPlaceholderIntent()
|
||||||
|
.getComponent();
|
||||||
|
// TODO(b/232330767): Do we have a better way to check this?
|
||||||
|
return placeholderName == null
|
||||||
|
|| placeholderName.equals(launchedActivity.getComponentName())
|
||||||
|
|| placeholderRule.getPlaceholderIntent().equals(launchedActivity.getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the new launched activity should be split with the primary top activity.
|
||||||
|
final Activity primaryActivity = splitContainer.getPrimaryContainer()
|
||||||
|
.getTopNonFinishingActivity();
|
||||||
|
if (primaryActivity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* TODO(b/231845476) we should always respect clearTop.
|
||||||
|
final SplitPairRule curSplitRule = (SplitPairRule) splitContainer.getSplitRule();
|
||||||
|
final SplitPairRule splitRule = getSplitRule(primaryActivity, launchedActivity);
|
||||||
|
return splitRule != null && haveSamePresentation(splitRule, curSplitRule)
|
||||||
|
// If the new launched split rule should clear top and it is not the bottom most,
|
||||||
|
// it means we should create a new split pair and clear the existing secondary.
|
||||||
|
&& (!splitRule.shouldClearTop()
|
||||||
|
|| container.getBottomMostActivity() == launchedActivity);
|
||||||
|
*/
|
||||||
|
return getSplitRule(primaryActivity, launchedActivity) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finds the activity below the given activity. */
|
||||||
|
@Nullable
|
||||||
|
private Activity findActivityBelow(@NonNull Activity activity) {
|
||||||
Activity activityBelow = null;
|
Activity activityBelow = null;
|
||||||
if (currentContainer != null) {
|
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||||
final List<Activity> containerActivities = currentContainer.collectActivities();
|
if (container != null) {
|
||||||
final int index = containerActivities.indexOf(launchedActivity);
|
final List<Activity> containerActivities = container.collectActivities();
|
||||||
|
final int index = containerActivities.indexOf(activity);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
activityBelow = containerActivities.get(index - 1);
|
activityBelow = containerActivities.get(index - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (activityBelow == null) {
|
if (activityBelow == null) {
|
||||||
IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
|
final IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
|
||||||
launchedActivity.getActivityToken());
|
activity.getActivityToken());
|
||||||
if (belowToken != null) {
|
if (belowToken != null) {
|
||||||
activityBelow = getActivity(belowToken);
|
activityBelow = getActivity(belowToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (activityBelow == null) {
|
return activityBelow;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the split is already set.
|
/**
|
||||||
final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
|
* Checks if there is a rule to split the two activities. If there is one, puts them into split
|
||||||
activityBelow);
|
* and returns {@code true}. Otherwise, returns {@code false}.
|
||||||
if (currentContainer != null && activityBelowContainer != null) {
|
*/
|
||||||
final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer,
|
private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
|
||||||
activityBelowContainer);
|
@NonNull Activity secondaryActivity) {
|
||||||
if (existingSplit != null) {
|
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
|
||||||
// There is already an active split with the activity below.
|
if (splitRule == null) {
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
final TaskFragmentContainer primaryContainer = getContainerWithActivity(
|
||||||
|
primaryActivity);
|
||||||
|
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
|
||||||
|
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
|
||||||
|
&& canReuseContainer(splitRule, splitContainer.getSplitRule())) {
|
||||||
|
// Can launch in the existing secondary container if the rules share the same
|
||||||
|
// presentation.
|
||||||
|
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||||
|
if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
|
||||||
|
// The activity is already in the target TaskFragment.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
secondaryContainer.addPendingAppearedActivity(secondaryActivity);
|
||||||
|
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||||
|
wct.reparentActivityToTaskFragment(
|
||||||
|
secondaryContainer.getTaskFragmentToken(),
|
||||||
|
secondaryActivity.getActivityToken());
|
||||||
|
mPresenter.applyTransaction(wct);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
// Create new split pair.
|
||||||
final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity);
|
mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
|
||||||
if (splitPairRule == null) {
|
return true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPresenter.createNewSplitContainer(activityBelow, launchedActivity,
|
|
||||||
splitPairRule);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onActivityConfigurationChanged(@NonNull Activity activity) {
|
private void onActivityConfigurationChanged(@NonNull Activity activity) {
|
||||||
@@ -601,7 +766,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
final IBinder activityToken = activity.getActivityToken();
|
final IBinder activityToken = activity.getActivityToken();
|
||||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||||
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|
||||||
for (TaskFragmentContainer container : containers) {
|
// Traverse from top to bottom in case an activity is added to top pending, and hasn't
|
||||||
|
// received update from server yet.
|
||||||
|
for (int j = containers.size() - 1; j >= 0; j--) {
|
||||||
|
final TaskFragmentContainer container = containers.get(j);
|
||||||
if (container.hasActivity(activityToken)) {
|
if (container.hasActivity(activityToken)) {
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -798,8 +966,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
if (splitContainer == null) {
|
if (splitContainer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
|
if (!isTopMostSplit(splitContainer)) {
|
||||||
if (splitContainer != splitContainers.get(splitContainers.size() - 1)) {
|
|
||||||
// Skip position update - it isn't the topmost split.
|
// Skip position update - it isn't the topmost split.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -815,6 +982,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
mPresenter.updateSplitContainer(splitContainer, container, wct);
|
mPresenter.updateSplitContainer(splitContainer, container, wct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether the given split is the topmost split in the Task. */
|
||||||
|
private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
|
||||||
|
final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
|
||||||
|
.getTaskContainer().mSplitContainers;
|
||||||
|
return splitContainer == splitContainers.get(splitContainers.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the top active split container that has the provided container, if available.
|
* Returns the top active split container that has the provided container, if available.
|
||||||
*/
|
*/
|
||||||
@@ -1014,14 +1188,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
if (container == null) {
|
if (container == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
|
return getActiveSplitForContainer(container) == null;
|
||||||
for (SplitContainer splitContainer : splitContainers) {
|
|
||||||
if (container.equals(splitContainer.getPrimaryContainer())
|
|
||||||
|| container.equals(splitContainer.getSecondaryContainer())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1279,15 +1446,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
|
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SplitPairRule pairRule1 = (SplitPairRule) rule1;
|
return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2);
|
||||||
final SplitPairRule pairRule2 = (SplitPairRule) rule2;
|
}
|
||||||
|
|
||||||
|
/** Whether the two rules have the same presentation. */
|
||||||
|
private static boolean haveSamePresentation(SplitPairRule rule1, SplitPairRule rule2) {
|
||||||
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
|
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
|
||||||
return pairRule1.getSplitRatio() == pairRule2.getSplitRatio()
|
return rule1.getSplitRatio() == rule2.getSplitRatio()
|
||||||
&& pairRule1.getLayoutDirection() == pairRule2.getLayoutDirection()
|
&& rule1.getLayoutDirection() == rule2.getLayoutDirection()
|
||||||
&& pairRule1.getFinishPrimaryWithSecondary()
|
&& rule1.getFinishPrimaryWithSecondary()
|
||||||
== pairRule2.getFinishPrimaryWithSecondary()
|
== rule2.getFinishPrimaryWithSecondary()
|
||||||
&& pairRule1.getFinishSecondaryWithPrimary()
|
&& rule1.getFinishSecondaryWithPrimary()
|
||||||
== pairRule2.getFinishSecondaryWithPrimary();
|
== rule2.getFinishSecondaryWithPrimary();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -121,20 +121,24 @@ class TaskFragmentContainer {
|
|||||||
/** List of activities that belong to this container and live in this process. */
|
/** List of activities that belong to this container and live in this process. */
|
||||||
@NonNull
|
@NonNull
|
||||||
List<Activity> collectActivities() {
|
List<Activity> collectActivities() {
|
||||||
|
final List<Activity> allActivities = new ArrayList<>();
|
||||||
|
if (mInfo != null) {
|
||||||
|
// Add activities reported from the server.
|
||||||
|
for (IBinder token : mInfo.getActivities()) {
|
||||||
|
final Activity activity = mController.getActivity(token);
|
||||||
|
if (activity != null && !activity.isFinishing()) {
|
||||||
|
allActivities.add(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the re-parenting activity, in case the server has not yet reported the task
|
// Add the re-parenting activity, in case the server has not yet reported the task
|
||||||
// fragment info update with it placed in this container. We still want to apply rules
|
// fragment info update with it placed in this container. We still want to apply rules
|
||||||
// in this intermediate state.
|
// in this intermediate state.
|
||||||
List<Activity> allActivities = new ArrayList<>();
|
// Place those on top of the list since they will be on the top after reported from the
|
||||||
if (!mPendingAppearedActivities.isEmpty()) {
|
// server.
|
||||||
allActivities.addAll(mPendingAppearedActivities);
|
for (Activity activity : mPendingAppearedActivities) {
|
||||||
}
|
if (!activity.isFinishing()) {
|
||||||
// Add activities reported from the server.
|
|
||||||
if (mInfo == null) {
|
|
||||||
return allActivities;
|
|
||||||
}
|
|
||||||
for (IBinder token : mInfo.getActivities()) {
|
|
||||||
Activity activity = mController.getActivity(token);
|
|
||||||
if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) {
|
|
||||||
allActivities.add(activity);
|
allActivities.add(activity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,6 +245,12 @@ class TaskFragmentContainer {
|
|||||||
return i >= 0 ? activities.get(i) : null;
|
return i >= 0 ? activities.get(i) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Activity getBottomMostActivity() {
|
||||||
|
final List<Activity> activities = collectActivities();
|
||||||
|
return activities.isEmpty() ? null : activities.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
boolean isEmpty() {
|
boolean isEmpty() {
|
||||||
return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
|
return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ import static org.junit.Assert.assertNull;
|
|||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -40,6 +43,7 @@ import static org.mockito.Mockito.never;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@@ -80,6 +84,8 @@ public class SplitControllerTest {
|
|||||||
private static final int TASK_ID = 10;
|
private static final int TASK_ID = 10;
|
||||||
private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
|
private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
|
||||||
private static final float SPLIT_RATIO = 0.5f;
|
private static final float SPLIT_RATIO = 0.5f;
|
||||||
|
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
|
||||||
|
new ComponentName("test", "placeholder"));
|
||||||
|
|
||||||
private Activity mActivity;
|
private Activity mActivity;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -101,6 +107,7 @@ public class SplitControllerTest {
|
|||||||
mSplitPresenter = mSplitController.mPresenter;
|
mSplitPresenter = mSplitController.mPresenter;
|
||||||
spyOn(mSplitController);
|
spyOn(mSplitController);
|
||||||
spyOn(mSplitPresenter);
|
spyOn(mSplitPresenter);
|
||||||
|
doNothing().when(mSplitPresenter).applyTransaction(any());
|
||||||
final Configuration activityConfig = new Configuration();
|
final Configuration activityConfig = new Configuration();
|
||||||
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
|
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
|
||||||
activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
|
activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
|
||||||
@@ -275,13 +282,25 @@ public class SplitControllerTest {
|
|||||||
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnActivityCreated() {
|
||||||
|
mSplitController.onActivityCreated(mActivity);
|
||||||
|
|
||||||
|
// Disallow to split as primary because we want the new launch to be always on top.
|
||||||
|
verify(mSplitController).resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOnActivityReparentToTask_sameProcess() {
|
public void testOnActivityReparentToTask_sameProcess() {
|
||||||
mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
|
mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
|
||||||
mActivity.getActivityToken());
|
mActivity.getActivityToken());
|
||||||
|
|
||||||
// Treated as on activity created.
|
// Treated as on activity created, but allow to split as primary.
|
||||||
verify(mSplitController).onActivityCreated(mActivity);
|
verify(mSplitController).resolveActivityToContainer(mActivity,
|
||||||
|
true /* canSplitAsPrimary */);
|
||||||
|
// Try to place the activity to the top TaskFragment when there is no matched rule.
|
||||||
|
verify(mSplitController).placeActivityInTopContainer(mActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -294,7 +313,7 @@ public class SplitControllerTest {
|
|||||||
mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
|
mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
|
||||||
|
|
||||||
// Treated as starting new intent
|
// Treated as starting new intent
|
||||||
verify(mSplitController, never()).onActivityCreated(mActivity);
|
verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean());
|
||||||
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
|
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
|
||||||
isNull());
|
isNull());
|
||||||
}
|
}
|
||||||
@@ -391,6 +410,327 @@ public class SplitControllerTest {
|
|||||||
assertSplitPair(primaryContainer, container);
|
assertSplitPair(primaryContainer, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPlaceActivityInTopContainer() {
|
||||||
|
mSplitController.placeActivityInTopContainer(mActivity);
|
||||||
|
|
||||||
|
verify(mSplitPresenter, never()).applyTransaction(any());
|
||||||
|
|
||||||
|
mSplitController.newContainer(null /* activity */, mActivity, TASK_ID);
|
||||||
|
mSplitController.placeActivityInTopContainer(mActivity);
|
||||||
|
|
||||||
|
verify(mSplitPresenter).applyTransaction(any());
|
||||||
|
|
||||||
|
// Not reparent if activity is in a TaskFragment.
|
||||||
|
clearInvocations(mSplitPresenter);
|
||||||
|
mSplitController.newContainer(mActivity, TASK_ID);
|
||||||
|
mSplitController.placeActivityInTopContainer(mActivity);
|
||||||
|
|
||||||
|
verify(mSplitPresenter, never()).applyTransaction(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_noRuleMatched() {
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_expandRule_notInTaskFragment() {
|
||||||
|
setupExpandRule(mActivity);
|
||||||
|
|
||||||
|
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
|
||||||
|
mActivity);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertNotNull(container);
|
||||||
|
verify(mSplitController).newContainer(mActivity, TASK_ID);
|
||||||
|
verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_expandRule_inSingleTaskFragment() {
|
||||||
|
setupExpandRule(mActivity);
|
||||||
|
|
||||||
|
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
|
||||||
|
final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_expandRule_inSplitTaskFragment() {
|
||||||
|
setupExpandRule(mActivity);
|
||||||
|
|
||||||
|
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
|
||||||
|
final Activity activity = createMockActivity();
|
||||||
|
addSplitTaskFragments(activity, mActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
|
||||||
|
mActivity);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertNotNull(container);
|
||||||
|
verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
|
||||||
|
setupPlaceholderRule(mActivity);
|
||||||
|
final SplitPlaceholderRule placeholderRule =
|
||||||
|
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
|
||||||
|
|
||||||
|
// Launch placeholder if the activity is not in any TaskFragment.
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
|
||||||
|
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_inOccludedTaskFragment() {
|
||||||
|
setupPlaceholderRule(mActivity);
|
||||||
|
|
||||||
|
// Don't launch placeholder if the activity is not in the topmost active TaskFragment.
|
||||||
|
final Activity activity = createMockActivity();
|
||||||
|
mSplitController.newContainer(mActivity, TASK_ID);
|
||||||
|
mSplitController.newContainer(activity, TASK_ID);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
|
||||||
|
anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
|
||||||
|
setupPlaceholderRule(mActivity);
|
||||||
|
final SplitPlaceholderRule placeholderRule =
|
||||||
|
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
|
||||||
|
|
||||||
|
// Launch placeholder if the activity is in the topmost expanded TaskFragment.
|
||||||
|
mSplitController.newContainer(mActivity, TASK_ID);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
|
||||||
|
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_inPrimarySplit() {
|
||||||
|
setupPlaceholderRule(mActivity);
|
||||||
|
|
||||||
|
// Don't launch placeholder if the activity is in primary split.
|
||||||
|
final Activity secondaryActivity = createMockActivity();
|
||||||
|
addSplitTaskFragments(mActivity, secondaryActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
|
||||||
|
anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
|
||||||
|
setupPlaceholderRule(mActivity);
|
||||||
|
final SplitPlaceholderRule placeholderRule =
|
||||||
|
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
|
||||||
|
|
||||||
|
// Launch placeholder if the activity is in secondary split.
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
addSplitTaskFragments(primaryActivity, mActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
|
||||||
|
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_inPrimarySplitWithRuleMatched() {
|
||||||
|
final Intent secondaryIntent = new Intent();
|
||||||
|
setupSplitRule(mActivity, secondaryIntent);
|
||||||
|
final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0);
|
||||||
|
|
||||||
|
// Activity is already in primary split, no need to create new split.
|
||||||
|
final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity,
|
||||||
|
TASK_ID);
|
||||||
|
final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(
|
||||||
|
null /* activity */, mActivity, TASK_ID);
|
||||||
|
mSplitController.registerSplit(
|
||||||
|
mTransaction,
|
||||||
|
primaryContainer,
|
||||||
|
mActivity,
|
||||||
|
secondaryContainer,
|
||||||
|
splitRule);
|
||||||
|
clearInvocations(mSplitController);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
||||||
|
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() {
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
setupSplitRule(primaryActivity, mActivity);
|
||||||
|
|
||||||
|
// Activity is already in secondary split, no need to create new split.
|
||||||
|
addSplitTaskFragments(primaryActivity, mActivity);
|
||||||
|
clearInvocations(mSplitController);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
||||||
|
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_inSecondarySplitWithNoRuleMatched() {
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
final Activity secondaryActivity = createMockActivity();
|
||||||
|
setupSplitRule(primaryActivity, secondaryActivity);
|
||||||
|
|
||||||
|
// Activity is in secondary split, but there is no rule to split it with primary.
|
||||||
|
addSplitTaskFragments(primaryActivity, secondaryActivity);
|
||||||
|
mSplitController.getContainerWithActivity(secondaryActivity)
|
||||||
|
.addPendingAppearedActivity(mActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_placeholderRule_isPlaceholderWithRuleMatched() {
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
setupPlaceholderRule(primaryActivity);
|
||||||
|
final SplitPlaceholderRule placeholderRule =
|
||||||
|
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
|
||||||
|
doReturn(PLACEHOLDER_INTENT).when(mActivity).getIntent();
|
||||||
|
|
||||||
|
// Activity is a placeholder.
|
||||||
|
final TaskFragmentContainer primaryContainer = mSplitController.newContainer(
|
||||||
|
primaryActivity, TASK_ID);
|
||||||
|
final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(mActivity,
|
||||||
|
TASK_ID);
|
||||||
|
mSplitController.registerSplit(
|
||||||
|
mTransaction,
|
||||||
|
primaryContainer,
|
||||||
|
mActivity,
|
||||||
|
secondaryContainer,
|
||||||
|
placeholderRule);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_splitWithActivityBelowAsSecondary() {
|
||||||
|
final Activity activityBelow = createMockActivity();
|
||||||
|
setupSplitRule(activityBelow, mActivity);
|
||||||
|
|
||||||
|
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
|
||||||
|
TASK_ID);
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertSplitPair(activityBelow, mActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_splitWithActivityBelowAsPrimary() {
|
||||||
|
final Activity activityBelow = createMockActivity();
|
||||||
|
setupSplitRule(mActivity, activityBelow);
|
||||||
|
|
||||||
|
// Disallow to split as primary.
|
||||||
|
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
|
||||||
|
TASK_ID);
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
|
||||||
|
|
||||||
|
// Allow to split as primary.
|
||||||
|
result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
true /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertSplitPair(mActivity, activityBelow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsSecondary() {
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
setupSplitRule(primaryActivity, mActivity);
|
||||||
|
|
||||||
|
final Activity activityBelow = createMockActivity();
|
||||||
|
addSplitTaskFragments(primaryActivity, activityBelow);
|
||||||
|
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||||
|
primaryActivity);
|
||||||
|
final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity(
|
||||||
|
activityBelow);
|
||||||
|
secondaryContainer.addPendingAppearedActivity(mActivity);
|
||||||
|
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
|
||||||
|
mActivity);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
// TODO(b/231845476) we should always respect clearTop.
|
||||||
|
// assertNotEquals(secondaryContainer, container);
|
||||||
|
assertSplitPair(primaryContainer, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsPrimary() {
|
||||||
|
final Activity primaryActivity = createMockActivity();
|
||||||
|
setupSplitRule(mActivity, primaryActivity);
|
||||||
|
|
||||||
|
final Activity activityBelow = createMockActivity();
|
||||||
|
addSplitTaskFragments(primaryActivity, activityBelow);
|
||||||
|
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
|
||||||
|
primaryActivity);
|
||||||
|
primaryContainer.addPendingAppearedActivity(mActivity);
|
||||||
|
boolean result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
false /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertFalse(result);
|
||||||
|
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
|
||||||
|
|
||||||
|
|
||||||
|
result = mSplitController.resolveActivityToContainer(mActivity,
|
||||||
|
true /* canSplitAsPrimary */);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertSplitPair(mActivity, primaryActivity);
|
||||||
|
}
|
||||||
|
|
||||||
/** 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);
|
||||||
@@ -398,6 +738,7 @@ public class SplitControllerTest {
|
|||||||
final IBinder activityToken = new Binder();
|
final IBinder activityToken = new Binder();
|
||||||
doReturn(activityToken).when(activity).getActivityToken();
|
doReturn(activityToken).when(activity).getActivityToken();
|
||||||
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
||||||
|
doReturn(TASK_ID).when(activity).getTaskId();
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,12 +759,18 @@ public class SplitControllerTest {
|
|||||||
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
||||||
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
|
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
|
||||||
final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
|
final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
|
||||||
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
|
setupTaskFragmentInfo(container, activity);
|
||||||
container.setInfo(createMockTaskFragmentInfo(container, activity));
|
|
||||||
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Setups the given TaskFragment as it has appeared in the server. */
|
||||||
|
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
|
||||||
|
@NonNull Activity activity) {
|
||||||
|
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
|
||||||
|
container.setInfo(info);
|
||||||
|
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
|
||||||
|
}
|
||||||
|
|
||||||
/** Setups a rule to always expand the given intent. */
|
/** Setups a rule to always expand the given intent. */
|
||||||
private void setupExpandRule(@NonNull Intent expandIntent) {
|
private void setupExpandRule(@NonNull Intent expandIntent) {
|
||||||
final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals)
|
final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals)
|
||||||
@@ -432,6 +779,23 @@ public class SplitControllerTest {
|
|||||||
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
|
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Setups a rule to always expand the given activity. */
|
||||||
|
private void setupExpandRule(@NonNull Activity expandActivity) {
|
||||||
|
final ActivityRule expandRule = new ActivityRule.Builder(expandActivity::equals, i -> false)
|
||||||
|
.setShouldAlwaysExpand(true)
|
||||||
|
.build();
|
||||||
|
mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setups a rule to launch placeholder for the given activity. */
|
||||||
|
private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
|
||||||
|
final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
|
||||||
|
primaryActivity::equals, i -> false, w -> true)
|
||||||
|
.setSplitRatio(SPLIT_RATIO)
|
||||||
|
.build();
|
||||||
|
mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
|
||||||
|
}
|
||||||
|
|
||||||
/** Setups a rule to always split the given activities. */
|
/** Setups a rule to always split the given activities. */
|
||||||
private void setupSplitRule(@NonNull Activity primaryActivity,
|
private void setupSplitRule(@NonNull Activity primaryActivity,
|
||||||
@NonNull Intent secondaryIntent) {
|
@NonNull Intent secondaryIntent) {
|
||||||
@@ -439,6 +803,13 @@ public class SplitControllerTest {
|
|||||||
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
|
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Setups a rule to always split the given activities. */
|
||||||
|
private void setupSplitRule(@NonNull Activity primaryActivity,
|
||||||
|
@NonNull Activity secondaryActivity) {
|
||||||
|
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity);
|
||||||
|
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates a rule to always split the given activity and the given intent. */
|
/** Creates a rule to always split the given activity and the given intent. */
|
||||||
private SplitRule createSplitRule(@NonNull Activity primaryActivity,
|
private SplitRule createSplitRule(@NonNull Activity primaryActivity,
|
||||||
@NonNull Intent secondaryIntent) {
|
@NonNull Intent secondaryIntent) {
|
||||||
@@ -499,19 +870,31 @@ public class SplitControllerTest {
|
|||||||
TASK_BOUNDS.bottom);
|
TASK_BOUNDS.bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Asserts that the two given activities are in split. */
|
||||||
|
private void assertSplitPair(@NonNull Activity primaryActivity,
|
||||||
|
@NonNull Activity secondaryActivity) {
|
||||||
|
assertSplitPair(mSplitController.getContainerWithActivity(primaryActivity),
|
||||||
|
mSplitController.getContainerWithActivity(secondaryActivity));
|
||||||
|
}
|
||||||
|
|
||||||
/** Asserts that the two given TaskFragments are in split. */
|
/** Asserts that the two given TaskFragments are in split. */
|
||||||
private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer,
|
private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer,
|
||||||
@NonNull TaskFragmentContainer secondaryContainer) {
|
@NonNull TaskFragmentContainer secondaryContainer) {
|
||||||
assertNotNull(primaryContainer);
|
assertNotNull(primaryContainer);
|
||||||
assertNotNull(secondaryContainer);
|
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,
|
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer,
|
||||||
secondaryContainer));
|
secondaryContainer));
|
||||||
|
if (primaryContainer.mInfo != null) {
|
||||||
|
assertTrue(primaryContainer.areLastRequestedBoundsEqual(
|
||||||
|
getSplitBounds(true /* isPrimary */)));
|
||||||
|
assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(
|
||||||
|
WINDOWING_MODE_MULTI_WINDOW));
|
||||||
|
}
|
||||||
|
if (secondaryContainer.mInfo != null) {
|
||||||
|
assertTrue(secondaryContainer.areLastRequestedBoundsEqual(
|
||||||
|
getSplitBounds(false /* isPrimary */)));
|
||||||
|
assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(
|
||||||
|
WINDOWING_MODE_MULTI_WINDOW));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package androidx.window.extensions.embedding;
|
|||||||
|
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@@ -29,7 +30,9 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.os.Binder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
import android.window.TaskFragmentInfo;
|
import android.window.TaskFragmentInfo;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
@@ -37,6 +40,8 @@ import android.window.WindowContainerTransaction;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -44,6 +49,7 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for {@link TaskFragmentContainer}.
|
* Test class for {@link TaskFragmentContainer}.
|
||||||
@@ -62,16 +68,16 @@ public class TaskFragmentContainerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private SplitController mController;
|
private SplitController mController;
|
||||||
@Mock
|
@Mock
|
||||||
private Activity mActivity;
|
|
||||||
@Mock
|
|
||||||
private TaskFragmentInfo mInfo;
|
private TaskFragmentInfo mInfo;
|
||||||
@Mock
|
@Mock
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
|
private Activity mActivity;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
doReturn(mHandler).when(mController).getHandler();
|
doReturn(mHandler).when(mController).getHandler();
|
||||||
|
mActivity = createMockActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -165,4 +171,72 @@ public class TaskFragmentContainerTest {
|
|||||||
assertNull(container.mAppearEmptyTimeout);
|
assertNull(container.mAppearEmptyTimeout);
|
||||||
verify(mController).onTaskFragmentAppearEmptyTimeout(container);
|
verify(mController).onTaskFragmentAppearEmptyTimeout(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCollectActivities() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
|
taskContainer, mController);
|
||||||
|
List<Activity> activities = container.collectActivities();
|
||||||
|
|
||||||
|
assertTrue(activities.isEmpty());
|
||||||
|
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
activities = container.collectActivities();
|
||||||
|
|
||||||
|
assertEquals(1, activities.size());
|
||||||
|
|
||||||
|
final Activity activity0 = createMockActivity();
|
||||||
|
final Activity activity1 = createMockActivity();
|
||||||
|
final List<IBinder> runningActivities = Lists.newArrayList(activity0.getActivityToken(),
|
||||||
|
activity1.getActivityToken());
|
||||||
|
doReturn(runningActivities).when(mInfo).getActivities();
|
||||||
|
container.setInfo(mInfo);
|
||||||
|
activities = container.collectActivities();
|
||||||
|
|
||||||
|
assertEquals(3, activities.size());
|
||||||
|
assertEquals(activity0, activities.get(0));
|
||||||
|
assertEquals(activity1, activities.get(1));
|
||||||
|
assertEquals(mActivity, activities.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddPendingActivity() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
|
taskContainer, mController);
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
|
assertEquals(1, container.collectActivities().size());
|
||||||
|
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
|
assertEquals(1, container.collectActivities().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetBottomMostActivity() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
|
taskContainer, mController);
|
||||||
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
|
assertEquals(mActivity, container.getBottomMostActivity());
|
||||||
|
|
||||||
|
final Activity activity = createMockActivity();
|
||||||
|
final List<IBinder> runningActivities = Lists.newArrayList(activity.getActivityToken());
|
||||||
|
doReturn(runningActivities).when(mInfo).getActivities();
|
||||||
|
container.setInfo(mInfo);
|
||||||
|
|
||||||
|
assertEquals(activity, container.getBottomMostActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a mock activity in the organizer process. */
|
||||||
|
private Activity createMockActivity() {
|
||||||
|
final Activity activity = mock(Activity.class);
|
||||||
|
final IBinder activityToken = new Binder();
|
||||||
|
doReturn(activityToken).when(activity).getActivityToken();
|
||||||
|
doReturn(activity).when(mController).getActivity(activityToken);
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -899,7 +899,11 @@ public class AppTransitionController {
|
|||||||
// We cannot promote the animation on Task's parent when the task is in
|
// We cannot promote the animation on Task's parent when the task is in
|
||||||
// clearing task in case the animating get stuck when performing the opening
|
// clearing task in case the animating get stuck when performing the opening
|
||||||
// task that behind it.
|
// task that behind it.
|
||||||
|| (current.asTask() != null && current.asTask().mInRemoveTask)) {
|
|| (current.asTask() != null && current.asTask().mInRemoveTask)
|
||||||
|
// We cannot promote the animation to changing window. This may happen when an
|
||||||
|
// activity is open in a TaskFragment that is resizing, while the existing
|
||||||
|
// activity in the TaskFragment is reparented to another TaskFragment.
|
||||||
|
|| parent.isChangingAppTransition()) {
|
||||||
canPromote = false;
|
canPromote = false;
|
||||||
} else {
|
} else {
|
||||||
// In case a descendant of the parent belongs to the other group, we cannot promote
|
// In case a descendant of the parent belongs to the other group, we cannot promote
|
||||||
|
|||||||
Reference in New Issue
Block a user