Introduce `hasTaskViews()` to RecentsView

- This function is going to replace all the call sites that compares
  `getTaskVieCount()` and `0`.
- This is based on the fact that this count can not be a negative
  number.
- Introduce `getLastTaskView()` to get the last TaskView inside the
  RecentsView.

Flag: EXEMPT as no functionality changes
Bug: 379942019
Test: Manual
Change-Id: Iefb107d1aba61549a870f14c18e025c34b19a1b5
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8ad00bf..1907b4e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -122,7 +122,7 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
 
             if (DisplayController.getNavigationMode(mContainer).hasGestures
-                    && overview.getTaskViewCount() > 0) {
+                    && overview.hasTaskViews()) {
                 // Overview is going offscreen, so keep it at its current scale and opacity.
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
@@ -178,7 +178,7 @@
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
 
                 // Scrolling in tasks, so show straight away
-                if (overview.getTaskViewCount() > 0) {
+                if (overview.hasTaskViews()) {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
                 } else {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9dec332..49a5ac5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -223,7 +223,7 @@
         updateNonOverviewAnim(QUICK_SWITCH_FROM_HOME, nonOverviewBuilder);
         mNonOverviewAnim.dispatchOnStart();
 
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> {
                 if (!isEmpty && mSwipeDetector.isDraggingState()) {
                     // We have loaded tasks, update the animators to start at the correct scale etc.
@@ -269,7 +269,7 @@
         // since we need to take potential taskbar into account.
         xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
                 QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR);
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
         }
         mXOverviewAnim = xAnim.createPlaybackController();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 8c440b1..68940dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -48,7 +48,7 @@
      * @return true if we should intercept the motion event
      */
     boolean canInterceptTouch(MotionEvent ev) {
-        if (mRecentsView.getTaskViewCount() > 0) {
+        if (mRecentsView.hasTaskViews()) {
             // Allow swiping up in the gap between the hotseat and overview.
             return ev.getY() >= mRecentsView.getFirstTaskView().getBottom();
         } else {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 28d95e2..980dee4 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -181,7 +181,7 @@
         Task runningTask = runningTasks[0];
         if (mHomeTask != null && runningTask != null
                 && mHomeTask.key.id == runningTask.key.id
-                && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
+                && !hasTaskViews() && mLoadPlanEverApplied) {
             // Do not add a stub task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a stub task and later removing it.
             // Ignore empty task signal if applyLoadPlan has never run.
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index e3e2cde..26f482c 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -98,6 +98,9 @@
     /** Returns the first TaskView if it exists, or null otherwise. */
     fun getFirstTaskView(taskViews: Iterable<TaskView>): TaskView? = taskViews.firstOrNull()
 
+    /** Returns the last TaskView if it exists, or null otherwise. */
+    fun getLastTaskView(taskViews: Iterable<TaskView>): TaskView? = taskViews.lastOrNull()
+
     /**
      * Returns the first TaskView that is not large
      *
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c9af856..6f29634 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -908,6 +908,11 @@
         return mUtils.getFirstTaskView(getTaskViews());
     }
 
+    @Nullable
+    private TaskView getLastTaskView() {
+        return mUtils.getLastTaskView(getTaskViews());
+    }
+
     public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
@@ -1222,7 +1227,7 @@
     public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
             @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
         mActionsView = actionsView;
-        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
         // Update flags for 1p/3p launchers
         mActionsView.updateFor3pLauncher(!supportsAppPairs());
         mSplitSelectStateController = splitController;
@@ -1318,7 +1323,7 @@
                 } else {
                     mTaskViewPool.recycle(taskView);
                 }
-                mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+                mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
             }
         }
     }
@@ -2134,11 +2139,16 @@
         CollectionsKt
                 .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
                 .forEach(this::removeView);
-        if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
+        if (!hasTaskViews() && indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
         }
     }
 
+    /** Returns true if there are at least one TaskView has been added to the RecentsView. */
+    public boolean hasTaskViews() {
+        return CollectionsKt.any(getTaskViews());
+    }
+
     public int getTaskViewCount() {
         return mTaskViewCount;
     }
@@ -2346,7 +2356,7 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
-        if (getTaskViewCount() == 0) {
+        if (!hasTaskViews()) {
             return;
         }
 
@@ -3231,8 +3241,7 @@
      *                            to skip rebalance
      */
     private void updateGridProperties(int startRebalanceAfter) {
-        int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
+        if (!hasTaskViews()) {
             return;
         }
 
@@ -3249,6 +3258,7 @@
         IntSet topSet = new IntSet();
         IntSet bottomSet = new IntSet();
 
+        final int taskCount = getTaskViewCount();
         // Horizontal grid translation for each task
         float[] gridTranslations = new float[taskCount];
 
@@ -4784,7 +4794,7 @@
     }
 
     public void updateEmptyMessage() {
-        boolean isEmpty = getTaskViewCount() == 0;
+        boolean isEmpty = !hasTaskViews();
         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
                 || mLastMeasureSize.y != getHeight();
         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
@@ -5543,12 +5553,12 @@
 
         // Get the deadzone rect between the task views
         mTaskViewDeadZoneRect.setEmpty();
-        int count = getTaskViewCount();
-        if (count > 0) {
-            final View taskView = requireTaskViewAt(0);
-            requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
-            mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
-                    taskView.getBottom());
+        if (hasTaskViews()) {
+            final View firstTaskView = getFirstTaskView();
+            getLastTaskView().getHitRect(mTaskViewDeadZoneRect);
+            mTaskViewDeadZoneRect.union(firstTaskView.getLeft(), firstTaskView.getTop(),
+                    firstTaskView.getRight(),
+                    firstTaskView.getBottom());
         }
     }
 
@@ -5712,7 +5722,7 @@
             throw new IllegalStateException("Another pending animation is still running");
         }
 
-        if (getTaskViewCount() == 0) {
+        if (!hasTaskViews()) {
             return new PendingAnimation(duration);
         }
 
@@ -5837,10 +5847,11 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(hasTaskViews());
 
+        // TODO(b/379942019): Revisit the logic below to make sure it does not rely on the
+        //  `taskViewCount` to update the indices or make it indices free.
         final int taskViewCount = getTaskViewCount();
-        event.setScrollable(taskViewCount > 0);
-
         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
             final int[] visibleTasks = getVisibleChildrenRange();
             event.setFromIndex(taskViewCount - visibleTasks[1]);
@@ -6071,7 +6082,7 @@
 
     @Override
     protected int computeMinScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMinScroll();
         }
 
@@ -6080,7 +6091,7 @@
 
     @Override
     protected int computeMaxScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMaxScroll();
         }