merge in jb-mr2-release history after reset to jb-mr2-dev
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 901b661..95ce337 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -240,6 +240,9 @@
 
     WidgetPreviewLoader mWidgetPreviewLoader;
 
+    private boolean mInBulkBind;
+    private boolean mNeedToUpdatePageCountsAndInvalidateData;
+
     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLayoutInflater = LayoutInflater.from(context);
@@ -440,38 +443,57 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
-    public void onPackagesUpdated() {
+    public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
         // Get the list of widgets and shortcuts
         mWidgets.clear();
-        List<AppWidgetProviderInfo> widgets =
-            AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
-        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
-        for (AppWidgetProviderInfo widget : widgets) {
-            widget.label = widget.label.trim();
-            if (widget.minWidth > 0 && widget.minHeight > 0) {
-                // Ensure that all widgets we show can be added on a workspace of this size
-                int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
-                int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
-                int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
-                int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
-                if (minSpanX <= LauncherModel.getCellCountX() &&
+        for (Object o : widgetsAndShortcuts) {
+            if (o instanceof AppWidgetProviderInfo) {
+                AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o;
+                widget.label = widget.label.trim();
+                if (widget.minWidth > 0 && widget.minHeight > 0) {
+                    // Ensure that all widgets we show can be added on a workspace of this size
+                    int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
+                    int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
+                    int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
+                    int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
+                    if (minSpanX <= LauncherModel.getCellCountX() &&
                         minSpanY <= LauncherModel.getCellCountY()) {
-                    mWidgets.add(widget);
+                        mWidgets.add(widget);
+                    } else {
+                        Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
+                              widget.minWidth + ", " + widget.minHeight + ")");
+                    }
                 } else {
-                    Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
-                            widget.minWidth + ", " + widget.minHeight + ")");
+                    Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
+                          widget.minWidth + ", " + widget.minHeight + ")");
                 }
             } else {
-                Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
-                        widget.minWidth + ", " + widget.minHeight + ")");
+                // just add shortcuts
+                mWidgets.add(o);
             }
         }
-        mWidgets.addAll(shortcuts);
-        Collections.sort(mWidgets,
-                new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager));
-        updatePageCounts();
-        invalidateOnDataChange();
+        updatePageCountsAndInvalidateData();
+    }
+
+    public void setBulkBind(boolean bulkBind) {
+        if (bulkBind) {
+            mInBulkBind = true;
+        } else {
+            mInBulkBind = false;
+            if (mNeedToUpdatePageCountsAndInvalidateData) {
+                updatePageCountsAndInvalidateData();
+            }
+        }
+    }
+
+    private void updatePageCountsAndInvalidateData() {
+        if (mInBulkBind) {
+            mNeedToUpdatePageCountsAndInvalidateData = true;
+        } else {
+            updatePageCounts();
+            invalidateOnDataChange();
+            mNeedToUpdatePageCountsAndInvalidateData = false;
+        }
     }
 
     @Override
@@ -1526,8 +1548,7 @@
     public void setApps(ArrayList<ApplicationInfo> list) {
         mApps = list;
         Collections.sort(mApps, LauncherModel.getAppNameComparator());
-        updatePageCounts();
-        invalidateOnDataChange();
+        updatePageCountsAndInvalidateData();
     }
     private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
         // We add it in place, in alphabetical order
@@ -1542,8 +1563,7 @@
     }
     public void addApps(ArrayList<ApplicationInfo> list) {
         addAppsWithoutInvalidate(list);
-        updatePageCounts();
-        invalidateOnDataChange();
+        updatePageCountsAndInvalidateData();
     }
     private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
         ComponentName removeComponent = item.intent.getComponent();
@@ -1569,8 +1589,7 @@
     }
     public void removeApps(ArrayList<ApplicationInfo> appInfos) {
         removeAppsWithoutInvalidate(appInfos);
-        updatePageCounts();
-        invalidateOnDataChange();
+        updatePageCountsAndInvalidateData();
     }
     public void updateApps(ArrayList<ApplicationInfo> list) {
         // We remove and re-add the updated applications list because it's properties may have
@@ -1578,8 +1597,7 @@
         // place in the list.
         removeAppsWithoutInvalidate(list);
         addAppsWithoutInvalidate(list);
-        updatePageCounts();
-        invalidateOnDataChange();
+        updatePageCountsAndInvalidateData();
     }
 
     public void reset() {
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index 337097e..2e86a7f 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -22,6 +22,10 @@
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Base64;
+import android.util.Log;
 import android.widget.Toast;
 
 import com.android.launcher.R;
@@ -31,12 +35,23 @@
 import java.util.Iterator;
 import java.util.Set;
 
+import org.json.*;
+
 public class InstallShortcutReceiver extends BroadcastReceiver {
     public static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher.action.INSTALL_SHORTCUT";
     public static final String NEW_APPS_PAGE_KEY = "apps.new.page";
     public static final String NEW_APPS_LIST_KEY = "apps.new.list";
 
+    public static final String DATA_INTENT_KEY = "intent.data";
+    public static final String LAUNCH_INTENT_KEY = "intent.launch";
+    public static final String NAME_KEY = "name";
+    public static final String ICON_KEY = "icon";
+    public static final String ICON_RESOURCE_NAME_KEY = "iconResource";
+    public static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
+    // The set of shortcuts that are pending install
+    public static final String APPS_PENDING_INSTALL = "apps_to_install";
+
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 75;
 
@@ -50,10 +65,6 @@
 
     private static Object sLock = new Object();
 
-    // The set of shortcuts that are pending install
-    private static ArrayList<PendingInstallShortcutInfo> mInstallQueue =
-            new ArrayList<PendingInstallShortcutInfo>();
-
     private static void addToStringSet(SharedPreferences sharedPrefs,
             SharedPreferences.Editor editor, String key, String value) {
         Set<String> strings = sharedPrefs.getStringSet(key, null);
@@ -65,6 +76,82 @@
         strings.add(value);
         editor.putStringSet(key, strings);
     }
+
+    private static void addToInstallQueue(
+            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
+        synchronized(sLock) {
+            try {
+                JSONStringer json = new JSONStringer()
+                    .object()
+                    .key(DATA_INTENT_KEY).value(info.data.toUri(0))
+                    .key(LAUNCH_INTENT_KEY).value(info.launchIntent.toUri(0))
+                    .key(NAME_KEY).value(info.name);
+                if (info.icon != null) {
+                    byte[] iconByteArray = ItemInfo.flattenBitmap(info.icon);
+                    json = json.key(ICON_KEY).value(
+                        Base64.encodeToString(
+                            iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+                }
+                if (info.iconResource != null) {
+                    json = json.key(ICON_RESOURCE_NAME_KEY).value(info.iconResource.resourceName);
+                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
+                        .value(info.iconResource.packageName);
+                }
+                json = json.endObject();
+                SharedPreferences.Editor editor = sharedPrefs.edit();
+                addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
+                editor.commit();
+            } catch (org.json.JSONException e) {
+                Log.d("InstallShortcutReceiver", "Exception when adding shortcut: " + e);
+            }
+        }
+    }
+
+    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
+            SharedPreferences sharedPrefs) {
+        synchronized(sLock) {
+            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            if (strings == null) {
+                return new ArrayList<PendingInstallShortcutInfo>();
+            }
+            ArrayList<PendingInstallShortcutInfo> infos =
+                new ArrayList<PendingInstallShortcutInfo>();
+            for (String json : strings) {
+                try {
+                    JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+                    Intent data = Intent.parseUri(object.getString(DATA_INTENT_KEY), 0);
+                    Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+                    String name = object.getString(NAME_KEY);
+                    String iconBase64 = object.optString(ICON_KEY);
+                    String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
+                    String iconResourcePackageName =
+                        object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+                    if (iconBase64 != null && !iconBase64.isEmpty()) {
+                        byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
+                        Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
+                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
+                    } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
+                        Intent.ShortcutIconResource iconResource =
+                            new Intent.ShortcutIconResource();
+                        iconResource.resourceName = iconResourceName;
+                        iconResource.packageName = iconResourcePackageName;
+                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+                    }
+                    data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
+                    PendingInstallShortcutInfo info =
+                        new PendingInstallShortcutInfo(data, name, launchIntent);
+                    infos.add(info);
+                } catch (org.json.JSONException e) {
+                    Log.d("InstallShortcutReceiver", "Exception reading shortcut to add: " + e);
+                } catch (java.net.URISyntaxException e) {
+                    Log.d("InstallShortcutReceiver", "Exception reading shortcut to add: " + e);
+                }
+            }
+            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
+            return infos;
+        }
+    }
+
     // Determines whether to defer installing shortcuts immediately until
     // processAllPendingInstalls() is called.
     private static boolean mUseInstallQueue = false;
@@ -73,6 +160,8 @@
         Intent data;
         Intent launchIntent;
         String name;
+        Bitmap icon;
+        Intent.ShortcutIconResource iconResource;
 
         public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
                 Intent shortcutIntent) {
@@ -103,13 +192,21 @@
                 return;
             }
         }
+        Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+        Intent.ShortcutIconResource iconResource =
+            data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+
         // Queue the item up for adding if launcher has not loaded properly yet
         boolean launcherNotLoaded = LauncherModel.getCellCountX() <= 0 ||
                 LauncherModel.getCellCountY() <= 0;
 
         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
+        info.icon = icon;
+        info.iconResource = iconResource;
         if (mUseInstallQueue || launcherNotLoaded) {
-            mInstallQueue.add(info);
+            String spKey = LauncherApplication.getSharedPreferencesKey();
+            SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+            addToInstallQueue(sp, info);
         } else {
             processInstallShortcut(context, info);
         }
@@ -123,10 +220,12 @@
         flushInstallQueue(context);
     }
     static void flushInstallQueue(Context context) {
-        Iterator<PendingInstallShortcutInfo> iter = mInstallQueue.iterator();
+        String spKey = LauncherApplication.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
+        Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
         while (iter.hasNext()) {
             processInstallShortcut(context, iter.next());
-            iter.remove();
         }
     }
 
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 473a3d1..94a0bca 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -126,7 +126,7 @@
     static final boolean PROFILE_STARTUP = false;
     static final boolean DEBUG_WIDGETS = false;
     static final boolean DEBUG_STRICT_MODE = false;
-    static final boolean DEBUG_RESUME_TIME = true;
+    static final boolean DEBUG_RESUME_TIME = false;
 
     private static final int MENU_GROUP_WALLPAPER = 1;
     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
@@ -392,7 +392,8 @@
 
         // Update customization drawer _after_ restoring the states
         if (mAppsCustomizeContent != null) {
-            mAppsCustomizeContent.onPackagesUpdated();
+            mAppsCustomizeContent.onPackagesUpdated(
+                LauncherModel.getSortedWidgetsAndShortcuts(this));
         }
 
         if (PROFILE_STARTUP) {
@@ -759,19 +760,28 @@
             mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
-        // We might have postponed some bind calls until onResume (see waitUntilResume) --
-        // execute them here
-        long startTimeCallbacks = 0;
-        if (DEBUG_RESUME_TIME) {
-            startTimeCallbacks = System.currentTimeMillis();
-        }
-        for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
-            mOnResumeCallbacks.get(i).run();
-        }
-        mOnResumeCallbacks.clear();
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent processing callbacks in onResume: " +
-                (System.currentTimeMillis() - startTimeCallbacks));
+        if (mOnResumeCallbacks.size() > 0) {
+            // We might have postponed some bind calls until onResume (see waitUntilResume) --
+            // execute them here
+            long startTimeCallbacks = 0;
+            if (DEBUG_RESUME_TIME) {
+                startTimeCallbacks = System.currentTimeMillis();
+            }
+
+            if (mAppsCustomizeContent != null) {
+                mAppsCustomizeContent.setBulkBind(true);
+            }
+            for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
+                mOnResumeCallbacks.get(i).run();
+            }
+            if (mAppsCustomizeContent != null) {
+                mAppsCustomizeContent.setBulkBind(false);
+            }
+            mOnResumeCallbacks.clear();
+            if (DEBUG_RESUME_TIME) {
+                Log.d(TAG, "Time spent processing callbacks in onResume: " +
+                    (System.currentTimeMillis() - startTimeCallbacks));
+            }
         }
 
         // Reset the pressed state of icons that were locked in the press state while activities
@@ -3308,9 +3318,13 @@
      * @return true if we are currently paused.  The caller might be able to
      * skip some work in that case since we will come back again.
      */
-    private boolean waitUntilResume(Runnable run) {
+    private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
         if (mPaused) {
             Log.i(TAG, "Deferring update until onResume");
+            if (deletePreviousRunnables) {
+                while (mOnResumeCallbacks.remove(run)) {
+                }
+            }
             mOnResumeCallbacks.add(run);
             return true;
         } else {
@@ -3318,6 +3332,10 @@
         }
     }
 
+    private boolean waitUntilResume(Runnable run) {
+        return waitUntilResume(run, false);
+    }
+
     /**
      * If the activity is currently paused, signal that we need to re-run the loader
      * in onResume.
@@ -3761,17 +3779,23 @@
     /**
      * A number of packages were updated.
      */
-    public void bindPackagesUpdated() {
-        if (waitUntilResume(new Runnable() {
+
+    private ArrayList<Object> mWidgetsAndShortcuts;
+    private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
             public void run() {
-                bindPackagesUpdated();
+                bindPackagesUpdated(mWidgetsAndShortcuts);
+                mWidgetsAndShortcuts = null;
             }
-        })) {
+        };
+
+    public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
+        if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
+            mWidgetsAndShortcuts = widgetsAndShortcuts;
             return;
         }
 
         if (mAppsCustomizeContent != null) {
-            mAppsCustomizeContent.onPackagesUpdated();
+            mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
         }
     }
 
diff --git a/src/com/android/launcher2/LauncherAppWidgetHost.java b/src/com/android/launcher2/LauncherAppWidgetHost.java
index 45c1d23..4d52ea8 100644
--- a/src/com/android/launcher2/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher2/LauncherAppWidgetHost.java
@@ -50,6 +50,6 @@
     protected void onProvidersChanged() {
         // Once we get the message that widget packages are updated, we need to rebind items
         // in AppsCustomize accordingly.
-        mLauncher.bindPackagesUpdated();
+        mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher));
     }
 }
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 99ebd96..1d562be 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -161,7 +161,7 @@
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<ApplicationInfo> appInfos,
                         boolean matchPackageNamesOnly);
-        public void bindPackagesUpdated();
+        public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
         public boolean isAllAppsVisible();
         public boolean isAllAppsButtonRank(int rank);
         public void bindSearchablesChanged();
@@ -2105,18 +2105,32 @@
                 });
             }
 
+            final ArrayList<Object> widgetsAndShortcuts =
+                getSortedWidgetsAndShortcuts(context);
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                     if (callbacks == cb && cb != null) {
-                        callbacks.bindPackagesUpdated();
+                        callbacks.bindPackagesUpdated(widgetsAndShortcuts);
                     }
                 }
             });
         }
     }
 
+    // Returns a list of ResolveInfos/AppWindowInfos in sorted order
+    public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
+        widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
+        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+        Collections.sort(widgetsAndShortcuts,
+            new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
+        return widgetsAndShortcuts;
+    }
+
     /**
      * This is called from the code that adds shortcuts from the intent receiver.  This
      * doesn't have a Cursor, but