Remove all external usages of `GroupTask.task1/2` from Launcher3

See go/refactor-group-task for details. This CL removes all the usages
of `task1` and `task2` from `GroupTask` in Launcher3. Follow-up CLs will
remove it from NexusLauncher and remove the fields altogether.

Bug: 388593902
Test: m
Flag: EXEMPT pure refactor with no behavior change.
Change-Id: I902c8135b3a0aae95acf25267b3bcbf286bd4e7d
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 3736e6d..23065b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -233,7 +233,8 @@
     }
 
     private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
-        return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+        return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
+                t -> taskIdsToExclude.contains(t.key.id));
     }
 
     private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 306443e..73f9bea 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -51,6 +51,9 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 import java.util.HashMap;
@@ -255,14 +258,21 @@
                     layoutInflater,
                     previousTaskView);
 
-            final boolean firstTaskIsLeftTopTask =
-                    groupTask.mSplitBounds == null
-                            || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
-                            || groupTask.task2 == null;
+            Task task1;
+            Task task2;
+            if (groupTask instanceof SplitTask splitTask) {
+                task1 = splitTask.getTopLeftTask();
+                task2 = splitTask.getBottomRightTask();
+            } else if (groupTask instanceof SingleTask singleTask) {
+                task1 = singleTask.getTask();
+                task2 = null;
+            } else {
+                continue;
+            }
 
             currentTaskView.setThumbnailsForSplitTasks(
-                    firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
-                    firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+                    task1,
+                    task2,
                     updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
                     updateTasks ? mViewCallbacks::updateIconInBackground : null,
                     groupTask.mSplitBounds);
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 8cb43d2..5af7ff8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.util.SlideInRemoteTransition;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -281,9 +282,10 @@
             return -1;
         }
         RemoteTransition remoteTransition = slideInTransition;
-        if (mOnDesktop
-                && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(task.task1.key.id)
-        ) {
+        boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
+                && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
+                        singleTask.getTask().key.id);
+        if (mOnDesktop && canUnminimizeDesktopTask) {
             // This app is being unminimized - use our own transition runner.
             remoteTransition = new RemoteTransition(
                     new DesktopAppLaunchTransition(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5a8fba6..4143157 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -49,7 +49,7 @@
 import com.android.quickstep.HomeVisibilityState;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -480,8 +480,8 @@
 
     @Override
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
-        mLauncher.launchSplitTasks(groupTask, remoteTransition);
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+        mLauncher.launchSplitTasks(splitTask, remoteTransition);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e8a0c45..6bfbfdd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -155,6 +155,8 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -1293,11 +1295,13 @@
 
         mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
 
-        if (tag instanceof GroupTask groupTask) {
+        // TODO: b/316004172, b/343289567: Handle `DesktopTask` and `SplitTask`.
+        if (tag instanceof SingleTask singleTask) {
             RemoteTransition remoteTransition =
-                    (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
+                    (areDesktopTasksVisible() && canUnminimizeDesktopTask(
+                            singleTask.getTask().key.id))
                             ? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
-                                    Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                            Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
                             : null;
             if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
                 RunnableList runnableList = recents.launchRunningDesktopTaskView();
@@ -1305,12 +1309,12 @@
                 // launch.
                 if (runnableList != null) {
                     runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
-                            () -> handleGroupTaskLaunch(groupTask, remoteTransition,
+                            () -> handleGroupTaskLaunch(singleTask, remoteTransition,
                                     areDesktopTasksVisible(),
                                     DesktopTaskToFrontReason.TASKBAR_TAP)));
                 }
             } else {
-                handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible(),
+                handleGroupTaskLaunch(singleTask, remoteTransition, areDesktopTasksVisible(),
                         DesktopTaskToFrontReason.TASKBAR_TAP);
             }
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -1480,13 +1484,13 @@
                             remoteTransition));
             return;
         }
-        if (onDesktop) {
-            boolean useRemoteTransition = canUnminimizeDesktopTask(task.task1.key.id);
+        if (onDesktop && task instanceof SingleTask singleTask) {
+            boolean useRemoteTransition = canUnminimizeDesktopTask(singleTask.getTask().key.id);
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (onStartCallback != null) {
                     onStartCallback.run();
                 }
-                SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id,
+                SystemUiProxy.INSTANCE.get(this).showDesktopApp(singleTask.getTask().key.id,
                         useRemoteTransition ? remoteTransition : null, toFrontReason);
                 if (onFinishCallback != null) {
                     onFinishCallback.run();
@@ -1494,18 +1498,19 @@
             });
             return;
         }
-        if (task.task2 == null) {
+        if (task instanceof SingleTask singleTask) {
             UI_HELPER_EXECUTOR.execute(() -> {
                 ActivityOptions activityOptions =
                         makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
                 activityOptions.setRemoteTransition(remoteTransition);
 
                 ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                        task.task1.key, activityOptions);
+                        singleTask.getTask().key, activityOptions);
             });
             return;
         }
-        mControllers.uiController.launchSplitTasks(task, remoteTransition);
+        assert task instanceof SplitTask;
+        mControllers.uiController.launchSplitTasks((SplitTask) task, remoteTransition);
     }
 
     /** Returns whether the given task is minimized and can be unminimized. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index a9e8d6d..3a83db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -80,9 +80,9 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LogUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
 
@@ -433,8 +433,8 @@
                                 null, item.user));
             }
             intent.putExtra(Intent.EXTRA_USER, item.user);
-        } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
-            Task task = groupTask.task1;
+        } else if (tag instanceof SingleTask singleTask) {
+            Task task = singleTask.getTask();
             clipDescription = new ClipDescription(task.titleDescription,
                     new String[] {
                             ClipDescription.MIMETYPE_APPLICATION_TASK
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 6047999..417ef7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -76,12 +76,14 @@
     var shownTasks: List<GroupTask> = emptyList()
         private set
 
+    val shownTaskIds: List<Int>
+        get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
+
     /**
-     * The task-state of an app, i.e. whether the app has a task and what state
-     * that task is in.
+     * The task-state of an app, i.e. whether the app has a task and what state that task is in.
      *
-     * @property taskId The ID of the task if one exists (i.e. if the state is
-     * RUNNING or MINIMIZED), null otherwise (NOT_RUNNING).
+     * @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
+     *   MINIMIZED), null otherwise (NOT_RUNNING).
      */
     data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
 
@@ -214,9 +216,9 @@
         return shownHotseatItems.toTypedArray()
     }
 
-    private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+    private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
         val tasks = desktopTask?.tasks ?: emptyList()
-        // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+        // We wrap each task in the Desktop as a `SingleTask`.
         val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
         val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
         return sortedTasks.map { SingleTask(it) }
@@ -286,7 +288,7 @@
     }
 
     private fun updateOrderedRunningTaskIds(): MutableList<Int> {
-        val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().flatMap { it.tasks }
+        val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
         val desktopTaskIds = desktopTasksAsList.map { it.key.id }
         var newOrder =
             orderedRunningTaskIds
@@ -311,42 +313,43 @@
         val newShownTasks =
             if (Flags.enableMultiInstanceMenuTaskbar()) {
                 val deduplicatedDesktopTasks =
-                    desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+                    desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
 
                 shownTasks
                     .filter {
-                        !it.supportsMultipleTasks() &&
-                            it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+                        it is SingleTask &&
+                            it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
                     }
                     .toMutableList()
                     .apply {
                         addAll(
                             deduplicatedDesktopTasks.filter { currentTask ->
-                                val currentTaskKey = currentTask.task1.key
-                                currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+                                val currentTaskKey = currentTask.task.key
+                                currentTaskKey.id !in shownTaskIds &&
                                     shownHotseatItems.none { hotseatItem ->
-                                        hotseatItem.targetPackage == currentTaskKey.packageName &&
-                                            hotseatItem.user.identifier == currentTaskKey.userId
+                                        currentTask.containsPackage(
+                                            hotseatItem.targetPackage,
+                                            hotseatItem.user.identifier,
+                                        )
                                     }
                             }
                         )
                     }
             } else {
-                val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+                val desktopTaskIds = desktopTasks.map { it.task.key.id }
                 val shownHotseatItemTaskIds =
                     shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
 
                 shownTasks
-                    .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+                    .filter { it is SingleTask && it.task.key.id in desktopTaskIds }
                     .toMutableList()
                     .apply {
                         addAll(
                             desktopTasks.filter { desktopTask ->
-                                desktopTask.task1.key.id !in
-                                    shownTasks.map { shownTask -> shownTask.task1.key.id }
+                                desktopTask.task.key.id !in shownTaskIds
                             }
                         )
-                        removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+                        removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
                     }
             }
 
@@ -371,21 +374,28 @@
         groupTasks: List<GroupTask>,
         shownHotseatItems: List<ItemInfo>,
     ): List<GroupTask> {
+        // TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
+        // become supported.
         return if (Flags.enableMultiInstanceMenuTaskbar()) {
             groupTasks.filter { groupTask ->
-                val taskKey = groupTask.task1.key
                 // Keep tasks that are group tasks or unique package name/user combinations
-                groupTask.hasMultipleTasks() ||
-                    shownHotseatItems.none {
-                        it.targetPackage == taskKey.packageName &&
-                            it.user.identifier == taskKey.userId
-                    }
+                when (groupTask) {
+                    is SingleTask ->
+                        shownHotseatItems.none {
+                            groupTask.containsPackage(it.targetPackage, it.user.identifier)
+                        }
+
+                    else -> true
+                }
             }
         } else {
             val hotseatPackages = shownHotseatItems.map { it.targetPackage }
             groupTasks.filter { groupTask ->
-                groupTask.hasMultipleTasks() ||
-                    !hotseatPackages.contains(groupTask.task1.key.packageName)
+                when (groupTask) {
+                    is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
+
+                    else -> true
+                }
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f29f95d..e5d642d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,7 +39,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
@@ -332,7 +332,7 @@
      * Launches the given task in split-screen.
      */
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
 
     /**
      * Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index e4e97e5..c7f33e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -70,6 +70,7 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -657,9 +658,10 @@
         final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
         for (GroupTask task : recentTasks) {
             if (mTaskbarOverflowView != null && overflownTasks != null
-                    && overflownTasks.size() < itemsToAddToOverflow) {
+                    && overflownTasks.size() < itemsToAddToOverflow
+                    && task instanceof SingleTask singleTask) {
                 // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
-                overflownTasks.add(task.task1);
+                overflownTasks.add(singleTask.getTask());
                 if (overflownTasks.size() == itemsToAddToOverflow) {
                     mTaskbarOverflowView.setItems(overflownTasks);
                 }
@@ -733,18 +735,22 @@
                 && tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
     }
 
-    /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+    /** Binds the SingleTask to the BubbleTextView to be ready to present to the user. */
     public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
-        // TODO(b/343289567): support app pairs.
-        Task task1 = groupTask.task1;
+        if (!(groupTask instanceof SingleTask singleTask)) {
+            // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+            return;
+        }
+
+        Task task = singleTask.getTask();
         // TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
         //  while dragging.
-        Drawable taskIcon = groupTask.task1.icon;
+        Drawable taskIcon = task.icon;
         if (taskIcon != null) {
             taskIcon = taskIcon.getConstantState().newDrawable().mutate();
         }
-        btv.applyIconAndLabel(taskIcon, task1.titleDescription);
-        btv.setTag(groupTask);
+        btv.applyIconAndLabel(taskIcon, task.titleDescription);
+        btv.setTag(singleTask);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0f05887..cbc5d3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -92,6 +92,7 @@
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
@@ -739,9 +740,9 @@
             return mControllers.taskbarRecentAppsController.getRunningAppState(
                     itemInfo.getTaskId());
         }
-        if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+        if (tag instanceof SingleTask singleTask) {
             return mControllers.taskbarRecentAppsController.getRunningAppState(
-                    groupTask.task1.key.id);
+                    singleTask.getTask().key.id);
         }
         return BubbleTextView.RunningAppState.NOT_RUNNING;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f672840..c880aa9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -178,10 +178,10 @@
 import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LauncherUnfoldAnimationController;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.util.SplitToWorkspaceController;
 import com.android.quickstep.util.SplitWithKeyboardShortcutController;
 import com.android.quickstep.util.TISBindHelper;
@@ -1386,33 +1386,23 @@
     }
 
     /**
-     * Launches the given {@link GroupTask} in splitscreen.
+     * Launches the given {@link SplitTask} in splitscreen.
      */
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
-        // SplitBounds can be null if coming from Taskbar launch.
-        final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
-        // task2 should never be null when calling this method. Allow a crash to catch invalid calls
-        Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
-        Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
         mSplitSelectStateController.launchExistingSplitPair(
                 null /* launchingTaskView */,
-                task1.key.id,
-                task2.key.id,
+                splitTask.getTopLeftTask().key.id,
+                splitTask.getBottomRightTask().key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
                 /* callback= */ success -> mSplitSelectStateController.resetState(),
                 /* freezeTaskList= */ false,
-                groupTask.mSplitBounds == null
+                splitTask.mSplitBounds == null
                         ? SNAP_TO_2_50_50
-                        : groupTask.mSplitBounds.snapPosition,
+                        : splitTask.mSplitBounds.snapPosition,
                 remoteTransition);
     }
 
-    private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
-        return groupTask.mSplitBounds == null
-                || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
-    }
-
     /**
      * Launches two apps as an app pair.
      */
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 5463cf7..0669185 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -27,8 +27,6 @@
 
     override fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
 
-    override fun hasMultipleTasks() = tasks.size > 1
-
     override fun supportsMultipleTasks() = true
 
     override fun copy() = DesktopTask(tasks)
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index d5bbcd3..8237d2e 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -50,9 +50,16 @@
     /**
      * Returns true if a task in this group has a package name that matches the given `packageName`.
      */
-    fun containsPackage(packageName: String) = tasks.any { it.key.packageName == packageName }
+    fun containsPackage(packageName: String?) = tasks.any { it.key.packageName == packageName }
 
-    open fun hasMultipleTasks() = task2 != null
+    /**
+     * Returns true if a task in this group has a package name that matches the given `packageName`,
+     * and its user ID matches the given `userId`.
+     */
+    fun containsPackage(packageName: String?, userId: Int) =
+        tasks.any { it.key.packageName == packageName && it.key.userId == userId }
+
+    fun isEmpty() = tasks.isEmpty()
 
     /** Returns whether this task supports multiple tasks or not. */
     open fun supportsMultipleTasks() = taskViewType == TaskViewType.GROUPED
@@ -78,6 +85,10 @@
 /** A [Task] container that must contain exactly one task in the recent tasks list. */
 class SingleTask(task: Task) :
     GroupTask(task, task2 = null, mSplitBounds = null, TaskViewType.SINGLE) {
+
+    val task: Task
+        get() = task1
+
     override fun copy() = SingleTask(task1)
 
     override fun toString() = "type=$taskViewType task=$task1"
@@ -96,6 +107,12 @@
 class SplitTask(task1: Task, task2: Task, splitBounds: SplitConfigurationOptions.SplitBounds) :
     GroupTask(task1, task2, splitBounds, TaskViewType.GROUPED) {
 
+    val topLeftTask: Task
+        get() = if (mSplitBounds!!.leftTopTaskId == task1.key.id) task1!! else task2!!
+
+    val bottomRightTask: Task
+        get() = if (topLeftTask == task1) task2!! else task1!!
+
     override fun copy() = SplitTask(task1, task2!!, mSplitBounds!!)
 
     override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5f8b4d9..fd8b356 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -71,7 +71,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
@@ -260,7 +259,7 @@
                     GroupTask groupTask = taskGroups.get(i);
                     if (isInstanceOfAppPair(
                             groupTask, componentKeys.get(0), componentKeys.get(1))) {
-                        lastActiveTasks[0] = groupTask.task1;
+                        lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask();
                         break;
                     }
                 }
@@ -314,11 +313,15 @@
      */
     public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
             @NonNull ComponentKey componentKey2) {
-        return ((isInstanceOfComponent(groupTask.task1, componentKey1)
-                && isInstanceOfComponent(groupTask.task2, componentKey2))
-                ||
-                (isInstanceOfComponent(groupTask.task1, componentKey2)
-                        && isInstanceOfComponent(groupTask.task2, componentKey1)));
+        if (groupTask instanceof SplitTask splitTask) {
+            return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1)
+                    && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2))
+                    ||
+                    (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2)
+                            && isInstanceOfComponent(splitTask.getBottomRightTask(),
+                            componentKey1)));
+        }
+        return false;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c0b026b..c6db576 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -212,9 +212,11 @@
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.util.SurfaceTransaction;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskGridNavHelper;
@@ -1968,7 +1970,7 @@
             GroupTask groupTask = taskGroups.get(i);
             boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
                     && groupTask.containsTask(stagedTaskIdToBeRemoved);
-            boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
+            boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
 
             if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
                     || shouldSkipGroupTask) {
@@ -1982,25 +1984,27 @@
             // to be a temporary container for the remaining task.
             TaskView taskView = getTaskViewFromPool(
                     containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
-            if (taskView instanceof GroupedTaskView) {
-                boolean firstTaskIsLeftTopTask =
-                        groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
-                Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
-                Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
-                ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
-                        mTaskOverlayFactory, groupTask.mSplitBounds);
-            } else if (taskView instanceof DesktopTaskView) {
+            if (taskView instanceof GroupedTaskView groupedTaskView) {
+                var splitTask = (SplitTask) groupTask;
+                groupedTaskView.bind(splitTask.getTopLeftTask(),
+                        splitTask.getBottomRightTask(), mOrientationState,
+                        mTaskOverlayFactory, splitTask.mSplitBounds);
+            } else if (taskView instanceof DesktopTaskView desktopTaskView) {
                 // Minimized tasks should not be shown in Overview
                 List<Task> nonMinimizedTasks =
                         groupTask.getTasks().stream()
                                 .filter(task -> !task.isMinimized)
                                 .toList();
-                ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+                desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
                         mTaskOverlayFactory);
-            } else {
-                Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
-                        : groupTask.task1;
+            } else if (groupTask instanceof SplitTask splitTask) {
+                Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
+                        ? splitTask.getBottomRightTask()
+                        : splitTask.getTopLeftTask();
                 taskView.bind(task, mOrientationState, mTaskOverlayFactory);
+            } else {
+                taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
+                        mTaskOverlayFactory);
             }
             addView(taskView);
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 0491c07..e4bdba5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -102,12 +102,12 @@
     fun activeTasks_noMatchingTasks() {
         val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("hotdog", "juice"),
                 ComponentName("personal", "computer"),
             )
@@ -143,12 +143,12 @@
         val matchingComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pomegranate", "juice"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -170,7 +170,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -196,12 +196,12 @@
         val nonPrimaryUserComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pomegranate", "juice"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -237,14 +237,14 @@
         val nonPrimaryUserComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 nonPrimaryUserHandle,
                 ComponentName("pomegranate", "juice"),
                 nonPrimaryUserHandle,
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -267,7 +267,7 @@
                     matchingClass,
                 )
                 assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -293,12 +293,12 @@
         val matchingComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -320,7 +320,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -348,9 +348,9 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -374,7 +374,7 @@
                     it[1].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[1], groupTask2.task2)
+                assertEquals(it[1], groupTask2.bottomRightTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -401,9 +401,9 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -426,7 +426,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask2.task2)
+                assertEquals(it[0], groupTask2.bottomRightTask)
                 assertNull("No tasks should have matched", it[1] /*task*/)
             }
 
@@ -454,12 +454,12 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -482,7 +482,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
                 assertEquals(
                     "ComponentName package mismatched",
                     it[1].key.baseIntent.component?.packageName,
@@ -493,7 +493,7 @@
                     it[1].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[1], groupTask2.task2)
+                assertEquals(it[1], groupTask2.bottomRightTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -524,14 +524,14 @@
             ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage2, matchingClass2),
                 ComponentName(matchingPackage, matchingClass),
             )
         val groupTask3 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("hotdog", "pie"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -545,7 +545,7 @@
         val taskConsumer =
             Consumer<Array<Task>> {
                 assertEquals("Expected array length 2", 2, it.size)
-                assertEquals("Found wrong task", it[0], groupTask2.task1)
+                assertEquals("Found wrong task", it[0], groupTask2.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -640,11 +640,11 @@
         verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState()
     }
 
-    // Generate GroupTask with default userId.
-    private fun generateGroupTask(
+    /** Generates a [SplitTask] with default userId. */
+    private fun generateSplitTask(
         task1ComponentName: ComponentName,
         task2ComponentName: ComponentName,
-    ): GroupTask {
+    ): SplitTask {
         val task1 = Task()
         var taskInfo = ActivityManager.RunningTaskInfo()
         taskInfo.taskId = getUniqueId()
@@ -666,20 +666,20 @@
             SplitConfigurationOptions.SplitBounds(
                 /* leftTopBounds = */ Rect(),
                 /* rightBottomBounds = */ Rect(),
-                /* leftTopTaskId = */ -1,
-                /* rightBottomTaskId = */ -1,
+                /* leftTopTaskId = */ task1.key.id,
+                /* rightBottomTaskId = */ task2.key.id,
                 /* snapPosition = */ SNAP_TO_2_50_50,
             ),
         )
     }
 
-    // Generate GroupTask with custom user handles.
-    private fun generateGroupTask(
+    /** Generates a [SplitTask] with custom user handles. */
+    private fun generateSplitTask(
         task1ComponentName: ComponentName,
         userHandle1: UserHandle,
         task2ComponentName: ComponentName,
         userHandle2: UserHandle,
-    ): GroupTask {
+    ): SplitTask {
         val task1 = Task()
         var taskInfo = ActivityManager.RunningTaskInfo()
         taskInfo.taskId = getUniqueId()
@@ -704,8 +704,8 @@
             SplitConfigurationOptions.SplitBounds(
                 /* leftTopBounds = */ Rect(),
                 /* rightBottomBounds = */ Rect(),
-                /* leftTopTaskId = */ -1,
-                /* rightBottomTaskId = */ -1,
+                /* leftTopTaskId = */ task1.key.id,
+                /* rightBottomTaskId = */ task2.key.id,
                 /* snapPosition = */ SNAP_TO_2_50_50,
             ),
         )