Merge "Use already-running app instances when splitting" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index c65fa5f..9554bd3 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -30,6 +30,8 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
@@ -41,6 +43,9 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.function.Consumer;
 
 public interface QuickstepSystemShortcut {
 
@@ -93,14 +98,22 @@
             }
 
             StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition);
-            SplitSelectSource source = new SplitSelectSource(mOriginalView,
-                    new BitmapDrawable(bitmap), intent, mPosition, mItemInfo, splitEvent);
-            if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
-                startSplitToHome(source);
-            } else {
-                RecentsView recentsView = mTarget.getOverviewPanel();
-                recentsView.initiateSplitSelect(source);
-            }
+            RecentsView recentsView = mTarget.getOverviewPanel();
+            // Check if there is already an instance of this app running, if so, initiate the split
+            // using that.
+            recentsView.findLastActiveTaskAndDoSplitOperation(
+                    intent.getComponent(),
+                    (Consumer<Task>) foundTask -> {
+                        SplitSelectSource source = new SplitSelectSource(mOriginalView,
+                                new BitmapDrawable(bitmap), intent, mPosition, mItemInfo,
+                                splitEvent, foundTask);
+                        if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                            startSplitToHome(source);
+                        } else {
+                            recentsView.initiateSplitSelect(source);
+                        }
+                    }
+            );
         }
 
         private void startSplitToHome(SplitSelectSource source) {
@@ -108,7 +121,7 @@
 
             SplitSelectStateController controller = mTarget.getSplitSelectStateController();
             controller.setInitialTaskSelect(source.intent, source.position.stagePosition,
-                    source.itemInfo, source.splitEvent);
+                    source.itemInfo, source.splitEvent, source.alreadyRunningTask);
 
             RecentsView recentsView = mTarget.getOverviewPanel();
             recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
@@ -142,16 +155,19 @@
         public final SplitPositionOption position;
         public final ItemInfo itemInfo;
         public final StatsLogManager.EventEnum splitEvent;
+        @Nullable
+        public final Task alreadyRunningTask;
 
         public SplitSelectSource(View view, Drawable drawable, Intent intent,
                 SplitPositionOption position, ItemInfo itemInfo,
-                StatsLogManager.EventEnum splitEvent) {
+                StatsLogManager.EventEnum splitEvent, @Nullable Task foundTask) {
             this.view = view;
             this.drawable = drawable;
             this.intent = intent;
             this.position = position;
             this.itemInfo = itemInfo;
             this.splitEvent = splitEvent;
+            this.alreadyRunningTask = foundTask;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index b3b53c2..d8e1311 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -28,8 +28,10 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 /**
  * Base class for providing different taskbar UI
@@ -160,23 +162,30 @@
      */
     public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
         RecentsView recents = getRecentsView();
-        TaskView foundTaskView = recents.getTaskViewByComponentName(info.getTargetComponent());
-        if (foundTaskView != null) {
-            recents.confirmSplitSelect(
-                    foundTaskView,
-                    foundTaskView.getTask(),
-                    foundTaskView.getIconView().getDrawable(),
-                    foundTaskView.getThumbnail(),
-                    foundTaskView.getThumbnail().getThumbnail(),
-                    /* intent */ null);
-        } else {
-            recents.confirmSplitSelect(
-                    /* containerTaskView */ null,
-                    /* task */ null,
-                    new BitmapDrawable(info.bitmap.icon),
-                    startingView,
-                    /* thumbnail */ null,
-                    intent);
-        }
+        recents.findLastActiveTaskAndDoSplitOperation(
+                info.getTargetComponent(),
+                (Consumer<Task>) foundTask -> {
+                    if (foundTask != null) {
+                        TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
+                        // There is already a running app of this type, use that as second app.
+                        recents.confirmSplitSelect(
+                                foundTaskView,
+                                foundTaskView.getTask(),
+                                foundTaskView.getIconView().getDrawable(),
+                                foundTaskView.getThumbnail(),
+                                foundTaskView.getThumbnail().getThumbnail(),
+                                null /* intent */);
+                    } else {
+                        // No running app of that type, create a new instance as second app.
+                        recents.confirmSplitSelect(
+                                null /* containerTaskView */,
+                                null /* task */,
+                                new BitmapDrawable(info.bitmap.icon),
+                                startingView,
+                                null /* thumbnail */,
+                                intent);
+                    }
+                }
+        );
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3053474..3d6da8e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -96,7 +96,8 @@
     }
 
     /**
-     * Fetches the list of recent tasks.
+     * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
+     * at the end of the list.
      *
      * @param callback The callback to receive the task plan once its complete or null. This is
      *                always called on the UI thread.
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c263fe8..681f068 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -37,7 +37,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.view.RemoteAnimationAdapter;
@@ -124,10 +123,15 @@
      * To be called after first task selected from home or all apps.
      */
     public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
-            @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent) {
-        mInitialTaskIntent = intent;
-        mUser = itemInfo.user;
-        mItemInfo = itemInfo;
+            @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
+            @Nullable Task alreadyRunningTask) {
+        if (alreadyRunningTask != null) {
+            mInitialTaskId = alreadyRunningTask.key.id;
+        } else {
+            mInitialTaskIntent = intent;
+            mUser = itemInfo.user;
+        }
+
         setInitialData(stagePosition, splitEvent, itemInfo);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0e49769..3a2841e 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1221,18 +1221,50 @@
     }
 
     /**
-     * Returns a {@link TaskView} that has ComponentName matching {@code componentName} or null if
-     * no match.
+     * Pulls the list of active Tasks from RecentModel, and finds the most recently active Task
+     * matching a given ComponentName. Then uses that Task (which could be null) with the given
+     * callback.
+     *
+     * Used in various splitscreen operations when we need to check if there is a currently running
+     * Task of a certain type and use the most recent one.
      */
-    @Nullable
-    public TaskView getTaskViewByComponentName(ComponentName componentName) {
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
-            if (taskView.getTask().key.sourceComponent.equals(componentName)) {
-                return taskView;
+    public void findLastActiveTaskAndDoSplitOperation(ComponentName componentName,
+            Consumer<Task> callback) {
+        mModel.getTasks(taskGroups -> {
+            Task lastActiveTask = null;
+            // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
+            for (int i = taskGroups.size() - 1; i >= 0; i--) {
+                GroupTask groupTask = taskGroups.get(i);
+                Task task1 = groupTask.task1;
+                if (isInstanceOfComponent(task1, componentName)) {
+                    lastActiveTask = task1;
+                    break;
+                }
+                Task task2 = groupTask.task2;
+                if (isInstanceOfComponent(task2, componentName)) {
+                    lastActiveTask = task2;
+                    break;
+                }
             }
+
+            callback.accept(lastActiveTask);
+        });
+    }
+
+    /**
+     * Checks if a given Task is the most recently-active Task of type componentName. Used for
+     * selecting already-running Tasks for splitscreen.
+     */
+    public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
+        if (task == null) {
+            return false;
         }
-        return null;
+        // Exclude the task that is already staged
+        if (mSplitHiddenTaskView != null && mSplitHiddenTaskView.getTask().equals(task)) {
+            return false;
+        }
+
+        return task.key.baseIntent.getComponent().equals(componentName);
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
@@ -1509,13 +1541,41 @@
         int previousCurrentPage = mCurrentPage;
         removeAllViews();
 
-        // Add views as children based on whether it's grouped or single task
+        // If we are entering Overview as a result of initiating a split from somewhere else
+        // (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
+        Task stagedTaskToBeRemovedFromGrid =
+                mSplitSelectSource != null ? mSplitSelectSource.alreadyRunningTask : null;
+
+        // Add views as children based on whether it's grouped or single task. Looping through
+        // taskGroups backwards populates the thumbnail grid from least recent to most recent.
         for (int i = taskGroups.size() - 1; i >= 0; i--) {
             GroupTask groupTask = taskGroups.get(i);
-            TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);
+            boolean isRemovalNeeded = stagedTaskToBeRemovedFromGrid != null
+                    && groupTask.containsTask(stagedTaskToBeRemovedFromGrid.key.id);
+
+            TaskView taskView;
+            if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
+                // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
+                // to be a temporary container for the remaining task.
+                taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
+            } else {
+                taskView = getTaskViewFromPool(groupTask.taskViewType);
+            }
+
             addView(taskView);
 
-            if (taskView instanceof GroupedTaskView) {
+            if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
+                if (groupTask.task1.equals(stagedTaskToBeRemovedFromGrid)) {
+                    taskView.bind(groupTask.task2, mOrientationState);
+                } else {
+                    taskView.bind(groupTask.task1, mOrientationState);
+                }
+            } else if (isRemovalNeeded) {
+                // If the task we need to remove is not part of a pair, bind it to the TaskView
+                // first (to prevent problems), then remove the whole thing.
+                taskView.bind(groupTask.task1, mOrientationState);
+                removeView(taskView);
+            } else if (taskView instanceof GroupedTaskView) {
                 boolean firstTaskIsLeftTopTask =
                         groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
                 Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
@@ -4269,7 +4329,7 @@
         mSplitSelectSource = splitSelectSource;
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                 splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
-                splitSelectSource.splitEvent);
+                splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask);
     }
 
     /**