diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java index c65fa5fd90..9554bd3d9c 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.graphics.drawable.Drawable; 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.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 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 @@ public interface QuickstepSystemShortcut { } 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) 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 @@ public interface QuickstepSystemShortcut { 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 interface QuickstepSystemShortcut { 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 b3b53c2732..d8e1311a9e 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.model.data.ItemInfoWithIcon; 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 class TaskbarUIController { */ 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) 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 305347414c..3d6da8e726 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -96,7 +96,8 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener } /** - * 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 c263fe8656..681f068e52 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.Handler; 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 @@ public class SplitSelectStateController { * 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 0e49769175..3a2841e7a8 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1221,18 +1221,50 @@ public abstract class RecentsView 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 @@ public abstract class RecentsView= 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 @@ public abstract class RecentsView