Pin desktop tile to the right in overview

When desktop windowing prototype is enabled, pin desktop tile to the
right of the focused tile.
Also making desktop tile persistent so it will always show up,
regardless whether there are tasks added to the desktop or not.
Fixing some nullpointer issues that stem from that.

Bug: 261234155
Test: no recent tasks, go to overview, shows empty view
Test: only desktop tasks, go to overview from home, desktop tile focused
Test: only desktop tasks, desktop open, go to overview, desktop tile
  focused
Test: 1 fullscreen task and some desktop tasks, go to overview from home
  screen, fullscreen tile focused
Test: fullscreen task open and no desktop tasks, go to overview,
  fullscreen task focused and empty desktop tile on the right
Test: fullscreen task open and some desktop tasks in background, go to
  overview, fullscreen tile focused, desktop tile on the right
Test: some desktop tasks focused, some fullscreen tasks in background,
  go to overview, desktop tile focused, fullscreen tasks on the left

Change-Id: Ib28185b40f639d36b82b6d0e3c61026ec84e245c
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 0c8543f..2a9674f 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -33,7 +33,8 @@
     <com.android.quickstep.views.TaskThumbnailView
         android:id="@+id/snapshot"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
 
     <com.android.quickstep.views.IconView
         android:id="@+id/icon"
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 44aa41a..709ce71 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -74,7 +74,8 @@
 
     private static final boolean DEBUG = true;
 
-    private List<Task> mTasks;
+    @NonNull
+    private List<Task> mTasks = new ArrayList<>();
 
     private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();
 
@@ -108,8 +109,6 @@
                 getContext().getTheme()));
         // TODO(b/244348395): this should be wallpaper
         setBackground(mBackground);
-
-        mSnapshotViews.add(mSnapshotView);
     }
 
     @Override
@@ -129,12 +128,9 @@
             }
             Log.d(TAG, sb.toString());
         }
-        if (tasks.isEmpty()) {
-            return;
-        }
         cancelPendingLoadTasks();
 
-        mTasks = tasks;
+        mTasks = new ArrayList<>(tasks);
         mSnapshotViewMap.clear();
 
         // Ensure there are equal number of snapshot views and tasks.
@@ -207,7 +203,8 @@
         if (task != null) {
             return mSnapshotViewMap.get(task.key.id);
         }
-        return null;
+        // Return the place holder snapshot views. Callers expect this to be non-null
+        return mSnapshotView;
     }
 
     @Override
@@ -314,6 +311,11 @@
     }
 
     @Override
+    public boolean isDesktopTask() {
+        return true;
+    }
+
+    @Override
     void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
         // Sets new thumbnails based on the incoming data and refreshes the rest.
         // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 8facb0a..d8ecd87 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1548,8 +1548,14 @@
         removeView(focusedTaskView);
         mMovingTaskView = null;
         focusedTaskView.resetPersistentViewTransforms();
-        addView(focusedTaskView, 0);
-        setCurrentPage(0);
+        int frontTaskIndex = 0;
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && !focusedTaskView.isDesktopTask()) {
+            // If desktop mode is enabled, desktop task view is pinned at first position.
+            // Move focused task to position 1
+            frontTaskIndex = 1;
+        }
+        addView(focusedTaskView, frontTaskIndex);
+        setCurrentPage(frontTaskIndex);
 
         updateGridProperties();
     }
@@ -1586,7 +1592,7 @@
 
         int currentTaskId = -1;
         TaskView currentTaskView = getTaskViewAt(mCurrentPage);
-        if (currentTaskView != null) {
+        if (currentTaskView != null && currentTaskView.getTask() != null) {
             currentTaskId = currentTaskView.getTask().key.id;
         }
 
@@ -1615,6 +1621,8 @@
         // update the map of instance counts
         mFilterState.updateInstanceCountMap(taskGroups);
 
+        DesktopTask desktopTask = 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--) {
@@ -1622,6 +1630,12 @@
             boolean isRemovalNeeded = stagedTaskIdToBeRemovedFromGrid != INVALID_TASK_ID
                     && groupTask.containsTask(stagedTaskIdToBeRemovedFromGrid);
 
+            if (groupTask instanceof DesktopTask) {
+                desktopTask = (DesktopTask) groupTask;
+                // Desktop task will be added separately in the end
+                continue;
+            }
+
             TaskView taskView;
             if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
                 // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
@@ -1652,9 +1666,6 @@
 
                 ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
                         groupTask.mSplitBounds);
-            } else if (taskView instanceof DesktopTaskView) {
-                ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
-                        mOrientationState);
             } else {
                 taskView.bind(groupTask.task1, mOrientationState);
             }
@@ -1667,6 +1678,14 @@
 
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
+
+            if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+                TaskView taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+                // Always add a desktop task to the first position. Even if it is empty
+                addView(taskView, 0);
+                ArrayList<Task> tasks = desktopTask != null ? desktopTask.tasks : new ArrayList<>();
+                ((DesktopTaskView) taskView).bind(tasks, mOrientationState);
+            }
         }
 
         // Keep same previous focused task
@@ -1674,6 +1693,12 @@
         // If the list changed, maybe the focused task doesn't exist anymore
         if (newFocusedTaskView == null && getTaskViewCount() > 0) {
             newFocusedTaskView = getTaskViewAt(0);
+            // Check if the first task is the desktop.
+            // If first task is desktop, try to find another task to set as the focused task
+            if (newFocusedTaskView != null && newFocusedTaskView.isDesktopTask()
+                    && getTaskViewCount() > 1) {
+                newFocusedTaskView = getTaskViewAt(1);
+            }
         }
         mFocusedTaskViewId = newFocusedTaskView != null ?
                 newFocusedTaskView.getTaskViewId() : -1;
@@ -1707,7 +1732,12 @@
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
-                targetPage = indexOfChild(requireTaskViewAt(0));
+                TaskView taskView = requireTaskViewAt(0);
+                // If first task id desktop, try to find another task to set the target page
+                if (taskView.isDesktopTask() && getTaskViewCount() > 1) {
+                    taskView = requireTaskViewAt(1);
+                }
+                targetPage = indexOfChild(taskView);
             }
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
@@ -2136,6 +2166,9 @@
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
             Task task = taskView.getTask();
+            if (task == null) {
+                continue;
+            }
             int index = indexOfChild(taskView);
             boolean visible;
             if (showAsGrid()) {
@@ -2710,6 +2743,8 @@
         TaskView homeTaskView = getHomeTaskView();
         TaskView nextFocusedTaskView = null;
 
+        int desktopTaskIndex = Integer.MAX_VALUE;
+
         if (!isTaskDismissal) {
             mTopRowIdSet.clear();
         }
@@ -2736,6 +2771,14 @@
                     // If focused task is snapped, the row width is just task width and spacing.
                     snappedTaskRowWidth = taskWidthAndSpacing;
                 }
+            } else if (taskView.isDesktopTask()) {
+                // Desktop task was not focused. Pin it to the right of focused
+                desktopTaskIndex = i;
+                gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+
+                // Center view vertically in case it's from different orientation.
+                taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
+                        - taskView.getLayoutParams().height) / 2f);
             } else {
                 if (i > focusedTaskIndex) {
                     // For tasks after the focused task, shift by focused task's width and spacing.
@@ -2776,7 +2819,7 @@
                     // Move horizontally into empty space.
                     float widthOffset = 0;
                     for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
-                        if (j == focusedTaskIndex) {
+                        if (j == focusedTaskIndex || j == desktopTaskIndex) {
                             continue;
                         }
                         widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -2795,7 +2838,7 @@
                     // Move horizontally into empty space.
                     float widthOffset = 0;
                     for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
-                        if (j == focusedTaskIndex) {
+                        if (j == focusedTaskIndex || j == desktopTaskIndex) {
                             continue;
                         }
                         widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -5096,6 +5139,10 @@
     }
 
     private int getFirstViewIndex() {
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+            // Desktop task is at position 0, that is the first view
+            return 0;
+        }
         TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
         return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index aa37fdd..473691d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1007,6 +1007,11 @@
         return deviceProfile.isTablet && !isFocusedTask();
     }
 
+    /** Whether this task view represents the desktop */
+    public boolean isDesktopTask() {
+        return false;
+    }
+
     /**
      * Called to animate a smooth transition when going directly from an app into Overview (and
      * vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
@@ -1517,7 +1522,7 @@
             int boxWidth;
             int boxHeight;
             boolean isFocusedTask = isFocusedTask();
-            if (isFocusedTask) {
+            if (isFocusedTask || isDesktopTask()) {
                 // Task will be focused and should use focused task size. Use focusTaskRatio
                 // that is associated with the original orientation of the focused task.
                 boxWidth = taskWidth;