Only inflating taskbar views for valid items

Also enabling view cache for folders

Bug: 187353581
Test: Manual
Change-Id: I5bc695fd86475f30611bc6b362b4ae93c48c26bb
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6e47700..aaf0d3f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -56,6 +56,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -88,6 +89,7 @@
     private int mLastRequestedNonFullscreenHeight;
 
     private final SysUINavigationMode.Mode mNavMode;
+    private final ViewCache mViewCache = new ViewCache();
 
     private final boolean mIsSafeModeEnabled;
 
@@ -187,6 +189,11 @@
         return mControllers.taskbarDragController;
     }
 
+    @Override
+    public ViewCache getViewCache() {
+        return mViewCache;
+    }
+
     /**
      * Sets a new data-source for this taskbar instance
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 4294eb5..67ebc02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Intent;
@@ -86,7 +89,7 @@
         mActivity.setTaskbarWindowFullscreen(true);
         view.post(() -> {
             startInternalDrag(btv);
-            btv.setAlpha(0);
+            btv.setVisibility(INVISIBLE);
         });
         return true;
     }
@@ -293,7 +296,7 @@
 
     private void maybeOnDragEnd() {
         if (!isDragging()) {
-            ((View) mDragObject.originalView).setAlpha(1);
+            ((View) mDragObject.originalView).setVisibility(VISIBLE);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 373ca2a..a952182 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -103,9 +103,6 @@
         mControllerCallbacks = callbacks;
         mIconClickListener = mControllerCallbacks.getOnClickListener();
         mIconLongClickListener = mControllerCallbacks.getOnLongClickListener();
-
-        int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
-        updateHotseatItems(new ItemInfo[numHotseatIcons]);
     }
 
     /**
@@ -127,13 +124,11 @@
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() != VISIBLE) {
-                continue;
-            }
+            ItemInfo info = (ItemInfo) child.getTag();
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
 
             float childCenter = (child.getLeft() + child.getRight()) / 2;
-            float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * (i)
+            float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
                     + hotseatCellSize / 2;
             setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
         }
@@ -155,34 +150,58 @@
                 mActivityContext.getDeviceProfile().taskbarSize);
     }
 
+    private void removeAndRecycle(View view) {
+        removeView(view);
+        view.setOnClickListener(null);
+        view.setOnLongClickListener(null);
+        if (!(view.getTag() instanceof FolderInfo)) {
+            mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
+        }
+        view.setTag(null);
+    }
+
     /**
      * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
      */
     protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+        int nextViewIndex = 0;
+
         for (int i = 0; i < hotseatItemInfos.length; i++) {
             ItemInfo hotseatItemInfo = hotseatItemInfos[i];
-            View hotseatView = getChildAt(i);
+            if (hotseatItemInfo == null) {
+                continue;
+            }
 
             // Replace any Hotseat views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
             boolean isFolder = false;
-            boolean needsReinflate = false;
-            if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+            if (hotseatItemInfo.isPredictedItem()) {
                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
             } else if (hotseatItemInfo instanceof FolderInfo) {
                 expectedLayoutResId = R.layout.folder_icon;
                 isFolder = true;
-                // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
-                // if the info changes we need to reinflate. This should only happen if a new folder
-                // is dragged to the position that another folder previously existed.
-                needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null
-                    || hotseatView.getSourceLayoutResId() != expectedLayoutResId
-                    || needsReinflate) {
-                removeView(hotseatView);
+
+            View hotseatView = null;
+            while (nextViewIndex < getChildCount()) {
+                hotseatView = getChildAt(nextViewIndex);
+
+                // see if the view can be reused
+                if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
+                        || (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
+                    // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
+                    // so if the info changes we need to reinflate. This should only happen if a new
+                    // folder is dragged to the position that another folder previously existed.
+                    removeAndRecycle(hotseatView);
+                } else {
+                    // View found
+                    break;
+                }
+            }
+
+            if (hotseatView == null) {
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
@@ -194,7 +213,7 @@
                 }
                 LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
                 hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
-                addView(hotseatView, i, lp);
+                addView(hotseatView, nextViewIndex, lp);
             }
 
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -202,15 +221,13 @@
                     && hotseatItemInfo instanceof WorkspaceItemInfo) {
                 ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
                         (WorkspaceItemInfo) hotseatItemInfo);
-                setClickAndLongClickListenersForIcon(hotseatView);
-            } else if (isFolder) {
-                setClickAndLongClickListenersForIcon(hotseatView);
-            } else {
-                hotseatView.setOnClickListener(null);
-                hotseatView.setOnLongClickListener(null);
-                hotseatView.setTag(null);
             }
-            hotseatView.setVisibility(hotseatView.getTag() != null ? VISIBLE : INVISIBLE);
+            setClickAndLongClickListenersForIcon(hotseatView);
+            nextViewIndex++;
+        }
+        // Remove remaining views
+        while (nextViewIndex < getChildCount()) {
+            removeAndRecycle(getChildAt(nextViewIndex));
         }
     }
 
@@ -225,15 +242,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         int count = getChildCount();
-        // Find total visible children
-        int visibleChildren = 0;
-        for (int i = 0; i < count; i++) {
-            if (getChildAt(i).getVisibility() == VISIBLE) {
-                visibleChildren++;
-            }
-        }
-
-        int spaceNeeded = visibleChildren * (mItemMarginLeftRight * 2 + mIconTouchSize);
+        int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
         int iconStart = (right - left - spaceNeeded) / 2;
         int startOffset = ApiWrapper.getHotseatStartOffset(getContext());
         if (startOffset > iconStart) {
@@ -246,12 +255,10 @@
         mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() == VISIBLE) {
-                iconStart += mItemMarginLeftRight;
-                int iconEnd = iconStart + mIconTouchSize;
-                child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
-                iconStart = iconEnd + mItemMarginLeftRight;
-            }
+            iconStart += mItemMarginLeftRight;
+            int iconEnd = iconStart + mIconTouchSize;
+            child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+            iconStart = iconEnd + mItemMarginLeftRight;
         }
         mIconLayoutBounds.right = iconStart;
     }
@@ -307,7 +314,7 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+        return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
     }
 
     @Override