Introduce TaskViewsIterator and TaskViewsIterable

- Create TaskViewsIterator to iterate all the current TaskViews inside
  RecentsView.
- Create TaskViewsIterable with TaskViewsIterator as its iterator.
- Create `mTaskViewsIterable`, an instance of TaskViewsIterable that
  used to make the TaskViews iterable.
- Current `getTaskViews()` return `mTaskViewsIterable` directly.
- Change `getTaskViews()` to be a public function, thus it can be
  used outside of the RecentsView.

Some follow-up work includes:
- Replace all the call sites of `getTaskViewAt(0)` with
  `getFirstTaskView()`
- Replace `0` as the index of the first TaskView  with
  `getFirstTaskViewIndex()`
- Audit all the call sites of iterating the TaskViews, let them use
  `getTaskViews()` or IterableTaskViews.iterator() if index is needed.

Flag: EXEMPT as no functionality changes
Bug: 379942019
Test: Ensure the TaskViews required are correct in the following
      scrnarios:
      1. Swiping up from home screen to enter overview with empty or
         multiple tasks
      2. Swiping up from a fullscreen app to enter overview with
         one or multiple tasks
      3. Dimissing one or multiple tasks, exit and enter overview w/o
         newly opened tasks
      4. Remvoing all the tasks, reenter overview w/o newly opened
         tasks
Change-Id: I930ce92b7e0bc37005af74a58fd4c0ce3013e5cb
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index 7393de4..e3e2cde 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -136,10 +136,6 @@
         isTaskViewFullyVisible: (TaskView) -> Boolean,
     ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
 
-    /** Returns the current list of [TaskView] children. */
-    fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
-        (0 until taskViewCount).map(requireTaskViewAt)
-
     /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
     fun applyAttachAlpha(
         taskViews: Iterable<TaskView>,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c2eae66..c9af856 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -237,12 +237,14 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 import kotlin.Unit;
+import kotlin.collections.CollectionsKt;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -848,6 +850,59 @@
 
     private int mTaskViewCount = 0;
 
+    private final TaskViewsIterable mTaskViewsIterable = new TaskViewsIterable();
+
+    public class TaskViewsIterable implements Iterable<TaskView> {
+        @Override
+        public TaskViewsIterator iterator() {
+            return new TaskViewsIterator();
+        }
+    }
+
+    // An Iterator to iterate all the current TaskViews inside the RecentsView.
+    public class TaskViewsIterator implements Iterator<TaskView> {
+        // Refers to the index of the `TaskView` that will be returned when `next()` is called.
+        private int mNextIndex = 0;
+
+        // The "limit" of this iterator. This is the number of children of the RecentsView when
+        // the iterator was created. Adding & removing elements will invalidate the iteration
+        // anyway (and cause next() to throw) so saving this value will guarantee that the
+        // value of hasNext() remains stable and won't flap between true and false when elements
+        // are added and removed from the RecentsView.
+        private final int mLimit = getChildCount();
+
+        TaskViewsIterator() {
+            advanceIfNeeded();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mNextIndex < mLimit && mNextIndex < getChildCount();
+        }
+
+        @Override
+        public TaskView next() {
+            if (!hasNext()) {
+                throw new IndexOutOfBoundsException(
+                        String.format("mNextIndex: %d, child count: %d", mNextIndex,
+                                getChildCount()));
+            }
+            TaskView taskView = requireTaskViewAt(mNextIndex);
+            mNextIndex++;
+            advanceIfNeeded();
+            return taskView;
+        }
+
+        // Advances `mNextIndex` until it either points to a `TaskView` or to the end of the
+        // Iterator.
+        private void advanceIfNeeded() {
+            while (mNextIndex < mLimit && mNextIndex < getChildCount() && !(getChildAt(
+                    mNextIndex) instanceof TaskView)) {
+                mNextIndex++;
+            }
+        }
+    }
+
     @Nullable
     public TaskView getFirstTaskView() {
         return mUtils.getFirstTaskView(getTaskViews());
@@ -2074,14 +2129,11 @@
     }
 
     private void removeTasksViewsAndClearAllButton() {
-        for (TaskView taskView : getTaskViews()) {
-            if (isGestureActive() && taskView.isRunningTask()) {
-                // This handles an edge case where applyLoadPlan happens during a gesture when the
-                // only Task is one with excludeFromRecents, in which case we should not remove it.
-                continue;
-            }
-            removeView(taskView);
-        }
+        // This handles an edge case where applyLoadPlan happens during a gesture when the only
+        // Task is one with excludeFromRecents, in which case we should not remove it.
+        CollectionsKt
+                .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
+                .forEach(this::removeView);
         if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
         }
@@ -4721,10 +4773,10 @@
     }
 
     /**
-     * Returns the current list of [TaskView] children.
+     * Returns iterable [TaskView] children.
      */
-    public Iterable<TaskView> getTaskViews() {
-        return mUtils.getTaskViews(getTaskViewCount(), this::requireTaskViewAt);
+    public TaskViewsIterable getTaskViews() {
+        return mTaskViewsIterable;
     }
 
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {