Only load thumbnails for visible tasks.

Bug: 73651529
Change-Id: Id45c5381c43b8c26447be5492c51af83cffe32a9
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 9e07b49..991852a 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -22,6 +22,7 @@
 import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
 
 import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -38,8 +39,10 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.DeviceProfile;
@@ -114,6 +117,7 @@
     private boolean mScrimOnLeft;
 
     private boolean mFirstTaskIconScaledDown = false;
+    private SparseBooleanArray mPrevVisibleTasks = new SparseBooleanArray();
 
     public RecentsView(Context context) {
         this(context, null);
@@ -167,12 +171,28 @@
 
     private void setupLayoutTransition() {
         // We want to show layout transitions when pages are deleted, to close the gap.
+        // TODO: We should this manually so we can control the animation (fill in the gap as the
+        // dismissing task is being tracked, and also so we can update the visible task data during
+        // the transition. For now, the workaround is to expand the visible tasks to load.
         mLayoutTransition = new LayoutTransition();
         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
 
         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        mLayoutTransition.addTransitionListener(new TransitionListener() {
+            @Override
+            public void startTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
+                    View view, int i) {
+                loadVisibleTaskData();
+            }
+
+            @Override
+            public void endTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
+                    View view, int i) {
+                loadVisibleTaskData();
+            }
+        });
         setLayoutTransition(mLayoutTransition);
     }
 
@@ -314,13 +334,16 @@
 
         // Rebind and reset all task views
         for (int i = tasks.size() - 1; i >= 0; i--) {
+            final int pageIndex = tasks.size() - i - 1 + mFirstTaskIndex;
             final Task task = tasks.get(i);
-            final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
+            final TaskView taskView = (TaskView) getChildAt(pageIndex);
             taskView.bind(task);
             taskView.resetVisualProperties();
-            loader.loadTaskData(task);
         }
         updateCurveProperties();
+        // Reload the set of visible task's data
+        mPrevVisibleTasks.clear();
+        loadVisibleTaskData();
         applyIconScale(false /* animate */);
 
         if (oldChildCount != getChildCount()) {
@@ -414,9 +437,14 @@
     }
 
     @Override
-    public void computeScroll() {
-        super.computeScroll();
+    protected boolean computeScrollHelper() {
+        boolean scrolling = super.computeScrollHelper();
         updateCurveProperties();
+        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+            // After scrolling, update the visible task's data
+            loadVisibleTaskData();
+        }
+        return scrolling;
     }
 
     /**
@@ -444,6 +472,32 @@
         }
     }
 
+    /**
+     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+     * and unloads the associated task data for tasks that are no longer visible.
+     */
+    private void loadVisibleTaskData() {
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        int centerPageIndex = getPageNearestToCenterOfScreen();
+        int lower = Math.max(mFirstTaskIndex, centerPageIndex - 2);
+        int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
+        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+            TaskView taskView = (TaskView) getChildAt(i);
+            Task task = taskView.getTask();
+            boolean visible = lower <= i && i <= upper;
+            if (visible) {
+                if (!mPrevVisibleTasks.get(i)) {
+                    loader.loadTaskData(task);
+                }
+            } else {
+                if (mPrevVisibleTasks.get(i)) {
+                    loader.unloadTaskData(task);
+                }
+            }
+            mPrevVisibleTasks.put(i, visible);
+        }
+    }
+
     public void onTaskDismissed(TaskView taskView) {
         ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
         removeView(taskView);
@@ -493,14 +547,16 @@
             addView(taskView, mFirstTaskIndex);
             setLayoutTransition(mLayoutTransition);
         }
+        mRunningTaskId = runningTaskId;
+        setCurrentPage(mFirstTaskIndex);
         if (!needsReload) {
             needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
         }
         if (needsReload) {
             mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+        } else {
+            loadVisibleTaskData();
         }
-        mRunningTaskId = runningTaskId;
-        setCurrentPage(mFirstTaskIndex);
         if (mCurrentPage >= mFirstTaskIndex) {
             getPageAt(mCurrentPage).setAlpha(0);
         }