diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 015205c7a0637..9e7f9d22db189 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -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()) { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 353c7df2cbc5f..7fbacbaa8761c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -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);