Updating IconCache to maintain a persistent of icons

> IconDB maintains a DB of icons keyed on ComponentName + User
> During loader, icons & labels are loaded first from the DB, and
if the entry doesn't exist, loaded using packageManager
> After the loader completes, IconDB updates any entry which might have
changed, while the launcher was dead.

Change-Id: I7a6021cb6d1ca1e66fa5a0bdd21e1543e0cf66fc
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 72c6693..5ed7a62 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -98,14 +98,14 @@
                 user);
 
         for (LauncherActivityInfoCompat info : matches) {
-            add(new AppInfo(context, info, user, mIconCache, null));
+            add(new AppInfo(context, info, user, mIconCache));
         }
     }
 
     /**
      * Remove the apps for the given apk identified by packageName.
      */
-    public void removePackage(String packageName, UserHandleCompat user, boolean clearCache) {
+    public void removePackage(String packageName, UserHandleCompat user) {
         final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
@@ -115,9 +115,6 @@
                 data.remove(i);
             }
         }
-        if (clearCache) {
-            mIconCache.remove(packageName, user);
-        }
     }
 
     /**
@@ -137,7 +134,6 @@
                         && packageName.equals(component.getPackageName())) {
                     if (!findActivity(matches, component)) {
                         removed.add(applicationInfo);
-                        mIconCache.remove(component, user);
                         data.remove(i);
                     }
                 }
@@ -150,10 +146,9 @@
                         info.getComponentName().getPackageName(), user,
                         info.getComponentName().getClassName());
                 if (applicationInfo == null) {
-                    add(new AppInfo(context, info, user, mIconCache, null));
+                    add(new AppInfo(context, info, user, mIconCache));
                 } else {
-                    mIconCache.remove(applicationInfo.componentName, user);
-                    mIconCache.getTitleAndIcon(applicationInfo, info, null);
+                    mIconCache.getTitleAndIcon(applicationInfo, info);
                     modified.add(applicationInfo);
                 }
             }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index a66bac0..455c6d1 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -19,19 +19,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 
 /**
  * Represents an app in AllAppsView.
@@ -77,13 +73,13 @@
      * Must not hold the Context.
      */
     public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
-            IconCache iconCache, HashMap<Object, CharSequence> labelCache) {
+            IconCache iconCache) {
         this.componentName = info.getComponentName();
         this.container = ItemInfo.NO_ID;
 
         flags = initFlags(info);
         firstInstallTime = info.getFirstInstallTime();
-        iconCache.getTitleAndIcon(this, info, labelCache);
+        iconCache.getTitleAndIcon(this, info);
         intent = makeLaunchIntent(context, info, user);
         this.user = user;
     }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5a0875b..91d4aaf 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -18,15 +18,19 @@
 
 import android.app.ActivityManager;
 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.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.SQLiteOpenHelper;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
@@ -37,15 +41,10 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map.Entry;
 
 /**
@@ -56,7 +55,6 @@
     private static final String TAG = "Launcher.IconCache";
 
     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
-    private static final String RESOURCE_FILE_PREFIX = "icon_";
 
     // Empty class name is used for storing package default entry.
     private static final String EMPTY_CLASS_NAME = ".";
@@ -98,7 +96,8 @@
     private final LauncherAppsCompat mLauncherApps;
     private final HashMap<CacheKey, CacheEntry> mCache =
             new HashMap<CacheKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
-    private int mIconDpi;
+    private final int mIconDpi;
+    private final IconDB mIconDb;
 
     public IconCache(Context context) {
         ActivityManager activityManager =
@@ -109,13 +108,10 @@
         mUserManager = UserManagerCompat.getInstance(mContext);
         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mIconDpi = activityManager.getLauncherLargeIconDensity();
-
-        // need to set mIconDpi before getting default icon
-        UserHandleCompat myUser = UserHandleCompat.myUserHandle();
-        mDefaultIcons.put(myUser, makeDefaultIcon(myUser));
+        mIconDb = new IconDB(context);
     }
 
-    public Drawable getFullResDefaultActivityIcon() {
+    private Drawable getFullResDefaultActivityIcon() {
         return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon);
     }
 
@@ -188,9 +184,9 @@
     }
 
     /**
-     * Remove any records for the supplied package name.
+     * Remove any records for the supplied package name from memory.
      */
-    public synchronized void remove(String packageName, UserHandleCompat user) {
+    private void removeFromMemCacheLocked(String packageName, UserHandleCompat user) {
         HashSet<CacheKey> forDeletion = new HashSet<CacheKey>();
         for (CacheKey key: mCache.keySet()) {
             if (key.componentName.getPackageName().equals(packageName)
@@ -204,6 +200,140 @@
     }
 
     /**
+     * Updates the entries related to the given package in memory and persistent DB.
+     */
+    public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
+        removeIconsForPkg(packageName, user);
+        try {
+            PackageInfo info = mPackageManager.getPackageInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+            long userSerial = mUserManager.getSerialNumberForUser(user);
+            for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
+                addIconToDB(app, info, userSerial);
+            }
+        } catch (NameNotFoundException e) {
+            Log.d(TAG, "Package not found", e);
+            return;
+        }
+    }
+
+    /**
+     * Removes the entries related to the given package in memory and persistent DB.
+     */
+    public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
+        removeFromMemCacheLocked(packageName, user);
+        long userSerial = mUserManager.getSerialNumberForUser(user);
+        mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+                IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+                new String[] {packageName + "/%", Long.toString(userSerial)});
+    }
+
+    /**
+     * 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 HashSet<String> updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps) {
+        long userSerial = mUserManager.getSerialNumberForUser(user);
+        PackageManager pm = mContext.getPackageManager();
+        HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
+        for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+            pkgInfoMap.put(info.packageName, info);
+        }
+
+        HashMap<ComponentName, LauncherActivityInfoCompat> componentMap = new HashMap<>();
+        for (LauncherActivityInfoCompat app : apps) {
+            componentMap.put(app.getComponentName(), app);
+        }
+
+        Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+                new String[] {IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+                    IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION},
+                IconDB.COLUMN_USER + " = ? ",
+                new String[] {Long.toString(userSerial)},
+                null, null, null);
+
+        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);
+
+        HashSet<Integer> itemsToRemove = new HashSet<Integer>();
+        HashSet<String> updatedPackages = new HashSet<String>();
+
+        while (c.moveToNext()) {
+            String cn = c.getString(indexComponent);
+            ComponentName component = ComponentName.unflattenFromString(cn);
+            PackageInfo info = pkgInfoMap.get(component.getPackageName());
+            if (info == null) {
+                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);
+            LauncherActivityInfoCompat app = componentMap.remove(component);
+            if (version == info.versionCode && updateTime == info.lastUpdateTime) {
+                continue;
+            }
+            if (app == null) {
+                itemsToRemove.add(c.getInt(rowIndex));
+                continue;
+            }
+            ContentValues values = updateCacheAndGetContentValues(app);
+            mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
+                    IconDB.COLUMN_COMPONENT + " = ?",
+                    new String[] { cn });
+
+            updatedPackages.add(component.getPackageName());
+        }
+        c.close();
+        if (!itemsToRemove.isEmpty()) {
+            mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+                    IconDB.COLUMN_ROWID + " IN ( " + TextUtils.join(", ", itemsToRemove) +" )",
+                    null);
+        }
+
+        // Insert remaining apps.
+        for (LauncherActivityInfoCompat app : componentMap.values()) {
+            PackageInfo info = pkgInfoMap.get(app.getComponentName().getPackageName());
+            if (info == null) {
+                continue;
+            }
+            addIconToDB(app, info, userSerial);
+        }
+        return updatedPackages;
+    }
+
+    private void addIconToDB(LauncherActivityInfoCompat app, PackageInfo info, long userSerial) {
+        ContentValues values = updateCacheAndGetContentValues(app);
+        values.put(IconDB.COLUMN_COMPONENT, app.getComponentName().flattenToString());
+        values.put(IconDB.COLUMN_USER, userSerial);
+        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+        values.put(IconDB.COLUMN_VERSION, info.versionCode);
+        mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+                SQLiteDatabase.CONFLICT_REPLACE);
+    }
+
+    private ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app) {
+        CacheEntry entry = new CacheEntry();
+        entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+        entry.title = app.getLabel();
+        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
+        mCache.put(new CacheKey(app.getComponentName(), app.getUser()), entry);
+
+        ContentValues values = new ContentValues();
+        values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(entry.icon));
+        values.put(IconDB.COLUMN_LABEL, entry.title.toString());
+        return values;
+    }
+
+
+    /**
      * Empty out the cache.
      */
     public synchronized void flush() {
@@ -227,10 +357,8 @@
     /**
      * Fill in "application" with the icon and label for "info."
      */
-    public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info,
-            HashMap<Object, CharSequence> labelCache) {
-        CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
-                info.getUser(), false);
+    public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info) {
+        CacheEntry entry = cacheLocked(application.componentName, info, info.getUser(), false);
 
         application.title = entry.title;
         application.iconBitmap = entry.icon;
@@ -246,15 +374,16 @@
         }
 
         LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
-        CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, true);
+        CacheEntry entry = cacheLocked(component, launcherActInfo, user, true);
         return entry.icon;
     }
 
     /**
-     * Fill in "shortcutInfo" with the icon and label for "info."
+     * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+     * corresponding activity is not found, it reverts to the package icon.
      */
     public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
-            UserHandleCompat user, boolean usePkgIcon) {
+            UserHandleCompat user) {
         ComponentName component = intent.getComponent();
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
@@ -263,16 +392,22 @@
             shortcutInfo.title = "";
             shortcutInfo.usingFallbackIcon = true;
         } else {
-            LauncherActivityInfoCompat launcherActInfo =
-                    mLauncherApps.resolveActivity(intent, user);
-            CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
-
-            shortcutInfo.setIcon(entry.icon);
-            shortcutInfo.title = entry.title;
-            shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+            LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
+            getTitleAndIcon(shortcutInfo, component, info, user, true);
         }
     }
 
+    /**
+     * Fill in {@param shortcutInfo} with the icon and label for {@param info}
+     */
+    public synchronized void getTitleAndIcon(
+            ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
+            UserHandleCompat user, boolean usePkgIcon) {
+        CacheEntry entry = cacheLocked(component, info, user, usePkgIcon);
+        shortcutInfo.setIcon(entry.icon);
+        shortcutInfo.title = entry.title;
+        shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+    }
 
     public synchronized Bitmap getDefaultIcon(UserHandleCompat user) {
         if (!mDefaultIcons.containsKey(user)) {
@@ -281,16 +416,6 @@
         return mDefaultIcons.get(user);
     }
 
-    public synchronized Bitmap getIcon(ComponentName component, LauncherActivityInfoCompat info,
-            HashMap<Object, CharSequence> labelCache) {
-        if (info == null || component == null) {
-            return null;
-        }
-
-        CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser(), false);
-        return entry.icon;
-    }
-
     public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
         return mDefaultIcons.get(user) == icon;
     }
@@ -300,35 +425,17 @@
      * This method is not thread safe, it must be called from a synchronized method.
      */
     private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
-            HashMap<Object, CharSequence> labelCache, UserHandleCompat user, boolean usePackageIcon) {
+            UserHandleCompat user, boolean usePackageIcon) {
         CacheKey cacheKey = new CacheKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
         if (entry == null) {
             entry = new CacheEntry();
-
             mCache.put(cacheKey, entry);
 
-            if (info != null) {
-                ComponentName labelKey = info.getComponentName();
-                if (labelCache != null && labelCache.containsKey(labelKey)) {
-                    entry.title = labelCache.get(labelKey).toString();
-                } else {
-                    entry.title = info.getLabel().toString();
-                    if (labelCache != null) {
-                        labelCache.put(labelKey, entry.title);
-                    }
-                }
-
-                entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                entry.icon = Utilities.createIconBitmap(
-                        info.getBadgedIcon(mIconDpi), mContext);
-            } else {
-                entry.title = "";
-                Bitmap preloaded = getPreloadedIcon(componentName, user);
-                if (preloaded != null) {
-                    if (DEBUG) Log.d(TAG, "using preloaded icon for " +
-                            componentName.toShortString());
-                    entry.icon = preloaded;
+            // Check the DB first.
+            if (!getEntryFromDB(componentName, user, entry)) {
+                if (info != null) {
+                    entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackage(
@@ -338,6 +445,7 @@
                                     componentName.toShortString());
                             entry.icon = packageEntry.icon;
                             entry.title = packageEntry.title;
+                            entry.contentDescription = packageEntry.contentDescription;
                         }
                     }
                     if (entry.icon == null) {
@@ -347,6 +455,11 @@
                     }
                 }
             }
+
+            if (TextUtils.isEmpty(entry.title) && info != null) {
+                entry.title = info.getLabel().toString();
+                entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+            }
         }
         return entry;
     }
@@ -357,7 +470,7 @@
      */
     public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user,
             Bitmap icon, CharSequence title) {
-        remove(packageName, user);
+        removeFromMemCacheLocked(packageName, user);
 
         CacheEntry entry = getEntryForPackage(packageName, user);
         if (!TextUtils.isEmpty(title)) {
@@ -379,48 +492,36 @@
         if (entry == null) {
             entry = new CacheEntry();
             entry.title = "";
+            entry.contentDescription = "";
             mCache.put(cacheKey, entry);
 
             try {
                 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
-                entry.title = info.loadLabel(mPackageManager);
                 entry.icon = Utilities.createIconBitmap(info.loadIcon(mPackageManager), mContext);
+                entry.title = info.loadLabel(mPackageManager);
+                entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
             } catch (NameNotFoundException e) {
                 if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
             }
-
-            if (entry.icon == null) {
-                entry.icon = getPreloadedIcon(cn, user);
-            }
         }
         return entry;
     }
 
-    public synchronized HashMap<ComponentName,Bitmap> getAllIcons() {
-        HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
-        for (CacheKey ck : mCache.keySet()) {
-            final CacheEntry e = mCache.get(ck);
-            set.put(ck.componentName, e.icon);
-        }
-        return set;
-    }
-
     /**
      * Pre-load an icon into the persistent cache.
      *
      * <P>Queries for a component that does not exist in the package manager
      * will be answered by the persistent cache.
      *
-     * @param context application context
      * @param componentName the icon should be returned for this component
      * @param icon the icon to be persisted
      * @param dpi the native density of the icon
      */
-    public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
-            int dpi) {
+    public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
+            long userSerial) {
         // TODO rescale to the correct native DPI
         try {
-            PackageManager packageManager = context.getPackageManager();
+            PackageManager packageManager = mContext.getPackageManager();
             packageManager.getActivityIcon(componentName);
             // component is present on the system already, do nothing
             return;
@@ -428,100 +529,86 @@
             // pass
         }
 
-        final String key = componentName.flattenToString();
-        FileOutputStream resourceFile = null;
+        ContentValues values = new ContentValues();
+        values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
+        values.put(IconDB.COLUMN_USER, userSerial);
+        values.put(IconDB.COLUMN_ICON, ItemInfo.flattenBitmap(icon));
+        values.put(IconDB.COLUMN_LABEL, label);
+        mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+                SQLiteDatabase.CONFLICT_REPLACE);
+    }
+
+    private boolean getEntryFromDB(ComponentName component, UserHandleCompat user, CacheEntry entry) {
+        Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+                new String[] {IconDB.COLUMN_ICON, IconDB.COLUMN_LABEL},
+                IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+                new String[] {component.flattenToString(),
+                    Long.toString(mUserManager.getSerialNumberForUser(user))},
+                null, null, null);
         try {
-            resourceFile = context.openFileOutput(getResourceFilename(componentName),
-                    Context.MODE_PRIVATE);
-            ByteArrayOutputStream os = new ByteArrayOutputStream();
-            if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
-                byte[] buffer = os.toByteArray();
-                resourceFile.write(buffer, 0, buffer.length);
-            } else {
-                Log.w(TAG, "failed to encode cache for " + key);
-                return;
-            }
-        } catch (FileNotFoundException e) {
-            Log.w(TAG, "failed to pre-load cache for " + key, e);
-        } catch (IOException e) {
-            Log.w(TAG, "failed to pre-load cache for " + key, e);
-        } finally {
-            if (resourceFile != null) {
-                try {
-                    resourceFile.close();
-                } catch (IOException e) {
-                    Log.d(TAG, "failed to save restored icon for: " + key, e);
+            if (c.moveToNext()) {
+                entry.icon = Utilities.createIconBitmap(c, 0, mContext);
+                entry.title = c.getString(1);
+                if (entry.title == null) {
+                    entry.title = "";
+                    entry.contentDescription = "";
+                } else {
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
                 }
+                return true;
             }
-        }
-    }
-
-    /**
-     * Read a pre-loaded icon from the persistent icon cache.
-     *
-     * @param componentName the component that should own the icon
-     * @returns a bitmap if one is cached, or null.
-     */
-    private Bitmap getPreloadedIcon(ComponentName componentName, UserHandleCompat user) {
-        final String key = componentName.flattenToShortString();
-
-        // We don't keep icons for other profiles in persistent cache.
-        if (!user.equals(UserHandleCompat.myUserHandle())) {
-            return null;
-        }
-
-        if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
-        Bitmap icon = null;
-        FileInputStream resourceFile = null;
-        try {
-            resourceFile = mContext.openFileInput(getResourceFilename(componentName));
-            byte[] buffer = new byte[1024];
-            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-            int bytesRead = 0;
-            while(bytesRead >= 0) {
-                bytes.write(buffer, 0, bytesRead);
-                bytesRead = resourceFile.read(buffer, 0, buffer.length);
-            }
-            if (DEBUG) Log.d(TAG, "read " + bytes.size());
-            icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
-            if (icon == null) {
-                Log.w(TAG, "failed to decode pre-load icon for " + key);
-            }
-        } catch (FileNotFoundException e) {
-            if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key);
-        } catch (IOException e) {
-            Log.w(TAG, "failed to read pre-load icon for: " + key, e);
         } finally {
-            if(resourceFile != null) {
-                try {
-                    resourceFile.close();
-                } catch (IOException e) {
-                    Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
-                }
+            c.close();
+        }
+        return false;
+    }
+
+    private static final class IconDB extends SQLiteOpenHelper {
+        private final static int DB_VERSION = 1;
+
+        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_LABEL = "label";
+
+        public IconDB(Context context) {
+            super(context, LauncherFiles.APP_ICONS_DB, null, DB_VERSION);
+        }
+
+        @Override
+        public void onCreate(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_LABEL + " TEXT, " +
+                    "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
+                    ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion != newVersion) {
+                clearDB(db);
             }
         }
 
-        return icon;
-    }
-
-    /**
-     * Remove a pre-loaded icon from the persistent icon cache.
-     *
-     * @param componentName the component that should own the icon
-     */
-    public void deletePreloadedIcon(ComponentName componentName, UserHandleCompat user) {
-        // We don't keep icons for other profiles in persistent cache.
-        if (!user.equals(UserHandleCompat.myUserHandle()) || componentName == null) {
-            return;
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion != newVersion) {
+                clearDB(db);
+            }
         }
-        remove(componentName, user);
-        boolean success = mContext.deleteFile(getResourceFilename(componentName));
-        if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
-    }
 
-    private static String getResourceFilename(ComponentName component) {
-        String resourceName = component.flattenToShortString();
-        String filename = resourceName.replace(File.separatorChar, '_');
-        return RESOURCE_FILE_PREFIX + filename;
+        private void clearDB(SQLiteDatabase db) {
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+            onCreate(db);
+        }
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 58b0854..06a8ab9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -5033,7 +5033,7 @@
         if (activityInfo == null) {
             return null;
         }
-        return new AppInfo(this, activityInfo, myUser, mIconCache, null);
+        return new AppInfo(this, activityInfo, myUser, mIconCache);
     }
 
     public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 353bf3f..97ff327 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -144,6 +144,7 @@
     private final HashSet<String> mExistingKeys;
     private final ArrayList<Key> mKeys;
     private final ItemTypeMatcher[] mItemTypeMatchers;
+    private final long mUserSerial;
 
     private IconCache mIconCache;
     private BackupManager mBackupManager;
@@ -161,6 +162,9 @@
         mKeys = new ArrayList<Key>();
         restoreSuccessful = true;
         mItemTypeMatchers = new ItemTypeMatcher[CommonAppTypeParser.SUPPORTED_TYPE_COUNT];
+
+        UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+        mUserSerial = userManager.getSerialNumberForUser(UserHandleCompat.myUserHandle());
     }
 
     private void dataChanged() {
@@ -297,6 +301,12 @@
         if (!restoreSuccessful) {
             return;
         }
+        if (!initializeIconCache()) {
+            // During restore we do not need an initialized instance of IconCache. We can create
+            // a temporary icon cache here, as the process will be rebooted after restore
+            // is complete.
+            mIconCache = new IconCache(mContext);
+        }
 
         int dataSize = data.size();
         if (mBuffer.length < dataSize) {
@@ -601,7 +611,8 @@
             Log.w(TAG, "failed to unpack icon for " + key.name);
         }
         if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
-        IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name), icon, res.dpi);
+        mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
+                "" /* label */, mUserSerial);
     }
 
     /**
@@ -693,8 +704,8 @@
             if (icon == null) {
                 Log.w(TAG, "failed to unpack widget icon for " + key.name);
             } else {
-                IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
-                        icon, widget.icon.dpi);
+                mIconCache.preloadIcon(ComponentName.unflattenFromString(widget.provider),
+                        icon, widget.icon.dpi, widget.label, mUserSerial);
             }
         }
 
@@ -1145,9 +1156,11 @@
 
         final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
         if (appState == null) {
-            Throwable stackTrace = new Throwable();
-            stackTrace.fillInStackTrace();
-            Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+            if (DEBUG) {
+                Throwable stackTrace = new Throwable();
+                stackTrace.fillInStackTrace();
+                Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+            }
             return false;
         }
         mIconCache = appState.getIconCache();
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index fa05365..cedb397 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -25,6 +25,7 @@
             WallpaperCropActivity.class.getName();
     public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
     public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db";
+    public static final String APP_ICONS_DB = "app_icons.db";
 
     public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
             DEFAULT_WALLPAPER_THUMBNAIL,
@@ -36,5 +37,6 @@
             STATS_LOG,
             WALLPAPER_CROP_PREFERENCES_KEY + XML,
             WALLPAPER_IMAGES_DB,
-            WIDGET_PREVIEWS_DB));
+            WIDGET_PREVIEWS_DB,
+            APP_ICONS_DB));
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3983835..489e329 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -36,7 +36,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Environment;
@@ -163,9 +162,6 @@
     // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
     static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
 
-    // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
-    static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
-
     // sBgWorkspaceScreens is the ordered set of workspace screens.
     static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
 
@@ -1131,7 +1127,6 @@
                                 break;
                         }
                         sBgItemsIdMap.remove(item.id);
-                        sBgDbIconCache.remove(item);
                     }
                 }
             }
@@ -1204,7 +1199,6 @@
                 synchronized (sBgLock) {
                     sBgItemsIdMap.remove(info.id);
                     sBgFolders.remove(info.id);
-                    sBgDbIconCache.remove(info);
                     sBgWorkspaceItems.remove(info);
                 }
 
@@ -1214,7 +1208,6 @@
                 synchronized (sBgLock) {
                     for (ItemInfo childInfo : info.contents) {
                         sBgItemsIdMap.remove(childInfo.id);
-                        sBgDbIconCache.remove(childInfo);
                     }
                 }
             }
@@ -1481,12 +1474,9 @@
         private boolean mLoadAndBindStepFinished;
         private int mFlags;
 
-        private HashMap<Object, CharSequence> mLabelCache;
-
         LoaderTask(Context context, boolean isLaunching, int flags) {
             mContext = context;
             mIsLaunching = isLaunching;
-            mLabelCache = new HashMap<Object, CharSequence>();
             mFlags = flags;
         }
 
@@ -1635,15 +1625,6 @@
                 }
             }
 
-            // Update the saved icons if necessary
-            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
-            synchronized (sBgLock) {
-                for (Object key : sBgDbIconCache.keySet()) {
-                    updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
-                }
-                sBgDbIconCache.clear();
-            }
-
             if (LauncherAppState.isDisableAllApps()) {
                 // Ensure that all the applications that are in the system are
                 // represented on the home screen.
@@ -1819,7 +1800,6 @@
                 sBgAppWidgets.clear();
                 sBgFolders.clear();
                 sBgItemsIdMap.clear();
-                sBgDbIconCache.clear();
                 sBgWorkspaceScreens.clear();
             }
         }
@@ -2068,8 +2048,8 @@
 
                                 if (itemReplaced) {
                                     if (user.equals(UserHandleCompat.myUserHandle())) {
-                                        info = getShortcutInfo(manager, intent, user, context, null,
-                                                iconIndex, titleIndex, mLabelCache, false);
+                                        info = getAppShortcutInfo(manager, intent, user, context, null,
+                                                iconIndex, titleIndex, false);
                                     } else {
                                         // Don't replace items for other profiles.
                                         itemsToRemove.add(id);
@@ -2089,8 +2069,8 @@
                                     }
                                 } else if (itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    info = getShortcutInfo(manager, intent, user, context, c,
-                                            iconIndex, titleIndex, mLabelCache, allowMissingTarget);
+                                    info = getAppShortcutInfo(manager, intent, user, context, c,
+                                            iconIndex, titleIndex, allowMissingTarget);
                                 } else {
                                     info = getShortcutInfo(c, context, iconTypeIndex,
                                             iconPackageIndex, iconResourceIndex, iconIndex,
@@ -2145,10 +2125,6 @@
                                         break;
                                     }
                                     sBgItemsIdMap.put(info.id, info);
-
-                                    // now that we've loaded everthing re-save it with the
-                                    // icon in case it disappears somehow.
-                                    queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
                                 } else {
                                     throw new RuntimeException("Unexpected null ShortcutInfo");
                                 }
@@ -2842,20 +2818,48 @@
                 if (apps == null || apps.isEmpty()) {
                     return;
                 }
-                // Sort the applications by name
-                final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-                Collections.sort(apps,
-                        new LauncherModel.ShortcutNameComparator(mLabelCache));
-                if (DEBUG_LOADERS) {
-                    Log.d(TAG, "sort took "
-                            + (SystemClock.uptimeMillis()-sortTime) + "ms");
+
+                // Update icon cache
+                HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
+
+                // If any package icon has changed (app was updated while launcher was dead),
+                // update the corresponding shortcuts.
+                if (!updatedPackages.isEmpty()) {
+                    final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
+                    synchronized (sBgLock) {
+                        for (ItemInfo info : sBgItemsIdMap.values()) {
+                            if (info instanceof ShortcutInfo && user.equals(info.user)
+                                    && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                ShortcutInfo si = (ShortcutInfo) info;
+                                ComponentName cn = si.getTargetComponent();
+                                if (cn != null && updatedPackages.contains(cn.getPackageName())) {
+                                    si.updateIcon(mIconCache);
+                                    updates.add(si);
+                                }
+                            }
+                        }
+                    }
+
+                    if (!updates.isEmpty()) {
+                        final UserHandleCompat userFinal = user;
+                        mHandler.post(new Runnable() {
+
+                            public void run() {
+                                Callbacks cb = getCallback();
+                                if (cb != null) {
+                                    cb.bindShortcutsChanged(
+                                            updates, new ArrayList<ShortcutInfo>(), userFinal);
+                                }
+                            }
+                        });
+                    }
                 }
 
                 // Create the ApplicationInfos
                 for (int i = 0; i < apps.size(); i++) {
                     LauncherActivityInfoCompat app = apps.get(i);
                     // This builds the icon bitmaps.
-                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));
+                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                 }
 
                 if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
@@ -2987,7 +2991,7 @@
                 case OP_ADD:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
-                        mIconCache.remove(packages[i], mUser);
+                        mIconCache.updateIconsForPkg(packages[i], mUser);
                         mBgAllAppsList.addPackage(context, packages[i], mUser);
                     }
 
@@ -3019,6 +3023,7 @@
                 case OP_UPDATE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                        mIconCache.updateIconsForPkg(packages[i], mUser);
                         mBgAllAppsList.updatePackage(context, packages[i], mUser);
                         WidgetPreviewLoader.removePackageFromDb(
                                 mApp.getWidgetPreviewCacheDb(), packages[i]);
@@ -3038,12 +3043,15 @@
                         shortcutSet.removeAll(Arrays.asList(mPackages));
                         prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
                     }
-                    // Fall through
-                case OP_UNAVAILABLE:
-                    boolean clearCache = mOp == OP_REMOVE;
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mBgAllAppsList.removePackage(packages[i], mUser, clearCache);
+                        mIconCache.removeIconsForPkg(packages[i], mUser);
+                    }
+                    // Fall through
+                case OP_UNAVAILABLE:
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+                        mBgAllAppsList.removePackage(packages[i], mUser);
                         WidgetPreviewLoader.removePackageFromDb(
                                 mApp.getWidgetPreviewCacheDb(), packages[i]);
                     }
@@ -3136,7 +3144,6 @@
                                 AppInfo appInfo = addedOrUpdatedApps.get(cn);
 
                                 if (si.isPromise()) {
-                                    mIconCache.deletePreloadedIcon(cn, mUser);
                                     if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
                                         // Auto install icon
                                         PackageManager pm = context.getPackageManager();
@@ -3381,7 +3388,7 @@
             int promiseType) {
         final ShortcutInfo info = new ShortcutInfo();
         info.user = UserHandleCompat.myUserHandle();
-        mIconCache.getTitleAndIcon(info, intent, info.user, true);
+        mIconCache.getTitleAndIcon(info, intent, info.user);
 
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
             String title = (cursor != null) ? cursor.getString(titleIndex) : null;
@@ -3424,22 +3431,13 @@
     }
 
     /**
-     * This is called from the code that adds shortcuts from the intent receiver.  This
-     * doesn't have a Cursor, but
-     */
-    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
-            UserHandleCompat user, Context context) {
-        return getShortcutInfo(manager, intent, user, context, null, -1, -1, null, false);
-    }
-
-    /**
      * Make an ShortcutInfo object for a shortcut that is an application.
      *
      * If c is not null, then it will be used to fill in missing data like the title and icon.
      */
-    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
+    public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
             UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
-            HashMap<Object, CharSequence> labelCache, boolean allowMissingTarget) {
+            boolean allowMissingTarget) {
         if (user == null) {
             Log.d(TAG, "Null user found in getShortcutInfo");
             return null;
@@ -3461,48 +3459,22 @@
         }
 
         final ShortcutInfo info = new ShortcutInfo();
-
-        // the resource -- This may implicitly give us back the fallback icon,
-        // but don't worry about that.  All we're doing with usingFallbackIcon is
-        // to avoid saving lots of copies of that in the database, and most apps
-        // have icons anyway.
-        Bitmap icon = mIconCache.getIcon(componentName, lai, labelCache);
-
-        // the db
-        if (icon == null) {
-            if (c != null) {
-                icon = getIconFromCursor(c, iconIndex, context);
-            }
-        }
-        // the fallback icon
-        if (icon == null) {
-            icon = mIconCache.getDefaultIcon(user);
-            info.usingFallbackIcon = true;
-        }
-        info.setIcon(icon);
-
-        // From the cache.
-        if (labelCache != null) {
-            info.title = labelCache.get(componentName);
+        mIconCache.getTitleAndIcon(info, componentName, lai, user, false);
+        if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
+            Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
+            info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
         }
 
-        // from the resource
-        if (info.title == null && lai != null) {
-            info.title = lai.getLabel();
-            if (labelCache != null) {
-                labelCache.put(componentName, info.title);
-            }
-        }
         // from the db
-        if (info.title == null) {
-            if (c != null) {
-                info.title =  c.getString(titleIndex);
-            }
+        if (TextUtils.isEmpty(info.title) && c != null) {
+            info.title =  c.getString(titleIndex);
         }
+
         // fall back to the class name of the activity
         if (info.title == null) {
             info.title = componentName.getClassName();
         }
+
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         info.user = user;
         info.contentDescription = mUserManager.getBadgedLabelForUser(
@@ -3581,7 +3553,7 @@
             icon = Utilities.createIconBitmap(packageName, resourceName, mIconCache, context);
             // the db
             if (icon == null) {
-                icon = getIconFromCursor(c, iconIndex, context);
+                icon = Utilities.createIconBitmap(c, iconIndex, context);
             }
             // the fallback icon
             if (icon == null) {
@@ -3590,7 +3562,7 @@
             }
             break;
         case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
-            icon = getIconFromCursor(c, iconIndex, context);
+            icon = Utilities.createIconBitmap(c, iconIndex, context);
             if (icon == null) {
                 icon = mIconCache.getDefaultIcon(info.user);
                 info.customIcon = false;
@@ -3609,22 +3581,6 @@
         return info;
     }
 
-    Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
-        @SuppressWarnings("all") // suppress dead code warning
-        final boolean debug = false;
-        if (debug) {
-            Log.d(TAG, "getIconFromCursor app="
-                    + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
-        }
-        byte[] data = c.getBlob(iconIndex);
-        try {
-            return Utilities.createIconBitmap(
-                    BitmapFactory.decodeByteArray(data, 0, data.length), context);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
     ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
@@ -3673,45 +3629,6 @@
         return info;
     }
 
-    boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
-            int iconIndex) {
-        // If apps can't be on SD, don't even bother.
-        if (!mAppsCanBeOnRemoveableStorage) {
-            return false;
-        }
-        // If this icon doesn't have a custom icon, check to see
-        // what's stored in the DB, and if it doesn't match what
-        // we're going to show, store what we are going to show back
-        // into the DB.  We do this so when we're loading, if the
-        // package manager can't find an icon (for example because
-        // the app is on SD) then we can use that instead.
-        if (!info.customIcon && !info.usingFallbackIcon) {
-            cache.put(info, c.getBlob(iconIndex));
-            return true;
-        }
-        return false;
-    }
-    void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
-        boolean needSave = false;
-        try {
-            if (data != null) {
-                Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
-                Bitmap loaded = info.getIcon(mIconCache);
-                needSave = !saved.sameAs(loaded);
-            } else {
-                needSave = true;
-            }
-        } catch (Exception e) {
-            needSave = true;
-        }
-        if (needSave) {
-            Log.d(TAG, "going to save icon bitmap for info=" + info);
-            // This is slower than is ideal, but this only happens once
-            // or when the app is updated with a new icon.
-            updateItemInDatabase(context, info);
-        }
-    }
-
     /**
      * Return an existing FolderInfo object if we have encountered this ID previously,
      * or make a new one.
@@ -3761,38 +3678,7 @@
             return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
         }
     }
-    public static class ShortcutNameComparator implements Comparator<LauncherActivityInfoCompat> {
-        private Collator mCollator;
-        private HashMap<Object, CharSequence> mLabelCache;
-        ShortcutNameComparator(PackageManager pm) {
-            mLabelCache = new HashMap<Object, CharSequence>();
-            mCollator = Collator.getInstance();
-        }
-        ShortcutNameComparator(HashMap<Object, CharSequence> labelCache) {
-            mLabelCache = labelCache;
-            mCollator = Collator.getInstance();
-        }
-        public final int compare(LauncherActivityInfoCompat a, LauncherActivityInfoCompat b) {
-            String labelA, labelB;
-            ComponentName keyA = a.getComponentName();
-            ComponentName keyB = b.getComponentName();
-            if (mLabelCache.containsKey(keyA)) {
-                labelA = mLabelCache.get(keyA).toString();
-            } else {
-                labelA = a.getLabel().toString().trim();
 
-                mLabelCache.put(keyA, labelA);
-            }
-            if (mLabelCache.containsKey(keyB)) {
-                labelB = mLabelCache.get(keyB).toString();
-            } else {
-                labelB = b.getLabel().toString().trim();
-
-                mLabelCache.put(keyB, labelB);
-            }
-            return mCollator.compare(labelA, labelB);
-        }
-    };
     public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
         private final AppWidgetManagerCompat mManager;
         private final PackageManager mPackageManager;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 15d6a3e..08ffaa2 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -23,6 +23,7 @@
 import android.graphics.Bitmap;
 import android.util.Log;
 
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.UserHandleCompat;
 
 import java.util.ArrayList;
@@ -190,8 +191,9 @@
     }
 
     public void updateIcon(IconCache iconCache) {
-        mIcon = iconCache.getIcon(promisedIntent != null ? promisedIntent : intent, user);
-        usingFallbackIcon = iconCache.isDefaultIcon(mIcon, user);
+        if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
+            iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user);
+        }
     }
 
     @Override
@@ -213,9 +215,9 @@
             if (!usingFallbackIcon) {
                 writeBitmap(values, mIcon);
             }
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
-                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
             if (iconResource != null) {
+                values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+                        LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
                 values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
                         iconResource.packageName);
                 values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1a9b9a1..497b438 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -31,7 +31,9 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -112,6 +114,15 @@
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
     }
 
+    static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
+        byte[] data = c.getBlob(iconIndex);
+        try {
+            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     /**
      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
      * exist, it returns null.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 626154e..b9c1f4d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -4772,7 +4772,7 @@
                         if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
                             // For auto install apps update the icon as well as label.
                             mIconCache.getTitleAndIcon(shortcutInfo,
-                                    shortcutInfo.promisedIntent, user, true);
+                                    shortcutInfo.promisedIntent, user);
                         } else {
                             // Only update the icon for restored apps.
                             shortcutInfo.updateIcon(mIconCache);