Adding shortcuts corresponding to ManagedUsers automatically.

Bug: 16188104
Change-Id: Ic07578dd187263f59f3c431cbb78dea90d0c24f4
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index bfcad84..a66bac0 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -84,16 +84,11 @@
         flags = initFlags(info);
         firstInstallTime = info.getFirstInstallTime();
         iconCache.getTitleAndIcon(this, info, labelCache);
-        intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.setComponent(info.getComponentName());
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
-        intent.putExtra(EXTRA_PROFILE, serialNumber);
+        intent = makeLaunchIntent(context, info, user);
         this.user = user;
     }
 
-    private static int initFlags(LauncherActivityInfoCompat info) {
+    public static int initFlags(LauncherActivityInfoCompat info) {
         int appFlags = info.getApplicationInfo().flags;
         int flags = 0;
         if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -137,4 +132,14 @@
     public ShortcutInfo makeShortcut() {
         return new ShortcutInfo(this);
     }
+
+    public static Intent makeLaunchIntent(Context context, LauncherActivityInfoCompat info,
+            UserHandleCompat user) {
+        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        return new Intent(Intent.ACTION_MAIN)
+            .addCategory(Intent.CATEGORY_LAUNCHER)
+            .setComponent(info.getComponentName())
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+            .putExtra(EXTRA_PROFILE, serialNumber);
+    }
 }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index e9fb499..9103fad 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -27,14 +27,18 @@
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
-import android.widget.Toast;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 
+import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONStringer;
 import org.json.JSONTokener;
 
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -47,73 +51,49 @@
     private static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher.action.INSTALL_SHORTCUT";
 
-    private static final String DATA_INTENT_KEY = "intent.data";
     private static final String LAUNCH_INTENT_KEY = "intent.launch";
     private static final String NAME_KEY = "name";
     private static final String ICON_KEY = "icon";
     private static final String ICON_RESOURCE_NAME_KEY = "iconResource";
     private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
+
+    private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
+    private static final String USER_HANDLE_KEY = "userHandle";
+
     // The set of shortcuts that are pending install
     private 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 = 85;
 
-    private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
-    private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
-
-    private static Object sLock = new Object();
-
-    private static void addToStringSet(SharedPreferences sharedPrefs,
-            SharedPreferences.Editor editor, String key, String value) {
-        Set<String> strings = sharedPrefs.getStringSet(key, null);
-        if (strings == null) {
-            strings = new HashSet<String>(1);
-        } else {
-            strings = new HashSet<String>(strings);
-        }
-        strings.add(value);
-        editor.putStringSet(key, strings);
-    }
+    private static final Object sLock = new Object();
 
     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));
+            String encoded = info.encodeToString();
+            if (encoded != null) {
+                Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+                if (strings == null) {
+                    strings = new HashSet<String>(1);
+                } else {
+                    strings = new HashSet<String>(strings);
                 }
-                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();
-                if (DBG) Log.d(TAG, "Adding to APPS_PENDING_INSTALL: " + json);
-                addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
-                editor.commit();
-            } catch (org.json.JSONException e) {
-                Log.d(TAG, "Exception when adding shortcut: " + e);
+                strings.add(encoded);
+                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).commit();
             }
         }
     }
 
-    public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
-                                              ArrayList<String> packageNames) {
+    public static void removeFromInstallQueue(Context context, ArrayList<String> packageNames,
+            UserHandleCompat user) {
         if (packageNames.isEmpty()) {
             return;
         }
+        String spKey = LauncherAppState.getSharedPreferencesKey();
+        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
         synchronized(sLock) {
-            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+            Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
             if (DBG) {
                 Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
                         + ", removing packages: " + packageNames);
@@ -122,34 +102,20 @@
                 Set<String> newStrings = new HashSet<String>(strings);
                 Iterator<String> newStringsIter = newStrings.iterator();
                 while (newStringsIter.hasNext()) {
-                    String json = newStringsIter.next();
-                    try {
-                        JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
-                        Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
-                        String pn = launchIntent.getPackage();
-                        if (pn == null) {
-                            if (launchIntent.getComponent() == null) {
-                                continue;
-                            }
-                            pn = launchIntent.getComponent().getPackageName();
-                        }
-                        if (packageNames.contains(pn)) {
-                            newStringsIter.remove();
-                        }
-                    } catch (org.json.JSONException e) {
-                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
-                    } catch (java.net.URISyntaxException e) {
-                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
+                    String encoded = newStringsIter.next();
+                    PendingInstallShortcutInfo info = decode(encoded, context);
+                    if (info == null || (packageNames.contains(info.getTargetPackage())
+                            && user.equals(info.user))) {
+                        newStringsIter.remove();
                     }
                 }
-                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
-                        new HashSet<String>(newStrings)).commit();
+                sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).commit();
             }
         }
     }
 
     private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
-            SharedPreferences sharedPrefs) {
+            SharedPreferences sharedPrefs, Context context) {
         synchronized(sLock) {
             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
             if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
@@ -158,36 +124,10 @@
             }
             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);
+            for (String encoded : strings) {
+                PendingInstallShortcutInfo info = decode(encoded, context);
+                if (info != null) {
                     infos.add(info);
-                } catch (org.json.JSONException e) {
-                    Log.d(TAG, "Exception reading shortcut to add: " + e);
-                } catch (java.net.URISyntaxException e) {
-                    Log.d(TAG, "Exception reading shortcut to add: " + e);
                 }
             }
             sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
@@ -199,49 +139,26 @@
     // processAllPendingInstalls() is called.
     private static boolean mUseInstallQueue = false;
 
-    private static class PendingInstallShortcutInfo {
-        Intent data;
-        Intent launchIntent;
-        String name;
-        Bitmap icon;
-        Intent.ShortcutIconResource iconResource;
-
-        public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
-                Intent shortcutIntent) {
-            data = rawData;
-            name = shortcutName;
-            launchIntent = shortcutIntent;
-        }
-    }
-
     public void onReceive(Context context, Intent data) {
         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
             return;
         }
 
         if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
 
-        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-        if (intent == null) {
-            return;
-        }
+        queuePendingShortcutInfo(info, context);
+    }
 
-        // This name is only used for comparisons and notifications, so fall back to activity name
-        // if not supplied
-        String name = ensureValidName(context, intent,
-                data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)).toString();
-        Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-        Intent.ShortcutIconResource iconResource =
-            data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+    static void queueInstallShortcut(LauncherActivityInfoCompat info, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+    }
 
+    private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
         LauncherAppState.setApplicationContext(context.getApplicationContext());
         LauncherAppState app = LauncherAppState.getInstance();
-        boolean launcherNotLoaded = (app.getDynamicGrid() == null);
-
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
-        info.icon = icon;
-        info.iconResource = iconResource;
+        boolean launcherNotLoaded = app.getModel().getCallback() == null;
 
         String spKey = LauncherAppState.getSharedPreferencesKey();
         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
@@ -261,33 +178,22 @@
     static void flushInstallQueue(Context context) {
         String spKey = LauncherAppState.getSharedPreferencesKey();
         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
+        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context);
         if (!installQueue.isEmpty()) {
             Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
             ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
-            int result = INSTALL_SHORTCUT_SUCCESSFUL;
-            String duplicateName = "";
             while (iter.hasNext()) {
                 final PendingInstallShortcutInfo pendingInfo = iter.next();
-                //final Intent data = pendingInfo.data;
                 final Intent intent = pendingInfo.launchIntent;
-                final String name = pendingInfo.name;
 
                 if (LauncherAppState.isDisableAllApps() && !isValidShortcutLaunchIntent(intent)) {
                     if (DBG) Log.d(TAG, "Ignoring shortcut with launchIntent:" + intent);
                     continue;
                 }
 
-                final boolean exists = LauncherModel.shortcutExists(context, name, intent);
-                //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
-
                 // If the intent specifies a package, make sure the package exists
-                String packageName = intent.getPackage();
-                if (packageName == null) {
-                    packageName = intent.getComponent() == null ? null :
-                        intent.getComponent().getPackageName();
-                }
-                if (packageName != null && !packageName.isEmpty()) {
+                String packageName = pendingInfo.getTargetPackage();
+                if (TextUtils.isEmpty(packageName)) {
                     UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
                     if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
                         if (DBG) Log.d(TAG, "Ignoring shortcut for absent package:" + intent);
@@ -295,19 +201,12 @@
                     }
                 }
 
+                final boolean exists = LauncherModel.shortcutExists(context, pendingInfo.label,
+                        intent, pendingInfo.user);
                 if (!exists) {
                     // Generate a shortcut info to add into the model
-                    ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
-                            pendingInfo.launchIntent);
-                    addShortcuts.add(info);
+                    addShortcuts.add(pendingInfo.getShortcutInfo());
                 }
-
-            }
-
-            // Notify the user once if we weren't able to place any duplicates
-            if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
-                Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
-                        duplicateName), Toast.LENGTH_SHORT).show();
             }
 
             // Add the new apps to the model and bind them
@@ -342,22 +241,6 @@
         return true;
     }
 
-    private static ShortcutInfo getShortcutInfo(Context context, Intent data,
-                                                Intent launchIntent) {
-        if (launchIntent.getAction() == null) {
-            launchIntent.setAction(Intent.ACTION_VIEW);
-        } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
-                launchIntent.getCategories() != null &&
-                launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-            launchIntent.addFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        }
-        LauncherAppState app = LauncherAppState.getInstance();
-        ShortcutInfo info = app.getModel().infoFromShortcutIntent(context, data);
-        info.title = ensureValidName(context, launchIntent, info.title);
-        return info;
-    }
-
     /**
      * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
      * the application name instead.
@@ -374,4 +257,168 @@
         }
         return name;
     }
+
+    private static class PendingInstallShortcutInfo {
+
+        final LauncherActivityInfoCompat activityInfo;
+
+        final Intent data;
+        final Context mContext;
+        final Intent launchIntent;
+        final String label;
+        final UserHandleCompat user;
+
+        /**
+         * Initializes a PendingInstallShortcutInfo received from a different app.
+         */
+        public PendingInstallShortcutInfo(Intent data, Context context) {
+            this.data = data;
+            mContext = context;
+
+            launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+            label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+            user = UserHandleCompat.myUserHandle();
+            activityInfo = null;
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) {
+            this.data = null;
+            mContext = context;
+            activityInfo = info;
+            user = info.getUser();
+
+            launchIntent = AppInfo.makeLaunchIntent(context, info, user);
+            label = info.getLabel().toString();
+        }
+
+        public String encodeToString() {
+            if (activityInfo != null) {
+                try {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                        .object()
+                        .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                        .key(APP_SHORTCUT_TYPE_KEY).value(true)
+                        .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                .getSerialNumberForUser(user))
+                        .endObject().toString();
+                } catch (JSONException e) {
+                    Log.d(TAG, "Exception when adding shortcut: " + e);
+                    return null;
+                }
+            }
+
+            if (launchIntent.getAction() == null) {
+                launchIntent.setAction(Intent.ACTION_VIEW);
+            } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+                    launchIntent.getCategories() != null &&
+                    launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                launchIntent.addFlags(
+                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+            }
+
+            // This name is only used for comparisons and notifications, so fall back to activity
+            // name if not supplied
+            String name = ensureValidName(mContext, launchIntent, label).toString();
+            Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+            Intent.ShortcutIconResource iconResource =
+                data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+
+            // Only encode the parameters which are supported by the API.
+            try {
+                JSONStringer json = new JSONStringer()
+                    .object()
+                    .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                    .key(NAME_KEY).value(name);
+                if (icon != null) {
+                    byte[] iconByteArray = ItemInfo.flattenBitmap(icon);
+                    json = json.key(ICON_KEY).value(
+                            Base64.encodeToString(
+                                    iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+                }
+                if (iconResource != null) {
+                    json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
+                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
+                            .value(iconResource.packageName);
+                }
+                return json.endObject().toString();
+            } catch (JSONException e) {
+                Log.d(TAG, "Exception when adding shortcut: " + e);
+            }
+            return null;
+        }
+
+        public ShortcutInfo getShortcutInfo() {
+            if (activityInfo != null) {
+                final ShortcutInfo info = new ShortcutInfo();
+                info.user = user;
+                info.title = label;
+                info.contentDescription = label;
+                info.customIcon = false;
+                info.intent = launchIntent;
+                info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+                info.flags = AppInfo.initFlags(activityInfo);
+                info.firstInstallTime = activityInfo.getFirstInstallTime();
+                return info;
+            } else {
+                return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
+            }
+        }
+
+        public String getTargetPackage() {
+            String packageName = launchIntent.getPackage();
+            if (packageName == null) {
+                packageName = launchIntent.getComponent() == null ? null :
+                    launchIntent.getComponent().getPackageName();
+            }
+            return packageName;
+        }
+    }
+
+    private static PendingInstallShortcutInfo decode(String encoded, Context context) {
+        try {
+            JSONObject object = (JSONObject) new JSONTokener(encoded).nextValue();
+            Intent launcherIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+
+            if (object.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+                // The is an internal launcher target shortcut.
+                UserHandleCompat user = UserManagerCompat.getInstance(context)
+                        .getUserForSerialNumber(object.getLong(USER_HANDLE_KEY));
+
+                LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
+                        .resolveActivity(launcherIntent, user);
+                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+            }
+
+            Intent data = new Intent();
+            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent);
+            data.putExtra(Intent.EXTRA_SHORTCUT_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);
+            }
+
+            return new PendingInstallShortcutInfo(data, context);
+        } catch (JSONException e) {
+            Log.d(TAG, "Exception reading shortcut to add: " + e);
+        } catch (URISyntaxException e) {
+            Log.d(TAG, "Exception reading shortcut to add: " + e);
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index e5fa032..ec914d8 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -46,12 +46,14 @@
 
     private final AppFilter mAppFilter;
     private final BuildInfo mBuildInfo;
-    private LauncherModel mModel;
-    private IconCache mIconCache;
+    private final LauncherModel mModel;
+    private final IconCache mIconCache;
+
+    private final boolean mIsScreenLarge;
+    private final float mScreenDensity;
+    private final int mLongPressTimeout = 300;
+
     private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
-    private boolean mIsScreenLarge;
-    private float mScreenDensity;
-    private int mLongPressTimeout = 300;
     private boolean mWallpaperChangedSinceLastCheck;
 
     private static WeakReference<LauncherProvider> sLauncherProvider;
@@ -159,9 +161,6 @@
     };
 
     LauncherModel setLauncher(Launcher launcher) {
-        if (mModel == null) {
-            throw new IllegalStateException("setLauncher() called before init()");
-        }
         mModel.initialize(launcher);
         return mModel;
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b04fc3f..11440c0 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -30,6 +30,7 @@
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.LauncherApps.Callback;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -75,7 +76,6 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -110,6 +110,11 @@
     private boolean mIsLoaderTaskRunning;
     private volatile boolean mFlushingWorkerThread;
 
+    /**
+     * Maintain a set of packages per user, for which we added a shortcut on the workspace.
+     */
+    private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
+
     // Specific runnable types that are run on the main thread deferred handler, this allows us to
     // clear all queued binding runnables when the Launcher activity is destroyed.
     private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
@@ -343,7 +348,7 @@
         // Process the updated package state
         Runnable r = new Runnable() {
             public void run() {
-                Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+                Callbacks callbacks = getCallback();
                 if (callbacks != null) {
                     callbacks.updatePackageState(installInfo);
                 }
@@ -356,7 +361,7 @@
         // Process the updated package badge
         Runnable r = new Runnable() {
             public void run() {
-                Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+                Callbacks callbacks = getCallback();
                 if (callbacks != null) {
                     callbacks.updatePackageBadge(packageName);
                 }
@@ -366,7 +371,7 @@
     }
 
     public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
-        final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+        final Callbacks callbacks = getCallback();
 
         if (allAppsApps == null) {
             throw new RuntimeException("allAppsApps must not be null");
@@ -380,7 +385,7 @@
             public void run() {
                 runOnMainThread(new Runnable() {
                     public void run() {
-                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        Callbacks cb = getCallback();
                         if (callbacks == cb && cb != null) {
                             callbacks.bindAppsAdded(null, null, null, allAppsApps);
                         }
@@ -393,7 +398,7 @@
 
     public void addAndBindAddedWorkspaceApps(final Context context,
             final ArrayList<ItemInfo> workspaceApps) {
-        final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+        final Callbacks callbacks = getCallback();
 
         if (workspaceApps == null) {
             throw new RuntimeException("workspaceApps and allAppsApps must not be null");
@@ -425,7 +430,7 @@
                         final Intent launchIntent = a.getIntent();
 
                         // Short-circuit this logic if the icon exists somewhere on the workspace
-                        if (shortcutExists(context, name, launchIntent)) {
+                        if (shortcutExists(context, name, launchIntent, a.user)) {
                             continue;
                         }
 
@@ -484,7 +489,7 @@
                 if (!addedShortcutsFinal.isEmpty()) {
                     runOnMainThread(new Runnable() {
                         public void run() {
-                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            Callbacks cb = getCallback();
                             if (callbacks == cb && cb != null) {
                                 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
                                 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
@@ -844,7 +849,8 @@
      * Returns true if the shortcuts already exists in the database.
      * we identify a shortcut by its title and intent.
      */
-    static boolean shortcutExists(Context context, String title, Intent intent) {
+    static boolean shortcutExists(Context context, String title, Intent intent,
+            UserHandleCompat user) {
         final ContentResolver cr = context.getContentResolver();
         final Intent intentWithPkg, intentWithoutPkg;
 
@@ -863,16 +869,18 @@
             intentWithPkg = intent;
             intentWithoutPkg = intent;
         }
+        String userSerial = Long.toString(UserManagerCompat.getInstance(context)
+                .getSerialNumberForUser(user));
         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
-            new String[] { "title", "intent" }, "title=? and (intent=? or intent=?)",
-            new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0) }, null);
-        boolean result = false;
+            new String[] { "title", "intent", "profileId" },
+            "title=? and (intent=? or intent=?) and profileId=?",
+            new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial },
+            null);
         try {
-            result = c.moveToFirst();
+            return c.moveToFirst();
         } finally {
             c.close();
         }
-        return result;
     }
 
     /**
@@ -1285,11 +1293,9 @@
              mPreviousConfigMcc = currentConfig.mcc;
         } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
                    SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
-            if (mCallbacks != null) {
-                Callbacks callbacks = mCallbacks.get();
-                if (callbacks != null) {
-                    callbacks.bindSearchablesChanged();
-                }
+            Callbacks callbacks = getCallback();
+            if (callbacks != null) {
+                callbacks.bindSearchablesChanged();
             }
         }
     }
@@ -1321,13 +1327,11 @@
      */
     public void startLoaderFromBackground() {
         boolean runLoader = false;
-        if (mCallbacks != null) {
-            Callbacks callbacks = mCallbacks.get();
-            if (callbacks != null) {
-                // Only actually run the loader if they're not paused.
-                if (!callbacks.setLoadOnResume()) {
-                    runLoader = true;
-                }
+        Callbacks callbacks = getCallback();
+        if (callbacks != null) {
+            // Only actually run the loader if they're not paused.
+            if (!callbacks.setLoadOnResume()) {
+                runLoader = true;
             }
         }
         if (runLoader) {
@@ -2811,6 +2815,8 @@
 
             // Clear the list of apps
             mBgAllAppsList.clear();
+            SharedPreferences prefs = mContext.getSharedPreferences(
+                    LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
             for (UserHandleCompat user : profiles) {
                 // Query for the set of apps
                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
@@ -2821,6 +2827,7 @@
                     Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
                 }
                 // Fail if we don't have any apps
+                // TODO: Fix this. Only fail for the current user.
                 if (apps == null || apps.isEmpty()) {
                     return;
                 }
@@ -2839,6 +2846,25 @@
                     // This builds the icon bitmaps.
                     mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));
                 }
+
+                if (!user.equals(UserHandleCompat.myUserHandle())) {
+                    // Add shortcuts for packages which were installed while launcher was dead.
+                    String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
+                            + mUserManager.getSerialNumberForUser(user);
+                    Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
+                    HashSet<String> newPackageSet = new HashSet<String>();
+
+                    for (LauncherActivityInfoCompat info : apps) {
+                        String packageName = info.getComponentName().getPackageName();
+                        if (!packagesAdded.contains(packageName)
+                                && !newPackageSet.contains(packageName)) {
+                            InstallShortcutReceiver.queueInstallShortcut(info, mContext);
+                        }
+                        newPackageSet.add(packageName);
+                    }
+
+                    prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
+                }
             }
             // Huh? Shouldn't this be inside the Runnable below?
             final ArrayList<AppInfo> added = mBgAllAppsList.added;
@@ -2953,6 +2979,30 @@
                         mIconCache.remove(packages[i], mUser);
                         mBgAllAppsList.addPackage(context, packages[i], mUser);
                     }
+
+                    // Auto add shortcuts for added packages.
+                    if (!UserHandleCompat.myUserHandle().equals(mUser)) {
+                        SharedPreferences prefs = context.getSharedPreferences(
+                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
+                                + mUserManager.getSerialNumberForUser(mUser);
+                        Set<String> shortcutSet = new HashSet<String>(
+                                prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
+
+                        for (int i=0; i<N; i++) {
+                            if (!shortcutSet.contains(packages[i])) {
+                                shortcutSet.add(packages[i]);
+                                List<LauncherActivityInfoCompat> activities =
+                                        mLauncherApps.getActivityList(packages[i], mUser);
+                                if (activities != null && !activities.isEmpty()) {
+                                    InstallShortcutReceiver.queueInstallShortcut(
+                                            activities.get(0), context);
+                                }
+                            }
+                        }
+
+                        prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+                    }
                     break;
                 case OP_UPDATE:
                     for (int i=0; i<N; i++) {
@@ -2963,6 +3013,19 @@
                     }
                     break;
                 case OP_REMOVE:
+                    // Remove the packageName for the set of auto-installed shortcuts. This
+                    // will ensure that the shortcut when the app is installed again.
+                    if (!UserHandleCompat.myUserHandle().equals(mUser)) {
+                        SharedPreferences prefs = context.getSharedPreferences(
+                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
+                                + mUserManager.getSerialNumberForUser(mUser);
+                        HashSet<String> shortcutSet = new HashSet<String>(
+                                prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
+                        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++) {
@@ -2991,7 +3054,7 @@
                 mBgAllAppsList.removed.clear();
             }
 
-            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+            final Callbacks callbacks = getCallback();
             if (callbacks == null) {
                 Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
                 return;
@@ -3021,7 +3084,7 @@
 
                 mHandler.post(new Runnable() {
                     public void run() {
-                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        Callbacks cb = getCallback();
                         if (callbacks == cb && cb != null) {
                             callbacks.bindAppsUpdated(modifiedFinal);
                         }
@@ -3134,7 +3197,7 @@
                     mHandler.post(new Runnable() {
 
                         public void run() {
-                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            Callbacks cb = getCallback();
                             if (callbacks == cb && cb != null) {
                                 callbacks.bindShortcutsChanged(
                                         updatedShortcuts, removedShortcuts, mUser);
@@ -3148,7 +3211,7 @@
                 if (!widgets.isEmpty()) {
                     mHandler.post(new Runnable() {
                         public void run() {
-                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            Callbacks cb = getCallback();
                             if (callbacks == cb && cb != null) {
                                 callbacks.bindWidgetsRestored(widgets);
                             }
@@ -3189,14 +3252,11 @@
                 }
 
                 // Remove any queued items from the install queue
-                String spKey = LauncherAppState.getSharedPreferencesKey();
-                SharedPreferences sp =
-                        context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
-                InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
+                InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
                 // Call the components-removed callback
                 mHandler.post(new Runnable() {
                     public void run() {
-                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        Callbacks cb = getCallback();
                         if (callbacks == cb && cb != null) {
                             callbacks.bindComponentsRemoved(
                                     removedPackageNames, removedApps, mUser, removeReason);
@@ -3210,7 +3270,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                    Callbacks cb = getCallback();
                     if (callbacks == cb && cb != null) {
                         callbacks.bindPackagesUpdated(widgetsAndShortcuts);
                     }
@@ -3220,7 +3280,7 @@
             // Write all the logs to disk
             mHandler.post(new Runnable() {
                 public void run() {
-                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                    Callbacks cb = getCallback();
                     if (callbacks == cb && cb != null) {
                         callbacks.dumpLogsToLocalData();
                     }
@@ -3754,4 +3814,8 @@
             Log.d(TAG, "mLoaderTask=null");
         }
     }
+
+    public Callbacks getCallback() {
+        return mCallbacks != null ? mCallbacks.get() : null;
+    }
 }