Adding quiet mode support for shortcuts

> LauncherApps returns empty list when the user is locked. Not relying on
LauncherApps in this case
> When the user is locked, removing all dynamic shortcuts
> Loading shortcuts from DB when the user is locked
> Verifying the shortcuts again when the user is available

Bug: 30411561
Change-Id: Ib6eb372c5b009cadb86a8f6e781f3f3cbf787ceb
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5f5ac2e..5ce98e2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -117,8 +117,10 @@
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.TestingUtils;
@@ -2655,14 +2657,17 @@
         final ShortcutInfo shortcut = (ShortcutInfo) tag;
 
         if (shortcut.isDisabled != 0) {
-            if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SUSPENDED) != 0
-                || (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_QUIET_USER) != 0) {
-                // Launch activity anyway, framework will tell the user why the app is suspended.
+            if ((shortcut.isDisabled &
+                    ~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
+                    ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
+                // If the app is only disabled because of the above flags, launch activity anyway.
+                // Framework will tell the user why the app is suspended.
             } else {
                 int error = R.string.activity_not_available;
                 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
                     error = R.string.safemode_shortcut_error;
-                } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0) {
+                } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
                     error = R.string.shortcut_not_available;
                 }
                 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
@@ -4208,8 +4213,12 @@
 
     /**
      * Some shortcuts were updated in the background.
-     *
      * Implementation of the method from LauncherModel.Callbacks.
+     *
+     * @param updated list of shortcuts which have changed.
+     * @param removed list of shortcuts which were deleted in the background. This can happen when
+     *                an app gets removed from the system or some of its components are no longer
+     *                available.
      */
     @Override
     public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
@@ -4228,13 +4237,28 @@
         }
 
         if (!removed.isEmpty()) {
-            HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
+            HashSet<ComponentName> removedComponents = new HashSet<>();
+            HashSet<ShortcutKey> removedDeepShortcuts = new HashSet<>();
+
             for (ShortcutInfo si : removed) {
-                removedComponents.add(si.getTargetComponent());
+                if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si));
+                } else {
+                    removedComponents.add(si.getTargetComponent());
+                }
             }
-            mWorkspace.removeItemsByComponentName(removedComponents, user);
-            // Notify the drag controller
-            mDragController.onAppsRemoved(new HashSet<String>(), removedComponents);
+
+            if (!removedComponents.isEmpty()) {
+                ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(removedComponents, user);
+                mWorkspace.removeItemsByMatcher(matcher);
+                mDragController.onAppsRemoved(matcher);
+            }
+
+            if (!removedDeepShortcuts.isEmpty()) {
+                ItemInfoMatcher matcher = ItemInfoMatcher.ofShortcutKeys(removedDeepShortcuts);
+                mWorkspace.removeItemsByMatcher(matcher);
+                mDragController.onAppsRemoved(matcher);
+            }
         }
     }
 
@@ -4277,14 +4301,16 @@
             return;
         }
         if (!packageNames.isEmpty()) {
-            mWorkspace.removeItemsByPackageName(packageNames, user);
+            ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageNames, user);
+            mWorkspace.removeItemsByMatcher(matcher);
+            mDragController.onAppsRemoved(matcher);
+
         }
         if (!components.isEmpty()) {
-            mWorkspace.removeItemsByComponentName(components, user);
+            ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(components, user);
+            mWorkspace.removeItemsByMatcher(matcher);
+            mDragController.onAppsRemoved(matcher);
         }
-        // Notify the drag controller
-        mDragController.onAppsRemoved(packageNames, components);
-
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4bc76fb..7861a10 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -109,10 +109,11 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         // For handling managed profiles
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
         // For extracting colors from the wallpaper
         if (Utilities.isNycOrAbove()) {
             // TODO: add a broadcast entry to the manifest for pre-N.
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b465b3a..89a6836 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -322,8 +322,8 @@
             @Override
             public void run() {
                 synchronized (sBgLock) {
-                    final ArrayList<ShortcutInfo> updates = new ArrayList<>();
-                    final UserHandleCompat user = UserHandleCompat.myUserHandle();
+                    ArrayList<ShortcutInfo> updates = new ArrayList<>();
+                    UserHandleCompat user = UserHandleCompat.myUserHandle();
 
                     for (ItemInfo info : sBgItemsIdMap) {
                         if (info instanceof ShortcutInfo) {
@@ -345,19 +345,7 @@
                         }
                     }
 
-                    if (!updates.isEmpty()) {
-                        // Push changes to the callback.
-                        Runnable r = new Runnable() {
-                            public void run() {
-                                Callbacks callbacks = getCallback();
-                                if (callbacks != null) {
-                                    callbacks.bindShortcutsChanged(updates,
-                                            new ArrayList<ShortcutInfo>(), user);
-                                }
-                            }
-                        };
-                        mHandler.post(r);
-                    }
+                    bindUpdatedShortcuts(updates, user);
                 }
             }
         };
@@ -926,12 +914,8 @@
                                 }
                             }
                             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 */);
+                                incrementPinnedShortcutCount(
+                                        ShortcutKey.fromItemInfo(item), true /* shouldPin */);
                             }
                             break;
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -1000,12 +984,7 @@
                                 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);
+                                decrementPinnedShortcutCount(ShortcutKey.fromItemInfo(item));
                                 // Fall through.
                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
@@ -1215,17 +1194,28 @@
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
-        } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
-                || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+        } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+                || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
             UserManagerCompat.getInstance(context).enableAndResetCache();
             forceReload();
-        } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
-                LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
+        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
+                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
+                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
             UserHandleCompat user = UserHandleCompat.fromIntent(intent);
             if (user != null) {
-                enqueueItemUpdatedTask(new PackageUpdatedTask(
-                        PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
-                        new String[0], user));
+                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
+                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
+                    enqueueItemUpdatedTask(new PackageUpdatedTask(
+                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
+                            new String[0], user));
+                }
+
+                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
+                // we need to run the state change task again.
+                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
+                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
+                    enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
+                }
             }
         } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
             ExtractionUtils.startColorExtractionServiceIfNecessary(context);
@@ -1702,8 +1692,6 @@
                     final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
                     final int intentIndex = c.getColumnIndexOrThrow
                             (LauncherSettings.Favorites.INTENT);
-                    final int titleIndex = c.getColumnIndexOrThrow
-                            (LauncherSettings.Favorites.TITLE);
                     final int containerIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.CONTAINER);
                     final int itemTypeIndex = c.getColumnIndexOrThrow(
@@ -1730,20 +1718,27 @@
                             LauncherSettings.Favorites.PROFILE_ID);
                     final int optionsIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.OPTIONS);
-                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
+                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
 
                     final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
                     final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
+                    final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
                     for (UserHandleCompat user : mUserManager.getUserProfiles()) {
                         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);
+                        boolean userUnlocked = mUserManager.isUserUnlocked(user);
+                        unlockedUsers.put(serialNo, userUnlocked);
+
+                        // We can only query for shortcuts when the user is unlocked.
+                        if (userUnlocked) {
+                            List<ShortcutInfoCompat> pinnedShortcuts = mDeepShortcutManager
+                                    .queryForPinnedShortcuts(null, user);
+                            for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
+                                shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
+                                        shortcut);
+                            }
                         }
                     }
 
@@ -1907,9 +1902,8 @@
 
                                 if (itemReplaced) {
                                     if (user.equals(UserHandleCompat.myUserHandle())) {
-                                        info = getAppShortcutInfo(intent, user, context, null,
-                                                cursorIconInfo.iconIndex, titleIndex,
-                                                false, useLowResIcon);
+                                        info = getAppShortcutInfo(intent, user, null,
+                                                cursorIconInfo, false, useLowResIcon);
                                     } else {
                                         // Don't replace items for other profiles.
                                         itemsToRemove.add(id);
@@ -1917,8 +1911,8 @@
                                     }
                                 } else if (restored) {
                                     if (user.equals(UserHandleCompat.myUserHandle())) {
-                                        info = getRestoredItemInfo(c, titleIndex, intent,
-                                                promiseType, itemType, cursorIconInfo, context);
+                                        info = getRestoredItemInfo(c, intent,
+                                                promiseType, itemType, cursorIconInfo);
                                         intent = getRestoredItemIntent(c, context, intent);
                                     } else {
                                         // Don't restore items for other profiles.
@@ -1927,46 +1921,34 @@
                                     }
                                 } else if (itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    info = getAppShortcutInfo(intent, user, context, c,
-                                            cursorIconInfo.iconIndex, titleIndex,
-                                            allowMissingTarget, useLowResIcon);
+                                    info = getAppShortcutInfo(intent, user, c,
+                                            cursorIconInfo, allowMissingTarget, useLowResIcon);
                                 } 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()) {
-                                            // There are no details for the shortcut. If this is due
-                                            // to a SecurityException, keep it in the database so
-                                            // we can restore the icon when the launcher regains
-                                            // permission. Otherwise remove the icon from the db.
-                                            if (!mDeepShortcutManager.wasLastCallSuccess()) {
-                                                itemsToRemove.add(id);
-                                                continue;
-                                            }
-                                        } else {
-                                            pinnedShortcut = fullDetails.get(0);
-                                            shouldPin = true;
+
+                                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
+                                    if (unlockedUsers.get(serialNumber)) {
+                                        ShortcutInfoCompat pinnedShortcut =
+                                                shortcutKeyToPinnedShortcuts.get(key);
+                                        if (pinnedShortcut == null) {
+                                            // The shortcut is no longer valid.
+                                            itemsToRemove.add(id);
+                                            continue;
                                         }
+                                        info = new ShortcutInfo(pinnedShortcut, context);
+                                        intent = info.intent;
+                                    } else {
+                                        // Create a shortcut info in disabled mode for now.
+                                        info = new ShortcutInfo();
+                                        info.user = user;
+                                        info.itemType = itemType;
+                                        loadInfoFromCursor(info, c, cursorIconInfo);
+
+                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
                                     }
-                                    incrementPinnedShortcutCount(key, shouldPin);
-                                    info = new ShortcutInfo(pinnedShortcut, context);
+                                    incrementPinnedShortcutCount(key, false /* shouldPin */);
                                 } else { // item type == ITEM_TYPE_SHORTCUT
-                                    info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
+                                    info = getShortcutInfo(c, cursorIconInfo);
 
                                     // Shortcuts are only available on the primary profile
                                     if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
@@ -2046,7 +2028,7 @@
                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
 
                                 // Do not trim the folder label, as is was set by the user.
-                                folderInfo.title = c.getString(titleIndex);
+                                folderInfo.title = c.getString(cursorIconInfo.titleIndex);
                                 folderInfo.id = id;
                                 folderInfo.container = container;
                                 folderInfo.screenId = c.getInt(screenIndex);
@@ -2800,9 +2782,11 @@
             if (!mDeepShortcutsLoaded) {
                 mBgDeepShortcutMap.clear();
                 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
-                    List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
-                            .queryForAllShortcuts(user);
-                    updateDeepShortcutMap(null, user, shortcuts);
+                    if (mUserManager.isUserUnlocked(user)) {
+                        List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
+                                .queryForAllShortcuts(user);
+                        updateDeepShortcutMap(null, user, shortcuts);
+                    }
                 }
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
@@ -2907,18 +2891,23 @@
         }
     }
 
-    private void bindUpdatedShortcuts(final ArrayList<ShortcutInfo> updatedShortcuts,
-            UserHandleCompat user) {
-        if (!updatedShortcuts.isEmpty()) {
+    private void bindUpdatedShortcuts(
+            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
+        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+    }
+
+    private void bindUpdatedShortcuts(
+            final ArrayList<ShortcutInfo> updatedShortcuts,
+            final ArrayList<ShortcutInfo> removedShortcuts,
+            final UserHandleCompat user) {
+        if (!updatedShortcuts.isEmpty() || !removedShortcuts.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);
+                        cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
                     }
                 }
             });
@@ -3210,22 +3199,11 @@
                     }
                 }
 
-                if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
-                    final Callbacks callbacks = getCallback();
-                    mHandler.post(new Runnable() {
-
-                        public void run() {
-                            Callbacks cb = getCallback();
-                            if (callbacks == cb && cb != null) {
-                                callbacks.bindShortcutsChanged(
-                                        updatedShortcuts, removedShortcuts, mUser);
-                            }
-                        }
-                    });
-                    if (!removedShortcuts.isEmpty()) {
-                        deleteItemsFromDatabase(context, removedShortcuts);
-                    }
+                bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+                if (!removedShortcuts.isEmpty()) {
+                    deleteItemsFromDatabase(context, removedShortcuts);
                 }
+
                 if (!widgets.isEmpty()) {
                     final Callbacks callbacks = getCallback();
                     mHandler.post(new Runnable() {
@@ -3387,6 +3365,74 @@
         }
     }
 
+    /**
+     * Task to handle changing of lock state of the user
+     */
+    private class UserLockStateChangedTask implements Runnable {
+
+        private final UserHandleCompat mUser;
+
+        public UserLockStateChangedTask(UserHandleCompat user) {
+            mUser = user;
+        }
+
+        @Override
+        public void run() {
+            boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
+            Context context = mApp.getContext();
+
+            HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
+            if (isUserUnlocked) {
+                for (ShortcutInfoCompat shortcut :
+                        mDeepShortcutManager.queryForPinnedShortcuts(null, mUser)) {
+                    pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
+                }
+            }
+
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+            ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
+            for (ItemInfo itemInfo : sBgItemsIdMap) {
+                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                        && mUser.equals(itemInfo.user)) {
+                    ShortcutInfo si = (ShortcutInfo) itemInfo;
+                    if (isUserUnlocked) {
+                        ShortcutInfoCompat shortcut =
+                                pinnedShortcuts.get(ShortcutKey.fromItemInfo(si));
+                        // We couldn't verify the shortcut during loader. If its no longer available
+                        // (probably due to clear data), delete the workspace item as well
+                        if (shortcut == null) {
+                            deletedShortcutInfos.add(si);
+                            continue;
+                        }
+                        si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                        si.updateFromDeepShortcutInfo(shortcut, context);
+                    } else {
+                        si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    }
+                    updatedShortcutInfos.add(si);
+                }
+            }
+            bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
+            if (!deletedShortcutInfos.isEmpty()) {
+                deleteItemsFromDatabase(context, deletedShortcutInfos);
+            }
+
+            // Remove shortcut id map for that user
+            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
+            while (keysIter.hasNext()) {
+                if (keysIter.next().user.equals(mUser)) {
+                    keysIter.remove();
+                }
+            }
+
+            if (isUserUnlocked) {
+                updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
+            }
+            bindDeepShortcuts();
+        }
+    }
+
     private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
         mHandler.post(new Runnable() {
             @Override
@@ -3447,12 +3493,12 @@
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
      */
-    public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
-            int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
+    public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
+            int promiseType, int itemType, CursorIconInfo iconInfo) {
         final ShortcutInfo info = new ShortcutInfo();
         info.user = UserHandleCompat.myUserHandle();
 
-        Bitmap icon = iconInfo.loadIcon(c, info, context);
+        Bitmap icon = iconInfo.loadIcon(c, info);
         // the fallback icon
         if (icon == null) {
             mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
@@ -3461,13 +3507,13 @@
         }
 
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
-            String title = (c != null) ? c.getString(titleIndex) : null;
+            String title = iconInfo.getTitle(c);
             if (!TextUtils.isEmpty(title)) {
                 info.title = Utilities.trim(title);
             }
         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
             if (TextUtils.isEmpty(info.title)) {
-                info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
+                info.title = iconInfo.getTitle(c);
             }
         } else {
             throw new InvalidParameterException("Invalid restoreType " + promiseType);
@@ -3504,7 +3550,7 @@
      * If c is not null, then it will be used to fill in missing data like the title and icon.
      */
     public ShortcutInfo getAppShortcutInfo(Intent intent,
-            UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
+            UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
             boolean allowMissingTarget, boolean useLowResIcon) {
         if (user == null) {
             Log.d(TAG, "Null user found in getShortcutInfo");
@@ -3529,7 +3575,7 @@
         final ShortcutInfo info = new ShortcutInfo();
         mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
         if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
-            Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
+            Bitmap icon = iconInfo.loadIcon(c);
             info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
         }
 
@@ -3539,7 +3585,7 @@
 
         // from the db
         if (TextUtils.isEmpty(info.title) && c != null) {
-            info.title =  Utilities.trim(c.getString(titleIndex));
+            info.title = iconInfo.getTitle(c);
         }
 
         // fall back to the class name of the activity
@@ -3603,8 +3649,7 @@
     /**
      * Make an ShortcutInfo object for a shortcut that isn't an application.
      */
-    @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
-            int titleIndex, CursorIconInfo iconInfo) {
+    @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
         final ShortcutInfo info = new ShortcutInfo();
         // Non-app shortcuts are only supported for current user.
         info.user = UserHandleCompat.myUserHandle();
@@ -3612,16 +3657,22 @@
 
         // TODO: If there's an explicit component and we can't install that, delete it.
 
-        info.title = Utilities.trim(c.getString(titleIndex));
+        loadInfoFromCursor(info, c, iconInfo);
+        return info;
+    }
 
-        Bitmap icon = iconInfo.loadIcon(c, info, context);
+    /**
+     * Make an ShortcutInfo object for a shortcut that isn't an application.
+     */
+    public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
+        info.title = iconInfo.getTitle(c);
+        Bitmap icon = iconInfo.loadIcon(c, info);
         // the fallback icon
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
             info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
-        return info;
     }
 
     ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 00ac9bd..b460944 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -117,13 +117,17 @@
      */
     public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
 
-
     /**
      * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
      */
     public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
 
     /**
+     * Indicates that the icon is disabled as the user partition is currently locked.
+     */
+    public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
+
+    /**
      * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
      * sd-card is not available).
      */
@@ -206,7 +210,6 @@
     public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         user = shortcutInfo.getUserHandle();
         itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        intent = shortcutInfo.makeIntent(context);
         flags = 0;
         updateFromDeepShortcutInfo(shortcutInfo, context);
     }
@@ -291,6 +294,8 @@
     }
 
     public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
+        // {@link ShortcutInfoCompat#getActivity} can change during an update. Recreate the intent
+        intent = shortcutInfo.makeIntent(context);
         title = shortcutInfo.getShortLabel();
 
         CharSequence label = shortcutInfo.getLongLabel();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5663444..5105c1a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -80,6 +80,7 @@
 import com.android.launcher3.shortcuts.ShortcutsContainerListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiStateAlphaController;
 import com.android.launcher3.util.Thunk;
@@ -4023,66 +4024,30 @@
         });
     }
 
-    // Removes ALL items that match a given package name, this is usually called when a package
-    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
-    // belong to that package.
-    void removeItemsByPackageName(final HashSet<String> packageNames, final UserHandleCompat user) {
-        // Filter out all the ItemInfos that this is going to affect
-        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
-        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
-        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
-        for (CellLayout layoutParent : cellLayouts) {
-            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
-            int childCount = layout.getChildCount();
-            for (int i = 0; i < childCount; ++i) {
-                View view = layout.getChildAt(i);
-                infos.add((ItemInfo) view.getTag());
-            }
-        }
-        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info,
-                                      ComponentName cn) {
-                if (packageNames.contains(cn.getPackageName())
-                        && info.user.equals(user)) {
-                    cns.add(cn);
-                    return true;
-                }
-                return false;
-            }
-        };
-        LauncherModel.filterItemInfos(infos, filter);
-
-        // Remove the affected components
-        removeItemsByComponentName(cns, user);
-    }
-
     /**
-     * Removes items that match the item info specified. When applications are removed
+     * Removes items that match the {@param matcher}. When applications are removed
      * as a part of an update, this is called to ensure that other widgets and application
      * shortcuts are not removed.
      */
-    void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
-            final UserHandleCompat user) {
+    public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
         for (final CellLayout layoutParent: cellLayouts) {
             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
 
-            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
+            final HashMap<ItemInfo, View> children = new HashMap<>();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 final View view = layout.getChildAt(j);
                 children.put((ItemInfo) view.getTag(), view);
             }
 
-            final ArrayList<View> childrenToRemove = new ArrayList<View>();
-            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
-                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
+            final ArrayList<View> childrenToRemove = new ArrayList<>();
+            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
             LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
                 @Override
                 public boolean filterItem(ItemInfo parent, ItemInfo info,
-                                          ComponentName cn) {
+                        ComponentName cn) {
                     if (parent instanceof FolderInfo) {
-                        if (componentNames.contains(cn) && info.user.equals(user)) {
+                        if (matcher.matches(info, cn)) {
                             FolderInfo folder = (FolderInfo) parent;
                             ArrayList<ShortcutInfo> appsToRemove;
                             if (folderAppsToRemove.containsKey(folder)) {
@@ -4095,7 +4060,7 @@
                             return true;
                         }
                     } else {
-                        if (componentNames.contains(cn) && info.user.equals(user)) {
+                        if (matcher.matches(info, cn)) {
                             childrenToRemove.add(children.get(info));
                             return true;
                         }
@@ -4227,7 +4192,7 @@
         HashSet<String> packages = new HashSet<>(1);
         packages.add(packageName);
         LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
-        removeItemsByPackageName(packages, user);
+        removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
     }
 
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 3c1013d..645e68a 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -31,15 +31,6 @@
 
 public abstract class LauncherAppsCompat {
 
-    public static final String ACTION_MANAGED_PROFILE_ADDED =
-            "android.intent.action.MANAGED_PROFILE_ADDED";
-    public static final String ACTION_MANAGED_PROFILE_REMOVED =
-            "android.intent.action.MANAGED_PROFILE_REMOVED";
-    public static final String ACTION_MANAGED_PROFILE_AVAILABLE =
-            "android.intent.action.MANAGED_PROFILE_AVAILABLE";
-    public static final String ACTION_MANAGED_PROFILE_UNAVAILABLE =
-            "android.intent.action.MANAGED_PROFILE_UNAVAILABLE";
-
     public interface OnAppsChangedCallbackCompat {
         void onPackageRemoved(String packageName, UserHandleCompat user);
         void onPackageAdded(String packageName, UserHandleCompat user);
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 978f922..29ed5d9 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -57,4 +57,5 @@
     public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
     public abstract long getUserCreationTime(UserHandleCompat user);
     public abstract boolean isQuietModeEnabled(UserHandleCompat user);
+    public abstract boolean isUserUnlocked(UserHandleCompat user);
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index a006efd..e678ffa 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -55,4 +55,9 @@
     public boolean isQuietModeEnabled(UserHandleCompat user) {
         return false;
     }
+
+    @Override
+    public boolean isUserUnlocked(UserHandleCompat user) {
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
index ae41e68..771d141 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java
@@ -16,15 +16,11 @@
 
 package com.android.launcher3.compat;
 
+import android.annotation.TargetApi;
 import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
+import android.os.Build;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-//TODO: Once gogole3 SDK is updated to N, add @TargetApi(Build.VERSION_CODES.N)
+@TargetApi(Build.VERSION_CODES.N)
 public class UserManagerCompatVN extends UserManagerCompatVL {
 
     private static final String TAG = "UserManagerCompatVN";
@@ -35,21 +31,17 @@
 
     @Override
     public boolean isQuietModeEnabled(UserHandleCompat user) {
-        if (user != null) {
-            try {
-                //TODO: Replace with proper API call once google3 SDK is updated.
-                Method isQuietModeEnabledMethod = UserManager.class.getMethod("isQuietModeEnabled",
-                        UserHandle.class);
-                return (boolean) isQuietModeEnabledMethod.invoke(mUserManager, user.getUser());
-            } catch (NoSuchMethodError | NoSuchMethodException | IllegalAccessException
-                    | InvocationTargetException e) {
-                Log.e(TAG, "Running on N without isQuietModeEnabled", e);
-            } catch (IllegalArgumentException e) {
-                // TODO remove this when API is fixed to not throw this
-                // when called on user that isn't a managed profile.
-            }
+        return mUserManager.isQuietModeEnabled(user.getUser());
+    }
+
+    @Override
+    public boolean isUserUnlocked(UserHandleCompat user) {
+        // TODO: Remove the try-catch block when the API permission has been relaxed (b/30475753)
+        try {
+            return mUserManager.isUserUnlocked(user.getUser());
+        } catch (RuntimeException e) {
+            return !isQuietModeEnabled(user);
         }
-        return false;
     }
 }
 
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 3447d51..9da1cb3 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
@@ -325,22 +326,14 @@
         endDrag();
     }
 
-    public void onAppsRemoved(final HashSet<String> packageNames, HashSet<ComponentName> cns) {
+    public void onAppsRemoved(ItemInfoMatcher matcher) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
-            Object rawDragInfo = mDragObject.dragInfo;
-            if (rawDragInfo instanceof ShortcutInfo) {
-                ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
-                for (ComponentName componentName : cns) {
-                    if (dragInfo.intent != null) {
-                        ComponentName cn = dragInfo.intent.getComponent();
-                        boolean isSameComponent = cn != null && (cn.equals(componentName) ||
-                                packageNames.contains(cn.getPackageName()));
-                        if (isSameComponent) {
-                            cancelDrag();
-                            return;
-                        }
-                    }
+            ItemInfo dragInfo = mDragObject.dragInfo;
+            if (dragInfo instanceof ShortcutInfo) {
+                ComponentName cn = dragInfo.getTargetComponent();
+                if (cn != null && matcher.matches(dragInfo, cn)) {
+                    cancelDrag();
                 }
             }
         }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 450c36d..36bb2b5 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -98,7 +98,7 @@
     public void unpinShortcut(final ShortcutKey key) {
         if (Utilities.isNycMR1OrAbove()) {
             String packageName = key.componentName.getPackageName();
-            String id = key.id;
+            String id = key.getId();
             UserHandleCompat user = key.user;
             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
             pinnedIds.remove(id);
@@ -120,7 +120,7 @@
     public void pinShortcut(final ShortcutKey key) {
         if (Utilities.isNycMR1OrAbove()) {
             String packageName = key.componentName.getPackageName();
-            String id = key.id;
+            String id = key.getId();
             UserHandleCompat user = key.user;
             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
             pinnedIds.add(id);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index c15f91d..4053030 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -1,7 +1,9 @@
 package com.android.launcher3.shortcuts;
 
 import android.content.ComponentName;
+import android.content.Intent;
 
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.ComponentKey;
 
@@ -9,16 +11,28 @@
  * A key that uniquely identifies a shortcut using its package, id, and user handle.
  */
 public class ShortcutKey extends ComponentKey {
-    final String id;
 
     public ShortcutKey(String packageName, UserHandleCompat user, String id) {
         // Use the id as the class name.
         super(new ComponentName(packageName, id), user);
-        this.id = id;
+    }
+
+    public String getId() {
+        return componentName.getClassName();
     }
 
     public static ShortcutKey fromInfo(ShortcutInfoCompat shortcutInfo) {
         return new ShortcutKey(shortcutInfo.getPackage(), shortcutInfo.getUserHandle(),
                 shortcutInfo.getId());
     }
+
+    public static ShortcutKey fromIntent(Intent intent, UserHandleCompat user) {
+        String shortcutId = intent.getStringExtra(
+                ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
+        return new ShortcutKey(intent.getPackage(), user, shortcutId);
+    }
+
+    public static ShortcutKey fromItemInfo(ItemInfo info) {
+        return fromIntent(info.getIntent(), info.user);
+    }
 }
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
index 120eacd..4fefa98 100644
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ b/src/com/android/launcher3/util/CursorIconInfo.java
@@ -34,13 +34,24 @@
     public final int iconResourceIndex;
     public final int iconIndex;
 
-    public CursorIconInfo(Cursor c) {
+    public final int titleIndex;
+
+    private final Context mContext;
+
+    public CursorIconInfo(Context context, Cursor c) {
+        mContext = context;
+
         iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
         iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
         iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+
+        titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
     }
 
-    public Bitmap loadIcon(Cursor c, ShortcutInfo info, Context context) {
+    /**
+     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
+     */
+    public Bitmap loadIcon(Cursor c, ShortcutInfo info) {
         Bitmap icon = null;
         String packageName = c.getString(iconPackageIndex);
         String resourceName = c.getString(iconResourceIndex);
@@ -48,12 +59,27 @@
             info.iconResource = new ShortcutIconResource();
             info.iconResource.packageName = packageName;
             info.iconResource.resourceName = resourceName;
-            icon = Utilities.createIconBitmap(packageName, resourceName, context);
+            icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
         }
         if (icon == null) {
             // Failed to load from resource, try loading from DB.
-            icon = Utilities.createIconBitmap(c, iconIndex, context);
+            icon = loadIcon(c);
         }
         return icon;
     }
+
+    /**
+     * Loads the fixed bitmap from the icon if available.
+     */
+    public Bitmap loadIcon(Cursor c) {
+        return Utilities.createIconBitmap(c, iconIndex, mContext);
+    }
+
+    /**
+     * Returns the title or empty string
+     */
+    public String getTitle(Cursor c) {
+        String title = c.getString(titleIndex);
+        return TextUtils.isEmpty(title) ? "" : Utilities.trim(c.getString(titleIndex));
+    }
 }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
new file mode 100644
index 0000000..6189bf2
--- /dev/null
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+
+import java.util.HashSet;
+
+/**
+ * A utility class to check for {@link ItemInfo}
+ */
+public abstract class ItemInfoMatcher {
+
+    public abstract boolean matches(ItemInfo info, ComponentName cn);
+
+    public static ItemInfoMatcher ofComponents(
+            final HashSet<ComponentName> components, final UserHandleCompat user) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return components.contains(cn) && info.user.equals(user);
+            }
+        };
+    }
+
+    public static ItemInfoMatcher ofPackages(
+            final HashSet<String> packageNames, final UserHandleCompat user) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return packageNames.contains(cn.getPackageName()) && info.user.equals(user);
+            }
+        };
+    }
+
+    public static ItemInfoMatcher ofShortcutKeys(final HashSet<ShortcutKey> keys) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+                        keys.contains(ShortcutKey.fromItemInfo(info));
+            }
+        };
+    }
+}