Add support for launcher shortcuts.

- This CL has no UI but provides the necessary backing for one.
- Adds new item type: ITEM_TYPE_DEEP_SHORTCUT, to distinguish from
  ITEM_TYPE_SHORTCUT. We can reconsider these names.
- Adds ShortcutCache, using LruCache for up to 30 dynamic shortcuts
  (pinned shortcuts are always cached in a HashMap).
- DeepShortcutManager queries for shortcuts and other things like
  pin them. In a future CL it will use the cache, but for now it
  simply makes an RPC for all queries.
- LauncherModel maintains counts for pinned shortcuts, pinning and
  unpinning when counts reach 1 or 0, respectively.
- LauncherModel maintains a map of components to lists of shortcut ids,
  which Launcher gets a copy of after it is changed in the background.
  This will allow us to know how many shortcuts an app has immediately,
  and query for details as the UI is animating.

Change-Id: Ic526f374dd10d72a261bae67f07f098fca8d8bca
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index a5e703e..9e87660 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -41,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.MutableInt;
 import android.util.Pair;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -59,6 +60,9 @@
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.FlagOp;
@@ -68,6 +72,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -82,6 +87,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -118,8 +124,9 @@
     // We start off with everything not loaded.  After that, we assume that
     // our monitoring of the package manager provides all updates and we never
     // need to do a requery.  These are only ever touched from the loader thread.
-    @Thunk boolean mWorkspaceLoaded;
-    @Thunk boolean mAllAppsLoaded;
+    private boolean mWorkspaceLoaded;
+    private boolean mAllAppsLoaded;
+    private boolean mDeepShortcutsLoaded;
 
     /**
      * Set of runnables to be called on the background thread after the workspace binding
@@ -134,6 +141,9 @@
     // Entire list of widgets.
     private final WidgetsModel mBgWidgetsModel;
 
+    // Maps all launcher activities to the id's of their shortcuts (if they have any).
+    private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
+
     // The lock that must be acquired before referencing any static bg data structures.  Unlike
     // other locks, this one can generally be held long-term because we never expect any of these
     // static data structures to be referenced outside of the worker thread except on the first
@@ -159,16 +169,21 @@
     // sBgWorkspaceScreens is the ordered set of workspace screens.
     static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
 
+    // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
+    // times it is pinned.
+    static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
+
     // sPendingPackages is a set of packages which could be on sdcard and are not available yet
     static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
             new HashMap<UserHandleCompat, HashSet<String>>();
 
     // </ only access in worker thread >
 
-    @Thunk IconCache mIconCache;
+    private IconCache mIconCache;
+    private DeepShortcutManager mDeepShortcutManager;
 
-    @Thunk final LauncherAppsCompat mLauncherApps;
-    @Thunk final UserManagerCompat mUserManager;
+    private final LauncherAppsCompat mLauncherApps;
+    private final UserManagerCompat mUserManager;
 
     public interface Callbacks {
         public boolean setLoadOnResume();
@@ -198,18 +213,21 @@
         public void bindWidgetsModel(WidgetsModel model);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
+        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
     }
 
     public interface ItemInfoFilter {
         public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
     }
 
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
+            DeepShortcutManager deepShortcutManager) {
         Context context = app.getContext();
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
         mIconCache = iconCache;
+        mDeepShortcutManager = deepShortcutManager;
 
         mLauncherApps = LauncherAppsCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
@@ -678,6 +696,7 @@
                 switch (modelItem.itemType) {
                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                         if (!sBgWorkspaceItems.contains(modelItem)) {
                             sBgWorkspaceItems.add(modelItem);
@@ -891,6 +910,7 @@
                             // Fall through
                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                                     item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                                 sBgWorkspaceItems.add(item);
@@ -902,6 +922,14 @@
                                     Log.e(TAG, msg);
                                 }
                             }
+                            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                ShortcutInfo shortcutInfo = (ShortcutInfo) item;
+                                ShortcutKey shortcutToPin = new ShortcutKey(
+                                        shortcutInfo.intent.getPackage(),
+                                        shortcutInfo.user,
+                                        shortcutInfo.getDeepShortcutId());
+                                incrementPinnedShortcutCount(shortcutToPin, true /* shouldPin */);
+                            }
                             break;
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                             sBgAppWidgets.add((LauncherAppWidgetInfo) item);
@@ -968,6 +996,14 @@
                                 }
                                 sBgWorkspaceItems.remove(item);
                                 break;
+                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                                ShortcutInfo shortcutInfo = ((ShortcutInfo) item);
+                                ShortcutKey pinnedShortcut = new ShortcutKey(
+                                        shortcutInfo.intent.getPackage(),
+                                        shortcutInfo.user,
+                                        shortcutInfo.getDeepShortcutId());
+                                decrementPinnedShortcutCount(pinnedShortcut);
+                                // Fall through.
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                                 sBgWorkspaceItems.remove(item);
@@ -985,6 +1021,39 @@
     }
 
     /**
+     * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
+     */
+    private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
+        synchronized (sBgLock) {
+            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+            if (count == null || --count.value == 0) {
+                LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
+            }
+        }
+    }
+
+    /**
+     * Increment the count for the given shortcut, pinning it if the count becomes 1.
+     *
+     * As an optimization, the caller can pass shouldPin == false to avoid
+     * unnecessary RPC's if the shortcut is already pinned.
+     */
+    private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
+        synchronized (sBgLock) {
+            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+            if (count == null) {
+                count = new MutableInt(1);
+                sBgPinnedShortcutCounts.put(pinnedShortcut, count);
+            } else {
+                count.value++;
+            }
+            if (shouldPin && count.value == 1) {
+                LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
+            }
+        }
+    }
+
+    /**
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
@@ -1076,28 +1145,28 @@
     @Override
     public void onPackageChanged(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_UPDATE;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackageRemoved(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackageAdded(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_ADD;
-        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
                 user));
     }
 
     @Override
     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
-        enqueuePackageUpdated(
+        enqueueItemUpdatedTask(
                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
     }
 
@@ -1105,7 +1174,7 @@
     public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
         if (!replacing) {
-            enqueuePackageUpdated(new PackageUpdatedTask(
+            enqueueItemUpdatedTask(new PackageUpdatedTask(
                     PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
                     user));
         }
@@ -1113,18 +1182,24 @@
 
     @Override
     public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
-        enqueuePackageUpdated(new PackageUpdatedTask(
+        enqueueItemUpdatedTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_SUSPEND, packageNames,
                 user));
     }
 
     @Override
     public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
-        enqueuePackageUpdated(new PackageUpdatedTask(
+        enqueueItemUpdatedTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_UNSUSPEND, packageNames,
                 user));
     }
 
+    @Override
+    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+            UserHandleCompat user) {
+        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user));
+    }
+
     /**
      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
      * ACTION_PACKAGE_CHANGED.
@@ -1145,7 +1220,7 @@
                 LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
             UserHandleCompat user = UserHandleCompat.fromIntent(intent);
             if (user != null) {
-                enqueuePackageUpdated(new PackageUpdatedTask(
+                enqueueItemUpdatedTask(new PackageUpdatedTask(
                         PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
                         new String[0], user));
             }
@@ -1170,6 +1245,8 @@
             stopLoaderLocked();
             if (resetAllAppsLoaded) mAllAppsLoaded = false;
             if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+            // Always reset deep shortcuts loaded.
+            mDeepShortcutsLoaded = false;
         }
     }
 
@@ -1256,6 +1333,7 @@
      *   - workspace icons
      *   - widgets
      *   - all apps icons
+     *   - deep shortcuts within apps
      */
     private class LoaderTask implements Runnable {
         private Context mContext;
@@ -1387,6 +1465,12 @@
                 // second step
                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                 loadAndBindAllApps();
+
+                waitForIdle();
+
+                // third step
+                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
+                loadAndBindDeepShortcuts();
             }
 
             // Clear out this reference, otherwise we end up holding it until all of the
@@ -1538,6 +1622,7 @@
                 sBgFolders.clear();
                 sBgItemsIdMap.clear();
                 sBgWorkspaceScreens.clear();
+                sBgPinnedShortcutCounts.clear();
             }
         }
 
@@ -1581,6 +1666,7 @@
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<>();
                 final ArrayList<Long> restoredRows = new ArrayList<>();
+                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1631,6 +1717,13 @@
                         long serialNo = mUserManager.getSerialNumberForUser(user);
                         allUsers.put(serialNo, user);
                         quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
+
+                        List<ShortcutInfoCompat> pinnedShortcuts = mDeepShortcutManager
+                                .queryForPinnedShortcuts(null, user);
+                        for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
+                            shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
+                                    shortcut);
+                        }
                     }
 
                     ShortcutInfo info;
@@ -1653,6 +1746,7 @@
                             switch (itemType) {
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                                 id = c.getLong(idIndex);
                                 intentDescription = c.getString(intentIndex);
                                 serialNumber = c.getInt(profileIdIndex);
@@ -1815,7 +1909,37 @@
                                     info = getAppShortcutInfo(intent, user, context, c,
                                             cursorIconInfo.iconIndex, titleIndex,
                                             allowMissingTarget, useLowResIcon);
-                                } else {
+                                } else if (itemType ==
+                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                    String shortcutId = intent.getStringExtra(
+                                            ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
+                                    String packageName = intent.getPackage();
+                                    ShortcutKey key = new ShortcutKey(intent.getPackage(),
+                                            user, shortcutId);
+                                    ShortcutInfoCompat pinnedShortcut =
+                                            shortcutKeyToPinnedShortcuts.get(key);
+                                    boolean shouldPin = false; // It's already pinned.
+                                    if (pinnedShortcut == null) {
+                                        // It shouldn't be possible for a shortcut to be on the
+                                        // workspace without being pinned, but if one somehow is,
+                                        // we should pin it now to get back to a good state.
+                                        Log.w(TAG, "Shortcut was on workspace but wasn't pinned");
+                                        // Get full details; incrementing the count will pin it.
+                                        List<ShortcutInfoCompat> fullDetails = mDeepShortcutManager
+                                                .queryForFullDetails(packageName,
+                                                Collections.singletonList(shortcutId), user);
+                                        if (fullDetails == null || fullDetails.isEmpty()) {
+                                            itemsToRemove.add(id);
+                                            continue;
+                                        } else {
+                                            pinnedShortcut = fullDetails.get(0);
+                                            shouldPin = true;
+                                        }
+                                    }
+                                    incrementPinnedShortcutCount(key, shouldPin);
+                                    info = ShortcutInfo.fromDeepShortcutInfo(pinnedShortcut,
+                                            context, launcherApps);
+                                } else { // item type == ITEM_TYPE_SHORTCUT
                                     info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
 
                                     // Shortcuts are only available on the primary profile
@@ -2094,6 +2218,15 @@
                     }
                 }
 
+                // Unpin shortcuts that don't exist on the workspace.
+                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
+                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+                    if (numTimesPinned == null || numTimesPinned.value == 0) {
+                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
+                        mDeepShortcutManager.unpinShortcut(key);
+                    }
+                }
+
                 // Sort all the folder items and make sure the first 3 items are high resolution.
                 for (FolderInfo folder : sBgFolders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
@@ -2622,6 +2755,27 @@
             }
         }
 
+        private void loadAndBindDeepShortcuts() {
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
+            }
+            if (!mDeepShortcutsLoaded) {
+                mBgDeepShortcutMap.clear();
+                for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+                    List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
+                            .queryForAllShortcuts(user);
+                    updateDeepShortcutMap(null, shortcuts);
+                }
+                synchronized (LoaderTask.this) {
+                    if (mStopped) {
+                        return;
+                    }
+                    mDeepShortcutsLoaded = true;
+                }
+            }
+            bindDeepShortcutMapOnMainThread();
+        }
+
         public void dumpState() {
             synchronized (sBgLock) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -2632,6 +2786,40 @@
         }
     }
 
+    // Clear all the shortcuts for the given package, and re-add the new shortcuts.
+    private void updateDeepShortcutMap(String packageName, List<ShortcutInfoCompat> shortcuts) {
+        // Remove all keys associated with the given package.
+        if (packageName != null) {
+            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
+            while (keysIter.hasNext()) {
+                if (keysIter.next().componentName.getPackageName().equals(packageName)) {
+                    keysIter.remove();
+                }
+            }
+        }
+
+        // Now add the new shortcuts to the map.
+        for (ShortcutInfoCompat shortcut : shortcuts) {
+            ComponentKey targetComponent
+                    = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+            mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+        }
+    }
+
+    private void bindDeepShortcutMapOnMainThread() {
+        final MultiHashMap<ComponentKey, String> shortcutMapCopy = new MultiHashMap<>();
+        shortcutMapCopy.putAll(mBgDeepShortcutMap);
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                Callbacks callbacks = getCallback();
+                if (callbacks != null) {
+                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
+                }
+            }
+        });
+    }
+
     /**
      * Called when the icons for packages have been updated in the icon cache.
      */
@@ -2657,19 +2845,7 @@
             mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
         }
 
-        if (!updatedShortcuts.isEmpty()) {
-            final UserHandleCompat userFinal = user;
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindShortcutsChanged(updatedShortcuts,
-                                new ArrayList<ShortcutInfo>(), userFinal);
-                    }
-                }
-            });
-        }
+        bindUpdatedShortcuts(updatedShortcuts, user);
 
         if (!updatedApps.isEmpty()) {
             mHandler.post(new Runnable() {
@@ -2684,7 +2860,25 @@
         }
     }
 
-    void enqueuePackageUpdated(PackageUpdatedTask task) {
+    private void bindUpdatedShortcuts(final ArrayList<ShortcutInfo> updatedShortcuts,
+            UserHandleCompat user) {
+        if (!updatedShortcuts.isEmpty()) {
+            final Callbacks callbacks = getCallback();
+            final UserHandleCompat userFinal = user;
+            mHandler.post(new Runnable() {
+
+                public void run() {
+                    Callbacks cb = getCallback();
+                    if (cb != null && callbacks == cb) {
+                        cb.bindShortcutsChanged(updatedShortcuts,
+                                new ArrayList<ShortcutInfo>(), userFinal);
+                    }
+                }
+            });
+        }
+    }
+
+    void enqueueItemUpdatedTask(Runnable task) {
         sWorker.post(task);
     }
 
@@ -2712,11 +2906,11 @@
                         }
                     }
                     if (!packagesRemoved.isEmpty()) {
-                        enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
+                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
                                 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
                     }
                     if (!packagesUnavailable.isEmpty()) {
-                        enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
+                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
                                 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
                     }
                 }
@@ -3074,6 +3268,60 @@
         }
     }
 
+    private class ShortcutsChangedTask implements Runnable {
+        private String mPackageName;
+        private List<ShortcutInfoCompat> mShortcuts;
+        private UserHandleCompat mUser;
+
+        public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+                UserHandleCompat user) {
+            mPackageName = packageName;
+            mShortcuts = shortcuts;
+            mUser = user;
+        }
+
+        @Override
+        public void run() {
+            mDeepShortcutManager.onShortcutsChanged(mShortcuts);
+
+            Map<String, ShortcutInfoCompat> idsToShortcuts = new HashMap<>();
+            for (ShortcutInfoCompat shortcut : mShortcuts) {
+                idsToShortcuts.put(shortcut.getId(), shortcut);
+            }
+
+            // Find ShortcutInfo's that have changed on the workspace.
+            MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+            for (ItemInfo itemInfo : sBgItemsIdMap) {
+                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    ShortcutInfo si = (ShortcutInfo) itemInfo;
+                    String shortcutId = si.getDeepShortcutId();
+                    if (idsToShortcuts.containsKey(shortcutId)) {
+                        idsToWorkspaceShortcutInfos.addToList(shortcutId, si);
+                    }
+                }
+            }
+
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
+                    mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+            Context context = LauncherAppState.getInstance().getContext();
+            for (ShortcutInfoCompat fullDetails : shortcuts) {
+                List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+                        .get(fullDetails.getId());
+                for (ShortcutInfo shortcutInfo : shortcutInfos) {
+                    shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context, mLauncherApps);
+                    updatedShortcutInfos.add(shortcutInfo);
+                }
+            }
+            bindUpdatedShortcuts(updatedShortcutInfos, mUser);
+
+            // Update the deep shortcut map, in case the list of ids has changed for an activity.
+            updateDeepShortcutMap(mPackageName, mShortcuts);
+            bindDeepShortcutMapOnMainThread();
+        }
+    }
+
     private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
         mHandler.post(new Runnable() {
             @Override