Merge changes from topic "reparentTasks"

* changes:
  1/ Refactor LegacySplitScreenTests
  Support WCT#reparentTasks and WCT#setLaunchRoot
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c03461a..1422561 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2512,7 +2512,6 @@
     method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
     method @BinderThread public void removeStartingWindow(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
     method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
   }
 
@@ -2527,6 +2526,7 @@
     method public int describeContents();
     method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
     method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
@@ -2534,6 +2534,7 @@
     method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
     method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
     method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
     method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 4a43a43..2d0211e 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -56,12 +56,6 @@
     WindowContainerToken getImeTarget(int display);
 
     /**
-     * Set's the root task to launch new tasks into on a display. {@code null} means no launch root
-     * and thus new tasks just end up directly on the display.
-     */
-    void setLaunchRoot(int displayId, in WindowContainerToken root);
-
-    /**
      * Requests that the given task organizer is notified when back is pressed on the root activity
      * of one of its controlled tasks.
      */
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 73b2fe1..f29eb39 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -184,19 +184,6 @@
     }
 
     /**
-     * Set's the root task to launch new tasks into on a display. {@code null} means no launch
-     * root and thus new tasks just end up directly on the display.
-     */
-    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
-    public void setLaunchRoot(int displayId, @NonNull WindowContainerToken root) {
-        try {
-            mTaskOrganizerController.setLaunchRoot(displayId, root);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Requests that the given task organizer is notified when back is pressed on the root activity
      * of one of its controlled tasks.
      */
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index eba4fd2..6bc3110 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -30,6 +30,7 @@
 import android.view.SurfaceControl;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -263,8 +264,9 @@
     @NonNull
     public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
             @Nullable WindowContainerToken parent, boolean onTop) {
-        mHierarchyOps.add(new HierarchyOp(child.asBinder(),
-                parent == null ? null : parent.asBinder(), onTop));
+        mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
+                parent == null ? null : parent.asBinder(),
+                onTop));
         return this;
     }
 
@@ -276,7 +278,47 @@
      */
     @NonNull
     public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
-        mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
+        mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop));
+        return this;
+    }
+
+    /**
+     * Reparent's all children tasks of {@param currentParent} in the specified
+     * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
+     * z-order.
+     *
+     * @param currentParent of the tasks to perform the operation no.
+     *                      {@code null} will perform the operation on the display.
+     * @param newParent for the tasks. {@code null} will perform the operation on the display.
+     * @param windowingModes of the tasks to reparent.
+     * @param activityTypes of the tasks to reparent.
+     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+     *              the bottom.
+     */
+    @NonNull
+    public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
+            @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
+            @Nullable int[] activityTypes, boolean onTop) {
+        mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
+                currentParent != null ? currentParent.asBinder() : null,
+                newParent != null ? newParent.asBinder() : null,
+                windowingModes,
+                activityTypes,
+                onTop));
+        return this;
+    }
+
+    /**
+     * Sets whether a container should be the launch root for the specified windowing mode and
+     * activity type. This currently only applies to Task containers created by organizer.
+     */
+    @NonNull
+    public WindowContainerTransaction setLaunchRoot(@NonNull WindowContainerToken container,
+            @Nullable int[] windowingModes, @Nullable int[] activityTypes) {
+        mHierarchyOps.add(HierarchyOp.createForSetLaunchRoot(
+                container.asBinder(),
+                windowingModes,
+                activityTypes));
         return this;
     }
 
@@ -363,6 +405,7 @@
         private boolean mFocusable = true;
         private boolean mHidden = false;
         private boolean mIgnoreOrientationRequest = false;
+
         private int mChangeMask = 0;
         private @ActivityInfo.Config int mConfigSetMask = 0;
         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
@@ -595,6 +638,14 @@
      * @hide
      */
     public static class HierarchyOp implements Parcelable {
+        public static final int HIERARCHY_OP_TYPE_REPARENT = 0;
+        public static final int HIERARCHY_OP_TYPE_REORDER = 1;
+        public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2;
+        public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT = 3;
+
+        private final int mType;
+
+        // Container we are performing the operation on.
         private final IBinder mContainer;
 
         // If this is same as mContainer, then only change position, don't reparent.
@@ -603,32 +654,68 @@
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
         private final boolean mToTop;
 
-        public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
-            mContainer = container;
-            mReparent = reparent;
-            mToTop = toTop;
+        final private int[]  mWindowingModes;
+        final private int[] mActivityTypes;
+
+        public static HierarchyOp createForReparent(
+                @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
+            return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT,
+                    container, reparent, null, null, toTop);
         }
 
-        public HierarchyOp(@NonNull IBinder container, boolean toTop) {
+        public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
+            return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER,
+                    container, container, null, null, toTop);
+        }
+
+        public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
+                IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
+            return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT,
+                    currentParent, newParent, windowingModes, activityTypes, onTop);
+        }
+
+        public static HierarchyOp createForSetLaunchRoot(IBinder container,
+                int[] windowingModes, int[] activityTypes) {
+            return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT,
+                    container, null, windowingModes, activityTypes, false);
+        }
+
+        private HierarchyOp(int type, @NonNull IBinder container, @Nullable IBinder reparent,
+                int[] windowingModes, int[] activityTypes, boolean toTop) {
+            mType = type;
             mContainer = container;
-            mReparent = container;
+            mReparent = reparent;
+            mWindowingModes = windowingModes != null ?
+                    Arrays.copyOf(windowingModes, windowingModes.length) : null;
+            mActivityTypes = activityTypes != null ?
+                    Arrays.copyOf(activityTypes, activityTypes.length) : null;
             mToTop = toTop;
         }
 
         public HierarchyOp(@NonNull HierarchyOp copy) {
+            mType = copy.mType;
             mContainer = copy.mContainer;
             mReparent = copy.mReparent;
             mToTop = copy.mToTop;
+            mWindowingModes = copy.mWindowingModes;
+            mActivityTypes = copy.mActivityTypes;
         }
 
         protected HierarchyOp(Parcel in) {
+            mType = in.readInt();
             mContainer = in.readStrongBinder();
             mReparent = in.readStrongBinder();
             mToTop = in.readBoolean();
+            mWindowingModes = in.createIntArray();
+            mActivityTypes = in.createIntArray();
+        }
+
+        public int getType() {
+            return mType;
         }
 
         public boolean isReparent() {
-            return mContainer != mReparent;
+            return mType == HIERARCHY_OP_TYPE_REPARENT;
         }
 
         @Nullable
@@ -645,21 +732,45 @@
             return mToTop;
         }
 
+        public int[] getWindowingModes() {
+            return mWindowingModes;
+        }
+
+        public int[] getActivityTypes() {
+            return mActivityTypes;
+        }
+
         @Override
         public String toString() {
-            if (isReparent()) {
-                return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
-                        + mReparent + "}";
-            } else {
-                return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+            switch (mType) {
+                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+                    return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
+                            + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
+                            + " mActivityType=" + mActivityTypes + "}";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
+                    return "{SetLaunchRoot: container=" + mContainer
+                            + " mWindowingMode=" + mWindowingModes
+                            + " mActivityType=" + mActivityTypes + "}";
+                case HIERARCHY_OP_TYPE_REPARENT:
+                    return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+                            + mReparent + "}";
+                case HIERARCHY_OP_TYPE_REORDER:
+                    return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+                default:
+                    return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+                            + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
+                            + " mActivityType=" + mActivityTypes + "}";
             }
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mType);
             dest.writeStrongBinder(mContainer);
             dest.writeStrongBinder(mReparent);
             dest.writeBoolean(mToTop);
+            dest.writeIntArray(mWindowingModes);
+            dest.writeIntArray(mActivityTypes);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index ad05e6d..a8cd1dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -18,7 +18,10 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -54,6 +57,17 @@
 
     private static final String TAG = "WindowManagerProxy";
     private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
+    private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+            ACTIVITY_TYPE_STANDARD,
+            ACTIVITY_TYPE_HOME,
+            ACTIVITY_TYPE_RECENTS,
+            ACTIVITY_TYPE_UNDEFINED
+    };
+    private static final int[] CONTROLLED_WINDOWING_MODES = {
+            WINDOWING_MODE_FULLSCREEN,
+            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+            WINDOWING_MODE_UNDEFINED
+    };
 
     @GuardedBy("mDockedRect")
     private final Rect mDockedRect = new Rect();
@@ -191,8 +205,9 @@
         // Set launchtile first so that any stack created after
         // getAllRootTaskInfos and before reparent (even if unlikely) are placed
         // correctly.
-        mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
         WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
+                CONTROLLED_ACTIVITY_TYPES);
         final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
         applySyncTransaction(wct);
         return isHomeResizable;
@@ -251,12 +266,12 @@
     /** @see #buildDismissSplit */
     void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
             boolean dismissOrMaximize) {
-        // Set launch root first so that any task created after getChildContainers and
-        // before reparent (pretty unlikely) are put into fullscreen.
-        mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
         // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
         //                 plus specific APIs to clean this up.
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        // Set launch root first so that any task created after getChildContainers and
+        // before reparent (pretty unlikely) are put into fullscreen.
+        wct.setLaunchRoot(tiles.mSecondary.token, null, null);
         buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
         applySyncTransaction(wct);
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 5ab1c39..8543850 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -44,6 +44,18 @@
 }
 
 @JvmOverloads
+fun LayersAssertion.appPairsDividerBecomesVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("dividerLayerBecomesVisible") {
+        this.hidesLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
+                .then()
+                .showsLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
+    }
+}
+
+@JvmOverloads
 fun LayersAssertion.dockedStackDividerIsVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
index b33fa55..85bf4a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.canSplitScreen
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.isInSplitScreen
@@ -35,6 +34,7 @@
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+
 import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
 import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
 import org.junit.Assert
@@ -59,8 +59,6 @@
     rotationName: String,
     rotation: Int
 ) : SplitScreenTestBase(rotationName, rotation) {
-    private val letterBox = "Letterbox"
-
     private val splitScreenSetup: FlickerBuilder
         get() = FlickerBuilder(instrumentation).apply {
             val testLaunchActivity = "launch_splitScreen_test_activity"
@@ -91,7 +89,6 @@
                 windowManagerTrace {
                     navBarWindowIsAlwaysVisible()
                     statusBarWindowIsAlwaysVisible()
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName))
                 }
             }
         }
@@ -114,7 +111,8 @@
                             rotation, splitScreenApp.defaultWindowName, 169271943)
                     dockedStackDividerBecomesVisible()
                     visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName, splitScreenApp.defaultWindowName)
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                    LIVE_WALLPAPER_PACKAGE_NAME)
                     )
                 }
                 windowManagerTrace {
@@ -148,7 +146,7 @@
                             rotation, secondaryApp.defaultWindowName, 169271943)
                     dockedStackDividerBecomesVisible()
                     visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName, splitScreenApp.defaultWindowName,
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
                                     secondaryApp.defaultWindowName)
                     )
                 }
@@ -157,6 +155,7 @@
                         showsAppWindow(splitScreenApp.defaultWindowName)
                                 .and().showsAppWindow(secondaryApp.defaultWindowName)
                     }
+                    visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
                 }
             }
         }
@@ -181,85 +180,14 @@
                 layersTrace {
                     dockedStackDividerIsInvisible()
                     visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName, nonResizeableApp.defaultWindowName)
+                            listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
                     )
                 }
                 windowManagerTrace {
                     end {
                         hidesAppWindow(nonResizeableApp.defaultWindowName)
                     }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testNonResizeableWhenAlreadyInSplitScreenPrimary() {
-        val testTag = "testNonResizeableWhenAlreadyInSplitScreenPrimary"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                nonResizeableApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                nonResizeableApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, letterBox)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        showsAppWindow(nonResizeableApp.defaultWindowName)
-                        hidesAppWindow(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testNonResizeableWhenAlreadyInSplitScreenSecondary() {
-        val testTag = "testNonResizeableWhenAlreadyInSplitScreenSecondary"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                uiDevice.pressBack()
-                nonResizeableApp.launchViaIntent()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, letterBox)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        showsAppWindow(nonResizeableApp.defaultWindowName)
-                        hidesAppWindow(splitScreenApp.defaultWindowName)
-                    }
+                    visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
index 573ffc6..9586fd1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
@@ -22,16 +22,20 @@
 import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
 import com.android.server.wm.flicker.helpers.isInSplitScreen
 import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+
 import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.testapp.Components
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -53,23 +57,24 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val testApp = StandardAppHelper(instrumentation,
-                    "com.android.wm.shell.flicker.testapp", "SimpleApp")
+            val splitScreenApp = SplitScreenHelper(instrumentation,
+                    TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+                    Components.SplitScreenActivity())
 
-            // b/161435597 causes the test not to work on 90 degrees
-            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+            // TODO(b/162923992) Use of multiple segments of flicker spec for testing
+            return FlickerTestRunnerFactory(instrumentation,
+                    listOf(Surface.ROTATION_0, Surface.ROTATION_90))
                     .buildTest { configuration ->
                         withTestName {
-                            buildTestTag("exitSplitScreenFromBottom", testApp,
+                            buildTestTag("exitSplitScreenFromBottom", splitScreenApp,
                                     configuration)
                         }
                         repeat { configuration.repetitions }
                         setup {
-                            test {
-                                device.wakeUpAndGoToHomeScreen()
-                            }
                             eachRun {
-                                testApp.open()
+                                device.wakeUpAndGoToHomeScreen()
+                                device.openQuickStepAndClearRecentAppsFromOverview()
+                                splitScreenApp.launchViaIntent()
                                 device.launchSplitScreen()
                                 device.waitForIdle()
                                 this.setRotation(configuration.endRotation)
@@ -77,12 +82,10 @@
                         }
                         teardown {
                             eachRun {
-                                testApp.exit()
-                            }
-                            test {
                                 if (device.isInSplitScreen()) {
                                     device.exitSplitScreen()
                                 }
+                                splitScreenApp.exit()
                             }
                         }
                         transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
index c51c73a..84bfe945 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
@@ -82,7 +82,7 @@
                 }
                 layersTrace {
                     visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(launcherPackageName))
+                            listOf(LAUNCHER_PACKAGE_NAME))
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
new file mode 100644
index 0000000..e9d3eb7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreenTest(
+    rotationName: String,
+    rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+    @Test
+    fun testNonResizableDismissInLegacySplitScreenTest() {
+        val testTag = "testNonResizableDismissInLegacySplitScreenTest"
+
+        runWithFlicker(transitionSetup) {
+            withTestName { testTag }
+            repeat { SplitScreenHelper.TEST_REPETITIONS }
+            transitions {
+                nonResizeableApp.launchViaIntent()
+                splitScreenApp.launchViaIntent()
+                device.launchSplitScreen()
+                nonResizeableApp.reopenAppFromOverview()
+            }
+            assertions {
+                layersTrace {
+                    dockedStackDividerIsInvisible()
+                    end("appsEndingBounds", enabled = false) {
+                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+                    }
+                    visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+                    )
+                }
+                windowManagerTrace {
+                    end {
+                        showsAppWindow(nonResizeableApp.defaultWindowName)
+                        hidesAppWindow(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
new file mode 100644
index 0000000..b5a36f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreenTest(
+    rotationName: String,
+    rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+    @Test
+    fun testNonResizableLaunchInLegacySplitScreenTest() {
+        val testTag = "NonResizableLaunchInLegacySplitScreenTest"
+
+        runWithFlicker(transitionSetup) {
+            withTestName { testTag }
+            repeat { SplitScreenHelper.TEST_REPETITIONS }
+            transitions {
+                nonResizeableApp.launchViaIntent()
+                splitScreenApp.launchViaIntent()
+                device.launchSplitScreen()
+                nonResizeableApp.reopenAppFromOverview()
+            }
+            assertions {
+                layersTrace {
+                    dockedStackDividerIsInvisible()
+                    end("appsEndingBounds", enabled = false) {
+                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+                    }
+                    visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+                    )
+                }
+                windowManagerTrace {
+                    end {
+                        showsAppWindow(nonResizeableApp.defaultWindowName)
+                        hidesAppWindow(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
index af03869..90577ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
@@ -17,37 +17,22 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
 import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -56,85 +41,59 @@
  * Test open app to split screen.
  * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
  */
-@Presubmit
+// TODO: Add back to pre-submit when stable.
+//@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppToLegacySplitScreenTest(
-    testName: String,
-    flickerSpec: Flicker
-) : FlickerTestRunner(testName, flickerSpec) {
+    rotationName: String,
+    rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+    @Test
+    fun OpenAppToLegacySplitScreenTest() {
+        val testTag = "OpenAppToLegacySplitScreenTest"
+
+        runWithFlicker(transitionSetup) {
+            withTestName { testTag }
+            repeat { SplitScreenHelper.TEST_REPETITIONS }
+            transitions {
+                splitScreenApp.launchViaIntent()
+                device.pressHome()
+                this.setRotation(rotation)
+                device.launchSplitScreen()
+            }
+            assertions {
+                windowManagerTrace {
+                    visibleWindowsShownMoreThanOneConsecutiveEntry()
+                    appWindowBecomesVisible(splitScreenApp.getPackage())
+                }
+
+                layersTrace {
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
+                    noUncoveredRegions(rotation, enabled = false)
+                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                    visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME))
+                    appPairsDividerBecomesVisible()
+                    layerBecomesVisible(splitScreenApp.getPackage())
+                }
+
+                eventLog {
+                    focusChanges(splitScreenApp.`package`,
+                            "recents_animation_input_consumer", "NexusLauncherActivity",
+                            bugId = 151179149)
+                }
+            }
+        }
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
-                    .launcherStrategy.supportedLauncherPackage
-            val testApp = StandardAppHelper(instrumentation,
-                "com.android.wm.shell.flicker.testapp", "SimpleApp")
-
-            // b/161435597 causes the test not to work on 90 degrees
-            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
-                    withTestName {
-                        buildTestTag("appToSplitScreen", testApp, configuration)
-                    }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                            device.openQuickStepAndClearRecentAppsFromOverview()
-                        }
-                        eachRun {
-                            testApp.open()
-                            device.pressHome()
-                            this.setRotation(configuration.endRotation)
-                        }
-                    }
-                    teardown {
-                        eachRun {
-                            if (device.isInSplitScreen()) {
-                                device.exitSplitScreen()
-                            }
-                        }
-                        test {
-                            testApp.exit()
-                        }
-                    }
-                    transitions {
-                        device.launchSplitScreen()
-                    }
-                    assertions {
-                        windowManagerTrace {
-                            navBarWindowIsAlwaysVisible()
-                            statusBarWindowIsAlwaysVisible()
-                            visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-                            appWindowBecomesVisible(testApp.getPackage())
-                        }
-
-                        layersTrace {
-                            navBarLayerIsAlwaysVisible(bugId = 140855415)
-                            statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.endRotation, enabled = false)
-                            navBarLayerRotatesAndScales(configuration.endRotation,
-                                bugId = 140855415)
-                            statusBarLayerRotatesScales(configuration.endRotation)
-                            visibleLayersShownMoreThanOneConsecutiveEntry(
-                                    listOf(launcherPackageName))
-
-                            dockedStackDividerBecomesVisible()
-                            layerBecomesVisible(testApp.getPackage())
-                        }
-
-                        eventLog {
-                            focusChanges(testApp.`package`,
-                                "recents_animation_input_consumer", "NexusLauncherActivity",
-                                bugId = 151179149)
-                        }
-                    }
-                }
+            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
index a536ec8..2b94c5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
@@ -17,6 +17,15 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.wm.shell.flicker.NonRotationTestBase
 import com.android.wm.shell.flicker.TEST_APP_NONRESIZEABLE_LABEL
 import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
@@ -37,6 +46,39 @@
     protected val nonResizeableApp = SplitScreenHelper(instrumentation,
             TEST_APP_NONRESIZEABLE_LABEL,
             Components.NonResizeableActivity())
-    protected val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
+
+    protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
             .launcherStrategy.supportedLauncherPackage
+    protected val LIVE_WALLPAPER_PACKAGE_NAME =
+            "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+    protected val LETTER_BOX_NAME = "Letterbox"
+
+    protected val transitionSetup: FlickerBuilder
+        get() = FlickerBuilder(instrumentation).apply {
+                setup {
+                    eachRun {
+                        uiDevice.wakeUpAndGoToHomeScreen()
+                        uiDevice.openQuickStepAndClearRecentAppsFromOverview()
+                    }
+                }
+                teardown {
+                    eachRun {
+                        if (uiDevice.isInSplitScreen()) {
+                            uiDevice.exitSplitScreen()
+                        }
+                        splitScreenApp.exit()
+                        nonResizeableApp.exit()
+                    }
+                }
+                assertions {
+                    layersTrace {
+                        navBarLayerIsAlwaysVisible()
+                        statusBarLayerIsAlwaysVisible()
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                    }
+                }
+            }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c6cc83b..7a4bcb1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1646,7 +1646,7 @@
         if (startedActivityStack != null && startedActivityStack.isAttached()
                 && !startedActivityStack.hasActivity()
                 && !startedActivityStack.isActivityTypeHome()) {
-            startedActivityStack.removeIfPossible();
+            startedActivityStack.removeIfPossible("handleStartResult");
             startedActivityStack = null;
         }
         return startedActivityStack;
@@ -1834,7 +1834,7 @@
                 return top.getTask();
             } else {
                 // Remove the stack if no activity in the stack.
-                stack.removeIfPossible();
+                stack.removeIfPossible("computeTargetTask");
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c3a7542..4494d99 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5490,7 +5490,7 @@
         if (!hasNonEmptyHomeRootTask && getRootTaskCount() > 0) {
             // Release this display if only empty home root task(s) are left. This display will be
             // released along with the root task(s) removal.
-            forAllRootTasks(Task::removeIfPossible);
+            forAllRootTasks(t -> {t.removeIfPossible("releaseSelfIfNeeded");});
         } else if (getTopRootTask() == null) {
             removeIfPossible();
             mRootWindowContainer.mTaskSupervisor
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a88e453..a2a6985 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -916,21 +916,26 @@
             mTaskSupervisor.mRecentTasks.remove(this);
         }
 
-        removeIfPossible();
+        removeIfPossible("cleanUpResourcesForDestroy");
     }
 
     @VisibleForTesting
     @Override
     void removeIfPossible() {
+        removeIfPossible("removeTaskIfPossible");
+    }
+
+    void removeIfPossible(String reason) {
         final boolean isRootTask = isRootTask();
         if (!isRootTask) {
             mAtmService.getLockTaskController().clearLockedTask(this);
         }
         if (shouldDeferRemoval()) {
-            if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
+            if (DEBUG_ROOT_TASK) Slog.i(TAG,
+                    "removeTask:" + reason + " deferring removing taskId=" + mTaskId);
             return;
         }
-        removeImmediately();
+        removeImmediately(reason);
         if (isLeafTask()) {
             mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
 
@@ -1774,8 +1779,8 @@
                 getRootTask().removeChild(this, reason);
             }
             EventLogTags.writeWmTaskRemoved(mTaskId,
-                    "removeChild: last r=" + r + " in t=" + this);
-            removeIfPossible();
+                    "removeChild:" + reason + " last r=" + r + " in t=" + this);
+            removeIfPossible(reason);
         }
     }
 
@@ -1818,7 +1823,7 @@
                 if (r.finishing) return;
                 // Task was restored from persistent storage.
                 r.takeFromHistory();
-                removeChild(r);
+                removeChild(r, reason);
             });
         } else {
             forAllActivities((r) -> {
@@ -3214,8 +3219,12 @@
 
     @Override
     void removeImmediately() {
-        if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
-        EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+        removeImmediately("removeTask");
+    }
+
+    void removeImmediately(String reason) {
+        if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask:" + reason + " removing taskId=" + mTaskId);
+        EventLogTags.writeWmTaskRemoved(mTaskId, reason);
 
         // If applicable let the TaskOrganizer know the Task is vanishing.
         setTaskOrganizer(null);
@@ -4986,7 +4995,7 @@
             mTaskAppearedSent = false;
             setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
             if (mCreatedByOrganizer) {
-                removeImmediately();
+                removeImmediately("setTaskOrganizer");
             }
         }
 
@@ -6708,7 +6717,7 @@
     /** Finish all activities in the stack without waiting. */
     void finishAllActivitiesImmediately() {
         if (!hasChild()) {
-            removeIfPossible();
+            removeIfPossible("finishAllActivitiesImmediately");
             return;
         }
         forAllActivities((r) -> {
@@ -8002,7 +8011,7 @@
 
             // Task created by organizer are added as root.
             final Task launchRootTask = mCreatedByOrganizer
-                    ? null : tda.updateLaunchRootTask(mWindowingMode);
+                    ? null : tda.getLaunchRootTask(mWindowingMode, mActivityType);
             if (launchRootTask != null) {
                 // Since this task will be put into a root task, its windowingMode will be
                 // inherited.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3f4150b..2ebdda6 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,7 +28,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -54,6 +53,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
@@ -117,8 +117,18 @@
 
     private RootWindowContainer mRootWindowContainer;
 
-    // When non-null, new tasks get put into this root task.
-    Task mLaunchRootTask = null;
+    // Launch root tasks by activityType then by windowingMode.
+    static private class LaunchRootTaskDef {
+        Task task;
+        int[] windowingModes;
+        int[] activityTypes;
+
+        boolean contains(int windowingMode, int activityType) {
+            return ArrayUtils.contains(windowingModes, windowingMode)
+                    && ArrayUtils.contains(activityTypes, activityType);
+        }
+    }
+    private final ArrayList<LaunchRootTaskDef> mLaunchRootTasks = new ArrayList<>();
 
     /**
      * A focusable stack that is purposely to be positioned at the top. Although the stack may not
@@ -1017,7 +1027,7 @@
         } else if (candidateTask != null) {
             final Task stack = candidateTask;
             final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
-            Task launchRootTask = updateLaunchRootTask(windowingMode);
+            final Task launchRootTask = getLaunchRootTask(windowingMode, activityType);
 
             if (launchRootTask != null) {
                 if (stack.getParent() == null) {
@@ -1096,40 +1106,41 @@
                 .build();
     }
 
-    /** @return the root task to create the next task in. */
-    Task updateLaunchRootTask(int windowingMode) {
-        if (!isSplitScreenWindowingMode(windowingMode)) {
-            // Only split-screen windowing modes can do this currently...
-            return null;
+    // TODO: Also clear when task is removed from system?
+    void setLaunchRootTask(Task rootTask, int[] windowingModes, int[] activityTypes) {
+        if (!rootTask.mCreatedByOrganizer) {
+            throw new IllegalArgumentException(
+                    "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
         }
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer child = mChildren.get(i);
-            if (child.asTaskDisplayArea() != null) {
-                final Task t = child.asTaskDisplayArea().updateLaunchRootTask(windowingMode);
-                if (t != null) {
-                    return t;
-                }
-                continue;
-            }
 
-            final Task t = mChildren.get(i).asTask();
-            if (t == null || !t.mCreatedByOrganizer
-                    || t.getRequestedOverrideWindowingMode() != windowingMode) {
-                continue;
-            }
-            // If not already set, pick a launch root which is not the one we are launching into.
-            if (mLaunchRootTask == null) {
-                for (int j = 0, n = mChildren.size(); j < n; ++j) {
-                    final Task tt = mChildren.get(j).asTask();
-                    if (tt != null && tt.mCreatedByOrganizer && tt != t) {
-                        mLaunchRootTask = tt;
-                        break;
-                    }
-                }
-            }
-            return t;
+        LaunchRootTaskDef def = null;
+        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+            if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
+            def = mLaunchRootTasks.get(i);
         }
-        return mLaunchRootTask;
+
+        if (def != null) {
+            // Remove so we add to the end of the list.
+            mLaunchRootTasks.remove(def);
+        } else {
+            def = new LaunchRootTaskDef();
+            def.task = rootTask;
+        }
+
+        def.activityTypes = activityTypes;
+        def.windowingModes = windowingModes;
+        if (!ArrayUtils.isEmpty(windowingModes) || !ArrayUtils.isEmpty(activityTypes)) {
+            mLaunchRootTasks.add(def);
+        }
+    }
+
+    Task getLaunchRootTask(int windowingMode, int activityType) {
+        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+            if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) {
+                return mLaunchRootTasks.get(i).task;
+            }
+        }
+        return null;
     }
 
     /**
@@ -1321,7 +1332,6 @@
     void onSplitScreenModeDismissed(Task toTop) {
         mAtmService.deferWindowLayout();
         try {
-            mLaunchRootTask = null;
             moveSplitScreenTasksToFullScreen();
         } finally {
             final Task topFullscreenStack = toTop != null
@@ -1919,7 +1929,20 @@
         if (mLastFocusedRootTask != null) {
             pw.println(doublePrefix + "mLastFocusedRootTask=" + mLastFocusedRootTask);
         }
+
         final String triplePrefix = doublePrefix + "  ";
+
+        if (mLaunchRootTasks.size() > 0) {
+            pw.println(doublePrefix + "mLaunchRootTasks:");
+            for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+                final LaunchRootTaskDef def = mLaunchRootTasks.get(i);
+                pw.println(triplePrefix
+                        + def.activityTypes + " "
+                        + def.windowingModes + " "
+                        + " task=" + def.task);
+            }
+        }
+
         pw.println(doublePrefix + "Application tokens in top down Z order:");
         for (int index = getChildCount() - 1; index >= 0; --index) {
             final WindowContainer child = getChildAt(index);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 089071f..9a83ac7 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -510,7 +510,7 @@
 
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete root task display=%d winMode=%d",
                         task.getDisplayId(), task.getWindowingMode());
-                task.removeImmediately();
+                task.removeImmediately("deleteRootTask");
                 return true;
             }
         } finally {
@@ -601,45 +601,6 @@
     }
 
     @Override
-    public void setLaunchRoot(int displayId, @Nullable WindowContainerToken token) {
-        enforceTaskPermission("setLaunchRoot()");
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                TaskDisplayArea defaultTaskDisplayArea = mService.mRootWindowContainer
-                        .getDisplayContent(displayId).getDefaultTaskDisplayArea();
-                if (defaultTaskDisplayArea == null) {
-                    return;
-                }
-                WindowContainer wc = null;
-                if (token != null) {
-                    wc = WindowContainer.fromBinder(token.asBinder());
-                    if (wc == null) {
-                        throw new IllegalArgumentException("Can't resolve window from token");
-                    }
-                }
-                final Task task = wc == null ? null : wc.asTask();
-                if (task == null) {
-                    defaultTaskDisplayArea.mLaunchRootTask = null;
-                    return;
-                }
-                if (!task.mCreatedByOrganizer) {
-                    throw new IllegalArgumentException("Attempt to set task not created by "
-                            + "organizer as launch root task=" + task);
-                }
-                if (task.getDisplayArea() == null
-                        || task.getDisplayArea().getDisplayId() != displayId) {
-                    throw new RuntimeException("Can't set launch root for display " + displayId
-                            + " to task on display " + task.getDisplayContent().getDisplayId());
-                }
-                task.getDisplayArea().mLaunchRootTask = task;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    @Override
     public List<RunningTaskInfo> getChildTasks(WindowContainerToken parent,
             @Nullable int[] activityTypes) {
         enforceTaskPermission("getChildTasks()");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b0e67ce..be1f7e1 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -17,6 +17,10 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -49,13 +53,16 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * Server side implementation for the interface for organizing windows
@@ -256,34 +263,46 @@
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             for (int i = 0, n = hops.size(); i < n; ++i) {
                 final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
-                final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
-                if (wc == null || !wc.isAttached()) {
-                    Slog.e(TAG, "Attempt to operate on detached container: " + wc);
-                    continue;
-                }
-                if (syncId >= 0) {
-                    addToSyncSet(syncId, wc);
-                }
-                if (transition != null) {
-                    transition.collect(wc);
-                    if (hop.isReparent()) {
-                        if (wc.getParent() != null) {
-                            // Collect the current parent. It's visibility may change as a result
-                            // of this reparenting.
-                            transition.collect(wc.getParent());
+                switch (hop.getType()) {
+                    case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
+                        final Task task = WindowContainer.fromBinder(hop.getContainer()).asTask();
+                            task.getDisplayArea().setLaunchRootTask(task,
+                                    hop.getWindowingModes(), hop.getActivityTypes());
+                        break;
+                    case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+                        effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
+                        break;
+                    case HIERARCHY_OP_TYPE_REORDER:
+                    case HIERARCHY_OP_TYPE_REPARENT:
+                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                        if (wc == null || !wc.isAttached()) {
+                            Slog.e(TAG, "Attempt to operate on detached container: " + wc);
+                            continue;
                         }
-                        if (hop.getNewParent() != null) {
-                            final WindowContainer parentWc =
-                                    WindowContainer.fromBinder(hop.getNewParent());
-                            if (parentWc == null) {
-                                Slog.e(TAG, "Can't resolve parent window from token");
-                                continue;
+                        if (syncId >= 0) {
+                            addToSyncSet(syncId, wc);
+                        }
+                        if (transition != null) {
+                            transition.collect(wc);
+                            if (hop.isReparent()) {
+                                if (wc.getParent() != null) {
+                                    // Collect the current parent. It's visibility may change as
+                                    // a result of this reparenting.
+                                    transition.collect(wc.getParent());
+                                }
+                                if (hop.getNewParent() != null) {
+                                    final WindowContainer parentWc =
+                                            WindowContainer.fromBinder(hop.getNewParent());
+                                    if (parentWc == null) {
+                                        Slog.e(TAG, "Can't resolve parent window from token");
+                                        continue;
+                                    }
+                                    transition.collect(parentWc);
+                                }
                             }
-                            transition.collect(parentWc);
                         }
-                    }
+                        effects |= sanitizeAndApplyHierarchyOp(wc, hop);
                 }
-                effects |= sanitizeAndApplyHierarchyOp(wc, hop);
             }
             // Queue-up bounds-change transactions for tasks which are now organized. Do
             // this after hierarchy ops so we have the final organized state.
@@ -492,6 +511,85 @@
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
+    private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop,
+            @Nullable Transition transition, int syncId) {
+        WindowContainer currentParent = hop.getContainer() != null
+                ? WindowContainer.fromBinder(hop.getContainer()) : null;
+        WindowContainer newParent = hop.getNewParent() != null
+                ? WindowContainer.fromBinder(hop.getNewParent()) : null;
+        if (currentParent == null && newParent == null) {
+            throw new IllegalArgumentException("reparentChildrenTasksHierarchyOp: " + hop);
+        } else if (currentParent == null) {
+            currentParent = newParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
+        } else if (newParent == null) {
+            newParent = currentParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
+        }
+
+        if (currentParent == newParent) {
+            Slog.e(TAG, "reparentChildrenTasksHierarchyOp parent not changing: " + hop);
+            return 0;
+        }
+        if (!currentParent.isAttached()) {
+            Slog.e(TAG, "reparentChildrenTasksHierarchyOp currentParent detached="
+                    + currentParent + " hop=" + hop);
+            return 0;
+        }
+        if (!newParent.isAttached()) {
+            Slog.e(TAG, "reparentChildrenTasksHierarchyOp newParent detached="
+                    + newParent + " hop=" + hop);
+            return 0;
+        }
+
+        final boolean newParentInMultiWindow = newParent.inMultiWindowMode();
+        final WindowContainer finalCurrentParent = currentParent;
+        Slog.i(TAG, "reparentChildrenTasksHierarchyOp"
+                + " currentParent=" + currentParent + " newParent=" + newParent + " hop=" + hop);
+
+        // We want to collect the tasks first before re-parenting to avoid array shifting on us.
+        final ArrayList<Task> tasksToReparent = new ArrayList<>();
+
+        currentParent.forAllTasks((Consumer<Task>) (task) -> {
+            Slog.i(TAG, " Processing task=" + task);
+            if (task.mCreatedByOrganizer
+                    || task.getParent() != finalCurrentParent) {
+                // We only care about non-organized task that are direct children of the thing we
+                // are reparenting from.
+                return;
+            }
+
+            if (newParentInMultiWindow && !task.isResizeable()) {
+                Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task=" + task);
+            }
+
+            if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return;
+            if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return;
+
+            tasksToReparent.add(task);
+        }, !hop.getToTop());
+
+        final int count = tasksToReparent.size();
+        for (int i = 0; i < count; ++i) {
+            final Task task = tasksToReparent.get(i);
+            if (syncId >= 0) {
+                addToSyncSet(syncId, task);
+            }
+            if (transition != null) transition.collect(task);
+
+            if (newParent instanceof TaskDisplayArea) {
+                // For now, reparenting to display area is different from other reparents...
+                task.reparent((TaskDisplayArea) newParent, hop.getToTop());
+            } else {
+                task.reparent((Task) newParent,
+                        hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+                        false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
+            }
+        }
+
+        if (transition != null) transition.collect(newParent);
+
+        return TRANSACT_EFFECTS_LIFECYCLE;
+    }
+
     private void sanitizeWindowContainer(WindowContainer wc) {
         if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
             throw new RuntimeException("Invalid token in task or displayArea transaction");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 21aa6bf..fc96b69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -156,8 +156,10 @@
         organizer.setMoveToSecondaryOnEnter(false);
 
         // Create primary splitscreen stack.
-        final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor)
+                .setParentTask(organizer.mPrimary)
+                .setOnTop(true)
+                .build();
 
         // Assert windowing mode.
         assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode());
@@ -205,14 +207,15 @@
     @Test
     public void testSplitScreenMoveToBack() {
         TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
-        // Set up split-screen with primary on top and secondary containing the home task below
-        // another stack.
+        // Explicitly reparent task to primary split root to enter split mode, in which implies
+        // primary on top and secondary containing the home task below another stack.
         final Task primaryTask = mDefaultTaskDisplayArea.createRootTask(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task homeRoot = mDefaultTaskDisplayArea.getRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
-        final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        primaryTask.reparent(organizer.mPrimary, POSITION_TOP);
         mDefaultTaskDisplayArea.positionChildAt(POSITION_TOP, organizer.mPrimary,
                 false /* includingParents */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index fee848b..4bfc837 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -485,8 +485,10 @@
         final ActivityRecord splitSecondActivity =
                 new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor)
-                .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).setCreateActivity(true)
-                .build().getTopMostActivity();
+                .setParentTask(splitOrg.mPrimary)
+                .setCreateActivity(true)
+                .build()
+                .getTopMostActivity();
         splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
 
         assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 31e2dce..8b93372 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -17,6 +17,8 @@
 package com.android.server.wm;
 
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -1107,6 +1109,17 @@
         // moves everything to secondary. Most tests expect this since sysui usually does it.
         boolean mMoveToSecondaryOnEnter = true;
         int mDisplayId;
+        private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+                ACTIVITY_TYPE_STANDARD,
+                ACTIVITY_TYPE_HOME,
+                ACTIVITY_TYPE_RECENTS,
+                ACTIVITY_TYPE_UNDEFINED
+        };
+        private static final int[] CONTROLLED_WINDOWING_MODES = {
+                WINDOWING_MODE_FULLSCREEN,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                WINDOWING_MODE_UNDEFINED
+        };
         TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) {
             mService = service;
             mDisplayId = display.mDisplayId;
@@ -1151,9 +1164,9 @@
             if (!mMoveToSecondaryOnEnter) {
                 return;
             }
-            mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
-                    mSecondary.mRemoteToken.toWindowContainerToken());
             DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
+            dc.getDefaultTaskDisplayArea().setLaunchRootTask(
+                    mSecondary, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
             dc.forAllRootTasks(rootTask -> {
                 if (!WindowConfiguration.isSplitScreenWindowingMode(rootTask.getWindowingMode())) {
                     rootTask.reparent(mSecondary, POSITION_BOTTOM);