Don't bring the Task to front when launching placeholder in background

If the secondary TaskFragment becomes empty, it may trigger to launch
the placeholder activity. If this happens when the Task is in the
background, we don't want the placeholder launch to bring the Task to
the front.

Fix: 232828672
Test: atest WMJetpackUnitTests:SplitControllerTest
Change-Id: Idc159a8c8117e6cc601d7f8650bcfa3c575af076
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 015205c..9e7f9d2 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 @@
             // 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 @@
     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 @@
          */
 
         // 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 @@
                         ? 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 @@
         }
 
         // Check if activity requires a placeholder
-        launchPlaceholderIfNecessary(activity);
+        launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
     }
 
     @VisibleForTesting
@@ -1043,10 +1041,10 @@
             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 @@
         }
 
         // 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 353c7df..7fbacba 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 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.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 @@
         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 @@
         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 @@
                 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 @@
     @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 @@
 
         // 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 @@
         // 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 @@
         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 @@
 
         // 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 @@
         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 @@
         // 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 @@
         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 @@
         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 @@
                 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 @@
         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 @@
         mSplitController.getContainerWithActivity(secondaryActivity)
                 .addPendingAppearedActivity(mActivity);
         final boolean result = mSplitController.resolveActivityToContainer(mActivity,
-                false /* canSplitAsPrimary */);
+                false /* isOnReparent */);
 
         assertFalse(result);
     }
@@ -641,7 +645,7 @@
                 secondaryContainer,
                 placeholderRule);
         final boolean result = mSplitController.resolveActivityToContainer(mActivity,
-                false /* canSplitAsPrimary */);
+                false /* isOnReparent */);
 
         assertTrue(result);
     }
@@ -655,7 +659,7 @@
                 TASK_ID);
         container.addPendingAppearedActivity(mActivity);
         final boolean result = mSplitController.resolveActivityToContainer(mActivity,
-                false /* canSplitAsPrimary */);
+                false /* isOnReparent */);
 
         assertTrue(result);
         assertSplitPair(activityBelow, mActivity);
@@ -671,14 +675,13 @@
                 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 @@
                 activityBelow);
         secondaryContainer.addPendingAppearedActivity(mActivity);
         final boolean result = mSplitController.resolveActivityToContainer(mActivity,
-                false /* canSplitAsPrimary */);
+                false /* isOnReparent */);
         final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
                 mActivity);
 
@@ -718,19 +721,38 @@
                 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);