Support alt+tab for desktop tasks

Updates keyboard quickswitch logic handle quickswitching while on
desktop. When on desktop, alt+tab moves only between desktop tasks.
Fullscreen tasks are shown in the overflow tile and can be accessed by
going to overview.

TODO:
- when not in desktop and using quickswitch, show the desktop tile as a
  combined tile in the row, similar to what is shown in overview

Flag: persist.wm.debug.desktop_mode_2
Bug: 280468885
Test: open some apps on desktop and have some fullscreen apps opened in
  the background, when on desktop, observe that alt+tab only switches
  between desktop tasks
Test: same setup, but switch to a fullscreen app, observe that alt+tab
  only switches between fullscreen tasks and desktop is accessible from
  overview

Change-Id: Ib19f2eaa24363bbd0669c8c8c3d99ed9d9118a17
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 7f655cf..ae1c979 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -22,9 +22,13 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.DesktopTaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -102,21 +106,75 @@
         mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
                 mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
 
+        DesktopVisibilityController desktopController =
+                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+        final boolean onDesktop =
+                DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
+                        && desktopController != null
+                        && desktopController.areFreeformTasksVisible();
+
         if (mModel.isTaskListValid(mTaskListChangeId)) {
-            mQuickSwitchViewController.openQuickSwitchView(
-                    mTasks, mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex);
+            mQuickSwitchViewController.openQuickSwitchView(mTasks,
+                    mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
             return;
         }
+
         mTaskListChangeId = mModel.getTasks((tasks) -> {
-            // Only store MAX_TASK tasks, from most to least recent
-            Collections.reverse(tasks);
-            mTasks = tasks.stream().limit(MAX_TASKS).collect(Collectors.toList());
-            mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
-            mQuickSwitchViewController.openQuickSwitchView(
-                    mTasks, mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex);
+            if (onDesktop) {
+                processLoadedTasksOnDesktop(tasks);
+            } else {
+                processLoadedTasks(tasks);
+            }
+            mQuickSwitchViewController.openQuickSwitchView(mTasks,
+                    mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
         });
     }
 
+    private void processLoadedTasks(ArrayList<GroupTask> tasks) {
+        // Only store MAX_TASK tasks, from most to least recent
+        Collections.reverse(tasks);
+
+        // Hide all desktop tasks and show them on the hidden tile
+        int hiddenDesktopTasks = 0;
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+            // TODO(b/280468885): show desktop task as a grouped desktop tile
+            DesktopTask desktopTask = findDesktopTask(tasks);
+            if (desktopTask != null) {
+                hiddenDesktopTasks = desktopTask.tasks.size();
+                tasks = tasks.stream()
+                        .filter(t -> !(t instanceof DesktopTask))
+                        .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
+            }
+        }
+        mTasks = tasks.stream()
+                .limit(MAX_TASKS)
+                .collect(Collectors.toList());
+        mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS) + hiddenDesktopTasks;
+    }
+
+    private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
+        // Find the single desktop task that contains a grouping of desktop tasks
+        DesktopTask desktopTask = findDesktopTask(tasks);
+
+        if (desktopTask != null) {
+            mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
+            // All other tasks, apart from the grouped desktop task, are hidden
+            mNumHiddenTasks = Math.max(0, tasks.size() - 1);
+        } else {
+            // Desktop tasks were visible, but the recents entry is missing. Fall back to empty list
+            mTasks = Collections.emptyList();
+            mNumHiddenTasks = tasks.size();
+        }
+    }
+
+    @Nullable
+    private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
+        return (DesktopTask) tasks.stream()
+                .filter(t -> t instanceof DesktopTask)
+                .findFirst()
+                .orElse(null);
+    }
+
     void closeQuickSwitchView() {
         if (mQuickSwitchViewController == null) {
             return;
@@ -169,7 +227,7 @@
     class ControllerCallbacks {
 
         int getTaskCount() {
-            return mNumHiddenTasks == 0 ? mTasks.size() : MAX_TASKS + 1;
+            return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
         }
 
         @Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 2cdfb18..15f2914 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -190,8 +190,12 @@
 
         ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
                 width, mTaskViewHeight);
-        lp.endToEnd = PARENT_ID;
-        lp.startToEnd = previousView.getId();
+        if (previousView == null) {
+            lp.startToStart = PARENT_ID;
+        } else {
+            lp.endToEnd = PARENT_ID;
+            lp.startToEnd = previousView.getId();
+        }
         lp.topToTop = PARENT_ID;
         lp.bottomToBottom = PARENT_ID;
         lp.setMarginEnd(mSpacing);
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 7bd8898..edb71a5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -52,6 +53,8 @@
 
     private int mCurrentFocusIndex = -1;
 
+    private boolean mOnDesktop;
+
     protected KeyboardQuickSwitchViewController(
             @NonNull TaskbarControllers controllers,
             @NonNull TaskbarOverlayContext overlayContext,
@@ -71,10 +74,12 @@
             @NonNull List<GroupTask> tasks,
             int numHiddenTasks,
             boolean updateTasks,
-            int currentFocusIndexOverride) {
+            int currentFocusIndexOverride,
+            boolean onDesktop) {
         TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
         dragLayer.addView(mKeyboardQuickSwitchView);
         dragLayer.runOnClickOnce(v -> closeQuickSwitchView(true));
+        mOnDesktop = onDesktop;
 
         mKeyboardQuickSwitchView.applyLoadPlan(
                 mOverlayContext,
@@ -136,6 +141,10 @@
         GroupTask task = mControllerCallbacks.getTaskAt(index);
         if (task == null) {
             return Math.max(0, index);
+        } else if (mOnDesktop) {
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
+                            .showDesktopApp(task.task1.key.id));
         } else if (task.task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 07db194..a0d49a4 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -186,11 +185,6 @@
                     && dp != null
                     && (dp.isTablet || dp.isTwoPanels);
 
-            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
-                // TODO(b/268075592): add support for quickswitch to/from desktop
-                allowQuickSwitch = false;
-            }
-
             if (cmd.type == TYPE_HIDE) {
                 if (!allowQuickSwitch) {
                     return true;
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 38ac5bb..34817c0 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -308,7 +308,6 @@
             task.setLastSnapshotData(taskInfo);
             task.positionInParent = taskInfo.positionInParent;
             task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
-            // TODO(b/244348395): tasks should be sorted from oldest to most recently used
             tasks.add(task);
         }
         return new DesktopTask(tasks);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 380aa69..61fabe4 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1186,6 +1186,19 @@
         }
     }
 
+    /**
+     * If task with the given id is on the desktop, bring it to front
+     */
+    public void showDesktopApp(int taskId) {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.showDesktopApp(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call showDesktopApp", e);
+            }
+        }
+    }
+
     /** Call shell to get number of visible freeform tasks */
     public int getVisibleDesktopTaskCount(int displayId) {
         if (mDesktopMode != null) {
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index 2be4f0a..9c49647 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -37,6 +37,10 @@
     @TaskView.Type
     public final int taskViewType;
 
+    public GroupTask(@NonNull Task task) {
+        this(task, null, null);
+    }
+
     public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
         this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
     }