Merge branch 'ub-launcher3-master' into launcher3merge2018-10-03

Test: will rely on presubmit
Bug: 116879058
Change-Id: Ifa5b85f0ce5099525d05feee265c7eb54823f560
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 17ff858..7ff664d 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java
index 4d56786..35f46cf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java
@@ -84,7 +84,8 @@
         }
         if (action == ACTION_MOVE) {
             float dy = ev.getY() - mTranslator.getDownY();
-            if (dy > mTouchSlop) {
+            float dx = ev.getX() - mTranslator.getDownX();
+            if (dy > mTouchSlop && dy > Math.abs(dx)) {
                 mTranslator.dispatchDownEvents(ev);
                 mTranslator.processMotionEvent(ev);
                 return true;
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/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index f39a007..206c8be 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -91,7 +91,7 @@
      * Updates the UI to indicate quick interaction.
      */
     void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo,
-            boolean activityVisible);
+            boolean activityVisible, TouchInteractionLog touchInteractionLog);
 
     float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
             Context context);
@@ -153,13 +153,14 @@
 
         @Override
         public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo,
-                boolean activityVisible) {
+                boolean activityVisible, TouchInteractionLog touchInteractionLog) {
             LauncherState fromState = activity.getStateManager().getState();
             activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
 
             QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
                     .getQuickScrubController();
-            controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this);
+            controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this,
+                    touchInteractionLog);
 
             if (!activityVisible) {
                 // For the duration of the gesture, lock the screen orientation to ensure that we
@@ -425,14 +426,14 @@
 
         @Override
         public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo,
-                boolean activityVisible) {
+                boolean activityVisible, TouchInteractionLog touchInteractionLog) {
             QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
                     .getQuickScrubController();
 
             // TODO: match user is as well
             boolean startingFromHome = !activityVisible &&
                     (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent));
-            controller.onQuickScrubStart(startingFromHome, this);
+            controller.onQuickScrubStart(startingFromHome, this, touchInteractionLog);
             if (activityVisible) {
                 mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub,
                         OVERVIEW_TRANSITION_MS);
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
index 8e83bd0..5996df7 100644
--- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -49,8 +49,8 @@
     }
 
     @Override
-    public void updateTouchTracking(int interactionType) {
-        mTarget.updateTouchTracking(interactionType);
+    public void onQuickScrubStart() {
+        mTarget.onQuickScrubStart();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index f73be6c..8a598a3 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -146,7 +146,7 @@
                 if (event.getActionMasked() == ACTION_VIRTUAL) {
                     switch (event.getAction()) {
                         case ACTION_QUICK_SCRUB_START:
-                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
+                            mConsumer.onQuickScrubStart();
                             break;
                         case ACTION_QUICK_SCRUB_PROGRESS:
                             mConsumer.onQuickScrubProgress(event.getX());
@@ -162,7 +162,7 @@
                             break;
                         case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
                             mConsumer.onShowOverviewFromAltTab();
-                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
+                            mConsumer.onQuickScrubStart();
                             break;
                         case ACTION_QUICK_STEP:
                             mConsumer.onQuickStep(event);
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/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 0e811f7..4417a3d 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -79,6 +79,7 @@
     private final Choreographer mBackgroundThreadChoreographer;
     private final OverviewCallbacks mOverviewCallbacks;
     private final TaskOverlayFactory mTaskOverlayFactory;
+    private final TouchInteractionLog mTouchInteractionLog;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -100,7 +101,8 @@
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
-            TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker) {
+            TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker,
+            TouchInteractionLog touchInteractionLog) {
         super(base);
 
         mRunningTask = runningTaskInfo;
@@ -113,6 +115,8 @@
         mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
         mOverviewCallbacks = overviewCallbacks;
         mTaskOverlayFactory = taskOverlayFactory;
+        mTouchInteractionLog = touchInteractionLog;
+        mTouchInteractionLog.setTouchConsumer(this);
     }
 
     @Override
@@ -125,6 +129,7 @@
         if (mVelocityTracker == null) {
             return;
         }
+        mTouchInteractionLog.addMotionEvent(ev);
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
                 TraceHelper.beginSection("TouchInt");
@@ -215,10 +220,13 @@
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
+        mTouchInteractionLog.startRecentsAnimation();
+
         // Create the shared handler
         RecentsAnimationState animationState = new RecentsAnimationState();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper);
+                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
+                mTouchInteractionLog);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -315,7 +323,13 @@
     }
 
     @Override
-    public void updateTouchTracking(int interactionType) {
+    public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
+        mEventQueue = queue;
+        return mBackgroundThreadChoreographer;
+    }
+
+    @Override
+    public void onQuickScrubStart() {
         if (!mPassedInitialSlop && mIsDeferredDownTarget && mInteractionHandler == null) {
             // If we deferred starting the window animation on touch down, then
             // start tracking now
@@ -323,20 +337,16 @@
             mPassedInitialSlop = true;
         }
 
+        mTouchInteractionLog.startQuickScrub();
         if (mInteractionHandler != null) {
-            mInteractionHandler.updateInteractionType(interactionType);
+            mInteractionHandler.onQuickScrubStart();
         }
         notifyGestureStarted();
     }
 
     @Override
-    public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
-        mEventQueue = queue;
-        return mBackgroundThreadChoreographer;
-    }
-
-    @Override
     public void onQuickScrubEnd() {
+        mTouchInteractionLog.endQuickScrub("onQuickScrubEnd");
         if (mInteractionHandler != null) {
             mInteractionHandler.onQuickScrubEnd();
         }
@@ -344,6 +354,7 @@
 
     @Override
     public void onQuickScrubProgress(float progress) {
+        mTouchInteractionLog.setQuickScrubProgress(progress);
         if (mInteractionHandler != null) {
             mInteractionHandler.onQuickScrubProgress(progress);
         }
@@ -351,6 +362,7 @@
 
     @Override
     public void onQuickStep(MotionEvent ev) {
+        mTouchInteractionLog.startQuickStep();
         if (mIsDeferredDownTarget) {
             // Deferred gesture, start the animation and gesture tracking once we pass the actual
             // touch slop
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/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index cbc7a67..3420767 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -67,8 +67,10 @@
     private int mQuickScrubSection;
     private boolean mStartedFromHome;
     private boolean mFinishedTransitionToQuickScrub;
+    private int mLaunchingTaskId;
     private Runnable mOnFinishedTransitionToQuickScrubRunnable;
     private ActivityControlHelper mActivityControlHelper;
+    private TouchInteractionLog mTouchInteractionLog;
 
     public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
         mActivity = activity;
@@ -79,13 +81,15 @@
         }
     }
 
-    public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper) {
+    public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper,
+            TouchInteractionLog touchInteractionLog) {
         prepareQuickScrub(TAG);
         mInQuickScrub = true;
         mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mFinishedTransitionToQuickScrub = false;
         mActivityControlHelper = controlHelper;
+        mTouchInteractionLog = touchInteractionLog;
 
         snapToNextTaskIfAvailable();
         mActivity.getUserEventDispatcher().resetActionDurationMillis();
@@ -101,7 +105,10 @@
             TaskView taskView = mRecentsView.getTaskViewAt(page);
             if (taskView != null) {
                 mWaitingForTaskLaunch = true;
+                mTouchInteractionLog.launchTaskStart();
+                mLaunchingTaskId = taskView.getTask().key.id;
                 taskView.launchTask(true, (result) -> {
+                    mTouchInteractionLog.launchTaskEnd(result);
                     if (!result) {
                         taskView.notifyTaskLaunchFailed(TAG);
                         breakOutOfQuickScrub();
@@ -141,6 +148,7 @@
         mActivityControlHelper = null;
         mOnFinishedTransitionToQuickScrubRunnable = null;
         mRecentsView.setNextPageSwitchRunnable(null);
+        mLaunchingTaskId = 0;
     }
 
     /**
@@ -206,6 +214,18 @@
         }
     }
 
+    public void onTaskRemoved(int taskId) {
+        if (mLaunchingTaskId == taskId) {
+            // The task has been removed mid-launch, break out of quickscrub and return the user
+            // to where they were before (and notify the launch failed)
+            TaskView taskView = mRecentsView.getTaskView(taskId);
+            if (taskView != null) {
+                taskView.notifyTaskLaunchFailed(TAG);
+            }
+            breakOutOfQuickScrub();
+        }
+    }
+
     public void snapToNextTaskIfAvailable() {
         if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
             int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 646fc57..225d29b 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -43,7 +43,7 @@
 
     default void reset() { }
 
-    default void updateTouchTracking(@InteractionType int interactionType) { }
+    default void onQuickScrubStart() { }
 
     default void onQuickScrubEnd() { }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/src/com/android/quickstep/TouchInteractionLog.java
new file mode 100644
index 0000000..053efbb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TouchInteractionLog.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.quickstep;
+
+import android.view.MotionEvent;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.LinkedList;
+
+/**
+ * Keeps track of debugging logs for a particular quickstep/scrub gesture.
+ */
+public class TouchInteractionLog {
+
+    // The number of gestures to log
+    private static final int MAX_NUM_LOG_GESTURES = 5;
+
+    private final Calendar mCalendar = Calendar.getInstance();
+    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS");
+    private final LinkedList<ArrayList<String>> mGestureLogs = new LinkedList<>();
+
+    public void prepareForNewGesture() {
+        mGestureLogs.add(new ArrayList<>());
+        while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) {
+            mGestureLogs.pop();
+        }
+        getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]");
+    }
+
+    public void setTouchConsumer(TouchConsumer consumer) {
+        getCurrentLog().add("tc=" + consumer.getClass().getSimpleName());
+    }
+
+    public void addMotionEvent(MotionEvent event) {
+        getCurrentLog().add("ev=" + event.getActionMasked());
+    }
+
+    public void startQuickStep() {
+        getCurrentLog().add("qstStart");
+    }
+
+    public void startQuickScrub() {
+        getCurrentLog().add("qsStart");
+    }
+
+    public void setQuickScrubProgress(float progress) {
+        getCurrentLog().add("qsP=" + progress);
+    }
+
+    public void endQuickScrub(String reason) {
+        getCurrentLog().add("qsEnd=" + reason);
+    }
+
+    public void startRecentsAnimation() {
+        getCurrentLog().add("raStart");
+    }
+
+    public void startRecentsAnimationCallback(int numTargets) {
+        getCurrentLog().add("raStartCb=" + numTargets);
+    }
+
+    public void cancelRecentsAnimation() {
+        getCurrentLog().add("raCancel");
+    }
+
+    public void finishRecentsAnimation(boolean toHome) {
+        getCurrentLog().add("raFinish=" + toHome);
+    }
+
+    public void launchTaskStart() {
+        getCurrentLog().add("launchStart");
+    }
+
+    public void launchTaskEnd(boolean result) {
+        getCurrentLog().add("launchEnd=" + result);
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("TouchInteractionLog {");
+        for (ArrayList<String> gesture : mGestureLogs) {
+            pw.print("    ");
+            for (String log : gesture) {
+                pw.print(log + " ");
+            }
+            pw.println();
+        }
+        pw.println("}");
+    }
+
+    private ArrayList<String> getCurrentLog() {
+        return mGestureLogs.getLast();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index bd79301..b9f95cc 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -52,6 +52,8 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ChoreographerCompat;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 /**
  * Service connected by system-UI for handling touch interaction.
@@ -81,6 +83,8 @@
 
         @Override
         public void onPreMotionEvent(@HitTarget int downHitTarget) {
+            mTouchInteractionLog.prepareForNewGesture();
+
             TraceHelper.beginSection("SysUiBinder");
             setupTouchConsumer(downHitTarget);
             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
@@ -179,6 +183,7 @@
     private OverviewInteractionState mOverviewInteractionState;
     private OverviewCallbacks mOverviewCallbacks;
     private TaskOverlayFactory mTaskOverlayFactory;
+    private TouchInteractionLog mTouchInteractionLog;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -196,6 +201,7 @@
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.get(this);
+        mTouchInteractionLog = new TouchInteractionLog();
 
         sConnected = true;
 
@@ -240,7 +246,7 @@
         } else if (forceToLauncher ||
                 runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) {
             return OverviewTouchConsumer.newInstance(
-                    mOverviewCommandHelper.getActivityControlHelper(), false);
+                    mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog);
         } else {
             if (tracker == null) {
                 tracker = VelocityTracker.obtain();
@@ -249,7 +255,7 @@
                             mOverviewCommandHelper.overviewIntent,
                             mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
                             mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
-                            mTaskOverlayFactory, tracker);
+                            mTaskOverlayFactory, tracker, mTouchInteractionLog);
         }
     }
 
@@ -262,6 +268,11 @@
                 mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance());
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mTouchInteractionLog.dump(pw);
+    }
+
     public static class OverviewTouchConsumer<T extends BaseDraggingActivity>
             implements TouchConsumer {
 
@@ -272,6 +283,7 @@
         private final PointF mDownPos = new PointF();
         private final int mTouchSlop;
         private final QuickScrubController mQuickScrubController;
+        private final TouchInteractionLog mTouchInteractionLog;
 
         private final boolean mStartingInActivityBounds;
 
@@ -283,7 +295,7 @@
         private boolean mEndPending = false;
 
         OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
-                boolean startingInActivityBounds) {
+                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
             mActivityHelper = activityHelper;
             mActivity = activity;
             mTarget = activity.getDragLayer();
@@ -292,6 +304,8 @@
 
             mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
                     .getQuickScrubController();
+            mTouchInteractionLog = touchInteractionLog;
+            mTouchInteractionLog.setTouchConsumer(this);
         }
 
         @Override
@@ -299,6 +313,7 @@
             if (mInvalidated) {
                 return;
             }
+            mTouchInteractionLog.addMotionEvent(ev);
             int action = ev.getActionMasked();
             if (action == ACTION_DOWN) {
                 if (mStartingInActivityBounds) {
@@ -372,44 +387,48 @@
             OverviewCallbacks.get(mActivity).closeAllWindows();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            mTouchInteractionLog.startQuickStep();
         }
 
         @Override
-        public void updateTouchTracking(int interactionType) {
+        public void onQuickScrubStart() {
             if (mInvalidated) {
                 return;
             }
-            if (interactionType == INTERACTION_QUICK_SCRUB) {
+            mTouchInteractionLog.startQuickScrub();
+            if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+                mInvalidated = true;
+                mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
+                return;
+            }
+            OverviewCallbacks.get(mActivity).closeAllWindows();
+            ActivityManagerWrapper.getInstance()
+                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
+            mStartPending = true;
+            Runnable action = () -> {
                 if (!mQuickScrubController.prepareQuickScrub(TAG)) {
                     mInvalidated = true;
+                    mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
                     return;
                 }
-                OverviewCallbacks.get(mActivity).closeAllWindows();
-                ActivityManagerWrapper.getInstance()
-                        .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                mActivityHelper.onQuickInteractionStart(mActivity, null, true,
+                        mTouchInteractionLog);
+                mQuickScrubController.onQuickScrubProgress(mLastProgress);
+                mStartPending = false;
 
-                mStartPending = true;
-                Runnable action = () -> {
-                    if (!mQuickScrubController.prepareQuickScrub(TAG)) {
-                        mInvalidated = true;
-                        return;
-                    }
-                    mActivityHelper.onQuickInteractionStart(mActivity, null, true);
-                    mQuickScrubController.onQuickScrubProgress(mLastProgress);
-                    mStartPending = false;
+                if (mEndPending) {
+                    mQuickScrubController.onQuickScrubEnd();
+                    mEndPending = false;
+                }
+            };
 
-                    if (mEndPending) {
-                        mQuickScrubController.onQuickScrubEnd();
-                        mEndPending = false;
-                    }
-                };
-
-                mActivityHelper.executeOnWindowAvailable(mActivity, action);
-            }
+            mActivityHelper.executeOnWindowAvailable(mActivity, action);
         }
 
         @Override
         public void onQuickScrubEnd() {
+            mTouchInteractionLog.endQuickScrub("onQuickScrubEnd");
             if (mInvalidated) {
                 return;
             }
@@ -422,6 +441,7 @@
 
         @Override
         public void onQuickScrubProgress(float progress) {
+            mTouchInteractionLog.setQuickScrubProgress(progress);
             mLastProgress = progress;
             if (mInvalidated || mStartPending) {
                 return;
@@ -429,13 +449,14 @@
             mQuickScrubController.onQuickScrubProgress(progress);
         }
 
-        public static TouchConsumer newInstance(
-                ActivityControlHelper activityHelper, boolean startingInActivityBounds) {
+        public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
+                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
             BaseDraggingActivity activity = activityHelper.getCreatedActivity();
             if (activity == null) {
                 return TouchConsumer.NO_OP;
             }
-            return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds);
+            return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds,
+                    touchInteractionLog);
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index a2e9af8..9991552 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -198,6 +198,7 @@
     private final Context mContext;
     private final ActivityControlHelper<T> mActivityControlHelper;
     private final ActivityInitListener mActivityInitListener;
+    private final TouchInteractionLog mTouchInteractionLog;
 
     private final int mRunningTaskId;
     private final RunningTaskInfo mRunningTaskInfo;
@@ -239,7 +240,8 @@
     private Bundle mAssistData;
 
     WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
-            long touchTimeMs, ActivityControlHelper<T> controller) {
+            long touchTimeMs, ActivityControlHelper<T> controller,
+            TouchInteractionLog touchInteractionLog) {
         this.id = id;
         mContext = context;
         mRunningTaskInfo = runningTaskInfo;
@@ -248,6 +250,7 @@
         mActivityControlHelper = controller;
         mActivityInitListener = mActivityControlHelper
                 .createActivityInitListener(this::onActivityInit);
+        mTouchInteractionLog = touchInteractionLog;
 
         initStateCallbacks();
     }
@@ -319,7 +322,7 @@
                 this::notifyTransitionCancelled);
 
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START
-                        | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStart);
+                        | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStartUi);
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START
                 | STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub);
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_CURRENT_TASK_FINISHED
@@ -503,25 +506,6 @@
                 .getHighResThumbnailLoader().setVisible(true);
     }
 
-    public void updateInteractionType(@InteractionType int interactionType) {
-        if (mInteractionType != INTERACTION_NORMAL) {
-            throw new IllegalArgumentException(
-                    "Can't change interaction type from " + mInteractionType);
-        }
-        if (interactionType != INTERACTION_QUICK_SCRUB) {
-            throw new IllegalArgumentException(
-                    "Can't change interaction type to " + interactionType);
-        }
-        mInteractionType = interactionType;
-        mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub);
-
-        setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
-
-        // Start the window animation without waiting for launcher.
-        animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
-                true /* goingToHome */);
-    }
-
     private void shiftAnimationDestinationForQuickscrub() {
         TransformedRect tempRect = new TransformedRect();
         mActivityControlHelper
@@ -669,6 +653,7 @@
         initTransitionEndpoints(dp);
 
         mRecentsAnimationWrapper.setController(controller, targets);
+        mTouchInteractionLog.startRecentsAnimationCallback(targets.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
@@ -678,6 +663,7 @@
         mRecentsAnimationWrapper.setController(null, null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+        mTouchInteractionLog.cancelRecentsAnimation();
     }
 
     public void onGestureStarted() {
@@ -729,7 +715,8 @@
         // Hide the task view, if not already hidden
         setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
 
-        return OverviewTouchConsumer.newInstance(mActivityControlHelper, true);
+        return OverviewTouchConsumer.newInstance(mActivityControlHelper, true,
+                mTouchInteractionLog);
     }
 
     private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
@@ -854,6 +841,7 @@
     @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toHome */, null);
+        mTouchInteractionLog.finishRecentsAnimation(false);
     }
 
     public void reset() {
@@ -950,6 +938,7 @@
             mRecentsAnimationWrapper.finish(true /* toHome */,
                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
+        mTouchInteractionLog.finishRecentsAnimation(true);
     }
 
     private void setupLauncherUiAfterSwipeUpAnimation() {
@@ -969,7 +958,22 @@
         reset();
     }
 
-    private void onQuickScrubStart() {
+    public void onQuickScrubStart() {
+        if (mInteractionType != INTERACTION_NORMAL) {
+            throw new IllegalArgumentException(
+                    "Can't change interaction type from " + mInteractionType);
+        }
+        mInteractionType = INTERACTION_QUICK_SCRUB;
+        mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub);
+
+        setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
+
+        // Start the window animation without waiting for launcher.
+        animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
+                true /* goingToHome */);
+    }
+
+    private void onQuickScrubStartUi() {
         if (!mQuickScrubController.prepareQuickScrub(TAG)) {
             mQuickScrubBlocked = true;
             setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED);
@@ -980,7 +984,8 @@
             mLauncherTransitionController = null;
         }
 
-        mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false);
+        mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false,
+                mTouchInteractionLog);
 
         // Inform the last progress in case we skipped before.
         mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f3dec9a..8ad3cee 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -183,6 +183,10 @@
             if (!mHandleTaskStackChanges) {
                 return;
             }
+
+            // Notify the quick scrub controller that a particular task has been removed
+            mQuickScrubController.onTaskRemoved(taskId);
+
             BackgroundExecutor.get().submit(() -> {
                 TaskView taskView = getTaskView(taskId);
                 if (taskView == null) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ee542d5..a0615f5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -57,6 +57,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
+import com.android.systemui.shared.system.ActivityOptionsCompat;
 import java.util.function.Consumer;
 
 /**
@@ -204,11 +205,26 @@
             if (animate) {
                 opts = ((BaseDraggingActivity) fromContext(getContext()))
                         .getActivityLaunchOptions(this);
+                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
+                        opts, resultCallback, resultCallbackHandler);
             } else {
-                opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0);
+                opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> {
+                    if (resultCallback != null) {
+                        // Only post the animation start after the system has indicated that the
+                        // transition has started
+                        resultCallbackHandler.post(() -> resultCallback.accept(true));
+                    }
+                }, resultCallbackHandler);
+                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
+                        opts, (success) -> {
+                            if (resultCallback != null && !success) {
+                                // If the call to start activity failed, then post the result
+                                // immediately, otherwise, wait for the animation start callback
+                                // from the activity options above
+                                resultCallbackHandler.post(() -> resultCallback.accept(false));
+                            }
+                        }, resultCallbackHandler);
             }
-            ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
-                    opts, resultCallback, resultCallbackHandler);
         }
     }
 
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/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 80758c9..b2b05b1 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -2,11 +2,15 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Parcel;
+import android.os.UserHandle;
+
+import com.android.launcher3.icons.ComponentWithLabel;
 
 /**
  * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
@@ -14,7 +18,8 @@
  * (who's implementation is owned by the launcher). This object represents a widget type / class,
  * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
  */
-public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
+public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
+        implements ComponentWithLabel {
 
     public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
 
@@ -102,4 +107,14 @@
             return 0;
         }
     }
- }
+
+    @Override
+    public final ComponentName getComponent() {
+        return provider;
+    }
+
+    @Override
+    public final UserHandle getUser() {
+        return getProfile();
+    }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index df1c693..5e09f8a 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;
@@ -599,6 +599,19 @@
                 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
     }
 
+    /**
+     * Called when the labels for the widgets has updated in the icon cache.
+     */
+    public void onWidgetLabelsUpdated(HashSet<String> updatedPackages, UserHandle user) {
+        enqueueModelUpdateTask(new BaseModelUpdateTask() {
+            @Override
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
+                bindUpdatedWidgets(dataModel);
+            }
+        });
+    }
+
     public void enqueueModelUpdateTask(ModelUpdateTask task) {
         task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
         runOnWorkerThread(task);
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/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index ba0ac6e..b641391 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -200,7 +200,7 @@
                 pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
             if (packageUser == null || packageUser.mPackageName
                     .equals(info.activityInfo.packageName)) {
-                result.add(new ShortcutConfigActivityInfoVL(info.activityInfo, pm));
+                result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
             }
         }
         return result;
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/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
index d260e24..76eec6d 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -40,7 +41,7 @@
 /**
  * Wrapper class for representing a shortcut configure activity.
  */
-public abstract class ShortcutConfigActivityInfo {
+public abstract class ShortcutConfigActivityInfo implements ComponentWithLabel {
 
     private static final String TAG = "SCActivityInfo";
 
@@ -52,10 +53,12 @@
         mUser = user;
     }
 
+    @Override
     public ComponentName getComponent() {
         return mCn;
     }
 
+    @Override
     public UserHandle getUser() {
         return mUser;
     }
@@ -64,8 +67,6 @@
         return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
     }
 
-    public abstract CharSequence getLabel();
-
     public abstract Drawable getFullResIcon(IconCache cache);
 
     /**
@@ -94,8 +95,8 @@
     }
 
     /**
-     * Returns true if various properties ({@link #getLabel()}, {@link #getFullResIcon}) can
-     * be safely persisted.
+     * Returns true if various properties ({@link #getLabel(PackageManager)},
+     * {@link #getFullResIcon}) can be safely persisted.
      */
     public boolean isPersistable() {
         return true;
@@ -104,18 +105,15 @@
     static class ShortcutConfigActivityInfoVL extends ShortcutConfigActivityInfo {
 
         private final ActivityInfo mInfo;
-        private final PackageManager mPm;
 
-
-        public ShortcutConfigActivityInfoVL(ActivityInfo info, PackageManager pm) {
+        public ShortcutConfigActivityInfoVL(ActivityInfo info) {
             super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
             mInfo = info;
-            mPm = pm;
         }
 
         @Override
-        public CharSequence getLabel() {
-            return mInfo.loadLabel(mPm);
+        public CharSequence getLabel(PackageManager pm) {
+            return mInfo.loadLabel(pm);
         }
 
         @Override
@@ -135,7 +133,7 @@
         }
 
         @Override
-        public CharSequence getLabel() {
+        public CharSequence getLabel(PackageManager pm) {
             return mInfo.getLabel();
         }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 278eefd..daf7dc6 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -32,6 +32,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.view.MotionEvent;
@@ -46,6 +47,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -54,6 +56,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -78,7 +82,6 @@
     // Widget request specific options.
     private LauncherAppWidgetHost mAppWidgetHost;
     private AppWidgetManagerCompat mAppWidgetManager;
-    private PendingAddWidgetInfo mPendingWidgetInfo;
     private int mPendingBindWidgetId;
     private Bundle mWidgetOptions;
 
@@ -189,10 +192,9 @@
     private void setupShortcut() {
         PinShortcutRequestActivityInfo shortcutInfo =
                 new PinShortcutRequestActivityInfo(mRequest, this);
-        WidgetItem item = new WidgetItem(shortcutInfo);
         mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
-        mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
-        mWidgetCell.ensurePreview();
+        applyWidgetItemAsync(
+                () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
     }
 
     private boolean setupWidget() {
@@ -207,18 +209,32 @@
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
         mAppWidgetHost = new LauncherAppWidgetHost(this);
 
-        mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo);
-        mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
-        mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
-        mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo);
+        PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(widgetInfo);
+        pendingInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
+        pendingInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
+        mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
+        mWidgetCell.getWidgetView().setTag(pendingInfo);
 
-        WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp);
-        mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo);
-        mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
-        mWidgetCell.ensurePreview();
+        applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache()));
         return true;
     }
 
+    private void applyWidgetItemAsync(final Provider<WidgetItem> itemProvider) {
+        new AsyncTask<Void, Void, WidgetItem>() {
+            @Override
+            protected WidgetItem doInBackground(Void... voids) {
+                return itemProvider.get();
+            }
+
+            @Override
+            protected void onPostExecute(WidgetItem item) {
+                mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
+                mWidgetCell.ensurePreview();
+            }
+        }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper()));
+        // TODO: Create a worker looper executor and reuse that everywhere.
+    }
+
     /**
      * Called when the cancel button is clicked.
      */
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/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index bd919bc..64655cc 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.PinItemRequest;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -65,7 +66,7 @@
     }
 
     @Override
-    public CharSequence getLabel() {
+    public CharSequence getLabel(PackageManager pm) {
         return mInfo.getShortLabel();
     }
 
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..6433103
--- /dev/null
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -0,0 +1,538 @@
+/*
+ * 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, mPackageManager);
+        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+        mCache.put(key, entry);
+
+        ContentValues values = newContentValues(entry, 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 UserHandle user,
+            @NonNull Provider<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
+            boolean usePackageIcon, boolean useLowResIcon) {
+        return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon,
+                useLowResIcon, true);
+    }
+
+    protected <T> CacheEntry cacheLocked(
+            @NonNull ComponentName componentName, @NonNull UserHandle user,
+            @NonNull Provider<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
+            boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) {
+        Preconditions.assertWorkerThread();
+        ComponentKey cacheKey = new ComponentKey(componentName, user);
+        CacheEntry entry = mCache.get(cacheKey);
+        if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
+            entry = new CacheEntry();
+            if (addToMemCache) {
+                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, mPackageManager);
+                    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, 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(BitmapInfo bitmapInfo, String label, String packageName) {
+        ContentValues values = new ContentValues();
+        values.put(IconDB.COLUMN_ICON,
+                bitmapInfo.isLowRes() ? null : Utilities.flattenBitmap(bitmapInfo.icon));
+        values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
+
+        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..24186ef
--- /dev/null
+++ b/src/com/android/launcher3/icons/CachingLogic.java
@@ -0,0 +1,87 @@
+/*
+ * 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.content.pm.PackageManager;
+import android.os.UserHandle;
+
+public interface CachingLogic<T> {
+
+    ComponentName getComponent(T object);
+
+    UserHandle getUser(T object);
+
+    CharSequence getLabel(T object, PackageManager pm);
+
+    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, PackageManager pm) {
+            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();
+        }
+    };
+
+    CachingLogic<ComponentWithLabel> COMPONENT_WITH_LABEL =
+            new CachingLogic<ComponentWithLabel>() {
+
+        @Override
+        public ComponentName getComponent(ComponentWithLabel object) {
+            return object.getComponent();
+        }
+
+        @Override
+        public UserHandle getUser(ComponentWithLabel object) {
+            return object.getUser();
+        }
+
+        @Override
+        public CharSequence getLabel(ComponentWithLabel object, PackageManager pm) {
+            return object.getLabel(pm);
+        }
+
+        @Override
+        public void loadIcon(Context context, BaseIconCache cache,
+                ComponentWithLabel object, BitmapInfo target) {
+            // Do not load icon.
+            target.icon = BitmapInfo.LOW_RES_ICON;
+        }
+    };
+}
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/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
new file mode 100644
index 0000000..2badb4c
--- /dev/null
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -0,0 +1,29 @@
+/*
+ * 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.pm.PackageManager;
+import android.os.UserHandle;
+
+public interface ComponentWithLabel {
+
+    ComponentName getComponent();
+
+    UserHandle getUser();
+
+    CharSequence getLabel(PackageManager pm);
+}
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..4349455 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -16,209 +16,43 @@
 
 package com.android.launcher3.icons;
 
-import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
+import static com.android.launcher3.icons.CachingLogic.COMPONENT_WITH_LABEL;
+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 +65,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 +74,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,8 +115,8 @@
      */
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
-                Provider.<LauncherActivityInfo>of(null),
-                application.user, false, application.usingLowResIcon());
+                application.user, Provider.of(null), LAUNCHER_ACTIVITY_INFO,
+                false, application.usingLowResIcon());
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
             applyCacheEntry(entry, application);
         }
@@ -377,6 +149,13 @@
         }
     }
 
+    public synchronized String getTitleNoCache(ComponentWithLabel info) {
+        CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), Provider.of(info),
+                COMPONENT_WITH_LABEL, false /* usePackageIcon */, true /* useLowResIcon */,
+                false /* addToMemCache */);
+        return Utilities.trim(entry.title);
+    }
+
     /**
      * Fill in {@param shortcutInfo} with the icon and label for {@param info}
      */
@@ -384,239 +163,12 @@
             @NonNull ItemInfoWithIcon infoInOut,
             @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
-        CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
-                infoInOut.user, usePkgIcon, useLowResIcon);
+        CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
+                activityInfoProvider,
+                LAUNCHER_ACTIVITY_INFO, 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 +193,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..8b3af01 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;
@@ -26,15 +25,17 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
-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.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.Stack;
 
@@ -45,13 +46,29 @@
 
     private static final String TAG = "IconCacheUpdateHandler";
 
+    /**
+     * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}.
+     * This mode is used for the first run.
+     */
+    private static final boolean MODE_SET_INVALID_ITEMS = true;
+
+    /**
+     * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all
+     * subsequent runs, which essentially acts as set-union of all valid items.
+     */
+    private static final boolean MODE_CLEAR_VALID_ITEMS = false;
+
     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) {
+    private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
+    private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
+
+    IconCacheUpdateHandler(BaseIconCache cache) {
         mIconCache = cache;
 
         mPkgInfoMap = new HashMap<>();
@@ -68,9 +85,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,25 +95,44 @@
      * the DB and are updated.
      * @return The set of packages for which icons have updated.
      */
-    public void updateIcons(List<LauncherActivityInfo> apps) {
-        if (apps.isEmpty()) {
-            return;
+    public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
+            OnUpdateCallback onUpdateCallback) {
+        // Filter the list per user
+        HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
+        int count = apps.size();
+        for (int i = 0; i < count; i++) {
+            T app = apps.get(i);
+            UserHandle userHandle = cachingLogic.getUser(app);
+            HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
+            if (componentMap == null) {
+                componentMap = new HashMap<>();
+                userComponentMap.put(userHandle, componentMap);
+            }
+            componentMap.put(cachingLogic.getComponent(app), app);
         }
-        UserHandle user = apps.get(0).getUser();
 
+        for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
+            updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
+        }
+
+        // From now on, clear every valid item from the global valid map.
+        mFilterMode = MODE_CLEAR_VALID_ITEMS;
+    }
+
+    /**
+     * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+     * the DB and are updated.
+     * @return The set of packages for which icons have updated.
+     */
+    private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
+            CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
         Set<String> ignorePackages = mPackagesToIgnore.get(user);
         if (ignorePackages == null) {
             ignorePackages = Collections.emptySet();
         }
-
         long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user);
-        HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
-        for (LauncherActivityInfo app : apps) {
-            componentMap.put(app.getComponentName(), 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,
@@ -116,10 +151,15 @@
                 String cn = c.getString(indexComponent);
                 ComponentName component = ComponentName.unflattenFromString(cn);
                 PackageInfo info = mPkgInfoMap.get(component.getPackageName());
+
+                int rowId = c.getInt(rowIndex);
                 if (info == null) {
                     if (!ignorePackages.contains(component.getPackageName())) {
-                        mIconCache.remove(component, user);
-                        itemsToRemove.add(c.getInt(rowIndex));
+
+                        if (mFilterMode == MODE_SET_INVALID_ITEMS) {
+                            mIconCache.remove(component, user);
+                            mItemsToDelete.put(rowId, true);
+                        }
                     }
                     continue;
                 }
@@ -130,15 +170,21 @@
 
                 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))) {
+
+                    if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
+                        mItemsToDelete.put(rowId, false);
+                    }
                     continue;
                 }
                 if (app == null) {
-                    mIconCache.remove(component, user);
-                    itemsToRemove.add(c.getInt(rowIndex));
+                    if (mFilterMode == MODE_SET_INVALID_ITEMS) {
+                        mIconCache.remove(component, user);
+                        mItemsToDelete.put(rowId, true);
+                    }
                 } else {
                     appsToUpdate.add(app);
                 }
@@ -147,16 +193,28 @@
             Log.d(TAG, "Error reading icon cache", e);
             // Continue updating whatever we have read so far
         }
-        if (!itemsToRemove.isEmpty()) {
-            mIconCache.mIconDb.delete(
-                    Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
-        }
 
         // 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,
+                    onUpdateCallback).scheduleNext();
+        }
+    }
+
+    public void finish() {
+        // Commit all deletes
+        ArrayList<Integer> deleteIds = new ArrayList<>();
+        int count = mItemsToDelete.size();
+        for (int i = 0;  i < count; i++) {
+            if (mItemsToDelete.valueAt(i)) {
+                deleteIds.add(mItemsToDelete.keyAt(i));
+            }
+        }
+        if (!deleteIds.isEmpty()) {
+            mIconCache.mIconDb.delete(
+                    Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, deleteIds), null);
         }
     }
 
@@ -166,47 +224,51 @@
      * 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<>();
+        private final OnUpdateCallback mOnUpdateCallback;
 
         SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
-                Stack<LauncherActivityInfo> appsToAdd, Stack<LauncherActivityInfo> appsToUpdate) {
+                Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic,
+                OnUpdateCallback onUpdateCallback) {
             mUserHandle = userHandle;
             mUserSerial = userSerial;
             mAppsToAdd = appsToAdd;
             mAppsToUpdate = appsToUpdate;
+            mCachingLogic = cachingLogic;
+            mOnUpdateCallback = onUpdateCallback;
         }
 
         @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()) {
-                    // No more app to update. Notify model.
-                    LauncherAppState.getInstance(mIconCache.mContext).getModel()
-                            .onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
+                    // No more app to update. Notify callback.
+                    mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
                 }
 
                 // 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()) {
@@ -220,4 +282,9 @@
                     SystemClock.uptimeMillis() + 1);
         }
     }
+
+    public interface OnUpdateCallback {
+
+        void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
+    }
 }
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..e0da6b1 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,8 @@
 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.COMPONENT_WITH_LABEL;
+import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO;
 import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -43,6 +45,7 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.IconCacheUpdateHandler;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -60,7 +63,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;
@@ -183,7 +186,7 @@
 
             // second step
             TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
-            List<List<LauncherActivityInfo>> activityListPerUser = loadAllApps();
+            List<LauncherActivityInfo> allActivityList = loadAllApps();
 
             TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
             verifyNotStopped();
@@ -193,7 +196,8 @@
             TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
-            updateIconCacheForApps(updateHandler, activityListPerUser);
+            updateHandler.updateIcons(allActivityList, LAUNCHER_ACTIVITY_INFO,
+                    mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
             TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
@@ -215,12 +219,21 @@
 
             // fourth step
             TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
-            mBgDataModel.widgetsModel.update(mApp, null);
+            List<ComponentWithLabel> allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null);
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
             mResults.bindWidgets();
 
+            verifyNotStopped();
+            TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache");
+            updateHandler.updateIcons(allWidgetsList, COMPONENT_WITH_LABEL,
+                    mApp.getModel()::onWidgetLabelsUpdated);
+
+            verifyNotStopped();
+            TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update");
+            updateHandler.finish();
+
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
@@ -798,17 +811,9 @@
         updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore);
     }
 
-    private void updateIconCacheForApps(IconCacheUpdateHandler updateHandler,
-            List<List<LauncherActivityInfo>> activityListPerUser) {
-        int userCount = activityListPerUser.size();
-        for (int i = 0; i < userCount; i++) {
-            updateHandler.updateIcons(activityListPerUser.get(i));
-        }
-    }
-
-    private List<List<LauncherActivityInfo>> loadAllApps() {
+    private List<LauncherActivityInfo> loadAllApps() {
         final List<UserHandle> profiles = mUserManager.getUserProfiles();
-        List<List<LauncherActivityInfo>> activityListPerUser = new ArrayList<>();
+        List<LauncherActivityInfo> allActivityList = new ArrayList<>();
         // Clear the list of apps
         mBgAllAppsList.clear();
         for (UserHandle user : profiles) {
@@ -817,7 +822,7 @@
             // Fail if we don't have any apps
             // TODO: Fix this. Only fail for the current user.
             if (apps == null || apps.isEmpty()) {
-                return activityListPerUser;
+                return allActivityList;
             }
             boolean quietMode = mUserManager.isQuietModeEnabled(user);
             // Create the ApplicationInfos
@@ -826,7 +831,7 @@
                 // This builds the icon bitmaps.
                 mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
             }
-            activityListPerUser.add(apps);
+            allActivityList.addAll(apps);
         }
 
         if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
@@ -839,7 +844,7 @@
         }
 
         mBgAllAppsList.added = new ArrayList<>();
-        return activityListPerUser;
+        return allActivityList;
     }
 
     private void loadDeepShortcuts() {
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/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 1e96dec..e38529b 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -9,6 +9,7 @@
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
@@ -29,11 +30,11 @@
     public final String label;
     public final int spanX, spanY;
 
-    public WidgetItem(LauncherAppWidgetProviderInfo info, PackageManager pm,
-            InvariantDeviceProfile idp) {
+    public WidgetItem(LauncherAppWidgetProviderInfo info,
+            InvariantDeviceProfile idp, IconCache iconCache) {
         super(info.provider, info.getProfile());
 
-        label = Utilities.trim(info.getLabel(pm));
+        label = iconCache.getTitleNoCache(info);
         widgetInfo = info;
         activityInfo = null;
 
@@ -41,9 +42,10 @@
         spanY = Math.min(info.spanY, idp.numRows);
     }
 
-    public WidgetItem(ShortcutConfigActivityInfo info) {
+    public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
         super(info.getComponent(), info.getUser());
-        label = Utilities.trim(info.getLabel());
+        label = info.isPersistable() ? iconCache.getTitleNoCache(info) :
+                Utilities.trim(info.getLabel(pm));
         widgetInfo = null;
         activityInfo = info;
         spanX = spanY = 1;
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 82f4fe1..9a17ec6 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -11,6 +11,7 @@
 import android.util.Log;
 
 import com.android.launcher3.AppFilter;
+import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
@@ -31,7 +32,10 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import androidx.annotation.Nullable;
 
@@ -76,26 +80,32 @@
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
      */
-    public void update(LauncherAppState app, @Nullable PackageUserKey packageUser) {
+    public List<ComponentWithLabel> update(LauncherAppState app, @Nullable PackageUserKey packageUser) {
         Preconditions.assertWorkerThread();
 
         Context context = app.getContext();
         final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
+        List<ComponentWithLabel> updatedItems = new ArrayList<>();
         try {
-            PackageManager pm = context.getPackageManager();
             InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+            PackageManager pm = app.getContext().getPackageManager();
 
             // Widgets
             AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
             for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
-                widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo
-                        .fromProviderInfo(context, widgetInfo), pm, idp));
+                LauncherAppWidgetProviderInfo launcherWidgetInfo =
+                        LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
+
+                widgetsAndShortcuts.add(new WidgetItem(
+                        launcherWidgetInfo, idp, app.getIconCache()));
+                updatedItems.add(launcherWidgetInfo);
             }
 
             // Shortcuts
             for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context)
                     .getCustomShortcutActivityList(packageUser)) {
-                widgetsAndShortcuts.add(new WidgetItem(info));
+                widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
+                updatedItems.add(info);
             }
             setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
         } catch (Exception e) {
@@ -110,6 +120,7 @@
         }
 
         app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser);
+        return updatedItems;
     }
 
     private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
@@ -204,4 +215,26 @@
             iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
         }
     }
+
+    public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
+            LauncherAppState app) {
+        for (Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
+            if (packageNames.contains(entry.getKey().packageName)) {
+                ArrayList<WidgetItem> items = entry.getValue();
+                int count = items.size();
+                for (int i = 0; i < count; i++) {
+                    WidgetItem item = items.get(i);
+                    if (item.user.equals(user)) {
+                        if (item.activityInfo != null) {
+                            items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(),
+                                    app.getContext().getPackageManager()));
+                        } else {
+                            items.set(i, new WidgetItem(item.widgetInfo,
+                                    app.getInvariantDeviceProfile(), app.getIconCache()));
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
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/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
index 8a5c932..8333d32 100644
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ b/src/com/android/launcher3/touch/TouchEventTranslator.java
@@ -19,7 +19,6 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
@@ -37,13 +36,15 @@
 
     private class DownState {
         long timeStamp;
+        float downX;
         float downY;
-        public DownState(long timeStamp, float downY) {
+        public DownState(long timeStamp, float downX, float downY) {
             this.timeStamp = timeStamp;
+            this.downX = downX;
             this.downY = downY;
         }
     };
-    private final DownState ZERO = new DownState(0, 0f);
+    private final DownState ZERO = new DownState(0, 0f, 0f);
 
     private final Consumer<MotionEvent> mListener;
 
@@ -65,12 +66,16 @@
         mFingers.clear();
     }
 
+    public float getDownX() {
+        return mDownEvents.get(0).downX;
+    }
+
     public float getDownY() {
         return mDownEvents.get(0).downY;
     }
 
     public void setDownParameters(int idx, MotionEvent e) {
-        DownState ev = new DownState(e.getEventTime(), e.getY(idx));
+        DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
         mDownEvents.append(idx, ev);
     }
 
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..8503547 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,10 +203,11 @@
         }
 
         @Override
-        protected CacheEntry cacheLocked(
+        protected <T> CacheEntry cacheLocked(
                 @NonNull ComponentName componentName,
-                @NonNull Provider<LauncherActivityInfo> infoProvider,
-                UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+                UserHandle user, @NonNull Provider<T> infoProvider,
+                @NonNull CachingLogic<T> cachingLogic,
+                boolean usePackageIcon, boolean useLowResIcon) {
             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
             if (entry == null) {
                 entry = new CacheEntry();
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..9cbab5e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
@@ -74,7 +75,7 @@
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
     public static final long SHORT_UI_TIMEOUT= 300;
-    public static final long DEFAULT_UI_TIMEOUT = 3000;
+    public static final long DEFAULT_UI_TIMEOUT = 10000;
     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -82,12 +83,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
@@ -146,16 +152,20 @@
      * @return the matching object.
      */
     protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
-        do {
+        container.setGestureMargins(0, 0, 0, 200);
+
+        int i = 0;
+        for (; ; ) {
             // findObject can only execute after spring settles.
             mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
             UiObject2 widget = container.findObject(condition);
             if (widget != null && widget.getVisibleBounds().intersects(
-                    0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight())) {
+                    0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight() - 200)) {
                 return widget;
             }
-        } while (container.scroll(Direction.DOWN, 1f));
-        return container.findObject(condition);
+            if (++i > 40) fail("Too many attempts");
+            container.scroll(Direction.DOWN, 1f);
+        }
     }
 
     /**
@@ -261,6 +271,7 @@
     }
 
     protected <T> T getFromLauncher(Function<Launcher, T> f) {
+        if (!mIsInLauncherProcess) return null;
         return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
     }
 
@@ -287,6 +298,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();
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
index ff1dcf3..793bd8f 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -17,6 +17,7 @@
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,12 +32,14 @@
     @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
     @Test
+    @Ignore
     public void testDragIcon_portrait() throws Throwable {
         lockRotation(true);
         performTest();
     }
 
     @Test
+    @Ignore
     public void testDragIcon_landscape() throws Throwable {
         lockRotation(false);
         performTest();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 050f046..d9fef81 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.WidgetCell;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,12 +48,14 @@
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
 
     @Test
+    @Ignore
     public void testDragIcon_portrait() throws Throwable {
         lockRotation(true);
         performTest();
     }
 
     @Test
+    @Ignore
     public void testDragIcon_landscape() throws Throwable {
         lockRotation(false);
         performTest();
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index 307a53e..a31d8a6 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -22,7 +22,6 @@
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -129,11 +128,10 @@
         if (num <= 0) return result;
 
         MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
-        PackageManager pm = mContext.getPackageManager();
         AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
         for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
             WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
-                    .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile);
+                    .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache);
 
             PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
             pInfo.title = pInfo.packageName;