Merge "Don't bring the Task to front when launching placeholder in background" into tm-dev

This commit is contained in:
Chris Li
2022-05-19 08:55:49 +00:00
committed by Android (Google) Code Review
2 changed files with 94 additions and 52 deletions

View File

@@ -241,7 +241,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// 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 */)) {
if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
placeActivityInTopContainer(activity);
@@ -353,21 +353,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
void onActivityCreated(@NonNull Activity 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 */);
resolveActivityToContainer(launchedActivity, false /* isOnReparent */);
updateCallbackIfNecessary();
}
/**
* Checks if the new added activity should be routed to a particular container. It can create a
* 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.
* @param activity the activity that is newly added to the Task.
* @param isOnReparent whether the activity is reparented to the Task instead of new launched.
* We only support to split as primary for reparented activity for now.
* @return {@code true} if the activity was placed in TaskFragment container.
*/
@VisibleForTesting
boolean resolveActivityToContainer(@NonNull Activity launchedActivity,
boolean canSplitAsPrimary) {
if (isInPictureInPicture(launchedActivity) || launchedActivity.isFinishing()) {
boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
// want any extra handling.
return true;
@@ -385,33 +385,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
// 1. Whether the new launched activity should always expand.
if (shouldExpand(launchedActivity, null /* intent */)) {
expandActivity(launchedActivity);
if (shouldExpand(activity, null /* intent */)) {
expandActivity(activity);
return true;
}
// 2. Whether the new launched activity should launch a placeholder.
if (launchPlaceholderIfNecessary(launchedActivity)) {
if (launchPlaceholderIfNecessary(activity, !isOnReparent)) {
return true;
}
// 3. Whether the new launched activity has already been in a split with a rule matched.
if (isNewActivityInSplitWithRuleMatched(launchedActivity)) {
if (isNewActivityInSplitWithRuleMatched(activity)) {
return true;
}
// 4. Whether the activity below (if any) should be split with the new launched activity.
final Activity activityBelow = findActivityBelow(launchedActivity);
final Activity activityBelow = findActivityBelow(activity);
if (activityBelow == null) {
// Can't find any activity below.
return false;
}
if (putActivitiesIntoSplitIfNecessary(activityBelow, launchedActivity)) {
if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) {
// Have split rule of [ activityBelow | launchedActivity ].
return true;
}
if (canSplitAsPrimary
&& putActivitiesIntoSplitIfNecessary(launchedActivity, activityBelow)) {
if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) {
// Have split rule of [ launchedActivity | activityBelow].
return true;
}
@@ -430,17 +429,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
? topSplit.getSecondaryContainer()
: topSplit.getPrimaryContainer();
final Activity otherTopActivity = otherTopContainer.getTopNonFinishingActivity();
if (otherTopActivity == null || otherTopActivity == launchedActivity) {
if (otherTopActivity == null || otherTopActivity == activity) {
// Can't find the top activity on the other split TaskFragment.
return false;
}
if (putActivitiesIntoSplitIfNecessary(otherTopActivity, launchedActivity)) {
if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) {
// Have split rule of [ otherTopActivity | launchedActivity ].
return true;
}
// Have split rule of [ launchedActivity | otherTopActivity].
return canSplitAsPrimary
&& putActivitiesIntoSplitIfNecessary(launchedActivity, otherTopActivity);
return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity);
}
/**
@@ -603,7 +601,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// Check if activity requires a placeholder
launchPlaceholderIfNecessary(activity);
launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
}
@VisibleForTesting
@@ -1043,10 +1041,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return false;
}
return launchPlaceholderIfNecessary(topActivity);
return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */);
}
boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
// Don't launch placeholder if the container is occluded.
if (container != null && container != getTopActiveContainer(container.getTaskId())) {
@@ -1067,11 +1065,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// TODO(b/190433398): Handle failed request
startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null /* options */,
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options,
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
return true;
}
/**
* Gets the activity options for starting the placeholder activity. In case the placeholder is
* launched when the Task is in the background, we don't want to bring the Task to the front.
* @param primaryActivity the primary activity to launch the placeholder from.
* @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
@Nullable
Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
// Setting avoid move to front will also skip the animation. We only want to do that when
// the Task is currently in background.
// Check if the primary is resumed or if this is called when the primary is onCreated
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
options.setAvoidMoveToFront();
return options.toBundle();
}
@VisibleForTesting
boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
if (!splitContainer.isPlaceholderContainer()) {

View File

@@ -43,6 +43,7 @@ import static org.mockito.Mockito.never;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
@@ -50,6 +51,7 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
@@ -219,7 +221,8 @@ public class SplitControllerTest {
spyOn(tf);
doReturn(mActivity).when(tf).getTopNonFinishingActivity();
doReturn(true).when(tf).isEmpty();
doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity);
doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity,
false /* isOnCreated */);
doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -287,8 +290,7 @@ public class SplitControllerTest {
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 */);
verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */);
}
@Test
@@ -297,8 +299,7 @@ public class SplitControllerTest {
mActivity.getActivityToken());
// Treated as on activity created, but allow to split as primary.
verify(mSplitController).resolveActivityToContainer(mActivity,
true /* canSplitAsPrimary */);
verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */);
// Try to place the activity to the top TaskFragment when there is no matched rule.
verify(mSplitController).placeActivityInTopContainer(mActivity);
}
@@ -432,7 +433,7 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_noRuleMatched() {
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
@@ -444,7 +445,7 @@ public class SplitControllerTest {
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -461,7 +462,7 @@ public class SplitControllerTest {
// 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 */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
@@ -475,7 +476,7 @@ public class SplitControllerTest {
final Activity activity = createMockActivity();
addSplitTaskFragments(activity, mActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -492,11 +493,12 @@ public class SplitControllerTest {
// Launch placeholder if the activity is not in any TaskFragment.
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@Test
@@ -508,7 +510,7 @@ public class SplitControllerTest {
mSplitController.newContainer(mActivity, TASK_ID);
mSplitController.newContainer(activity, TASK_ID);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
@@ -524,11 +526,12 @@ public class SplitControllerTest {
// Launch placeholder if the activity is in the topmost expanded TaskFragment.
mSplitController.newContainer(mActivity, TASK_ID);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@Test
@@ -539,7 +542,7 @@ public class SplitControllerTest {
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
@@ -556,11 +559,12 @@ public class SplitControllerTest {
final Activity primaryActivity = createMockActivity();
addSplitTaskFragments(primaryActivity, mActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
null /* activityOptions */, placeholderRule, true /* isPlaceholder */);
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@Test
@@ -582,7 +586,7 @@ public class SplitControllerTest {
splitRule);
clearInvocations(mSplitController);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
@@ -598,7 +602,7 @@ public class SplitControllerTest {
addSplitTaskFragments(primaryActivity, mActivity);
clearInvocations(mSplitController);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
@@ -616,7 +620,7 @@ public class SplitControllerTest {
mSplitController.getContainerWithActivity(secondaryActivity)
.addPendingAppearedActivity(mActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
}
@@ -641,7 +645,7 @@ public class SplitControllerTest {
secondaryContainer,
placeholderRule);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
}
@@ -655,7 +659,7 @@ public class SplitControllerTest {
TASK_ID);
container.addPendingAppearedActivity(mActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(activityBelow, mActivity);
@@ -671,14 +675,13 @@ public class SplitControllerTest {
TASK_ID);
container.addPendingAppearedActivity(mActivity);
boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
// Allow to split as primary.
result = mSplitController.resolveActivityToContainer(mActivity,
true /* canSplitAsPrimary */);
result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, activityBelow);
@@ -697,7 +700,7 @@ public class SplitControllerTest {
activityBelow);
secondaryContainer.addPendingAppearedActivity(mActivity);
final boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -718,19 +721,38 @@ public class SplitControllerTest {
primaryActivity);
primaryContainer.addPendingAppearedActivity(mActivity);
boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* canSplitAsPrimary */);
false /* isOnReparent */);
assertFalse(result);
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
result = mSplitController.resolveActivityToContainer(mActivity,
true /* canSplitAsPrimary */);
result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, primaryActivity);
}
@Test
public void testGetPlaceholderOptions() {
doReturn(true).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
doReturn(false).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */));
// Launch placeholder without moving the Task to front if the Task is now in background (not
// resumed or onCreated).
final Bundle options = mSplitController.getPlaceholderOptions(mActivity,
false /* isOnCreated */);
assertNotNull(options);
final ActivityOptions activityOptions = new ActivityOptions(options);
assertTrue(activityOptions.getAvoidMoveToFront());
}
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);