Merge "Resurrect the All Apps button."
diff --git a/src/com/android/launcher2/AllApps2D.java b/src/com/android/launcher2/AllApps2D.java
index 1262880..0bb4ee9 100644
--- a/src/com/android/launcher2/AllApps2D.java
+++ b/src/com/android/launcher2/AllApps2D.java
@@ -279,7 +279,7 @@
         mAppsAdapter.notifyDataSetChanged();
     }
 
-    public void updateApps(String packageName, ArrayList<ApplicationInfo> list) {
+    public void updateApps(ArrayList<ApplicationInfo> list) {
         // Just remove and add, because they may need to be re-sorted.
         removeApps(list);
         addApps(list);
diff --git a/src/com/android/launcher2/AllApps3D.java b/src/com/android/launcher2/AllApps3D.java
index f571eff..c329b6c 100644
--- a/src/com/android/launcher2/AllApps3D.java
+++ b/src/com/android/launcher2/AllApps3D.java
@@ -772,7 +772,7 @@
         }
     }
     
-    public void updateApps(String packageName, ArrayList<ApplicationInfo> list) {
+    public void updateApps(ArrayList<ApplicationInfo> list) {
         // Just remove and add, because they may need to be re-sorted.
         removeApps(list);
         addApps(list);
diff --git a/src/com/android/launcher2/AllAppsView.java b/src/com/android/launcher2/AllAppsView.java
index 04f4040..a936f8b 100644
--- a/src/com/android/launcher2/AllAppsView.java
+++ b/src/com/android/launcher2/AllAppsView.java
@@ -39,7 +39,7 @@
 
     public void removeApps(ArrayList<ApplicationInfo> list);
 
-    public void updateApps(String packageName, ArrayList<ApplicationInfo> list);
+    public void updateApps(ArrayList<ApplicationInfo> list);
 
     public void dumpState();
 }
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 903db1d..df2dd66 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -2055,7 +2055,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindPackageAdded(ArrayList<ApplicationInfo> apps) {
+    public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
         removeDialog(DIALOG_CREATE_SHORTCUT);
         mAllAppsGrid.addApps(apps);
     }
@@ -2065,10 +2065,10 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps) {
+    public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
         removeDialog(DIALOG_CREATE_SHORTCUT);
-        mWorkspace.updateShortcutsForPackage(packageName);
-        mAllAppsGrid.updateApps(packageName, apps);
+        mWorkspace.updateShortcuts(apps);
+        mAllAppsGrid.updateApps(apps);
     }
 
     /**
@@ -2076,9 +2076,9 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps) {
+    public void bindAppsRemoved(ArrayList<ApplicationInfo> apps) {
         removeDialog(DIALOG_CREATE_SHORTCUT);
-        mWorkspace.removeItemsForPackage(packageName);
+        mWorkspace.removeItems(apps);
         mAllAppsGrid.removeApps(apps);
     }
 
diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index 183dbf5..be448a8 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -43,6 +43,10 @@
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
         registerReceiver(mModel, filter);
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        registerReceiver(mModel, filter);
 
         // Register for changes to the favorites
         ContentResolver resolver = getContentResolver();
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 8790fd7..00b1754 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -45,6 +45,7 @@
 import java.net.URISyntaxException;
 import java.text.Collator;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Collections;
 import java.util.HashMap;
@@ -82,9 +83,9 @@
         public void finishBindingItems();
         public void bindAppWidget(LauncherAppWidgetInfo info);
         public void bindAllApplications(ArrayList<ApplicationInfo> apps);
-        public void bindPackageAdded(ArrayList<ApplicationInfo> apps);
-        public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps);
-        public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps);
+        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
+        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
+        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps);
     }
 
     LauncherModel(LauncherApplication app, IconCache iconCache) {
@@ -96,7 +97,7 @@
                 app.getPackageManager().getDefaultActivityIcon(), app);
     }
 
-    public Bitmap getDefaultIcon() {
+    public Bitmap getFallbackIcon() {
         return Bitmap.createBitmap(mDefaultIcon);
     }
 
@@ -294,8 +295,6 @@
         // Use the app as the context.
         context = mApp;
 
-        final String packageName = intent.getData().getSchemeSpecificPart();
-
         ArrayList<ApplicationInfo> added = null;
         ArrayList<ApplicationInfo> removed = null;
         ArrayList<ApplicationInfo> modified = null;
@@ -308,74 +307,100 @@
             }
 
             final String action = intent.getAction();
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
 
-            if (packageName == null || packageName.length() == 0) {
-                // they sent us a bad intent
-                return;
-            }
+            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                final String packageName = intent.getData().getSchemeSpecificPart();
+                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
 
-            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                mAllAppsList.updatePackage(context, packageName);
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                if (!replacing) {
-                    mAllAppsList.removePackage(packageName);
+                if (packageName == null || packageName.length() == 0) {
+                    // they sent us a bad intent
+                    return;
                 }
-                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
-                // later, we will update the package at this time
-            } else {
-                if (!replacing) {
-                    mAllAppsList.addPackage(context, packageName);
-                } else {
+
+                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                     mAllAppsList.updatePackage(context, packageName);
+                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                    if (!replacing) {
+                        mAllAppsList.removePackage(packageName);
+                    }
+                    // else, we are replacing the package, so a PACKAGE_ADDED will be sent
+                    // later, we will update the package at this time
+                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                    if (!replacing) {
+                        mAllAppsList.addPackage(context, packageName);
+                    } else {
+                        mAllAppsList.updatePackage(context, packageName);
+                    }
                 }
-            }
 
-            if (mAllAppsList.added.size() > 0) {
-                added = mAllAppsList.added;
-                mAllAppsList.added = new ArrayList<ApplicationInfo>();
-            }
-            if (mAllAppsList.removed.size() > 0) {
-                removed = mAllAppsList.removed;
-                mAllAppsList.removed = new ArrayList<ApplicationInfo>();
-                for (ApplicationInfo info: removed) {
-                    mIconCache.remove(info.intent.getComponent());
+                if (mAllAppsList.added.size() > 0) {
+                    added = mAllAppsList.added;
+                    mAllAppsList.added = new ArrayList<ApplicationInfo>();
                 }
-            }
-            if (mAllAppsList.modified.size() > 0) {
-                modified = mAllAppsList.modified;
-                mAllAppsList.modified = new ArrayList<ApplicationInfo>();
-            }
+                if (mAllAppsList.removed.size() > 0) {
+                    removed = mAllAppsList.removed;
+                    mAllAppsList.removed = new ArrayList<ApplicationInfo>();
+                    for (ApplicationInfo info: removed) {
+                        mIconCache.remove(info.intent.getComponent());
+                    }
+                }
+                if (mAllAppsList.modified.size() > 0) {
+                    modified = mAllAppsList.modified;
+                    mAllAppsList.modified = new ArrayList<ApplicationInfo>();
+                }
 
-            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
-            if (callbacks == null) {
-                Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
-                return;
-            }
+                final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+                if (callbacks == null) {
+                    Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
+                    return;
+                }
 
-            if (added != null) {
-                final ArrayList<ApplicationInfo> addedFinal = added;
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        callbacks.bindPackageAdded(addedFinal);
-                    }
-                });
-            }
-            if (modified != null) {
-                final ArrayList<ApplicationInfo> modifiedFinal = modified;
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        callbacks.bindPackageUpdated(packageName, modifiedFinal);
-                    }
-                });
-            }
-            if (removed != null) {
-                final ArrayList<ApplicationInfo> removedFinal = removed;
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        callbacks.bindPackageRemoved(packageName, removedFinal);
-                    }
-                });
+                if (added != null) {
+                    final ArrayList<ApplicationInfo> addedFinal = added;
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            callbacks.bindAppsAdded(addedFinal);
+                        }
+                    });
+                }
+                if (modified != null) {
+                    final ArrayList<ApplicationInfo> modifiedFinal = modified;
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            callbacks.bindAppsUpdated(modifiedFinal);
+                        }
+                    });
+                }
+                if (removed != null) {
+                    final ArrayList<ApplicationInfo> removedFinal = removed;
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            callbacks.bindAppsRemoved(removedFinal);
+                        }
+                    });
+                }
+            } else {
+                if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+                     String packages[] = intent.getStringArrayExtra(
+                             Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                     if (packages == null || packages.length == 0) {
+                         return;
+                     }
+                     setAllAppsDirty();
+                     setWorkspaceDirty();
+                     startLoader(context, false);
+                } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+                     String packages[] = intent.getStringArrayExtra(
+                             Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                     if (packages == null || packages.length == 0) {
+                         return;
+                     }
+                     setAllAppsDirty();
+                     setWorkspaceDirty();
+                     startLoader(context, false);
+                }
             }
         }
     }
@@ -626,12 +651,6 @@
                 final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
                 final boolean isSafeMode = manager.isSafeMode();
 
-                /* TODO
-                if (mLocaleChanged) {
-                    updateShortcutLabels(contentResolver, manager);
-                }
-                */
-
                 mItems.clear();
                 mAppWidgets.clear();
                 mFolders.clear();
@@ -696,24 +715,18 @@
                                 }
 
                                 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    info = getShortcutInfo(manager, intent, context);
+                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
+                                            titleIndex);
                                 } else {
                                     info = getShortcutInfo(c, context, iconTypeIndex,
-                                            iconPackageIndex, iconResourceIndex, iconIndex);
-                                }
-
-                                if (info == null) {
-                                    info = new ShortcutInfo();
-                                    info.setIcon(getDefaultIcon());
+                                            iconPackageIndex, iconResourceIndex, iconIndex,
+                                            titleIndex);
                                 }
 
                                 if (info != null) {
-                                    if (itemType
-                                            != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                        info.title = c.getString(titleIndex);
-                                    }
-                                    info.intent = intent;
+                                    updateSavedIcon(context, info, c, iconIndex);
 
+                                    info.intent = intent;
                                     info.id = c.getLong(idIndex);
                                     container = c.getInt(containerIndex);
                                     info.container = container;
@@ -732,6 +745,15 @@
                                         folderInfo.add(info);
                                         break;
                                     }
+                                } else {
+                                    // Failed to load the shortcut, probably because the
+                                    // activity manager couldn't resolve it (maybe the app
+                                    // was uninstalled), or the db row was somehow screwed up.
+                                    // Delete it.
+                                    id = c.getLong(idIndex);
+                                    Log.e(TAG, "Error loading shortcut " + id + ", removing it");
+                                    contentResolver.delete(LauncherSettings.Favorites.getContentUri(
+                                                id, false), null, null);
                                 }
                                 break;
 
@@ -1063,24 +1085,62 @@
     }
 
     /**
-     * Make an ShortcutInfo object for a sortcut that is an application.
+     * 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,
-                                                      Context context) {
-        final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
+    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
+        return getShortcutInfo(manager, intent, context);
+    }
 
-        if (resolveInfo == null) {
+    /**
+     * 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, Context context,
+            Cursor c, int iconIndex, int titleIndex) {
+        Bitmap icon = null;
+        final ShortcutInfo info = new ShortcutInfo();
+
+        ComponentName componentName = intent.getComponent();
+        if (componentName == null) {
             return null;
         }
 
-        final ShortcutInfo info = new ShortcutInfo();
-        final ActivityInfo activityInfo = resolveInfo.activityInfo;
-        info.setIcon(mIconCache.getIcon(intent.getComponent(), resolveInfo));
-        if (info.title == null || info.title.length() == 0) {
-            info.title = activityInfo.loadLabel(manager);
+        // 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.
+        final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
+        if (resolveInfo != null) {
+            icon = mIconCache.getIcon(componentName, resolveInfo);
         }
+        // the db
+        if (icon == null) {
+            if (c != null) {
+                icon = getIconFromCursor(c, iconIndex);
+            }
+        }
+        // the fallback icon
+        if (icon == null) {
+            icon = getFallbackIcon();
+            info.usingFallbackIcon = true;
+        }
+        info.setIcon(icon);
+
+        // from the resource
+        if (resolveInfo != null) {
+            info.title = resolveInfo.activityInfo.loadLabel(manager);
+        }
+        // from the db
         if (info.title == null) {
-            info.title = activityInfo.name;
+            if (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;
         return info;
@@ -1090,47 +1150,75 @@
      * Make an ShortcutInfo object for a shortcut that isn't an application.
      */
     private ShortcutInfo getShortcutInfo(Cursor c, Context context,
-            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
+            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
+            int titleIndex) {
 
+        Bitmap icon = null;
         final ShortcutInfo info = new ShortcutInfo();
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 
+        info.title = c.getString(titleIndex);
+
         int iconType = c.getInt(iconTypeIndex);
         switch (iconType) {
         case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
             String packageName = c.getString(iconPackageIndex);
             String resourceName = c.getString(iconResourceIndex);
             PackageManager packageManager = context.getPackageManager();
+            info.customIcon = false;
+            // the resource
             try {
                 Resources resources = packageManager.getResourcesForApplication(packageName);
-                final int id = resources.getIdentifier(resourceName, null, null);
-                info.setIcon(Utilities.createIconBitmap(resources.getDrawable(id), context));
+                if (resources != null) {
+                    final int id = resources.getIdentifier(resourceName, null, null);
+                    icon = Utilities.createIconBitmap(resources.getDrawable(id), context);
+                }
             } catch (Exception e) {
-                info.setIcon(getDefaultIcon());
+                // drop this.  we have other places to look for icons
             }
-            info.iconResource = new Intent.ShortcutIconResource();
-            info.iconResource.packageName = packageName;
-            info.iconResource.resourceName = resourceName;
-            info.customIcon = false;
+            // the db
+            if (icon == null) {
+                icon = getIconFromCursor(c, iconIndex);
+            }
+            // the fallback icon
+            if (icon == null) {
+                icon = getFallbackIcon();
+                info.usingFallbackIcon = true;
+            }
             break;
         case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
-            byte[] data = c.getBlob(iconIndex);
-            try {
-                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
-                info.setIcon(bitmap);
-            } catch (Exception e) {
-                info.setIcon(getDefaultIcon());
+            icon = getIconFromCursor(c, iconIndex);
+            if (icon == null) {
+                icon = getFallbackIcon();
+                info.customIcon = false;
+                info.usingFallbackIcon = true;
+            } else {
+                info.customIcon = true;
             }
-            info.customIcon = true;
+            info.setIcon(icon);
             break;
         default:
-            info.setIcon(getDefaultIcon());
+            info.setIcon(getFallbackIcon());
+            info.usingFallbackIcon = true;
             info.customIcon = false;
             break;
         }
         return info;
     }
 
+    Bitmap getIconFromCursor(Cursor c, int iconIndex) {
+        if (false) {
+            Log.d(TAG, "getIconFromCursor app="
+                    + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
+        }
+        byte[] data = c.getBlob(iconIndex);
+        try {
+            return BitmapFactory.decodeByteArray(data, 0, data.length);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     ShortcutInfo addShortcut(Context context, Intent data,
             CellLayout.CellInfo cellInfo, boolean notify) {
 
@@ -1171,12 +1259,14 @@
             }
         }
 
-        if (icon == null) {
-            icon = getDefaultIcon();
-        }
-
         final ShortcutInfo info = new ShortcutInfo();
+
+        if (icon == null) {
+            icon = getFallbackIcon();
+            info.usingFallbackIcon = true;
+        }
         info.setIcon(icon);
+
         info.title = name;
         info.intent = intent;
         info.customIcon = customIcon;
@@ -1215,6 +1305,37 @@
         }
     }
 
+    void updateSavedIcon(Context context, ShortcutInfo info, Cursor c, int iconIndex) {
+        // 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.onExternalStorage && !info.customIcon && !info.usingFallbackIcon) {
+            boolean needSave;
+            byte[] data = c.getBlob(iconIndex);
+            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 either
+                // after the froyo OTA or when the app is updated with a new
+                // icon.
+                updateItemInDatabase(context, info);
+            }
+        }
+    }
+
     /**
      * Return an existing UserFolderInfo object if we have encountered this ID previously,
      * or make a new one.
@@ -1245,64 +1366,6 @@
         return (LiveFolderInfo) folderInfo;
     }
 
-    private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
-        final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
-                        LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
-                null, null, null);
-
-        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
-        final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
-        final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
-        final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
-
-        // boolean changed = false;
-
-        try {
-            while (c.moveToNext()) {
-                try {
-                    if (c.getInt(itemTypeIndex) !=
-                            LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                        continue;
-                    }
-
-                    final String intentUri = c.getString(intentIndex);
-                    if (intentUri != null) {
-                        final Intent shortcut = Intent.parseUri(intentUri, 0);
-                        if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
-                            final ComponentName name = shortcut.getComponent();
-                            if (name != null) {
-                                final ActivityInfo activityInfo = manager.getActivityInfo(name, 0);
-                                final String title = c.getString(titleIndex);
-                                String label = getLabel(manager, activityInfo);
-
-                                if (title == null || !title.equals(label)) {
-                                    final ContentValues values = new ContentValues();
-                                    values.put(LauncherSettings.Favorites.TITLE, label);
-
-                                    resolver.update(
-                                            LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
-                                            values, "_id=?",
-                                            new String[] { String.valueOf(c.getLong(idIndex)) });
-
-                                    // changed = true;
-                                }
-                            }
-                        }
-                    }
-                } catch (URISyntaxException e) {
-                    // Ignore
-                } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore
-                }
-            }
-        } finally {
-            c.close();
-        }
-
-        // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null);
-    }
-
     private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
         String label = activityInfo.loadLabel(manager).toString();
         if (label == null) {
diff --git a/src/com/android/launcher2/ShortcutInfo.java b/src/com/android/launcher2/ShortcutInfo.java
index cb73ac0..6e2d767 100644
--- a/src/com/android/launcher2/ShortcutInfo.java
+++ b/src/com/android/launcher2/ShortcutInfo.java
@@ -48,6 +48,17 @@
     boolean customIcon;
 
     /**
+     * Indicates whether we're using the default fallback icon instead of something from the
+     * app.
+     */
+    boolean usingFallbackIcon;
+
+    /**
+     * Indicates whether the shortcut is on external storage and may go away at any time.
+     */
+    boolean onExternalStorage;
+
+    /**
      * If isShortcut=true and customIcon=false, this contains a reference to the
      * shortcut icon as an application's resource.
      */
@@ -122,9 +133,11 @@
         if (customIcon) {
             values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
                     LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
-            Bitmap bitmap = this.mIcon;
-            writeBitmap(values, bitmap);
+            writeBitmap(values, mIcon);
         } else {
+            if (onExternalStorage && !usingFallbackIcon) {
+                writeBitmap(values, mIcon);
+            }
             values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
                     LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
             if (iconResource != null) {
diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java
index 7bc1e82..757e48e 100644
--- a/src/com/android/launcher2/Utilities.java
+++ b/src/com/android/launcher2/Utilities.java
@@ -22,6 +22,8 @@
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.PixelFormat;
@@ -57,6 +59,7 @@
     private static final Paint sBlurPaint = new Paint();
     private static final Paint sGlowColorPressedPaint = new Paint();
     private static final Paint sGlowColorFocusedPaint = new Paint();
+    private static final Paint sDisabledPaint = new Paint();
     private static final Rect sBounds = new Rect();
     private static final Rect sOldBounds = new Rect();
     private static final Canvas sCanvas = new Canvas();
@@ -214,6 +217,22 @@
         }
     }
 
+    static Bitmap drawDisabledBitmap(Bitmap bitmap, Context context) {
+        synchronized (sCanvas) { // we share the statics :-(
+            if (sIconWidth == -1) {
+                initStatics(context);
+            }
+            final Bitmap disabled = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
+                    Bitmap.Config.ARGB_8888);
+            final Canvas canvas = sCanvas;
+            canvas.setBitmap(disabled);
+            
+            canvas.drawBitmap(bitmap, 0.0f, 0.0f, sDisabledPaint);
+
+            return disabled;
+        }
+    }
+
     private static void initStatics(Context context) {
         final Resources resources = context.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
@@ -227,6 +246,11 @@
         sGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
         sGlowColorFocusedPaint.setColor(0xffff8e00);
         sGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+
+        ColorMatrix cm = new ColorMatrix();
+        cm.setSaturation(0.2f);
+        sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm));
+        sDisabledPaint.setAlpha(0x88);
     }
 
     static class BubbleText {
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index d65a98c..cb4ba11 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -43,6 +43,7 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 
 import com.android.launcher.R;
 
@@ -1223,11 +1224,17 @@
         mAllowLongPress = allowLongPress;
     }
 
-    void removeItemsForPackage(final String packageName) {
+    void removeItems(final ArrayList<ApplicationInfo> apps) {
         final int count = getChildCount();
         final PackageManager manager = getContext().getPackageManager();
         final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
 
+        final HashSet<String> packageNames = new HashSet<String>();
+        final int appCount = apps.size();
+        for (int i = 0; i < appCount; i++) {
+            packageNames.add(apps.get(i).componentName.getPackageName());
+        }
+
         for (int i = 0; i < count; i++) {
             final CellLayout layout = (CellLayout) getChildAt(i);
 
@@ -1244,17 +1251,17 @@
         
                         if (tag instanceof ShortcutInfo) {
                             final ShortcutInfo info = (ShortcutInfo) tag;
-                            // We need to check for ACTION_MAIN otherwise getComponent() might
-                            // return null for some shortcuts (for instance, for shortcuts to
-                            // web pages.)
                             final Intent intent = info.intent;
                             final ComponentName name = intent.getComponent();
         
-                            if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
-                                    name != null && packageName.equals(name.getPackageName())) {
-                                // TODO: This should probably be done on a worker thread
-                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
-                                childrenToRemove.add(view);
+                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+                                for (String packageName: packageNames) {
+                                    if (packageName.equals(name.getPackageName())) {
+                                        // TODO: This should probably be done on a worker thread
+                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
+                                        childrenToRemove.add(view);
+                                    }
+                                }
                             }
                         } else if (tag instanceof UserFolderInfo) {
                             final UserFolderInfo info = (UserFolderInfo) tag;
@@ -1268,12 +1275,16 @@
                                 final Intent intent = appInfo.intent;
                                 final ComponentName name = intent.getComponent();
         
-                                if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
-                                        name != null && packageName.equals(name.getPackageName())) {
-                                    toRemove.add(appInfo);
-                                    // TODO: This should probably be done on a worker thread
-                                    LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
-                                    removedFromFolder = true;
+                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+                                    for (String packageName: packageNames) {
+                                        if (packageName.equals(name.getPackageName())) {
+                                            toRemove.add(appInfo);
+                                            // TODO: This should probably be done on a worker thread
+                                            LauncherModel.deleteItemFromDatabase(
+                                                    mLauncher, appInfo);
+                                            removedFromFolder = true;
+                                        }
+                                    }
                                 }
                             }
         
@@ -1288,21 +1299,27 @@
                             final ProviderInfo providerInfo = manager.resolveContentProvider(
                                     uri.getAuthority(), 0);
 
-                            if (providerInfo == null ||
-                                    packageName.equals(providerInfo.packageName)) {
-                                // TODO: This should probably be done on a worker thread
-                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
-                                childrenToRemove.add(view);                        
+                            if (providerInfo == null) {
+                                for (String packageName: packageNames) {
+                                    if (packageName.equals(providerInfo.packageName)) {
+                                        // TODO: This should probably be done on a worker thread
+                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
+                                        childrenToRemove.add(view);                        
+                                    }
+                                }
                             }
                         } else if (tag instanceof LauncherAppWidgetInfo) {
                             final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
                             final AppWidgetProviderInfo provider =
                                     widgets.getAppWidgetInfo(info.appWidgetId);
-                            if (provider == null ||
-                                    packageName.equals(provider.provider.getPackageName())) {
-                                // TODO: This should probably be done on a worker thread
-                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
-                                childrenToRemove.add(view);                                
+                            if (provider == null) {
+                                for (String packageName: packageNames) {
+                                    if (packageName.equals(provider.provider.getPackageName())) {
+                                        // TODO: This should probably be done on a worker thread
+                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
+                                        childrenToRemove.add(view);                                
+                                    }
+                                }
                             }
                         }
                     }
@@ -1325,7 +1342,7 @@
         }
     }
 
-    void updateShortcutsForPackage(String packageName) {
+    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
         final PackageManager pm = mLauncher.getPackageManager();
 
         final int count = getChildCount();
@@ -1343,12 +1360,17 @@
                     final Intent intent = info.intent;
                     final ComponentName name = intent.getComponent();
                     if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
-                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
-                            packageName.equals(name.getPackageName())) {
-
-                        info.setIcon(mIconCache.getIcon(info.intent));
-                        ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
-                                new FastBitmapDrawable(info.getIcon(mIconCache)), null, null);
+                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+                        final int appCount = apps.size();
+                        for (int k=0; k<appCount; k++) {
+                            ApplicationInfo app = apps.get(k);
+                            if (app.componentName.equals(name)) {
+                                info.setIcon(mIconCache.getIcon(info.intent));
+                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
+                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
+                                        null, null);
+                                }
+                        }
                     }
                 }
             }