Removing additional rpc due to icon cache update

When launcher loads, it fetches the list of apps twice, once for
loading all-apps and again for updating icons. Instead reusing
the previously fetched apps list.
Also moving the icon loading in a separate package for further
generalization

Change-Id: Ibd2dae56e6027a31b633da030bc6b43a90b27e1b
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 3d60605..733f295 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5d40143..8e6703c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,8 +40,8 @@
 import android.view.ViewDebug;
 import android.widget.TextView;
 
-import com.android.launcher3.IconCache.IconLoadRequest;
-import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.IconCache.IconLoadRequest;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 55074f8..b5bbb9c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -87,6 +87,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.FileLog;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 5159de1..b02182c 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 7a90a55..df1c693 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index a84bfd5..7717d91 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -25,6 +25,7 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.util.ContentWriter;
 
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 84bad08..cf497fd 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.graphics.ShadowGenerator;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index dd17916..fe7b4e5 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -27,7 +27,7 @@
 import android.text.TextUtils;
 import android.util.SparseArray;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.config.FeatureFlags;
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
index 31c0087..d260e24 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -32,7 +32,7 @@
 import android.util.Log;
 import android.widget.Toast;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 52167bb..bd919bc 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -28,7 +28,7 @@
 import android.os.Process;
 
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 087362c..7db67c9 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -44,7 +44,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
similarity index 74%
rename from src/com/android/launcher3/IconCache.java
rename to src/com/android/launcher3/icons/IconCache.java
index 61fb3e3..2035d0e 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.icons;
 
 import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON;
 
@@ -38,11 +38,19 @@
 import android.os.Build.VERSION;
 import android.os.Handler;
 import android.os.Process;
-import android.os.SystemClock;
 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;
@@ -54,14 +62,9 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
-import com.android.launcher3.util.Thunk;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.Stack;
 
 import androidx.annotation.NonNull;
 import androidx.core.graphics.ColorUtils;
@@ -81,8 +84,6 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_IGNORE_CACHE = false;
 
-    @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
-
     public static class CacheEntry extends BitmapInfo {
         public CharSequence title = "";
         public CharSequence contentDescription = "";
@@ -93,20 +94,21 @@
     }
 
     private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
-    @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
 
-    private final Context mContext;
-    private final PackageManager mPackageManager;
-    private final IconProvider mIconProvider;
-    @Thunk final UserManagerCompat mUserManager;
-    private final LauncherAppsCompat mLauncherApps;
+    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;
-    @Thunk final IconDB mIconDb;
 
-    @Thunk final Handler mWorkerHandler;
+    final IconDB mIconDb;
+    final Handler mWorkerHandler;
 
     private final BitmapFactory.Options mDecodeOptions;
 
@@ -247,115 +249,9 @@
                 new String[]{packageName + "/%", Long.toString(userSerial)});
     }
 
-    public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
-        // Remove all active icon update tasks.
-        mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
-
+    public IconCacheUpdateHandler getUpdateHandler() {
         mIconProvider.updateSystemStateString(mContext);
-        for (UserHandle user : mUserManager.getUserProfiles()) {
-            // Query for the set of apps
-            final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
-            // Fail if we don't have any apps
-            // TODO: Fix this. Only fail for the current user.
-            if (apps == null || apps.isEmpty()) {
-                return;
-            }
-
-            // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
-            // is called by the icon cache when the job is complete.
-            updateDBIcons(user, apps, Process.myUserHandle().equals(user)
-                    ? ignorePackagesForMainUser : Collections.<String>emptySet());
-        }
-    }
-
-    /**
-     * 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 void updateDBIcons(UserHandle user, List<LauncherActivityInfo> apps,
-            Set<String> ignorePackages) {
-        long userSerial = mUserManager.getSerialNumberForUser(user);
-        PackageManager pm = mContext.getPackageManager();
-        HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
-        for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
-            pkgInfoMap.put(info.packageName, info);
-        }
-
-        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<>();
-
-        Cursor c = null;
-        try {
-            c = mIconDb.query(
-                    new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
-                            IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
-                            IconDB.COLUMN_SYSTEM_STATE},
-                    IconDB.COLUMN_USER + " = ? ",
-                    new String[]{Long.toString(userSerial)});
-
-            final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
-            final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
-            final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
-            final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
-            final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
-
-            while (c.moveToNext()) {
-                String cn = c.getString(indexComponent);
-                ComponentName component = ComponentName.unflattenFromString(cn);
-                PackageInfo info = pkgInfoMap.get(component.getPackageName());
-                if (info == null) {
-                    if (!ignorePackages.contains(component.getPackageName())) {
-                        remove(component, user);
-                        itemsToRemove.add(c.getInt(rowIndex));
-                    }
-                    continue;
-                }
-                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
-                    // Application is not present
-                    continue;
-                }
-
-                long updateTime = c.getLong(indexLastUpdate);
-                int version = c.getInt(indexVersion);
-                LauncherActivityInfo app = componentMap.remove(component);
-                if (version == info.versionCode && updateTime == info.lastUpdateTime &&
-                        TextUtils.equals(c.getString(systemStateIndex),
-                                mIconProvider.getIconSystemState(info.packageName))) {
-                    continue;
-                }
-                if (app == null) {
-                    remove(component, user);
-                    itemsToRemove.add(c.getInt(rowIndex));
-                } else {
-                    appsToUpdate.add(app);
-                }
-            }
-        } catch (SQLiteException e) {
-            Log.d(TAG, "Error reading icon cache", e);
-            // Continue updating whatever we have read so far
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        if (!itemsToRemove.isEmpty()) {
-            mIconDb.delete(
-                    Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
-        }
-
-        // Insert remaining apps.
-        if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
-            Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
-            appsToAdd.addAll(componentMap.values());
-            new SerializedIconUpdateTask(userSerial, pkgInfoMap,
-                    appsToAdd, appsToUpdate).scheduleNext();
-        }
+        return new IconCacheUpdateHandler(this);
     }
 
     /**
@@ -363,8 +259,9 @@
      * @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
      */
-    @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
+    synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
             PackageInfo info, long userSerial, boolean replaceExisting) {
         final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
         CacheEntry entry = null;
@@ -744,81 +641,23 @@
         }
     }
 
-    /**
-     * A runnable that updates invalid icons and adds missing icons in the DB for the provided
-     * LauncherActivityInfo list. Items are updated/added one at a time, so that the
-     * worker thread doesn't get blocked.
-     */
-    @Thunk class SerializedIconUpdateTask implements Runnable {
-        private final long mUserSerial;
-        private final HashMap<String, PackageInfo> mPkgInfoMap;
-        private final Stack<LauncherActivityInfo> mAppsToAdd;
-        private final Stack<LauncherActivityInfo> mAppsToUpdate;
-        private final HashSet<String> mUpdatedPackages = new HashSet<>();
-
-        @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
-                Stack<LauncherActivityInfo> appsToAdd,
-                Stack<LauncherActivityInfo> appsToUpdate) {
-            mUserSerial = userSerial;
-            mPkgInfoMap = pkgInfoMap;
-            mAppsToAdd = appsToAdd;
-            mAppsToUpdate = appsToUpdate;
-        }
-
-        @Override
-        public void run() {
-            if (!mAppsToUpdate.isEmpty()) {
-                LauncherActivityInfo app = mAppsToUpdate.pop();
-                String pkg = app.getComponentName().getPackageName();
-                PackageInfo info = mPkgInfoMap.get(pkg);
-                addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
-                mUpdatedPackages.add(pkg);
-
-                if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
-                    // No more app to update. Notify model.
-                    LauncherAppState.getInstance(mContext).getModel().onPackageIconsUpdated(
-                            mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
-                }
-
-                // Let it run one more time.
-                scheduleNext();
-            } else if (!mAppsToAdd.isEmpty()) {
-                LauncherActivityInfo app = mAppsToAdd.pop();
-                PackageInfo info = mPkgInfoMap.get(app.getComponentName().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) {
-                    addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
-                }
-
-                if (!mAppsToAdd.isEmpty()) {
-                    scheduleNext();
-                }
-            }
-        }
-
-        public void scheduleNext() {
-            mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
-        }
-    }
-
-    private static final class IconDB extends SQLiteCacheHelper {
+    static final class IconDB extends SQLiteCacheHelper {
         private final static int RELEASE_VERSION = 25;
 
-        private final static String TABLE_NAME = "icons";
-        private final static String COLUMN_ROWID = "rowid";
-        private final static String COLUMN_COMPONENT = "componentName";
-        private final static String COLUMN_USER = "profileId";
-        private final static String COLUMN_LAST_UPDATED = "lastUpdated";
-        private final static String COLUMN_VERSION = "version";
-        private final static String COLUMN_ICON = "icon";
-        private final static String COLUMN_ICON_COLOR = "icon_color";
-        private final static String COLUMN_LABEL = "label";
-        private final static String COLUMN_SYSTEM_STATE = "system_state";
+        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";
 
-        private final static String[] COLUMNS_HIGH_RES = new String[] {
+        public final static String[] COLUMNS_HIGH_RES = new String[] {
                 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
-        private final static String[] COLUMNS_LOW_RES = new String[] {
+        public final static String[] COLUMNS_LOW_RES = new String[] {
                 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
 
         public IconDB(Context context, int iconPixelSize) {
diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
new file mode 100644
index 0000000..04e2901
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java
@@ -0,0 +1,223 @@
+/*
+ * 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.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.IconCache.IconDB;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Utility class to handle updating the Icon cache
+ */
+public class IconCacheUpdateHandler {
+
+    private static final String TAG = "IconCacheUpdateHandler";
+
+    private static final Object ICON_UPDATE_TOKEN = new Object();
+
+    private final HashMap<String, PackageInfo> mPkgInfoMap;
+    private final IconCache mIconCache;
+    private final HashMap<UserHandle, Set<String>> mPackagesToIgnore = new HashMap<>();
+
+    IconCacheUpdateHandler(IconCache cache) {
+        mIconCache = cache;
+
+        mPkgInfoMap = new HashMap<>();
+
+        // Remove all active icon update tasks.
+        mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
+
+        createPackageInfoMap();
+    }
+
+    public void setPackagesToIgnore(UserHandle userHandle, Set<String> packages) {
+        mPackagesToIgnore.put(userHandle, packages);
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public void updateIcons(List<LauncherActivityInfo> apps) {
+        if (apps.isEmpty()) {
+            return;
+        }
+        UserHandle user = apps.get(0).getUser();
+
+        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<>();
+
+        try (Cursor c = mIconCache.mIconDb.query(
+                new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+                        IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+                        IconDB.COLUMN_SYSTEM_STATE},
+                IconDB.COLUMN_USER + " = ? ",
+                new String[]{Long.toString(userSerial)})) {
+
+            final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+            final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+            final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+            final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+            final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+            while (c.moveToNext()) {
+                String cn = c.getString(indexComponent);
+                ComponentName component = ComponentName.unflattenFromString(cn);
+                PackageInfo info = mPkgInfoMap.get(component.getPackageName());
+                if (info == null) {
+                    if (!ignorePackages.contains(component.getPackageName())) {
+                        mIconCache.remove(component, user);
+                        itemsToRemove.add(c.getInt(rowIndex));
+                    }
+                    continue;
+                }
+                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+                    // Application is not present
+                    continue;
+                }
+
+                long updateTime = c.getLong(indexLastUpdate);
+                int version = c.getInt(indexVersion);
+                LauncherActivityInfo app = componentMap.remove(component);
+                if (version == info.versionCode && updateTime == info.lastUpdateTime &&
+                        TextUtils.equals(c.getString(systemStateIndex),
+                                mIconCache.mIconProvider.getIconSystemState(info.packageName))) {
+                    continue;
+                }
+                if (app == null) {
+                    mIconCache.remove(component, user);
+                    itemsToRemove.add(c.getInt(rowIndex));
+                } else {
+                    appsToUpdate.add(app);
+                }
+            }
+        } catch (SQLiteException e) {
+            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<>();
+            appsToAdd.addAll(componentMap.values());
+            new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate).scheduleNext();
+        }
+    }
+
+
+    /**
+     * A runnable that updates invalid icons and adds missing icons in the DB for the provided
+     * 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 final long mUserSerial;
+        private final UserHandle mUserHandle;
+        private final Stack<LauncherActivityInfo> mAppsToAdd;
+        private final Stack<LauncherActivityInfo> mAppsToUpdate;
+        private final HashSet<String> mUpdatedPackages = new HashSet<>();
+
+        SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
+                Stack<LauncherActivityInfo> appsToAdd, Stack<LauncherActivityInfo> appsToUpdate) {
+            mUserHandle = userHandle;
+            mUserSerial = userSerial;
+            mAppsToAdd = appsToAdd;
+            mAppsToUpdate = appsToUpdate;
+        }
+
+        @Override
+        public void run() {
+            if (!mAppsToUpdate.isEmpty()) {
+                LauncherActivityInfo app = mAppsToUpdate.pop();
+                String pkg = app.getComponentName().getPackageName();
+                PackageInfo info = mPkgInfoMap.get(pkg);
+                mIconCache.addIconToDBAndMemCache(
+                        app, 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);
+                }
+
+                // Let it run one more time.
+                scheduleNext();
+            } else if (!mAppsToAdd.isEmpty()) {
+                LauncherActivityInfo app = mAppsToAdd.pop();
+                PackageInfo info = mPkgInfoMap.get(app.getComponentName().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*/);
+                }
+
+                if (!mAppsToAdd.isEmpty()) {
+                    scheduleNext();
+                }
+            }
+        }
+
+        public void scheduleNext() {
+            mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
+                    SystemClock.uptimeMillis() + 1);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 54c0542..be83d36 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -20,7 +20,7 @@
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 744e98a..77e9721 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -32,7 +32,7 @@
 import android.util.LongSparseArray;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8f8bc09..4ccb8d8 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -43,7 +43,8 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCacheUpdateHandler;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
@@ -182,7 +183,7 @@
 
             // second step
             TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
-            loadAllApps();
+            List<List<LauncherActivityInfo>> activityListPerUser = loadAllApps();
 
             TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
             verifyNotStopped();
@@ -190,7 +191,9 @@
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
-            updateIconCache();
+            IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
+            setIgnorePackages(updateHandler);
+            updateIconCacheForApps(updateHandler, activityListPerUser);
 
             // Take a break
             TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
@@ -774,7 +777,7 @@
         }
     }
 
-    private void updateIconCache() {
+    private void setIgnorePackages(IconCacheUpdateHandler updateHandler) {
         // Ignore packages which have a promise icon.
         HashSet<String> packagesToIgnore = new HashSet<>();
         synchronized (mBgDataModel) {
@@ -792,12 +795,20 @@
                 }
             }
         }
-        mIconCache.updateDbIcons(packagesToIgnore);
+        updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore);
     }
 
-    private void loadAllApps() {
-        final List<UserHandle> profiles = mUserManager.getUserProfiles();
+    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() {
+        final List<UserHandle> profiles = mUserManager.getUserProfiles();
+        List<List<LauncherActivityInfo>> activityListPerUser = new ArrayList<>();
         // Clear the list of apps
         mBgAllAppsList.clear();
         for (UserHandle user : profiles) {
@@ -806,7 +817,7 @@
             // Fail if we don't have any apps
             // TODO: Fix this. Only fail for the current user.
             if (apps == null || apps.isEmpty()) {
-                return;
+                return activityListPerUser;
             }
             boolean quietMode = mUserManager.isQuietModeEnabled(user);
             // Create the ApplicationInfos
@@ -815,6 +826,7 @@
                 // This builds the icon bitmaps.
                 mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
             }
+            activityListPerUser.add(apps);
         }
 
         if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
@@ -827,6 +839,7 @@
         }
 
         mBgAllAppsList.added = new ArrayList<>();
+        return activityListPerUser;
     }
 
     private void loadDeepShortcuts() {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index c556083..0af5311 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -24,7 +24,7 @@
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 448ff6c..82f4fe1 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -11,7 +11,7 @@
 import android.util.Log;
 
 import com.android.launcher3.AppFilter;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 29b4b0b..50db40f 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -33,8 +33,8 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.IconCache;
-import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.R;
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 2ba672d..435125b 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
 
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 1016d04..a45521d 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -23,7 +23,7 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.model.WidgetItem;
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index dd3e859..59b2da0 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -24,7 +24,7 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 29e54b1..42a2764 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -10,7 +10,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index d5b14f2..307a53e 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -29,7 +29,7 @@
 import androidx.test.runner.AndroidJUnit4;
 import android.view.LayoutInflater;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.WidgetPreviewLoader;