Adding a utility class to cache views at an activity level

Bug: 122345781
Change-Id: I9a939e0b19c06c1089c1ceb515f8b97fb5dbb49e
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 90b5536..4b2e487 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -118,7 +118,8 @@
         final RectF iconLocation = new RectF();
         boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
         FloatingIconView floatingIconView = canUseWorkspaceView
-                ? recentsView.getFloatingIconView(activity, workspaceView, iconLocation)
+                ? FloatingIconView.getFloatingIconView(activity, workspaceView,
+                        true /* hideOriginal */, iconLocation, false /* isOpening */)
                 : null;
 
         return new HomeAnimationFactory() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 661468a..1e1007e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -66,7 +66,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -77,7 +76,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
@@ -95,7 +93,6 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
-import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RecentsAnimationWrapper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
@@ -308,8 +305,6 @@
     private Layout mEmptyTextLayout;
     private LiveTileOverlay mLiveTileOverlay;
 
-    private FloatingIconView mFloatingIconView;
-
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
         if (!inMultiWindowMode && mOverviewStateEnabled) {
@@ -1687,12 +1682,6 @@
         }
     }
 
-    public FloatingIconView getFloatingIconView(Launcher launcher, View view, RectF iconLocation) {
-        mFloatingIconView = FloatingIconView.getFloatingIconView(launcher, view,
-                true /* hideOriginal */, iconLocation, false /* isOpening */, mFloatingIconView);
-        return  mFloatingIconView;
-    }
-
     public ClipAnimationHelper getTempClipAnimationHelper() {
         return mTempClipAnimationHelper;
     }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 91c4601..95ae312 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -142,7 +142,6 @@
     private final float mClosingWindowTransY;
 
     private DeviceProfile mDeviceProfile;
-    private FloatingIconView mFloatingView;
 
     private RemoteAnimationProvider mRemoteAnimationProvider;
 
@@ -411,15 +410,15 @@
     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
             Rect windowTargetBounds, boolean toggleVisibility) {
         RectF bounds = new RectF();
-        mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
-                bounds, true /* isOpening */, mFloatingView);
+        FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
+                toggleVisibility, bounds, true /* isOpening */);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
         RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
                 MODE_OPENING);
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
-                new SyncRtSurfaceTransactionApplierCompat(mFloatingView);
+                new SyncRtSurfaceTransactionApplierCompat(floatingView);
         openingTargets.addDependentTransactionApplier(surfaceApplier);
 
         // Scale the app icon to take up the entire screen. This simplifies the math when
@@ -463,7 +462,7 @@
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setDuration(APP_LAUNCH_DURATION);
         appAnimator.setInterpolator(LINEAR);
-        appAnimator.addListener(mFloatingView);
+        appAnimator.addListener(floatingView);
         appAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -557,7 +556,7 @@
                         } else {
                             currentBounds.bottom -= croppedHeight;
                         }
-                        mFloatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
+                        floatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
                                 cornerRadius * scale, true /* isOpening */);
                     } else {
                         matrix.setTranslate(target.position.x, target.position.y);
diff --git a/res/layout/floating_icon_view.xml b/res/layout/floating_icon_view.xml
new file mode 100644
index 0000000..240c486
--- /dev/null
+++ b/res/layout/floating_icon_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.FloatingIconView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 83aea8b..984729b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -86,7 +86,6 @@
 
     <!-- View IDs to store item highlight information -->
     <item type="id" name="view_unhighlight_background" />
-    <item type="id" name="view_highlighted" />
 
     <!-- Menu id for feature flags -->
     <item type="id" name="menu_apply_flags" />
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 7f72242..424ffde 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
 
 import java.io.FileDescriptor;
@@ -102,6 +103,12 @@
     // animation
     @InvisibilityFlags private int mForceInvisible;
 
+    private final ViewCache mViewCache = new ViewCache();
+
+    public ViewCache getViewCache() {
+        return mViewCache;
+    }
+
     @Override
     public DeviceProfile getDeviceProfile() {
         return mDeviceProfile;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 7af12c5..9d46cf2 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -44,7 +44,6 @@
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
@@ -55,6 +54,7 @@
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.UiThreadHelper;
 
 import java.util.Arrays;
 
@@ -210,8 +210,7 @@
             return;
         }
         // Load the adaptive icon on a background thread and add the view in ui thread.
-        final Looper workerLooper = LauncherModel.getWorkerLooper();
-        new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
+        new Handler(UiThreadHelper.getBackgroundLooper()).postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
                 Object[] outObj = new Object[1];
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 0a9bc72..250169c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -102,7 +102,6 @@
     private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>();
 
     boolean mAnimating = false;
-    private Rect mTempBounds = new Rect();
 
     private float mSlop;
 
diff --git a/src/com/android/launcher3/util/ViewCache.java b/src/com/android/launcher3/util/ViewCache.java
new file mode 100644
index 0000000..08b8744
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewCache.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Utility class to cache views at an activity level
+ */
+public class ViewCache {
+
+    protected final SparseArray<CacheEntry> mCache = new SparseArray();
+
+    public void setCacheSize(int layoutId, int size) {
+        mCache.put(layoutId, new CacheEntry(size));
+    }
+
+    public <T extends View> T getView(int layoutId, Context context, ViewGroup parent) {
+        CacheEntry entry = mCache.get(layoutId);
+        if (entry == null) {
+            entry = new CacheEntry(1);
+            mCache.put(layoutId, entry);
+        }
+
+        if (entry.mCurrentSize > 0) {
+            entry.mCurrentSize --;
+            T result = (T) entry.mViews[entry.mCurrentSize];
+            entry.mViews[entry.mCurrentSize] = null;
+            return result;
+        }
+
+        return (T) LayoutInflater.from(context).inflate(layoutId, parent, false);
+    }
+
+    public void recycleView(int layoutId, View view) {
+        CacheEntry entry = mCache.get(layoutId);
+        if (entry != null && entry.mCurrentSize < entry.mMaxSize) {
+            entry.mViews[entry.mCurrentSize] = view;
+            entry.mCurrentSize++;
+        }
+    }
+
+    private static class CacheEntry {
+
+        final int mMaxSize;
+        final View[] mViews;
+
+        int mCurrentSize;
+
+        public CacheEntry(int maxSize) {
+            mMaxSize = maxSize;
+            mViews = new View[maxSize];
+            mCurrentSize = 0;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 3d5877a..f63bcdd 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -27,6 +27,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Outline;
@@ -40,6 +41,7 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
@@ -61,6 +63,7 @@
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.util.UiThreadHelper;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
@@ -148,12 +151,20 @@
     private final SpringAnimation mFgSpringX;
     private float mFgTransX;
 
-    private FloatingIconView(Launcher launcher) {
-        super(launcher);
-        mLauncher = launcher;
+    public FloatingIconView(Context context) {
+        this(context, null);
+    }
+
+    public FloatingIconView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
         mBlurSizeOutline = getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
-        mListenerView = new ListenerView(launcher, null);
+        mListenerView = new ListenerView(context, attrs);
 
         mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
                 .setSpring(new SpringForce()
@@ -350,6 +361,7 @@
     }
 
     @WorkerThread
+    @SuppressWarnings("WrongThread")
     private void getIcon(View v, ItemInfo info, boolean isOpening,
             Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
         final LayoutParams lp = (LayoutParams) getLayoutParams();
@@ -396,7 +408,7 @@
                 && finalDrawable instanceof AdaptiveIconDrawable;
         int iconOffset = getOffsetForIconBounds(finalDrawable);
 
-        new Handler(Looper.getMainLooper()).post(() -> {
+        mLauncher.getMainExecutor().execute(() -> {
             if (isAdaptiveIcon) {
                 mIsAdaptiveIcon = true;
                 boolean isFolderIcon = finalDrawable instanceof FolderAdaptiveIcon;
@@ -505,6 +517,7 @@
     }
 
     @WorkerThread
+    @SuppressWarnings("WrongThread")
     private int getOffsetForIconBounds(Drawable drawable) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
                 !(drawable instanceof AdaptiveIconDrawable)) {
@@ -515,7 +528,7 @@
         Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline);
         bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
 
-        try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
+        try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
             Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null));
         }
 
@@ -604,11 +617,14 @@
      * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, RectF positionOut, boolean isOpening, FloatingIconView recycle) {
-        if (recycle != null) {
-            recycle.recycle();
-        }
-        FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
+            boolean hideOriginal, RectF positionOut, boolean isOpening) {
+        final DragLayer dragLayer = launcher.getDragLayer();
+        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+
+        FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view,
+                launcher, parent);
+        view.recycle();
+
         view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
 
         view.mOriginalIcon = originalView;
@@ -626,16 +642,15 @@
                 originalView.setVisibility(INVISIBLE);
             };
             CancellationSignal loadIconSignal = view.mLoadIconSignal;
-            new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
+            new Handler(UiThreadHelper.getBackgroundLooper()).postAtFrontOfQueue(() -> {
                 view.getIcon(originalView, (ItemInfo) originalView.getTag(), isOpening,
                         onIconLoaded, loadIconSignal);
             });
         }
 
         // We need to add it to the overlay, but keep it invisible until animation starts..
-        final DragLayer dragLayer = launcher.getDragLayer();
         view.setVisibility(INVISIBLE);
-        ((ViewGroup) dragLayer.getParent()).addView(view);
+        parent.addView(view);
         dragLayer.addView(view.mListenerView);
         view.mListenerView.setListener(view::onListenerViewClosed);
 
@@ -714,6 +729,7 @@
         ((ViewGroup) dragLayer.getParent()).removeView(this);
         dragLayer.removeView(mListenerView);
         recycle();
+        mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this);
     }
 
     private void recycle() {