Merge "Add some gesture logging to track down quickscrub launch issue" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 88f2315..54269f09 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -220,7 +220,7 @@
     }
 
     @Override
-    public boolean onDrag(float displacement, float velocity) {
+    public boolean onDrag(float displacement) {
         float totalDisplacement = displacement + mDisplacementShift;
         boolean isGoingUp =
                 totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index 8f7dbdd..6557761 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -28,9 +28,9 @@
 import android.util.SparseArray;
 
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.graphics.DrawableFactory;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.systemui.shared.recents.model.IconLoader;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
 
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 1a9915c..d9626c4 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -280,7 +280,6 @@
             factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL);
             mActivity = activity;
             mRecentsView = mActivity.getOverviewPanel();
-            mRecentsView.setRunningTaskIconScaledDown(true);
             if (!mUserEventLogged) {
                 activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON,
                         mHelper.getContainerType(), ContainerType.TASKSWITCHER);
@@ -298,6 +297,9 @@
             if (mListener != null) {
                 mListener.unregister();
             }
+            if (mRecentsView != null) {
+                mRecentsView.setRunningTaskIconScaledDown(true);
+            }
             AnimatorSet anim = new AnimatorSet();
             anim.addListener(new AnimationSuccessListener() {
                 @Override
diff --git a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
index 2d78b69..9f13cf5 100644
--- a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
+++ b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
@@ -17,6 +17,6 @@
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@color/legacy_icon_background"/>
     <foreground>
-        <com.android.launcher3.graphics.FixedScaleDrawable />
+        <com.android.launcher3.icons.FixedScaleDrawable />
     </foreground>
 </adaptive-icon>
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 6b0a90a..4c34071 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -39,7 +39,7 @@
 
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8e6703c..8513d63 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -272,8 +272,8 @@
     /**
      * Overrides the default long press timeout.
      */
-    public void setLongPressTimeout(int longPressTimeout) {
-        mLongPressHelper.setLongPressTimeout(longPressTimeout);
+    public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
+        mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor);
     }
 
     @Override
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index dde733c..639c173 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -17,17 +17,18 @@
 package com.android.launcher3;
 
 import android.view.View;
+import android.view.ViewConfiguration;
 
 import com.android.launcher3.util.Thunk;
 
 public class CheckLongPressHelper {
 
-    public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300;
+    public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
 
     @Thunk View mView;
     @Thunk View.OnLongClickListener mListener;
     @Thunk boolean mHasPerformedLongPress;
-    private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT;
+    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
     private CheckForLongPress mPendingCheckForLongPress;
 
     class CheckForLongPress implements Runnable {
@@ -60,8 +61,8 @@
     /**
      * Overrides the default long press timeout.
      */
-    public void setLongPressTimeout(int longPressTimeout) {
-        mLongPressTimeout = longPressTimeout;
+    public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
+        mLongPressTimeoutFactor = longPressTimeoutFactor;
     }
 
     public void postCheckForLongPress() {
@@ -70,7 +71,8 @@
         if (mPendingCheckForLongPress == null) {
             mPendingCheckForLongPress = new CheckForLongPress();
         }
-        mView.postDelayed(mPendingCheckForLongPress, mLongPressTimeout);
+        mView.postDelayed(mPendingCheckForLongPress,
+                (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
     }
 
     public void cancelLongPress() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9839c12..256fd6c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -30,7 +30,7 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.badge.BadgeRenderer;
-import com.android.launcher3.graphics.IconNormalizer;
+import com.android.launcher3.icons.IconNormalizer;
 
 public class DeviceProfile {
 
@@ -373,7 +373,7 @@
         updateFolderCellSize(1f, dm, res);
 
         // Don't let the folder get too close to the edges of the screen.
-        int folderMargin = edgeMarginPx;
+        int folderMargin = edgeMarginPx * 2;
         Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the icons fit within the available height.
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 7efb6ec..daf587a 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -34,7 +34,7 @@
 import android.util.Property;
 import android.util.SparseArray;
 
-import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.icons.BitmapInfo;
 
 public class FastBitmapDrawable extends Drawable {
 
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index b9d45fb..7190f12 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -40,8 +40,8 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 2ceb0dd..6d453c9 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
 
 import android.graphics.Bitmap;
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index df1c693..68abd68 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -38,7 +38,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
 import com.android.launcher3.model.BaseModelUpdateTask;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index cf497fd..d47dcee 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -31,8 +31,8 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.LauncherIcons;
-import com.android.launcher3.graphics.ShadowGenerator;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShadowGenerator;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 0f13550..69b4bdb 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -22,7 +22,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
@@ -252,7 +251,7 @@
                         R.layout.all_apps_icon, parent, false);
                 icon.setOnClickListener(ItemClickHandler.INSTANCE);
                 icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
-                icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
+                icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index ab6635e..15cc2ca 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -19,7 +19,7 @@
 import static android.view.View.MeasureSpec.getSize;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
-import static com.android.launcher3.graphics.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
 import android.content.Context;
 import android.graphics.Rect;
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 8998b24..5d38aad 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -27,7 +27,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 
-import com.android.launcher3.graphics.ShadowGenerator;
+import com.android.launcher3.icons.ShadowGenerator;
 
 /**
  * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 386b3b3..fb44660 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 6ac56eb..6d1efd5 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -56,7 +56,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 8377adf..ce83a17 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
-
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -37,6 +35,7 @@
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 
@@ -66,7 +65,7 @@
      * Returns a FastBitmapDrawable with the icon.
      */
     public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
-        FastBitmapDrawable drawable = info.iconBitmap == LOW_RES_ICON
+        FastBitmapDrawable drawable = info.usingLowResIcon()
                 ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
                 : new FastBitmapDrawable(info);
         drawable.setIsDisabled(info.isDisabled());
@@ -74,7 +73,7 @@
     }
 
     public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
-        return info.icon == LOW_RES_ICON
+        return info.isLowRes()
                 ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
                 : new FastBitmapDrawable(info);
     }
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index 18efd47..5f2fb59 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.util.Themes;
 
 /**
diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java
new file mode 100644
index 0000000..f7e2be1
--- /dev/null
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2018 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.icons;
+
+import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.IconProvider;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.SQLiteCacheHelper;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.ColorUtils;
+
+public class BaseIconCache {
+
+    private static final String TAG = "BaseIconCache";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_IGNORE_CACHE = false;
+
+    private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
+
+    // Empty class name is used for storing package default entry.
+    public static final String EMPTY_CLASS_NAME = ".";
+
+    public static class CacheEntry extends BitmapInfo {
+        public CharSequence title = "";
+        public CharSequence contentDescription = "";
+    }
+
+    private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
+
+    final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+    final Context mContext;
+    final PackageManager mPackageManager;
+    final IconProvider mIconProvider;
+    final UserManagerCompat mUserManager;
+    final LauncherAppsCompat mLauncherApps;
+
+    private final HashMap<ComponentKey, CacheEntry> mCache =
+            new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
+    private final InstantAppResolver mInstantAppResolver;
+    final int mIconDpi;
+
+    final IconDB mIconDb;
+    final Handler mWorkerHandler;
+
+    private final BitmapFactory.Options mDecodeOptions;
+
+    public BaseIconCache(Context context, int iconDpi, int iconPixelSize) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mUserManager = UserManagerCompat.getInstance(mContext);
+        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+        mInstantAppResolver = InstantAppResolver.newInstance(mContext);
+        mIconDpi = iconDpi;
+        mIconDb = new IconDB(context, iconPixelSize);
+
+        mIconProvider = IconProvider.newInstance(context);
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
+
+        if (BitmapRenderer.USE_HARDWARE_BITMAP) {
+            mDecodeOptions = new BitmapFactory.Options();
+            mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
+        } else {
+            mDecodeOptions = null;
+        }
+    }
+
+    private Drawable getFullResDefaultActivityIcon() {
+        return Resources.getSystem().getDrawableForDensity(Utilities.ATLEAST_OREO
+                ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
+                mIconDpi);
+    }
+
+    private Drawable getFullResIcon(Resources resources, int iconId) {
+        if (resources != null && iconId != 0) {
+            try {
+                return resources.getDrawableForDensity(iconId, mIconDpi);
+            } catch (Resources.NotFoundException e) { }
+        }
+        return getFullResDefaultActivityIcon();
+    }
+
+    public Drawable getFullResIcon(String packageName, int iconId) {
+        try {
+            return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
+        } catch (PackageManager.NameNotFoundException e) { }
+        return getFullResDefaultActivityIcon();
+    }
+
+    public Drawable getFullResIcon(ActivityInfo info) {
+        try {
+            return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
+                    info.getIconResource());
+        } catch (PackageManager.NameNotFoundException e) { }
+        return getFullResDefaultActivityIcon();
+    }
+
+    public Drawable getFullResIcon(LauncherActivityInfo info) {
+        return getFullResIcon(info, true);
+    }
+
+    public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
+        return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
+    }
+
+    protected BitmapInfo makeDefaultIcon(UserHandle user) {
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            return li.createBadgedIconBitmap(
+                    getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
+        }
+    }
+
+    /**
+     * Remove any records for the supplied ComponentName.
+     */
+    public synchronized void remove(ComponentName componentName, UserHandle user) {
+        mCache.remove(new ComponentKey(componentName, user));
+    }
+
+    /**
+     * Remove any records for the supplied package name from memory.
+     */
+    private void removeFromMemCacheLocked(String packageName, UserHandle user) {
+        HashSet<ComponentKey> forDeletion = new HashSet<>();
+        for (ComponentKey key: mCache.keySet()) {
+            if (key.componentName.getPackageName().equals(packageName)
+                    && key.user.equals(user)) {
+                forDeletion.add(key);
+            }
+        }
+        for (ComponentKey condemned: forDeletion) {
+            mCache.remove(condemned);
+        }
+    }
+
+    /**
+     * Removes the entries related to the given package in memory and persistent DB.
+     */
+    public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
+        removeFromMemCacheLocked(packageName, user);
+        long userSerial = mUserManager.getSerialNumberForUser(user);
+        mIconDb.delete(
+                IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+                new String[]{packageName + "/%", Long.toString(userSerial)});
+    }
+
+    public IconCacheUpdateHandler getUpdateHandler() {
+        mIconProvider.updateSystemStateString(mContext);
+        return new IconCacheUpdateHandler(this);
+    }
+
+    /**
+     * Adds an entry into the DB and the in-memory cache.
+     * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+     *                        the memory. This is useful then the previous bitmap was created using
+     *                        old data.
+     * package private
+     */
+    synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
+            PackageInfo info, long userSerial, boolean replaceExisting) {
+        UserHandle user = cachingLogic.getUser(object);
+        ComponentName componentName = cachingLogic.getComponent(object);
+
+        final ComponentKey key = new ComponentKey(componentName, user);
+        CacheEntry entry = null;
+        if (!replaceExisting) {
+            entry = mCache.get(key);
+            // We can't reuse the entry if the high-res icon is not present.
+            if (entry == null || entry.icon == null || entry.isLowRes()) {
+                entry = null;
+            }
+        }
+        if (entry == null) {
+            entry = new CacheEntry();
+            cachingLogic.loadIcon(mContext, this, object, entry);
+        }
+        entry.title = cachingLogic.getLabel(object);
+        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+        mCache.put(key, entry);
+
+        ContentValues values = newContentValues(entry.icon, entry.color,
+                entry.title.toString(), componentName.getPackageName());
+        addIconToDB(values, componentName, info, userSerial);
+    }
+
+    /**
+     * Updates {@param values} to contain versioning information and adds it to the DB.
+     * @param values {@link ContentValues} containing icon & title
+     */
+    private void addIconToDB(ContentValues values, ComponentName key,
+            PackageInfo info, long userSerial) {
+        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+        values.put(IconDB.COLUMN_USER, userSerial);
+        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+        values.put(IconDB.COLUMN_VERSION, info.versionCode);
+        mIconDb.insertOrReplace(values);
+    }
+
+    /**
+     * Fill in {@param infoInOut} with the corresponding icon and label.
+     */
+    public synchronized void getTitleAndIconForApp(
+            PackageItemInfo infoInOut, boolean useLowResIcon) {
+        CacheEntry entry = getEntryForPackageLocked(
+                infoInOut.packageName, infoInOut.user, useLowResIcon);
+        applyCacheEntry(entry, infoInOut);
+    }
+
+    protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+        info.title = Utilities.trim(entry.title);
+        info.contentDescription = entry.contentDescription;
+        ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
+    }
+
+    public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
+        if (!mDefaultIcons.containsKey(user)) {
+            mDefaultIcons.put(user, makeDefaultIcon(user));
+        }
+        return mDefaultIcons.get(user);
+    }
+
+    public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
+        return getDefaultIcon(user).icon == icon;
+    }
+
+    /**
+     * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
+     * This method is not thread safe, it must be called from a synchronized method.
+     */
+    protected <T> CacheEntry cacheLocked(
+            @NonNull ComponentName componentName,
+            @NonNull Provider<T> infoProvider,
+            @NonNull CachingLogic<T> cachingLogic,
+            UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+        Preconditions.assertWorkerThread();
+        ComponentKey cacheKey = new ComponentKey(componentName, user);
+        CacheEntry entry = mCache.get(cacheKey);
+        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+            entry = new CacheEntry();
+            mCache.put(cacheKey, entry);
+
+            // Check the DB first.
+            T object = null;
+            boolean providerFetchedOnce = false;
+
+            if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
+                object = infoProvider.get();
+                providerFetchedOnce = true;
+
+                if (object != null) {
+                    cachingLogic.loadIcon(mContext, this, object, entry);
+                } else {
+                    if (usePackageIcon) {
+                        CacheEntry packageEntry = getEntryForPackageLocked(
+                                componentName.getPackageName(), user, false);
+                        if (packageEntry != null) {
+                            if (DEBUG) Log.d(TAG, "using package default icon for " +
+                                    componentName.toShortString());
+                            packageEntry.applyTo(entry);
+                            entry.title = packageEntry.title;
+                            entry.contentDescription = packageEntry.contentDescription;
+                        }
+                    }
+                    if (entry.icon == null) {
+                        if (DEBUG) Log.d(TAG, "using default icon for " +
+                                componentName.toShortString());
+                        getDefaultIcon(user).applyTo(entry);
+                    }
+                }
+            }
+
+            if (TextUtils.isEmpty(entry.title)) {
+                if (object == null && !providerFetchedOnce) {
+                    object = infoProvider.get();
+                    providerFetchedOnce = true;
+                }
+                if (object != null) {
+                    entry.title = cachingLogic.getLabel(object);
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+                }
+            }
+        }
+        return entry;
+    }
+
+    public synchronized void clear() {
+        Preconditions.assertWorkerThread();
+        mIconDb.clear();
+    }
+
+    /**
+     * Adds a default package entry in the cache. This entry is not persisted and will be removed
+     * when the cache is flushed.
+     */
+    public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
+            Bitmap icon, CharSequence title) {
+        removeFromMemCacheLocked(packageName, user);
+
+        ComponentKey cacheKey = getPackageKey(packageName, user);
+        CacheEntry entry = mCache.get(cacheKey);
+
+        // For icon caching, do not go through DB. Just update the in-memory entry.
+        if (entry == null) {
+            entry = new CacheEntry();
+        }
+        if (!TextUtils.isEmpty(title)) {
+            entry.title = title;
+        }
+        if (icon != null) {
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            li.createIconBitmap(icon).applyTo(entry);
+            li.recycle();
+        }
+        if (!TextUtils.isEmpty(title) && entry.icon != null) {
+            mCache.put(cacheKey, entry);
+        }
+    }
+
+    private static ComponentKey getPackageKey(String packageName, UserHandle user) {
+        ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+        return new ComponentKey(cn, user);
+    }
+
+    /**
+     * Gets an entry for the package, which can be used as a fallback entry for various components.
+     * This method is not thread safe, it must be called from a synchronized method.
+     */
+    private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
+            boolean useLowResIcon) {
+        Preconditions.assertWorkerThread();
+        ComponentKey cacheKey = getPackageKey(packageName, user);
+        CacheEntry entry = mCache.get(cacheKey);
+
+        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+            entry = new CacheEntry();
+            boolean entryUpdated = true;
+
+            // Check the DB first.
+            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+                try {
+                    int flags = Process.myUserHandle().equals(user) ? 0 :
+                            PackageManager.GET_UNINSTALLED_PACKAGES;
+                    PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
+                    ApplicationInfo appInfo = info.applicationInfo;
+                    if (appInfo == null) {
+                        throw new NameNotFoundException("ApplicationInfo is null");
+                    }
+
+                    LauncherIcons li = LauncherIcons.obtain(mContext);
+                    // Load the full res icon for the application, but if useLowResIcon is set, then
+                    // only keep the low resolution icon instead of the larger full-sized icon
+                    BitmapInfo iconInfo = li.createBadgedIconBitmap(
+                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
+                            mInstantAppResolver.isInstantApp(appInfo));
+                    li.recycle();
+
+                    entry.title = appInfo.loadLabel(mPackageManager);
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+                    entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
+                    entry.color = iconInfo.color;
+
+                    // Add the icon in the DB here, since these do not get written during
+                    // package updates.
+                    ContentValues values = newContentValues(iconInfo.icon, entry.color,
+                            entry.title.toString(), packageName);
+                    addIconToDB(values, cacheKey.componentName, info,
+                            mUserManager.getSerialNumberForUser(user));
+
+                } catch (NameNotFoundException e) {
+                    if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+                    entryUpdated = false;
+                }
+            }
+
+            // Only add a filled-out entry to the cache
+            if (entryUpdated) {
+                mCache.put(cacheKey, entry);
+            }
+        }
+        return entry;
+    }
+
+    private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+        Cursor c = null;
+        try {
+            c = mIconDb.query(
+                    lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
+                    IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+                    new String[]{
+                            cacheKey.componentName.flattenToString(),
+                            Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
+            if (c.moveToNext()) {
+                // Set the alpha to be 255, so that we never have a wrong color
+                entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255);
+                entry.title = c.getString(1);
+                if (entry.title == null) {
+                    entry.title = "";
+                    entry.contentDescription = "";
+                } else {
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(
+                            entry.title, cacheKey.user);
+                }
+
+                if (lowRes) {
+                    entry.icon = LOW_RES_ICON;
+                } else {
+                    byte[] data = c.getBlob(2);
+                    try {
+                        entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
+                                mDecodeOptions);
+                    } catch (Exception e) { }
+                }
+                return true;
+            }
+        } catch (SQLiteException e) {
+            Log.d(TAG, "Error reading icon cache", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return false;
+    }
+
+    static final class IconDB extends SQLiteCacheHelper {
+        private final static int RELEASE_VERSION = 25;
+
+        public final static String TABLE_NAME = "icons";
+        public final static String COLUMN_ROWID = "rowid";
+        public final static String COLUMN_COMPONENT = "componentName";
+        public final static String COLUMN_USER = "profileId";
+        public final static String COLUMN_LAST_UPDATED = "lastUpdated";
+        public final static String COLUMN_VERSION = "version";
+        public final static String COLUMN_ICON = "icon";
+        public final static String COLUMN_ICON_COLOR = "icon_color";
+        public final static String COLUMN_LABEL = "label";
+        public final static String COLUMN_SYSTEM_STATE = "system_state";
+
+        public final static String[] COLUMNS_HIGH_RES = new String[] {
+                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
+        public final static String[] COLUMNS_LOW_RES = new String[] {
+                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
+
+        public IconDB(Context context, int iconPixelSize) {
+            super(context, LauncherFiles.APP_ICONS_DB,
+                    (RELEASE_VERSION << 16) + iconPixelSize,
+                    TABLE_NAME);
+        }
+
+        @Override
+        protected void onCreateTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+                    COLUMN_COMPONENT + " TEXT NOT NULL, " +
+                    COLUMN_USER + " INTEGER NOT NULL, " +
+                    COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
+                    COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
+                    COLUMN_ICON + " BLOB, " +
+                    COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
+                    COLUMN_LABEL + " TEXT, " +
+                    COLUMN_SYSTEM_STATE + " TEXT, " +
+                    "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
+                    ");");
+        }
+    }
+
+    private ContentValues newContentValues(Bitmap icon, int iconColor, String label,
+            String packageName) {
+        ContentValues values = new ContentValues();
+        values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
+        values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
+
+        values.put(IconDB.COLUMN_LABEL, label);
+        values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
+
+        return values;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java
similarity index 92%
rename from src/com/android/launcher3/graphics/BitmapInfo.java
rename to src/com/android/launcher3/icons/BitmapInfo.java
index 69c0684..ebe0511 100644
--- a/src/com/android/launcher3/graphics/BitmapInfo.java
+++ b/src/com/android/launcher3/icons/BitmapInfo.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -37,6 +37,10 @@
         info.color = color;
     }
 
+    public final boolean isLowRes() {
+        return LOW_RES_ICON == icon;
+    }
+
     public static BitmapInfo fromBitmap(Bitmap bitmap) {
         return fromBitmap(bitmap, null);
     }
diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java
new file mode 100644
index 0000000..13fdc6a
--- /dev/null
+++ b/src/com/android/launcher3/icons/CachingLogic.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.icons;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
+
+public interface CachingLogic<T> {
+
+    ComponentName getComponent(T object);
+
+    UserHandle getUser(T object);
+
+    CharSequence getLabel(T object);
+
+    void loadIcon(Context context, BaseIconCache cache, T object, BitmapInfo target);
+
+    CachingLogic<LauncherActivityInfo> LAUNCHER_ACTIVITY_INFO =
+            new CachingLogic<LauncherActivityInfo>() {
+
+        @Override
+        public ComponentName getComponent(LauncherActivityInfo object) {
+            return object.getComponentName();
+        }
+
+        @Override
+        public UserHandle getUser(LauncherActivityInfo object) {
+            return object.getUser();
+        }
+
+        @Override
+        public CharSequence getLabel(LauncherActivityInfo object) {
+            return object.getLabel();
+        }
+
+        @Override
+        public void loadIcon(Context context, BaseIconCache cache, LauncherActivityInfo object,
+                BitmapInfo target) {
+            LauncherIcons li = LauncherIcons.obtain(context);
+            li.createBadgedIconBitmap(cache.getFullResIcon(object), object.getUser(),
+                    object.getApplicationInfo().targetSdkVersion).applyTo(target);
+            li.recycle();
+        }
+    };
+}
diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/icons/ColorExtractor.java
similarity index 98%
rename from src/com/android/launcher3/graphics/ColorExtractor.java
rename to src/com/android/launcher3/icons/ColorExtractor.java
index da5da9c..87bda82 100644
--- a/src/com/android/launcher3/graphics/ColorExtractor.java
+++ b/src/com/android/launcher3/icons/ColorExtractor.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/icons/FixedScaleDrawable.java
similarity index 97%
rename from src/com/android/launcher3/graphics/FixedScaleDrawable.java
rename to src/com/android/launcher3/icons/FixedScaleDrawable.java
index 0f0e424..e594f47 100644
--- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java
+++ b/src/com/android/launcher3/icons/FixedScaleDrawable.java
@@ -1,4 +1,4 @@
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import android.annotation.TargetApi;
 import android.content.res.Resources;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 2035d0e..eae6f01 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -16,209 +16,42 @@
 
 package com.android.launcher3.icons;
 
-import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO;
 
-import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
 import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconProvider;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.BitmapRenderer;
-import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Provider;
-import com.android.launcher3.util.SQLiteCacheHelper;
-
-import java.util.HashMap;
-import java.util.HashSet;
 
 import androidx.annotation.NonNull;
-import androidx.core.graphics.ColorUtils;
 
 /**
  * Cache of application icons.  Icons can be made from any thread.
  */
-public class IconCache {
+public class IconCache extends BaseIconCache {
 
     private static final String TAG = "Launcher.IconCache";
 
-    private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
-
-    // Empty class name is used for storing package default entry.
-    public static final String EMPTY_CLASS_NAME = ".";
-
-    private static final boolean DEBUG = false;
-    private static final boolean DEBUG_IGNORE_CACHE = false;
-
-    public static class CacheEntry extends BitmapInfo {
-        public CharSequence title = "";
-        public CharSequence contentDescription = "";
-
-        public boolean isLowRes() {
-            return LOW_RES_ICON == icon;
-        }
-    }
-
-    private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
-
-    final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    final Context mContext;
-    final PackageManager mPackageManager;
-    final IconProvider mIconProvider;
-    final UserManagerCompat mUserManager;
-    final LauncherAppsCompat mLauncherApps;
-
-    private final HashMap<ComponentKey, CacheEntry> mCache =
-            new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
-    private final InstantAppResolver mInstantAppResolver;
-    private final int mIconDpi;
-
-    final IconDB mIconDb;
-    final Handler mWorkerHandler;
-
-    private final BitmapFactory.Options mDecodeOptions;
-
     private int mPendingIconRequestCount = 0;
 
     public IconCache(Context context, InvariantDeviceProfile inv) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mUserManager = UserManagerCompat.getInstance(mContext);
-        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
-        mInstantAppResolver = InstantAppResolver.newInstance(mContext);
-        mIconDpi = inv.fillResIconDpi;
-        mIconDb = new IconDB(context, inv.iconBitmapSize);
-
-        mIconProvider = IconProvider.newInstance(context);
-        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
-
-        if (BitmapRenderer.USE_HARDWARE_BITMAP) {
-            mDecodeOptions = new BitmapFactory.Options();
-            mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
-        } else {
-            mDecodeOptions = null;
-        }
-    }
-
-    private Drawable getFullResDefaultActivityIcon() {
-        return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ?
-                android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon);
-    }
-
-    private Drawable getFullResIcon(Resources resources, int iconId) {
-        Drawable d;
-        try {
-            d = resources.getDrawableForDensity(iconId, mIconDpi);
-        } catch (Resources.NotFoundException e) {
-            d = null;
-        }
-
-        return (d != null) ? d : getFullResDefaultActivityIcon();
-    }
-
-    public Drawable getFullResIcon(String packageName, int iconId) {
-        Resources resources;
-        try {
-            resources = mPackageManager.getResourcesForApplication(packageName);
-        } catch (PackageManager.NameNotFoundException e) {
-            resources = null;
-        }
-        if (resources != null) {
-            if (iconId != 0) {
-                return getFullResIcon(resources, iconId);
-            }
-        }
-        return getFullResDefaultActivityIcon();
-    }
-
-    public Drawable getFullResIcon(ActivityInfo info) {
-        Resources resources;
-        try {
-            resources = mPackageManager.getResourcesForApplication(
-                    info.applicationInfo);
-        } catch (PackageManager.NameNotFoundException e) {
-            resources = null;
-        }
-        if (resources != null) {
-            int iconId = info.getIconResource();
-            if (iconId != 0) {
-                return getFullResIcon(resources, iconId);
-            }
-        }
-
-        return getFullResDefaultActivityIcon();
-    }
-
-    public Drawable getFullResIcon(LauncherActivityInfo info) {
-        return getFullResIcon(info, true);
-    }
-
-    public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
-        return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
-    }
-
-    protected BitmapInfo makeDefaultIcon(UserHandle user) {
-        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-            return li.createBadgedIconBitmap(
-                    getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
-        }
-    }
-
-    /**
-     * Remove any records for the supplied ComponentName.
-     */
-    public synchronized void remove(ComponentName componentName, UserHandle user) {
-        mCache.remove(new ComponentKey(componentName, user));
-    }
-
-    /**
-     * Remove any records for the supplied package name from memory.
-     */
-    private void removeFromMemCacheLocked(String packageName, UserHandle user) {
-        HashSet<ComponentKey> forDeletion = new HashSet<>();
-        for (ComponentKey key: mCache.keySet()) {
-            if (key.componentName.getPackageName().equals(packageName)
-                    && key.user.equals(user)) {
-                forDeletion.add(key);
-            }
-        }
-        for (ComponentKey condemned: forDeletion) {
-            mCache.remove(condemned);
-        }
+        super(context, inv.fillResIconDpi, inv.iconBitmapSize);
     }
 
     /**
@@ -231,7 +64,8 @@
                     PackageManager.GET_UNINSTALLED_PACKAGES);
             long userSerial = mUserManager.getSerialNumberForUser(user);
             for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
-                addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
+                addIconToDBAndMemCache(app, LAUNCHER_ACTIVITY_INFO, info, userSerial,
+                        false /*replace existing*/);
             }
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Package not found", e);
@@ -239,69 +73,6 @@
     }
 
     /**
-     * Removes the entries related to the given package in memory and persistent DB.
-     */
-    public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
-        removeFromMemCacheLocked(packageName, user);
-        long userSerial = mUserManager.getSerialNumberForUser(user);
-        mIconDb.delete(
-                IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
-                new String[]{packageName + "/%", Long.toString(userSerial)});
-    }
-
-    public IconCacheUpdateHandler getUpdateHandler() {
-        mIconProvider.updateSystemStateString(mContext);
-        return new IconCacheUpdateHandler(this);
-    }
-
-    /**
-     * Adds an entry into the DB and the in-memory cache.
-     * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
-     *                        the memory. This is useful then the previous bitmap was created using
-     *                        old data.
-     * package private
-     */
-    synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
-            PackageInfo info, long userSerial, boolean replaceExisting) {
-        final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
-        CacheEntry entry = null;
-        if (!replaceExisting) {
-            entry = mCache.get(key);
-            // We can't reuse the entry if the high-res icon is not present.
-            if (entry == null || entry.icon == null || entry.isLowRes()) {
-                entry = null;
-            }
-        }
-        if (entry == null) {
-            entry = new CacheEntry();
-            LauncherIcons li = LauncherIcons.obtain(mContext);
-            li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
-                    app.getApplicationInfo().targetSdkVersion).applyTo(entry);
-            li.recycle();
-        }
-        entry.title = app.getLabel();
-        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
-        mCache.put(key, entry);
-
-        ContentValues values = newContentValues(entry.icon, entry.color,
-                entry.title.toString(), app.getApplicationInfo().packageName);
-        addIconToDB(values, app.getComponentName(), info, userSerial);
-    }
-
-    /**
-     * Updates {@param values} to contain versioning information and adds it to the DB.
-     * @param values {@link ContentValues} containing icon & title
-     */
-    private void addIconToDB(ContentValues values, ComponentName key,
-            PackageInfo info, long userSerial) {
-        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
-        values.put(IconDB.COLUMN_USER, userSerial);
-        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
-        values.put(IconDB.COLUMN_VERSION, info.versionCode);
-        mIconDb.insertOrReplace(values);
-    }
-
-    /**
      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
      * @return a request ID that can be used to cancel the request.
      */
@@ -343,7 +114,7 @@
      */
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
-                Provider.<LauncherActivityInfo>of(null),
+                Provider.of(null), LAUNCHER_ACTIVITY_INFO,
                 application.user, false, application.usingLowResIcon());
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
             applyCacheEntry(entry, application);
@@ -385,238 +156,10 @@
             @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
-                infoInOut.user, usePkgIcon, useLowResIcon);
+                LAUNCHER_ACTIVITY_INFO, infoInOut.user, usePkgIcon, useLowResIcon);
         applyCacheEntry(entry, infoInOut);
     }
 
-    /**
-     * Fill in {@param infoInOut} with the corresponding icon and label.
-     */
-    public synchronized void getTitleAndIconForApp(
-            PackageItemInfo infoInOut, boolean useLowResIcon) {
-        CacheEntry entry = getEntryForPackageLocked(
-                infoInOut.packageName, infoInOut.user, useLowResIcon);
-        applyCacheEntry(entry, infoInOut);
-    }
-
-    private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
-        info.title = Utilities.trim(entry.title);
-        info.contentDescription = entry.contentDescription;
-        ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
-    }
-
-    public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
-        if (!mDefaultIcons.containsKey(user)) {
-            mDefaultIcons.put(user, makeDefaultIcon(user));
-        }
-        return mDefaultIcons.get(user);
-    }
-
-    public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
-        return getDefaultIcon(user).icon == icon;
-    }
-
-    /**
-     * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
-     * This method is not thread safe, it must be called from a synchronized method.
-     */
-    protected CacheEntry cacheLocked(
-            @NonNull ComponentName componentName,
-            @NonNull Provider<LauncherActivityInfo> infoProvider,
-            UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
-        Preconditions.assertWorkerThread();
-        ComponentKey cacheKey = new ComponentKey(componentName, user);
-        CacheEntry entry = mCache.get(cacheKey);
-        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
-            entry = new CacheEntry();
-            mCache.put(cacheKey, entry);
-
-            // Check the DB first.
-            LauncherActivityInfo info = null;
-            boolean providerFetchedOnce = false;
-
-            if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
-                info = infoProvider.get();
-                providerFetchedOnce = true;
-
-                if (info != null) {
-                    LauncherIcons li = LauncherIcons.obtain(mContext);
-                    li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(),
-                            info.getApplicationInfo().targetSdkVersion).applyTo(entry);
-                    li.recycle();
-                } else {
-                    if (usePackageIcon) {
-                        CacheEntry packageEntry = getEntryForPackageLocked(
-                                componentName.getPackageName(), user, false);
-                        if (packageEntry != null) {
-                            if (DEBUG) Log.d(TAG, "using package default icon for " +
-                                    componentName.toShortString());
-                            packageEntry.applyTo(entry);
-                            entry.title = packageEntry.title;
-                            entry.contentDescription = packageEntry.contentDescription;
-                        }
-                    }
-                    if (entry.icon == null) {
-                        if (DEBUG) Log.d(TAG, "using default icon for " +
-                                componentName.toShortString());
-                        getDefaultIcon(user).applyTo(entry);
-                    }
-                }
-            }
-
-            if (TextUtils.isEmpty(entry.title)) {
-                if (info == null && !providerFetchedOnce) {
-                    info = infoProvider.get();
-                    providerFetchedOnce = true;
-                }
-                if (info != null) {
-                    entry.title = info.getLabel();
-                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                }
-            }
-        }
-        return entry;
-    }
-
-    public synchronized void clear() {
-        Preconditions.assertWorkerThread();
-        mIconDb.clear();
-    }
-
-    /**
-     * Adds a default package entry in the cache. This entry is not persisted and will be removed
-     * when the cache is flushed.
-     */
-    public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
-            Bitmap icon, CharSequence title) {
-        removeFromMemCacheLocked(packageName, user);
-
-        ComponentKey cacheKey = getPackageKey(packageName, user);
-        CacheEntry entry = mCache.get(cacheKey);
-
-        // For icon caching, do not go through DB. Just update the in-memory entry.
-        if (entry == null) {
-            entry = new CacheEntry();
-        }
-        if (!TextUtils.isEmpty(title)) {
-            entry.title = title;
-        }
-        if (icon != null) {
-            LauncherIcons li = LauncherIcons.obtain(mContext);
-            li.createIconBitmap(icon).applyTo(entry);
-            li.recycle();
-        }
-        if (!TextUtils.isEmpty(title) && entry.icon != null) {
-            mCache.put(cacheKey, entry);
-        }
-    }
-
-    private static ComponentKey getPackageKey(String packageName, UserHandle user) {
-        ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
-        return new ComponentKey(cn, user);
-    }
-
-    /**
-     * Gets an entry for the package, which can be used as a fallback entry for various components.
-     * This method is not thread safe, it must be called from a synchronized method.
-     */
-    private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
-            boolean useLowResIcon) {
-        Preconditions.assertWorkerThread();
-        ComponentKey cacheKey = getPackageKey(packageName, user);
-        CacheEntry entry = mCache.get(cacheKey);
-
-        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
-            entry = new CacheEntry();
-            boolean entryUpdated = true;
-
-            // Check the DB first.
-            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
-                try {
-                    int flags = Process.myUserHandle().equals(user) ? 0 :
-                        PackageManager.GET_UNINSTALLED_PACKAGES;
-                    PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
-                    ApplicationInfo appInfo = info.applicationInfo;
-                    if (appInfo == null) {
-                        throw new NameNotFoundException("ApplicationInfo is null");
-                    }
-
-                    LauncherIcons li = LauncherIcons.obtain(mContext);
-                    // Load the full res icon for the application, but if useLowResIcon is set, then
-                    // only keep the low resolution icon instead of the larger full-sized icon
-                    BitmapInfo iconInfo = li.createBadgedIconBitmap(
-                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
-                            mInstantAppResolver.isInstantApp(appInfo));
-                    li.recycle();
-
-                    entry.title = appInfo.loadLabel(mPackageManager);
-                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                    entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
-                    entry.color = iconInfo.color;
-
-                    // Add the icon in the DB here, since these do not get written during
-                    // package updates.
-                    ContentValues values = newContentValues(iconInfo.icon, entry.color,
-                            entry.title.toString(), packageName);
-                    addIconToDB(values, cacheKey.componentName, info,
-                            mUserManager.getSerialNumberForUser(user));
-
-                } catch (NameNotFoundException e) {
-                    if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
-                    entryUpdated = false;
-                }
-            }
-
-            // Only add a filled-out entry to the cache
-            if (entryUpdated) {
-                mCache.put(cacheKey, entry);
-            }
-        }
-        return entry;
-    }
-
-    private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
-        Cursor c = null;
-        try {
-            c = mIconDb.query(
-                    lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
-                    IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
-                    new String[]{
-                            cacheKey.componentName.flattenToString(),
-                            Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
-            if (c.moveToNext()) {
-                // Set the alpha to be 255, so that we never have a wrong color
-                entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255);
-                entry.title = c.getString(1);
-                if (entry.title == null) {
-                    entry.title = "";
-                    entry.contentDescription = "";
-                } else {
-                    entry.contentDescription = mUserManager.getBadgedLabelForUser(
-                            entry.title, cacheKey.user);
-                }
-
-                if (lowRes) {
-                    entry.icon = LOW_RES_ICON;
-                } else {
-                    byte[] data = c.getBlob(2);
-                    try {
-                        entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
-                                mDecodeOptions);
-                    } catch (Exception e) { }
-                }
-                return true;
-            }
-        } catch (SQLiteException e) {
-            Log.d(TAG, "Error reading icon cache", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return false;
-    }
-
     public static abstract class IconLoadRequest implements Runnable {
         private final Handler mHandler;
         private final Runnable mEndRunnable;
@@ -641,59 +184,6 @@
         }
     }
 
-    static final class IconDB extends SQLiteCacheHelper {
-        private final static int RELEASE_VERSION = 25;
-
-        public final static String TABLE_NAME = "icons";
-        public final static String COLUMN_ROWID = "rowid";
-        public final static String COLUMN_COMPONENT = "componentName";
-        public final static String COLUMN_USER = "profileId";
-        public final static String COLUMN_LAST_UPDATED = "lastUpdated";
-        public final static String COLUMN_VERSION = "version";
-        public final static String COLUMN_ICON = "icon";
-        public final static String COLUMN_ICON_COLOR = "icon_color";
-        public final static String COLUMN_LABEL = "label";
-        public final static String COLUMN_SYSTEM_STATE = "system_state";
-
-        public final static String[] COLUMNS_HIGH_RES = new String[] {
-                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
-        public final static String[] COLUMNS_LOW_RES = new String[] {
-                IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
-
-        public IconDB(Context context, int iconPixelSize) {
-            super(context, LauncherFiles.APP_ICONS_DB,
-                    (RELEASE_VERSION << 16) + iconPixelSize,
-                    TABLE_NAME);
-        }
-
-        @Override
-        protected void onCreateTable(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
-                    COLUMN_COMPONENT + " TEXT NOT NULL, " +
-                    COLUMN_USER + " INTEGER NOT NULL, " +
-                    COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_ICON + " BLOB, " +
-                    COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_LABEL + " TEXT, " +
-                    COLUMN_SYSTEM_STATE + " TEXT, " +
-                    "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
-                    ");");
-        }
-    }
-
-    private ContentValues newContentValues(Bitmap icon, int iconColor, String label,
-            String packageName) {
-        ContentValues values = new ContentValues();
-        values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
-        values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
-
-        values.put(IconDB.COLUMN_LABEL, label);
-        values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
-
-        return values;
-    }
-
     /**
      * Interface for receiving itemInfo with high-res icon.
      */
diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
index 04e2901..64e3fbf 100644
--- a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
+++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
@@ -17,7 +17,6 @@
 
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
@@ -29,7 +28,7 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.IconCache.IconDB;
+import com.android.launcher3.icons.BaseIconCache.IconDB;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -48,10 +47,10 @@
     private static final Object ICON_UPDATE_TOKEN = new Object();
 
     private final HashMap<String, PackageInfo> mPkgInfoMap;
-    private final IconCache mIconCache;
+    private final BaseIconCache mIconCache;
     private final HashMap<UserHandle, Set<String>> mPackagesToIgnore = new HashMap<>();
 
-    IconCacheUpdateHandler(IconCache cache) {
+    IconCacheUpdateHandler(BaseIconCache cache) {
         mIconCache = cache;
 
         mPkgInfoMap = new HashMap<>();
@@ -68,9 +67,8 @@
 
     private void createPackageInfoMap() {
         PackageManager pm = mIconCache.mPackageManager;
-        HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
         for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
-            pkgInfoMap.put(info.packageName, info);
+            mPkgInfoMap.put(info.packageName, info);
         }
     }
 
@@ -79,11 +77,11 @@
      * the DB and are updated.
      * @return The set of packages for which icons have updated.
      */
-    public void updateIcons(List<LauncherActivityInfo> apps) {
+    public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic) {
         if (apps.isEmpty()) {
             return;
         }
-        UserHandle user = apps.get(0).getUser();
+        UserHandle user = cachingLogic.getUser(apps.get(0));
 
         Set<String> ignorePackages = mPackagesToIgnore.get(user);
         if (ignorePackages == null) {
@@ -91,13 +89,13 @@
         }
 
         long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user);
-        HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
-        for (LauncherActivityInfo app : apps) {
-            componentMap.put(app.getComponentName(), app);
+        HashMap<ComponentName, T> componentMap = new HashMap<>();
+        for (T app : apps) {
+            componentMap.put(cachingLogic.getComponent(app), app);
         }
 
         HashSet<Integer> itemsToRemove = new HashSet<>();
-        Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
+        Stack<T> appsToUpdate = new Stack<>();
 
         try (Cursor c = mIconCache.mIconDb.query(
                 new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
@@ -130,7 +128,7 @@
 
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
-                LauncherActivityInfo app = componentMap.remove(component);
+                T app = componentMap.remove(component);
                 if (version == info.versionCode && updateTime == info.lastUpdateTime &&
                         TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.mIconProvider.getIconSystemState(info.packageName))) {
@@ -154,9 +152,10 @@
 
         // Insert remaining apps.
         if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
-            Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
+            Stack<T> appsToAdd = new Stack<>();
             appsToAdd.addAll(componentMap.values());
-            new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate).scheduleNext();
+            new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic)
+                    .scheduleNext();
         }
     }
 
@@ -166,29 +165,31 @@
      * LauncherActivityInfo list. Items are updated/added one at a time, so that the
      * worker thread doesn't get blocked.
      */
-    private class SerializedIconUpdateTask implements Runnable {
+    private class SerializedIconUpdateTask<T> implements Runnable {
         private final long mUserSerial;
         private final UserHandle mUserHandle;
-        private final Stack<LauncherActivityInfo> mAppsToAdd;
-        private final Stack<LauncherActivityInfo> mAppsToUpdate;
+        private final Stack<T> mAppsToAdd;
+        private final Stack<T> mAppsToUpdate;
+        private final CachingLogic<T> mCachingLogic;
         private final HashSet<String> mUpdatedPackages = new HashSet<>();
 
         SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
-                Stack<LauncherActivityInfo> appsToAdd, Stack<LauncherActivityInfo> appsToUpdate) {
+                Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic) {
             mUserHandle = userHandle;
             mUserSerial = userSerial;
             mAppsToAdd = appsToAdd;
             mAppsToUpdate = appsToUpdate;
+            mCachingLogic = cachingLogic;
         }
 
         @Override
         public void run() {
             if (!mAppsToUpdate.isEmpty()) {
-                LauncherActivityInfo app = mAppsToUpdate.pop();
-                String pkg = app.getComponentName().getPackageName();
+                T app = mAppsToUpdate.pop();
+                String pkg = mCachingLogic.getComponent(app).getPackageName();
                 PackageInfo info = mPkgInfoMap.get(pkg);
                 mIconCache.addIconToDBAndMemCache(
-                        app, info, mUserSerial, true /*replace existing*/);
+                        app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
                 mUpdatedPackages.add(pkg);
 
                 if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
@@ -200,13 +201,13 @@
                 // Let it run one more time.
                 scheduleNext();
             } else if (!mAppsToAdd.isEmpty()) {
-                LauncherActivityInfo app = mAppsToAdd.pop();
-                PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
+                T app = mAppsToAdd.pop();
+                PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
                 // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
                 // app should have package info, this is not guaranteed by the api
                 if (info != null) {
-                    mIconCache.addIconToDBAndMemCache(
-                            app, info, mUserSerial, false /*replace existing*/);
+                    mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
+                            mUserSerial, false /*replace existing*/);
                 }
 
                 if (!mAppsToAdd.isEmpty()) {
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java
similarity index 99%
rename from src/com/android/launcher3/graphics/IconNormalizer.java
rename to src/com/android/launcher3/icons/IconNormalizer.java
index df00815..7317782 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/icons/IconNormalizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import android.content.Context;
 import android.graphics.Bitmap;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
similarity index 98%
rename from src/com/android/launcher3/graphics/LauncherIcons.java
rename to src/com/android/launcher3/icons/LauncherIcons.java
index 7db67c9..b4cbf65 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
-import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
+import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,7 +44,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java
similarity index 99%
rename from src/com/android/launcher3/graphics/ShadowGenerator.java
rename to src/com/android/launcher3/icons/ShadowGenerator.java
index d2d1699..57d463a 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.graphics;
+package com.android.launcher3.icons;
 
 import android.content.Context;
 import android.graphics.Bitmap;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 77e9721..87aef02 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -43,8 +43,8 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.GridOccupancy;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4ccb8d8..d6b7b0f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO;
 import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -60,7 +61,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -802,7 +803,7 @@
             List<List<LauncherActivityInfo>> activityListPerUser) {
         int userCount = activityListPerUser.size();
         for (int i = 0; i < userCount; i++) {
-            updateHandler.updateIcons(activityListPerUser.get(i));
+            updateHandler.updateIcons(activityListPerUser.get(i), LAUNCHER_ACTIVITY_INFO);
         }
     }
 
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0af5311..c004e21 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -38,8 +38,8 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 59f3d1c..020bc41 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 9521a9e..9f02d4f 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -26,7 +26,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 5c0e259..78627ec 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -179,7 +179,7 @@
 
 
     @Override
-    public boolean onDrag(float displacement, float velocity) {
+    public boolean onDrag(float displacement) {
         setContentTranslation(canChildBeDismissed()
                 ? displacement : OverScroll.dampedScroll(displacement, getWidth()));
         mContentTranslateAnimator.cancel();
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 4500629..c14c00e 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -24,7 +24,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 7b09bcc..ce1cc89 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -256,7 +256,7 @@
     }
 
     @Override
-    public boolean onDrag(float displacement, float velocity) {
+    public boolean onDrag(float displacement) {
         float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
         float progress = deltaProgress + mStartProgress;
         updateProgress(progress);
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 6ffc0ef..a0a410e 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -21,6 +21,7 @@
 import android.graphics.PointF;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.NonNull;
@@ -52,12 +53,6 @@
      */
     public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
 
-    /**
-     * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
-     * Cutoff frequency is set at 10 Hz.
-     */
-    public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
-
     /* Scroll state, this is set to true during dragging and animation. */
     private ScrollState mState = ScrollState.IDLE;
 
@@ -75,6 +70,8 @@
          * Distance in pixels a touch can wander before we think the user is scrolling.
          */
         abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+
+        abstract float getVelocity(VelocityTracker tracker);
     }
 
     public static final Direction VERTICAL = new Direction() {
@@ -88,6 +85,11 @@
         float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
             return Math.abs(ev.getX(pointerIndex) - downPos.x);
         }
+
+        @Override
+        float getVelocity(VelocityTracker tracker) {
+            return tracker.getYVelocity();
+        }
     };
 
     public static final Direction HORIZONTAL = new Direction() {
@@ -101,6 +103,11 @@
         float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
             return Math.abs(ev.getY(pointerIndex) - downPos.y);
         }
+
+        @Override
+        float getVelocity(VelocityTracker tracker) {
+            return tracker.getXVelocity();
+        }
     };
 
     //------------------- ScrollState transition diagram -----------------------------------
@@ -151,16 +158,16 @@
 
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
-    private Direction mDir;
+    private final Direction mDir;
 
     private final float mTouchSlop;
+    private final float mMaxVelocity;
 
     /* Client of this gesture detector can register a callback. */
     private final Listener mListener;
 
-    private long mCurrentMillis;
+    private VelocityTracker mVelocityTracker;
 
-    private float mVelocity;
     private float mLastDisplacement;
     private float mDisplacement;
 
@@ -170,24 +177,22 @@
     public interface Listener {
         void onDragStart(boolean start);
 
-        boolean onDrag(float displacement, float velocity);
+        boolean onDrag(float displacement);
 
         void onDragEnd(float velocity, boolean fling);
     }
 
     public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
-        this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+        this(ViewConfiguration.get(context), l, dir);
     }
 
     @VisibleForTesting
-    protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
-        mTouchSlop = touchSlope;
+    protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            @NonNull Direction dir) {
         mListener = l;
         mDir = dir;
-    }
-
-    public void updateDirection(Direction dir) {
-        mDir = dir;
+        mTouchSlop = config.getScaledTouchSlop();
+        mMaxVelocity = config.getScaledMaximumFlingVelocity();
     }
 
     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
@@ -215,14 +220,22 @@
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
-        switch (ev.getActionMasked()) {
+        int actionMasked = ev.getActionMasked();
+        if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+            mVelocityTracker.clear();
+        }
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        switch (actionMasked) {
             case MotionEvent.ACTION_DOWN:
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
                 mLastDisplacement = 0;
                 mDisplacement = 0;
-                mVelocity = 0;
 
                 if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
                     setState(ScrollState.DRAGGING);
@@ -247,8 +260,6 @@
                     break;
                 }
                 mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
-                computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
-                        ev.getEventTime());
 
                 // handle state and listener calls.
                 if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
@@ -265,6 +276,8 @@
                 if (mState == ScrollState.DRAGGING) {
                     setState(ScrollState.SETTLING);
                 }
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
                 break;
             default:
                 break;
@@ -308,55 +321,24 @@
     private boolean reportDragging() {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
-                Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
-                        mDisplacement, mVelocity));
+                Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
             }
 
             mLastDisplacement = mDisplacement;
-            return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+            return mListener.onDrag(mDisplacement - mSubtractDisplacement);
         }
         return true;
     }
 
     private void reportDragEnd() {
+        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+        float velocity = mDir.getVelocity(mVelocityTracker) / 1000;
         if (DBG) {
             Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
-                    mDisplacement, mVelocity));
+                    mDisplacement, velocity));
         }
-        mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
 
-    }
-
-    /**
-     * Computes the damped velocity.
-     */
-    public float computeVelocity(float delta, long currentMillis) {
-        long previousMillis = mCurrentMillis;
-        mCurrentMillis = currentMillis;
-
-        float deltaTimeMillis = mCurrentMillis - previousMillis;
-        float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
-        if (Math.abs(mVelocity) < 0.001f) {
-            mVelocity = velocity;
-        } else {
-            float alpha = computeDampeningFactor(deltaTimeMillis);
-            mVelocity = interpolate(mVelocity, velocity, alpha);
-        }
-        return mVelocity;
-    }
-
-    /**
-     * Returns a time-dependent dampening factor using delta time.
-     */
-    private static float computeDampeningFactor(float deltaTime) {
-        return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
-    }
-
-    /**
-     * Returns the linear interpolation between two values
-     */
-    public static float interpolate(float from, float to, float alpha) {
-        return (1.0f - alpha) * from + alpha * to;
+        mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
     }
 
     public static long calculateDuration(float velocity, float progressNeeded) {
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 26c8f24..f948beb 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -128,7 +128,7 @@
     public void onDragStart(boolean start) { }
 
     @Override
-    public boolean onDrag(float displacement, float velocity) {
+    public boolean onDrag(float displacement) {
         float range = mContent.getHeight();
         displacement = Utilities.boundToRange(displacement, 0, range);
         setTranslationShift(displacement / range);
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index f0d6de2..883cbee 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -175,6 +175,7 @@
         if (mThumbOffsetY == y) {
             return;
         }
+        updatePopupY((int) y);
         mThumbOffsetY = y;
         invalidate();
     }
@@ -281,7 +282,6 @@
             mPopupView.setText(sectionName);
         }
         animatePopupVisibility(!sectionName.isEmpty());
-        updatePopupY(lastY);
         mLastTouchY = boundedY;
         setThumbOffsetY((int) mLastTouchY);
     }
@@ -299,11 +299,14 @@
 
         canvas.translate(0, mThumbOffsetY);
         halfW += mThumbPadding;
-        float r = mWidth + mThumbPadding + mThumbPadding;
+        float r = getScrollThumbRadius();
         canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint);
         canvas.restoreToCount(saveCount);
     }
 
+    private float getScrollThumbRadius() {
+        return mWidth + mThumbPadding + mThumbPadding;
+    }
 
     /**
      * Animates the width of the scrollbar.
@@ -352,11 +355,15 @@
     }
 
     private void updatePopupY(int lastTouchY) {
+        if (!mPopupVisible) {
+            return;
+        }
         int height = mPopupView.getHeight();
-        float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height)
-                + mRv.getScrollBarTop();
-        top = Utilities.boundToRange(top,
-                mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
+        // Aligns the rounded corner of the pop up with the top of the thumb.
+        float top = mRv.getScrollBarTop() + lastTouchY + (getScrollThumbRadius() / 2f)
+                - (height / 2f);
+        top = Utilities.boundToRange(top, 0,
+                getTop() + mRv.getScrollBarTop() + mRv.getScrollbarTrackHeight() - height);
         mPopupView.setTranslationY(top);
     }
 
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 74ab14f..8ea9bd4 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.LauncherIcons;
 
 /**
  * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
index 7883442..6808859 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
@@ -45,7 +45,7 @@
 import android.util.Pair;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.graphics.ColorExtractor;
+import com.android.launcher3.icons.ColorExtractor;
 
 import java.io.IOException;
 import java.util.ArrayList;
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 59b2da0..1e56439 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -11,7 +11,6 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
-import android.content.pm.LauncherActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -24,6 +23,7 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.icons.CachingLogic;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -32,7 +32,7 @@
 import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.TestLauncherProvider;
@@ -203,9 +203,10 @@
         }
 
         @Override
-        protected CacheEntry cacheLocked(
+        protected <T> CacheEntry cacheLocked(
                 @NonNull ComponentName componentName,
-                @NonNull Provider<LauncherActivityInfo> infoProvider,
+                @NonNull Provider<T> infoProvider,
+                @NonNull CachingLogic<T> cachingLogic,
                 UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
             if (entry == null) {
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 42a2764..a21f861 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -17,7 +17,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.icons.BitmapInfo;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
index 73b6daf..b600473 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
@@ -19,7 +19,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 import android.util.Log;
-import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.testcomponent.TouchEventGenerator;
@@ -32,6 +31,7 @@
 
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -51,25 +51,28 @@
     @Mock
     private SwipeDetector.Listener mMockListener;
 
+    @Mock
+    private ViewConfiguration mMockConfig;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mGenerator = new TouchEventGenerator(new TouchEventGenerator.Listener() {
-            @Override
-            public void onTouchEvent(MotionEvent event) {
-                mDetector.onTouchEvent(event);
-            }
-        });
+        mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev));
+        ViewConfiguration orgConfig = ViewConfiguration
+                .get(InstrumentationRegistry.getTargetContext());
+        doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
+                .getScaledMaximumFlingVelocity();
 
-        mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.VERTICAL);
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL);
         mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
-        mTouchSlop = ViewConfiguration.get(InstrumentationRegistry.getTargetContext())
-                .getScaledTouchSlop();
+        mTouchSlop = orgConfig.getScaledTouchSlop();
+        doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
+
         L("mTouchSlop=", mTouchSlop);
     }
 
     @Test
-    public void testDragStart_vertical() throws Exception {
+    public void testDragStart_vertical() {
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
@@ -77,7 +80,7 @@
     }
 
     @Test
-    public void testDragStart_failed() throws Exception {
+    public void testDragStart_failed() {
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
@@ -85,8 +88,8 @@
     }
 
     @Test
-    public void testDragStart_horizontal() throws Exception {
-        mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.HORIZONTAL);
+    public void testDragStart_horizontal() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL);
         mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
 
         mGenerator.put(0, 100, 100);
@@ -96,15 +99,15 @@
     }
 
     @Test
-    public void testDrag() throws Exception {
+    public void testDrag() {
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDrag(anyFloat(), anyFloat());
+        verify(mMockListener).onDrag(anyFloat());
     }
 
     @Test
-    public void testDragEnd() throws Exception {
+    public void testDragEnd() {
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         mGenerator.move(0, 100, 100 + mTouchSlop * 2);
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index ba7d9c5..75efea4 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -82,12 +82,17 @@
     protected final LauncherInstrumentation mLauncher;
     protected Context mTargetContext;
     protected String mTargetPackage;
+    protected final boolean mIsInLauncherProcess;
 
     private static final String TAG = "AbstractLauncherUiTest";
 
     protected AbstractLauncherUiTest() {
-        mDevice = UiDevice.getInstance(getInstrumentation());
-        mLauncher = new LauncherInstrumentation(getInstrumentation());
+        final Instrumentation instrumentation = getInstrumentation();
+        mDevice = UiDevice.getInstance(instrumentation);
+        mLauncher = new LauncherInstrumentation(instrumentation);
+
+        mIsInLauncherProcess = instrumentation.getTargetContext().getPackageName().equals(
+                mDevice.getLauncherPackageName());
     }
 
     @Rule
@@ -261,6 +266,7 @@
     }
 
     protected <T> T getFromLauncher(Function<Launcher, T> f) {
+        if (!mIsInLauncherProcess) return null;
         return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
     }
 
@@ -287,6 +293,7 @@
     // flakiness.
     protected boolean waitForLauncherCondition(
             Function<Launcher, Boolean> condition, long timeout) {
+        if (!mIsInLauncherProcess) return true;
         return Wait.atMost(() -> getFromLauncher(condition), timeout);
     }
 
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 23560e8..a50a8b1 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -42,7 +42,8 @@
         LauncherActivityInfo settingsApp = getSettingsApp();
 
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
+        mDevice.waitForIdle();
 
         // Open all apps and wait for load complete.
         final UiObject2 appsContainer = openAllApps();