Loading quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java +26 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -93,22 +98,30 @@ public interface QuickstepSystemShortcut { } StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition); 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); new BitmapDrawable(bitmap), intent, mPosition, mItemInfo, splitEvent, foundTask); if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { startSplitToHome(source); } else { RecentsView recentsView = mTarget.getOverviewPanel(); recentsView.initiateSplitSelect(source); } } ); } private void startSplitToHome(SplitSelectSource source) { AbstractFloatingView.closeAllOpenViews(mTarget); 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( Loading Loading @@ -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; } } } quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +27 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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.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(), /* intent */ null); null /* intent */); } else { // No running app of that type, create a new instance as second app. recents.confirmSplitSelect( /* containerTaskView */ null, /* task */ null, null /* containerTaskView */, null /* task */, new BitmapDrawable(info.bitmap.icon), startingView, /* thumbnail */ null, null /* thumbnail */, intent); } } ); } } quickstep/src/com/android/quickstep/RecentsModel.java +2 −1 Original line number Diff line number Diff line Loading @@ -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. Loading quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +9 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, @Nullable Task alreadyRunningTask) { if (alreadyRunningTask != null) { mInitialTaskId = alreadyRunningTask.key.id; } else { mInitialTaskIntent = intent; mUser = itemInfo.user; mItemInfo = itemInfo; } setInitialData(stagePosition, splitEvent, itemInfo); } Loading quickstep/src/com/android/quickstep/views/RecentsView.java +73 −13 Original line number Diff line number Diff line Loading @@ -1221,18 +1221,50 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T } /** * 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; } return null; } 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; } // 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) { Loading Loading @@ -1509,13 +1541,41 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T 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; Loading Loading @@ -4269,7 +4329,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T mSplitSelectSource = splitSelectSource; mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent, splitSelectSource.position.stagePosition, splitSelectSource.itemInfo, splitSelectSource.splitEvent); splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask); } /** Loading Loading
quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java +26 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -93,22 +98,30 @@ public interface QuickstepSystemShortcut { } StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition); 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); new BitmapDrawable(bitmap), intent, mPosition, mItemInfo, splitEvent, foundTask); if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { startSplitToHome(source); } else { RecentsView recentsView = mTarget.getOverviewPanel(); recentsView.initiateSplitSelect(source); } } ); } private void startSplitToHome(SplitSelectSource source) { AbstractFloatingView.closeAllOpenViews(mTarget); 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( Loading Loading @@ -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; } } }
quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +27 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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.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(), /* intent */ null); null /* intent */); } else { // No running app of that type, create a new instance as second app. recents.confirmSplitSelect( /* containerTaskView */ null, /* task */ null, null /* containerTaskView */, null /* task */, new BitmapDrawable(info.bitmap.icon), startingView, /* thumbnail */ null, null /* thumbnail */, intent); } } ); } }
quickstep/src/com/android/quickstep/RecentsModel.java +2 −1 Original line number Diff line number Diff line Loading @@ -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. Loading
quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +9 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, @Nullable Task alreadyRunningTask) { if (alreadyRunningTask != null) { mInitialTaskId = alreadyRunningTask.key.id; } else { mInitialTaskIntent = intent; mUser = itemInfo.user; mItemInfo = itemInfo; } setInitialData(stagePosition, splitEvent, itemInfo); } Loading
quickstep/src/com/android/quickstep/views/RecentsView.java +73 −13 Original line number Diff line number Diff line Loading @@ -1221,18 +1221,50 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T } /** * 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; } return null; } 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; } // 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) { Loading Loading @@ -1509,13 +1541,41 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T 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; Loading Loading @@ -4269,7 +4329,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T mSplitSelectSource = splitSelectSource; mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent, splitSelectSource.position.stagePosition, splitSelectSource.itemInfo, splitSelectSource.splitEvent); splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask); } /** Loading