Moving various runnables in LauncherModel to individual tasks

> Adding tests for some of the runnable

Change-Id: I1a315d38878857df3371f0e69d622a41fc3b081a
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 0e465a4..b13c20b 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -155,10 +155,9 @@
             // to the removed list.
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
-                final ComponentName component = applicationInfo.intent.getComponent();
                 if (user.equals(applicationInfo.user)
-                        && packageName.equals(component.getPackageName())) {
-                    if (!findActivity(matches, component)) {
+                        && packageName.equals(applicationInfo.componentName.getPackageName())) {
+                    if (!findActivity(matches, applicationInfo.componentName)) {
                         removed.add(applicationInfo);
                         data.remove(i);
                     }
@@ -182,11 +181,10 @@
             // Remove all data for this package.
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
-                final ComponentName component = applicationInfo.intent.getComponent();
                 if (user.equals(applicationInfo.user)
-                        && packageName.equals(component.getPackageName())) {
+                        && packageName.equals(applicationInfo.componentName.getPackageName())) {
                     removed.add(applicationInfo);
-                    mIconCache.remove(component, user);
+                    mIconCache.remove(applicationInfo.componentName, user);
                     data.remove(i);
                 }
             }
@@ -238,9 +236,8 @@
     private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
             String className) {
         for (AppInfo info: data) {
-            final ComponentName component = info.intent.getComponent();
-            if (user.equals(info.user) && packageName.equals(component.getPackageName())
-                    && className.equals(component.getClassName())) {
+            if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName())
+                    && className.equals(info.componentName.getClassName())) {
                 return info;
             }
         }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 661f99b..04d0c8c 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -79,7 +79,7 @@
 
     @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
 
-    @Thunk static class CacheEntry {
+    public static class CacheEntry {
         public Bitmap icon;
         public CharSequence title = "";
         public CharSequence contentDescription = "";
@@ -544,7 +544,7 @@
      * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
      * This method is not thread safe, it must be called from a synchronized method.
      */
-    private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+    protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
             UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index bd20e32..b810740 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -241,7 +241,7 @@
             // Add the new apps to the model and bind them
             if (!addShortcuts.isEmpty()) {
                 LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
+                app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 66d8957..78f5b8e 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -84,12 +84,12 @@
     /**
      * Indicates the restore status of the widget.
      */
-    int restoreStatus;
+    public int restoreStatus;
 
     /**
      * Indicates the installation progress of the widget provider
      */
-    int installProgress = -1;
+    public int installProgress = -1;
 
     /**
      * Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}.
@@ -98,7 +98,7 @@
 
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
-    LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+    public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
         if (appWidgetId == CUSTOM_WIDGET_ID) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
@@ -117,6 +117,11 @@
         restoreStatus = RESTORE_COMPLETED;
     }
 
+    /** Used for testing **/
+    public LauncherAppWidgetInfo() {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+    }
+
     public boolean isCustomWidget() {
         return appWidgetId == CUSTOM_WIDGET_ID;
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 955f51f..c70a475 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -27,7 +27,6 @@
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -43,7 +42,6 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableInt;
-import android.util.Pair;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -59,11 +57,18 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.ExtendedModelTask;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.CacheDataUpdatedTask;
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.SdCardAvailableReceiver;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.PackageInstallStateChangedTask;
+import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.ShortcutsChangedTask;
+import com.android.launcher3.model.UserLockStateChangedTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -72,7 +77,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
@@ -87,7 +91,6 @@
 import java.net.URISyntaxException;
 import java.security.InvalidParameterException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -168,8 +171,8 @@
 
     // </ only access in worker thread >
 
-    private IconCache mIconCache;
-    private DeepShortcutManager mDeepShortcutManager;
+    private final IconCache mIconCache;
+    private final DeepShortcutManager mDeepShortcutManager;
 
     private final LauncherAppsCompat mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -241,286 +244,26 @@
         }
     }
 
-    public void setPackageState(final PackageInstallInfo installInfo) {
-        Runnable updateRunnable = new Runnable() {
-
-            @Override
-            public void run() {
-                synchronized (sBgDataModel) {
-                    final HashSet<ItemInfo> updates = new HashSet<>();
-
-                    if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
-                        // Ignore install success events as they are handled by Package add events.
-                        return;
-                    }
-
-                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
-                        if (info instanceof ShortcutInfo) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            ComponentName cn = si.getTargetComponent();
-                            if (si.isPromise() && (cn != null)
-                                    && installInfo.packageName.equals(cn.getPackageName())) {
-                                si.setInstallProgress(installInfo.progress);
-
-                                if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
-                                    // Mark this info as broken.
-                                    si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
-                                }
-                                updates.add(si);
-                            }
-                        }
-                    }
-
-                    for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) {
-                        if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
-                            widget.installProgress = installInfo.progress;
-                            updates.add(widget);
-                        }
-                    }
-
-                    if (!updates.isEmpty()) {
-                        // Push changes to the callback.
-                        Runnable r = new Runnable() {
-                            public void run() {
-                                Callbacks callbacks = getCallback();
-                                if (callbacks != null) {
-                                    callbacks.bindRestoreItemsChange(updates);
-                                }
-                            }
-                        };
-                        mHandler.post(r);
-                    }
-                }
-            }
-        };
-        runOnWorkerThread(updateRunnable);
+    public void setPackageState(PackageInstallInfo installInfo) {
+        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
     }
 
     /**
      * Updates the icons and label of all pending icons for the provided package name.
      */
     public void updateSessionDisplayInfo(final String packageName) {
-        Runnable updateRunnable = new Runnable() {
-
-            @Override
-            public void run() {
-                synchronized (sBgDataModel) {
-                    ArrayList<ShortcutInfo> updates = new ArrayList<>();
-                    UserHandleCompat user = UserHandleCompat.myUserHandle();
-
-                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
-                        if (info instanceof ShortcutInfo) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            ComponentName cn = si.getTargetComponent();
-                            if (si.isPromise() && (cn != null)
-                                    && packageName.equals(cn.getPackageName())) {
-                                si.updateIcon(mIconCache);
-                                updates.add(si);
-                            }
-                        }
-                    }
-
-                    bindUpdatedShortcuts(updates, user);
-                }
-            }
-        };
-        runOnWorkerThread(updateRunnable);
-    }
-
-    public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
-        final Callbacks callbacks = getCallback();
-
-        if (allAppsApps == null) {
-            throw new RuntimeException("allAppsApps must not be null");
-        }
-        if (allAppsApps.isEmpty()) {
-            return;
-        }
-
-        // Process the newly added applications and add them to the database first
-        Runnable r = new Runnable() {
-            public void run() {
-                runOnMainThread(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppsAdded(null, null, null, allAppsApps);
-                        }
-                    }
-                });
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
-            int[] xy, int spanX, int spanY) {
-        LauncherAppState app = LauncherAppState.getInstance();
-        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
-        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
-        if (occupiedPos != null) {
-            for (ItemInfo r : occupiedPos) {
-                occupied.markCells(r, true);
-            }
-        }
-        return occupied.findVacantCell(xy, spanX, spanY);
-    }
-
-    /**
-     * Find a position on the screen for the given size or adds a new screen.
-     * @return screenId and the coordinates for the item.
-     */
-    @Thunk Pair<Long, int[]> findSpaceForItem(
-            Context context,
-            ArrayList<Long> workspaceScreens,
-            ArrayList<Long> addedWorkspaceScreensFinal,
-            int spanX, int spanY) {
-        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
-        // Use sBgItemsIdMap as all the items are already loaded.
-        assertWorkspaceLoaded();
-        synchronized (sBgDataModel) {
-            for (ItemInfo info : sBgDataModel.itemsIdMap) {
-                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
-                    if (items == null) {
-                        items = new ArrayList<>();
-                        screenItems.put(info.screenId, items);
-                    }
-                    items.add(info);
-                }
-            }
-        }
-
-        // Find appropriate space for the item.
-        long screenId = 0;
-        int[] cordinates = new int[2];
-        boolean found = false;
-
-        int screenCount = workspaceScreens.size();
-        // First check the preferred screen.
-        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
-        if (preferredScreenIndex < screenCount) {
-            screenId = workspaceScreens.get(preferredScreenIndex);
-            found = findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), cordinates, spanX, spanY);
-        }
-
-        if (!found) {
-            // Search on any of the screens starting from the first screen.
-            for (int screen = 1; screen < screenCount; screen++) {
-                screenId = workspaceScreens.get(screen);
-                if (findNextAvailableIconSpaceInScreen(
-                        screenItems.get(screenId), cordinates, spanX, spanY)) {
-                    // We found a space for it
-                    found = true;
-                    break;
-                }
-            }
-        }
-
-        if (!found) {
-            // Still no position found. Add a new screen to the end.
-            screenId = LauncherSettings.Settings.call(context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
-            // Save the screen id for binding in the workspace
-            workspaceScreens.add(screenId);
-            addedWorkspaceScreensFinal.add(screenId);
-
-            // If we still can't find an empty space, then God help us all!!!
-            if (!findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), cordinates, spanX, spanY)) {
-                throw new RuntimeException("Can't find space to add the item");
-            }
-        }
-        return Pair.create(screenId, cordinates);
+        HashSet<String> packages = new HashSet<>();
+        packages.add(packageName);
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages));
     }
 
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(final Context context,
+    public void addAndBindAddedWorkspaceItems(
             final ArrayList<? extends ItemInfo> workspaceApps) {
-        final Callbacks callbacks = getCallback();
-        if (workspaceApps.isEmpty()) {
-            return;
-        }
-        // Process the newly added applications and add them to the database first
-        Runnable r = new Runnable() {
-            public void run() {
-                final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
-                final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
-
-                // Get the list of workspace screens.  We need to append to this list and
-                // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
-                // called.
-                ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
-                synchronized(sBgDataModel) {
-                    for (ItemInfo item : workspaceApps) {
-                        if (item instanceof ShortcutInfo) {
-                            // Short-circuit this logic if the icon exists somewhere on the workspace
-                            if (shortcutExists(context, item.getIntent(), item.user)) {
-                                continue;
-                            }
-                        }
-
-                        // Find appropriate space for the item.
-                        Pair<Long, int[]> coords = findSpaceForItem(context,
-                                workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
-                        long screenId = coords.first;
-                        int[] cordinates = coords.second;
-
-                        ItemInfo itemInfo;
-                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
-                            itemInfo = item;
-                        } else if (item instanceof AppInfo) {
-                            itemInfo = ((AppInfo) item).makeShortcut();
-                        } else {
-                            throw new RuntimeException("Unexpected info type");
-                        }
-
-                        // Add the shortcut to the db
-                        addItemToDatabase(context, itemInfo,
-                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
-                                screenId, cordinates[0], cordinates[1]);
-                        // Save the ShortcutInfo for binding in the workspace
-                        addedShortcutsFinal.add(itemInfo);
-                    }
-                }
-
-                // Update the workspace screens
-                updateWorkspaceScreenOrder(context, workspaceScreens);
-
-                if (!addedShortcutsFinal.isEmpty()) {
-                    runOnMainThread(new Runnable() {
-                        public void run() {
-                            Callbacks cb = getCallback();
-                            if (callbacks == cb && cb != null) {
-                                final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
-                                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
-                                if (!addedShortcutsFinal.isEmpty()) {
-                                    ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
-                                    long lastScreenId = info.screenId;
-                                    for (ItemInfo i : addedShortcutsFinal) {
-                                        if (i.screenId == lastScreenId) {
-                                            addAnimated.add(i);
-                                        } else {
-                                            addNotAnimated.add(i);
-                                        }
-                                    }
-                                }
-                                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
-                                        addNotAnimated, addAnimated, null);
-                            }
-                        }
-                    });
-                }
-            }
-        };
-        runOnWorkerThread(r);
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps));
     }
 
     /**
@@ -784,60 +527,6 @@
         updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
     }
 
-    private void assertWorkspaceLoaded() {
-        if (ProviderConfig.IS_DOGFOOD_BUILD) {
-            synchronized (mLock) {
-                if (!mHasLoaderCompletedOnce ||
-                        (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
-                    throw new RuntimeException("Trying to add shortcut while loader is running");
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns true if the shortcuts already exists on the workspace. This must be called after
-     * the workspace has been loaded. We identify a shortcut by its intent.
-     */
-    @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
-        assertWorkspaceLoaded();
-        final String intentWithPkg, intentWithoutPkg;
-        if (intent.getComponent() != null) {
-            // If component is not null, an intent with null package will produce
-            // the same result and should also be a match.
-            String packageName = intent.getComponent().getPackageName();
-            if (intent.getPackage() != null) {
-                intentWithPkg = intent.toUri(0);
-                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
-            } else {
-                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
-                intentWithoutPkg = intent.toUri(0);
-            }
-        } else {
-            intentWithPkg = intent.toUri(0);
-            intentWithoutPkg = intent.toUri(0);
-        }
-
-        synchronized (sBgDataModel) {
-            for (ItemInfo item : sBgDataModel.itemsIdMap) {
-                if (item instanceof ShortcutInfo) {
-                    ShortcutInfo info = (ShortcutInfo) item;
-                    Intent targetIntent = info.promisedIntent == null
-                            ? info.intent : info.promisedIntent;
-                    if (targetIntent != null && info.user.equals(user)) {
-                        Intent copyIntent = new Intent(targetIntent);
-                        copyIntent.setSourceBounds(intent.getSourceBounds());
-                        String s = copyIntent.toUri(0);
-                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Add an item to the database in a specified container. Sets the container, screen, cellX and
      * cellY fields of the item. Also assigns an ID to the item.
@@ -899,7 +588,8 @@
     /**
      * Removes the specified items from the database
      */
-    static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) {
+    public static void deleteItemsFromDatabase(Context context,
+            final Iterable<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
         Runnable r = new Runnable() {
             public void run() {
@@ -918,7 +608,7 @@
      * 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.
      */
-    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -997,8 +687,7 @@
     @Override
     public void onPackageChanged(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_UPDATE;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
@@ -1008,56 +697,52 @@
 
     public void onPackagesRemoved(UserHandleCompat user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
     @Override
     public void onPackageAdded(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_ADD;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
-        enqueueItemUpdatedTask(
-                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
+        enqueueModelUpdateTask(
+                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
     }
 
     @Override
     public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
         if (!replacing) {
-            enqueueItemUpdatedTask(new PackageUpdatedTask(
-                    PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
-                    user));
+            enqueueModelUpdateTask(new PackageUpdatedTask(
+                    PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
         }
     }
 
     @Override
     public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
-        enqueueItemUpdatedTask(new PackageUpdatedTask(
-                PackageUpdatedTask.OP_SUSPEND, packageNames,
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(
+                PackageUpdatedTask.OP_SUSPEND, user, packageNames));
     }
 
     @Override
     public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
-        enqueueItemUpdatedTask(new PackageUpdatedTask(
-                PackageUpdatedTask.OP_UNSUSPEND, packageNames,
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(
+                PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
     }
 
     @Override
     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
             UserHandleCompat user) {
-        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
     public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
             UserHandleCompat user) {
-        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
     }
 
     /**
@@ -1083,16 +768,15 @@
             if (user != null) {
                 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));
+                    enqueueModelUpdateTask(new PackageUpdatedTask(
+                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, 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));
+                    enqueueModelUpdateTask(new UserLockStateChangedTask(user));
                 }
             }
         } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
@@ -2702,397 +2386,68 @@
      * Called when the icons for packages have been updated in the icon cache.
      */
     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
-        final Callbacks callbacks = getCallback();
-        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
-        final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-
         // If any package icon has changed (app was updated while launcher was dead),
         // update the corresponding shortcuts.
-        synchronized (sBgDataModel) {
-            for (ItemInfo info : sBgDataModel.itemsIdMap) {
-                if (info instanceof ShortcutInfo && user.equals(info.user)
-                        && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                    ShortcutInfo si = (ShortcutInfo) info;
-                    ComponentName cn = si.getTargetComponent();
-                    if (cn != null && updatedPackages.contains(cn.getPackageName())) {
-                        si.updateIcon(mIconCache);
-                        updatedShortcuts.add(si);
-                    }
-                }
-            }
-            mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
-        }
-
-        bindUpdatedShortcuts(updatedShortcuts, user);
-
-        if (!updatedApps.isEmpty()) {
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindAppsUpdated(updatedApps);
-                    }
-                }
-            });
-        }
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
     }
 
-    private void bindUpdatedShortcuts(
-            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
-        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+    void enqueueModelUpdateTask(BaseModelUpdateTask task) {
+        task.init(this);
+        runOnWorkerThread(task);
     }
 
-    private void bindUpdatedShortcuts(
-            final ArrayList<ShortcutInfo> updatedShortcuts,
-            final ArrayList<ShortcutInfo> removedShortcuts,
-            final UserHandleCompat user) {
-        if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
-            final Callbacks callbacks = getCallback();
-            mHandler.post(new Runnable() {
+    /**
+     * A task to be executed on the current callbacks on the UI thread.
+     * If there is no current callbacks, the task is ignored.
+     */
+    public interface CallbackTask {
 
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
-                    }
-                }
-            });
-        }
+        void execute(Callbacks callbacks);
     }
 
-    void enqueueItemUpdatedTask(Runnable task) {
-        sWorker.post(task);
-    }
+    /**
+     * A runnable which changes/updates the data model of the launcher based on certain events.
+     */
+    public static abstract class BaseModelUpdateTask implements Runnable {
 
-    private class PackageUpdatedTask implements Runnable {
-        final int mOp;
-        final String[] mPackages;
-        final UserHandleCompat mUser;
+        private LauncherModel mModel;
+        private DeferredHandler mUiHandler;
 
-        public static final int OP_NONE = 0;
-        public static final int OP_ADD = 1;
-        public static final int OP_UPDATE = 2;
-        public static final int OP_REMOVE = 3; // uninstlled
-        public static final int OP_UNAVAILABLE = 4; // external media unmounted
-        public static final int OP_SUSPEND = 5; // package suspended
-        public static final int OP_UNSUSPEND = 6; // package unsuspended
-        public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
-
-        public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
-            mOp = op;
-            mPackages = packages;
-            mUser = user;
+        /* package private */
+        void init(LauncherModel model) {
+            mModel = model;
+            mUiHandler = mModel.mHandler;
         }
 
+        @Override
         public void run() {
-            if (!mHasLoaderCompletedOnce) {
+            if (!mModel.mHasLoaderCompletedOnce) {
                 // Loader has not yet run.
                 return;
             }
-            final Context context = mApp.getContext();
+            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
+        }
 
-            final String[] packages = mPackages;
-            final int N = packages.length;
-            FlagOp flagOp = FlagOp.NO_OP;
-            final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
-            switch (mOp) {
-                case OP_ADD: {
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
-                        mIconCache.updateIconsForPkg(packages[i], mUser);
-                        mBgAllAppsList.addPackage(context, packages[i], mUser);
-                    }
+        /**
+         * Execute the actual task. Called on the worker thread.
+         */
+        public abstract void execute(
+                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
 
-                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                    if (heuristic != null) {
-                        heuristic.processPackageAdd(mPackages);
-                    }
-                    break;
-                }
-                case OP_UPDATE:
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
-                        mIconCache.updateIconsForPkg(packages[i], mUser);
-                        mBgAllAppsList.updatePackage(context, packages[i], mUser);
-                        mApp.getWidgetCache().removePackage(packages[i], mUser);
-                    }
-                    // Since package was just updated, the target must be available now.
-                    flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
-                    break;
-                case OP_REMOVE: {
-                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                    if (heuristic != null) {
-                        heuristic.processPackageRemoved(mPackages);
-                    }
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mIconCache.removeIconsForPkg(packages[i], mUser);
-                    }
-                    // Fall through
-                }
-                case OP_UNAVAILABLE:
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mBgAllAppsList.removePackage(packages[i], mUser);
-                        mApp.getWidgetCache().removePackage(packages[i], mUser);
-                    }
-                    flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
-                    break;
-                case OP_SUSPEND:
-                case OP_UNSUSPEND:
-                    flagOp = mOp == OP_SUSPEND ?
-                            FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
-                                    FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
-                    if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
-                    mBgAllAppsList.updateDisabledFlags(
-                            ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp);
-                    break;
-                case OP_USER_AVAILABILITY_CHANGE:
-                    flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
-                            ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
-                            : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
-                    // We want to update all packages for this user.
-                    mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp);
-                    break;
-            }
-
-            ArrayList<AppInfo> added = null;
-            ArrayList<AppInfo> modified = null;
-            final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
-
-            if (mBgAllAppsList.added.size() > 0) {
-                added = new ArrayList<>(mBgAllAppsList.added);
-                mBgAllAppsList.added.clear();
-            }
-            if (mBgAllAppsList.modified.size() > 0) {
-                modified = new ArrayList<>(mBgAllAppsList.modified);
-                mBgAllAppsList.modified.clear();
-            }
-            if (mBgAllAppsList.removed.size() > 0) {
-                removedApps.addAll(mBgAllAppsList.removed);
-                mBgAllAppsList.removed.clear();
-            }
-
-            final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
-
-            if (added != null) {
-                addAppsToAllApps(context, added);
-                for (AppInfo ai : added) {
-                    addedOrUpdatedApps.put(ai.componentName, ai);
-                }
-            }
-
-            if (modified != null) {
-                final Callbacks callbacks = getCallback();
-                final ArrayList<AppInfo> modifiedFinal = modified;
-                for (AppInfo ai : modified) {
-                    addedOrUpdatedApps.put(ai.componentName, ai);
-                }
-
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppsUpdated(modifiedFinal);
-                        }
-                    }
-                });
-            }
-
-            // Update shortcut infos
-            if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-                final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-                final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
-                final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
-
-                synchronized (sBgDataModel) {
-                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
-                        if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            boolean infoUpdated = false;
-                            boolean shortcutUpdated = false;
-
-                            // Update shortcuts which use iconResource.
-                            if ((si.iconResource != null)
-                                    && packageSet.contains(si.iconResource.packageName)) {
-                                Bitmap icon = LauncherIcons.createIconBitmap(
-                                        si.iconResource.packageName,
-                                        si.iconResource.resourceName, context);
-                                if (icon != null) {
-                                    si.setIcon(icon);
-                                    si.usingFallbackIcon = false;
-                                    infoUpdated = true;
-                                }
-                            }
-
-                            ComponentName cn = si.getTargetComponent();
-                            if (cn != null && packageSet.contains(cn.getPackageName())) {
-                                AppInfo appInfo = addedOrUpdatedApps.get(cn);
-
-                                if (si.isPromise()) {
-                                    if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
-                                        // Auto install icon
-                                        PackageManager pm = context.getPackageManager();
-                                        ResolveInfo matched = pm.resolveActivity(
-                                                new Intent(Intent.ACTION_MAIN)
-                                                .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
-                                                PackageManager.MATCH_DEFAULT_ONLY);
-                                        if (matched == null) {
-                                            // Try to find the best match activity.
-                                            Intent intent = pm.getLaunchIntentForPackage(
-                                                    cn.getPackageName());
-                                            if (intent != null) {
-                                                cn = intent.getComponent();
-                                                appInfo = addedOrUpdatedApps.get(cn);
-                                            }
-
-                                            if ((intent == null) || (appInfo == null)) {
-                                                removedShortcuts.add(si);
-                                                continue;
-                                            }
-                                            si.promisedIntent = intent;
-                                        }
-                                    }
-
-                                    si.intent = si.promisedIntent;
-                                    si.promisedIntent = null;
-                                    si.status = ShortcutInfo.DEFAULT;
-                                    infoUpdated = true;
-                                    si.updateIcon(mIconCache);
-                                }
-
-                                if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
-                                        && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    si.updateIcon(mIconCache);
-                                    si.title = Utilities.trim(appInfo.title);
-                                    si.contentDescription = appInfo.contentDescription;
-                                    infoUpdated = true;
-                                }
-
-                                int oldDisabledFlags = si.isDisabled;
-                                si.isDisabled = flagOp.apply(si.isDisabled);
-                                if (si.isDisabled != oldDisabledFlags) {
-                                    shortcutUpdated = true;
-                                }
-                            }
-
-                            if (infoUpdated || shortcutUpdated) {
-                                updatedShortcuts.add(si);
-                            }
-                            if (infoUpdated) {
-                                updateItemInDatabase(context, si);
-                            }
-                        } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
-                            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
-                            if (mUser.equals(widgetInfo.user)
-                                    && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                                    && packageSet.contains(widgetInfo.providerName.getPackageName())) {
-                                widgetInfo.restoreStatus &=
-                                        ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
-                                        ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
-
-                                // adding this flag ensures that launcher shows 'click to setup'
-                                // if the widget has a config activity. In case there is no config
-                                // activity, it will be marked as 'restored' during bind.
-                                widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-                                widgets.add(widgetInfo);
-                                updateItemInDatabase(context, widgetInfo);
-                            }
-                        }
+        /**
+         * Schedules a {@param task} to be executed on the current callbacks.
+         */
+        public final void scheduleCallbackTask(final CallbackTask task) {
+            final Callbacks callbacks = mModel.getCallback();
+            mUiHandler.post(new Runnable() {
+                public void run() {
+                    Callbacks cb = mModel.getCallback();
+                    if (callbacks == cb && cb != null) {
+                        task.execute(callbacks);
                     }
                 }
-
-                bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
-                if (!removedShortcuts.isEmpty()) {
-                    deleteItemsFromDatabase(context, removedShortcuts);
-                }
-
-                if (!widgets.isEmpty()) {
-                    final Callbacks callbacks = getCallback();
-                    mHandler.post(new Runnable() {
-                        public void run() {
-                            Callbacks cb = getCallback();
-                            if (callbacks == cb && cb != null) {
-                                callbacks.bindWidgetsRestored(widgets);
-                            }
-                        }
-                    });
-                }
-            }
-
-            final HashSet<String> removedPackages = new HashSet<>();
-            final HashSet<ComponentName> removedComponents = new HashSet<>();
-            if (mOp == OP_REMOVE) {
-                // Mark all packages in the broadcast to be removed
-                Collections.addAll(removedPackages, packages);
-
-                // No need to update the removedComponents as
-                // removedPackages is a super-set of removedComponents
-            } else if (mOp == OP_UPDATE) {
-                // Mark disabled packages in the broadcast to be removed
-                for (int i=0; i<N; i++) {
-                    if (isPackageDisabled(context, packages[i], mUser)) {
-                        removedPackages.add(packages[i]);
-                    }
-                }
-
-                // Update removedComponents as some components can get removed during package update
-                for (AppInfo info : removedApps) {
-                    removedComponents.add(info.componentName);
-                }
-            }
-
-            if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
-                deleteItemsFromDatabase(
-                        context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
-                deleteItemsFromDatabase(
-                        context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
-
-                // Remove any queued items from the install queue
-                InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
-
-                // Call the components-removed callback
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindWorkspaceComponentsRemoved(
-                                    removedPackages, removedComponents, mUser);
-                        }
-                    }
-                });
-            }
-
-            if (!removedApps.isEmpty()) {
-                // Remove corresponding apps from All-Apps
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppInfosRemoved(removedApps);
-                        }
-                    }
-                });
-            }
-
-            // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
-            // get widget update signals.
-            if (!Utilities.ATLEAST_MARSHMALLOW &&
-                    (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.notifyWidgetProvidersChanged();
-                        }
-                    }
-                });
-            }
+            });
         }
     }
 
@@ -3100,171 +2455,18 @@
      * Repopulates the shortcut info, possibly updating any icon already on the workspace.
      */
     public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
-        enqueueItemUpdatedTask(new Runnable() {
+        enqueueModelUpdateTask(new ExtendedModelTask() {
             @Override
-            public void run() {
-                info.updateFromDeepShortcutInfo(
-                        fullDetail, LauncherAppState.getInstance().getContext());
-                ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                info.updateFromDeepShortcutInfo(fullDetail, app.getContext());
+
+                ArrayList<ShortcutInfo> update = new ArrayList<>();
                 update.add(info);
                 bindUpdatedShortcuts(update, fullDetail.getUserHandle());
             }
         });
     }
 
-    private class ShortcutsChangedTask implements Runnable {
-        private final String mPackageName;
-        private final List<ShortcutInfoCompat> mShortcuts;
-        private final UserHandleCompat mUser;
-        private final boolean mUpdateIdMap;
-
-        public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
-                UserHandleCompat user, boolean updateIdMap) {
-            mPackageName = packageName;
-            mShortcuts = shortcuts;
-            mUser = user;
-            mUpdateIdMap = updateIdMap;
-        }
-
-        @Override
-        public void run() {
-            mDeepShortcutManager.onShortcutsChanged(mShortcuts);
-
-            // Find ShortcutInfo's that have changed on the workspace.
-            final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
-            MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
-            for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) {
-                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                    ShortcutInfo si = (ShortcutInfo) itemInfo;
-                    if (si.getPromisedIntent().getPackage().equals(mPackageName)
-                            && si.user.equals(mUser)) {
-                        idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
-                    }
-                }
-            }
-
-            final Context context = LauncherAppState.getInstance().getContext();
-            final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
-            if (!idsToWorkspaceShortcutInfos.isEmpty()) {
-                // Update the workspace to reflect the changes to updated shortcuts residing on it.
-                List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
-                        mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
-                for (ShortcutInfoCompat fullDetails : shortcuts) {
-                    List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
-                            .remove(fullDetails.getId());
-                    if (!fullDetails.isPinned()) {
-                        // The shortcut was previously pinned but is no longer, so remove it from
-                        // the workspace and our pinned shortcut counts.
-                        // Note that we put this check here, after querying for full details,
-                        // because there's a possible race condition between pinning and
-                        // receiving this callback.
-                        removedShortcutInfos.addAll(shortcutInfos);
-                        continue;
-                    }
-                    for (ShortcutInfo shortcutInfo : shortcutInfos) {
-                        shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                        updatedShortcutInfos.add(shortcutInfo);
-                    }
-                }
-            }
-
-            // If there are still entries in idsToWorkspaceShortcutInfos, that means that
-            // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
-            // means they were cleared, so we remove and unpin them now.
-            for (String id : idsToWorkspaceShortcutInfos.keySet()) {
-                removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
-            }
-
-            bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
-            if (!removedShortcutInfos.isEmpty()) {
-                deleteItemsFromDatabase(context, removedShortcutInfos);
-            }
-
-            if (mUpdateIdMap) {
-                // Update the deep shortcut map if the list of ids has changed for an activity.
-                sBgDataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
-                bindDeepShortcuts();
-            }
-        }
-    }
-
-    /**
-     * 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) {
-                List<ShortcutInfoCompat> shortcuts =
-                        mDeepShortcutManager.queryForPinnedShortcuts(null, mUser);
-                if (mDeepShortcutManager.wasLastCallSuccess()) {
-                    for (ShortcutInfoCompat shortcut : shortcuts) {
-                        pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
-                    }
-                } else {
-                    // Shortcut manager can fail due to some race condition when the lock state
-                    // changes too frequently. For the purpose of the update,
-                    // consider it as still locked.
-                    isUserUnlocked = false;
-                }
-            }
-
-            // 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 : sBgDataModel.itemsIdMap) {
-                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                        && mUser.equals(itemInfo.user)) {
-                    ShortcutInfo si = (ShortcutInfo) itemInfo;
-                    if (isUserUnlocked) {
-                        ShortcutInfoCompat shortcut =
-                                pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(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 = sBgDataModel.deepShortcutMap.keySet().iterator();
-            while (keysIter.hasNext()) {
-                if (keysIter.next().user.equals(mUser)) {
-                    keysIter.remove();
-                }
-            }
-
-            if (isUserUnlocked) {
-                sBgDataModel.updateDeepShortcutMap(
-                        null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
-            }
-            bindDeepShortcuts();
-        }
-    }
-
     private void bindWidgetsModel(final Callbacks callbacks) {
         final MultiHashMap<PackageItemInfo, WidgetItem> widgets
                 = mBgWidgetsModel.getWidgetsMap().clone();
@@ -3296,12 +2498,6 @@
         });
     }
 
-    @Thunk static boolean isPackageDisabled(Context context, String packageName,
-            UserHandleCompat user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        return !launcherApps.isPackageEnabledForProfile(packageName, user);
-    }
-
     /**
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 9a92872..fc08736 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -80,7 +80,7 @@
      * Indicates whether we're using the default fallback icon instead of something from the
      * app.
      */
-    boolean usingFallbackIcon;
+    public boolean usingFallbackIcon;
 
     /**
      * Indicates whether we're using a low res icon
@@ -132,7 +132,7 @@
      * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
      * sd-card is not available).
      */
-    int isDisabled = DEFAULT;
+    public int isDisabled = DEFAULT;
 
     /**
      * A message to display when the user tries to start a disabled shortcut.
@@ -140,7 +140,7 @@
      */
     CharSequence disabledMessage;
 
-    int status;
+    public int status;
 
     /**
      * The installation progress [0-100] of the package that this shortcut represents.
@@ -152,7 +152,7 @@
      * this will hold the original intent from the database.  Otherwise, null.
      * Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON}
      */
-    Intent promisedIntent;
+    public Intent promisedIntent;
 
     public ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
new file mode 100644
index 0000000..986e163
--- /dev/null
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -0,0 +1,262 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.ArrayList;
+
+/**
+ * Task to add auto-created workspace items.
+ */
+public class AddWorkspaceItemsTask extends ExtendedModelTask {
+
+    private final ArrayList<? extends ItemInfo> mWorkspaceApps;
+
+    /**
+     * @param workspaceApps items to add on the workspace
+     */
+    public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
+        mWorkspaceApps = workspaceApps;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        if (mWorkspaceApps.isEmpty()) {
+            return;
+        }
+        Context context = app.getContext();
+
+        final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
+        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+
+        // Get the list of workspace screens.  We need to append to this list and
+        // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
+        // called.
+        ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
+        synchronized(dataModel) {
+            for (ItemInfo item : mWorkspaceApps) {
+                if (item instanceof ShortcutInfo) {
+                    // Short-circuit this logic if the icon exists somewhere on the workspace
+                    if (shortcutExists(dataModel, item.getIntent(), item.user)) {
+                        continue;
+                    }
+                }
+
+                // Find appropriate space for the item.
+                Pair<Long, int[]> coords = findSpaceForItem(
+                        app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
+                long screenId = coords.first;
+                int[] cordinates = coords.second;
+
+                ItemInfo itemInfo;
+                if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                    itemInfo = item;
+                } else if (item instanceof AppInfo) {
+                    itemInfo = ((AppInfo) item).makeShortcut();
+                } else {
+                    throw new RuntimeException("Unexpected info type");
+                }
+
+                // Add the shortcut to the db
+                addItemToDatabase(context, itemInfo, screenId, cordinates);
+
+                // Save the ShortcutInfo for binding in the workspace
+                addedShortcutsFinal.add(itemInfo);
+            }
+        }
+
+        // Update the workspace screens
+        updateScreens(context, workspaceScreens);
+
+        if (!addedShortcutsFinal.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+                    final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+                    if (!addedShortcutsFinal.isEmpty()) {
+                        ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                        long lastScreenId = info.screenId;
+                        for (ItemInfo i : addedShortcutsFinal) {
+                            if (i.screenId == lastScreenId) {
+                                addAnimated.add(i);
+                            } else {
+                                addNotAnimated.add(i);
+                            }
+                        }
+                    }
+                    callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+                            addNotAnimated, addAnimated, null);
+                }
+            });
+        }
+    }
+
+    protected void addItemToDatabase(Context context, ItemInfo item, long screenId, int[] pos) {
+        LauncherModel.addItemToDatabase(context, item,
+                LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, pos[0], pos[1]);
+    }
+
+    protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
+        LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
+    }
+
+    /**
+     * Returns true if the shortcuts already exists on the workspace. This must be called after
+     * the workspace has been loaded. We identify a shortcut by its intent.
+     */
+    protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandleCompat user) {
+        final String intentWithPkg, intentWithoutPkg;
+        if (intent.getComponent() != null) {
+            // If component is not null, an intent with null package will produce
+            // the same result and should also be a match.
+            String packageName = intent.getComponent().getPackageName();
+            if (intent.getPackage() != null) {
+                intentWithPkg = intent.toUri(0);
+                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
+            } else {
+                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
+                intentWithoutPkg = intent.toUri(0);
+            }
+        } else {
+            intentWithPkg = intent.toUri(0);
+            intentWithoutPkg = intent.toUri(0);
+        }
+
+        synchronized (dataModel) {
+            for (ItemInfo item : dataModel.itemsIdMap) {
+                if (item instanceof ShortcutInfo) {
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    Intent targetIntent = info.promisedIntent == null
+                            ? info.intent : info.promisedIntent;
+                    if (targetIntent != null && info.user.equals(user)) {
+                        Intent copyIntent = new Intent(targetIntent);
+                        copyIntent.setSourceBounds(intent.getSourceBounds());
+                        String s = copyIntent.toUri(0);
+                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Find a position on the screen for the given size or adds a new screen.
+     * @return screenId and the coordinates for the item.
+     */
+    protected Pair<Long, int[]> findSpaceForItem(
+            LauncherAppState app, BgDataModel dataModel,
+            ArrayList<Long> workspaceScreens,
+            ArrayList<Long> addedWorkspaceScreensFinal,
+            int spanX, int spanY) {
+        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+        // Use sBgItemsIdMap as all the items are already loaded.
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+                    if (items == null) {
+                        items = new ArrayList<>();
+                        screenItems.put(info.screenId, items);
+                    }
+                    items.add(info);
+                }
+            }
+        }
+
+        // Find appropriate space for the item.
+        long screenId = 0;
+        int[] cordinates = new int[2];
+        boolean found = false;
+
+        int screenCount = workspaceScreens.size();
+        // First check the preferred screen.
+        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
+        if (preferredScreenIndex < screenCount) {
+            screenId = workspaceScreens.get(preferredScreenIndex);
+            found = findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), cordinates, spanX, spanY);
+        }
+
+        if (!found) {
+            // Search on any of the screens starting from the first screen.
+            for (int screen = 1; screen < screenCount; screen++) {
+                screenId = workspaceScreens.get(screen);
+                if (findNextAvailableIconSpaceInScreen(
+                        app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+                    // We found a space for it
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found) {
+            // Still no position found. Add a new screen to the end.
+            screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+
+            // Save the screen id for binding in the workspace
+            workspaceScreens.add(screenId);
+            addedWorkspaceScreensFinal.add(screenId);
+
+            // If we still can't find an empty space, then God help us all!!!
+            if (!findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+                throw new RuntimeException("Can't find space to add the item");
+            }
+        }
+        return Pair.create(screenId, cordinates);
+    }
+
+    private boolean findNextAvailableIconSpaceInScreen(
+            LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+            int[] xy, int spanX, int spanY) {
+        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+        if (occupiedPos != null) {
+            for (ItemInfo r : occupiedPos) {
+                occupied.markCells(r, true);
+            }
+        }
+        return occupied.findVacantCell(xy, spanX, spanY);
+    }
+
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
new file mode 100644
index 0000000..9f24e90
--- /dev/null
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -0,0 +1,95 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Handles changes due to cache updates.
+ */
+public class CacheDataUpdatedTask extends ExtendedModelTask {
+
+    public static final int OP_CACHE_UPDATE = 1;
+    public static final int OP_SESSION_UPDATE = 2;
+
+    private final int mOp;
+    private final UserHandleCompat mUser;
+    private final HashSet<String> mPackages;
+
+    public CacheDataUpdatedTask(int op, UserHandleCompat user, HashSet<String> packages) {
+        mOp = op;
+        mUser = user;
+        mPackages = packages;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        IconCache iconCache = app.getIconCache();
+
+        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+        ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+                    ShortcutInfo si = (ShortcutInfo) info;
+                    ComponentName cn = si.getTargetComponent();
+                    if (isValidShortcut(si) &&
+                            cn != null && mPackages.contains(cn.getPackageName())) {
+                        si.updateIcon(iconCache);
+                        updatedShortcuts.add(si);
+                    }
+                }
+            }
+            apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+        }
+        bindUpdatedShortcuts(updatedShortcuts, mUser);
+
+        if (!updatedApps.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsUpdated(updatedApps);
+                }
+            });
+        }
+    }
+
+    public boolean isValidShortcut(ShortcutInfo si) {
+        switch (mOp) {
+            case OP_CACHE_UPDATE:
+                return si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+            case OP_SESSION_UPDATE:
+                return si.isPromise();
+            default:
+                return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java
new file mode 100644
index 0000000..ccc6007
--- /dev/null
+++ b/src/com/android/launcher3/model/ExtendedModelTask.java
@@ -0,0 +1,61 @@
+/*
+ * 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.model;
+
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link BaseModelUpdateTask} with some utility methods
+ */
+public abstract class ExtendedModelTask extends BaseModelUpdateTask {
+
+    public void bindUpdatedShortcuts(
+            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
+        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+    }
+
+    public void bindUpdatedShortcuts(
+            final ArrayList<ShortcutInfo> updatedShortcuts,
+            final ArrayList<ShortcutInfo> removedShortcuts,
+            final UserHandleCompat user) {
+        if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
+                }
+            });
+        }
+    }
+
+    public void bindDeepShortcuts(BgDataModel dataModel) {
+        final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
+        scheduleCallbackTask(new CallbackTask() {
+            @Override
+            public void execute(Callbacks callbacks) {
+                callbacks.bindDeepShortcutMap(shortcutMapCopy);
+            }
+        });
+    }
+}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
new file mode 100644
index 0000000..5d04325
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -0,0 +1,86 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.HashSet;
+
+/**
+ * Handles changes due to a sessions updates for a currently installing app.
+ */
+public class PackageInstallStateChangedTask extends ExtendedModelTask {
+
+    private final PackageInstallInfo mInstallInfo;
+
+    public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+        mInstallInfo = installInfo;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+            // Ignore install success events as they are handled by Package add events.
+            return;
+        }
+
+        synchronized (dataModel) {
+            final HashSet<ItemInfo> updates = new HashSet<>();
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info instanceof ShortcutInfo) {
+                    ShortcutInfo si = (ShortcutInfo) info;
+                    ComponentName cn = si.getTargetComponent();
+                    if (si.isPromise() && (cn != null)
+                            && mInstallInfo.packageName.equals(cn.getPackageName())) {
+                        si.setInstallProgress(mInstallInfo.progress);
+
+                        if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                            // Mark this info as broken.
+                            si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+                        }
+                        updates.add(si);
+                    }
+                }
+            }
+
+            for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
+                if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
+                    widget.installProgress = mInstallInfo.progress;
+                    updates.add(widget);
+                }
+            }
+
+            if (!updates.isEmpty()) {
+                scheduleCallbackTask(new CallbackTask() {
+                    @Override
+                    public void execute(Callbacks callbacks) {
+                        callbacks.bindRestoreItemsChange(updates);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
new file mode 100644
index 0000000..7286bf5
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -0,0 +1,378 @@
+/*
+ * 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.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.util.FlagOp;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Handles updates due to changes in package manager (app installed/updated/removed)
+ * or when a user availability changes.
+ */
+public class PackageUpdatedTask extends ExtendedModelTask {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PackageUpdatedTask";
+
+    public static final int OP_NONE = 0;
+    public static final int OP_ADD = 1;
+    public static final int OP_UPDATE = 2;
+    public static final int OP_REMOVE = 3; // uninstalled
+    public static final int OP_UNAVAILABLE = 4; // external media unmounted
+    public static final int OP_SUSPEND = 5; // package suspended
+    public static final int OP_UNSUSPEND = 6; // package unsuspended
+    public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
+
+    private final int mOp;
+    private final UserHandleCompat mUser;
+    private final String[] mPackages;
+
+    public PackageUpdatedTask(int op, UserHandleCompat user, String... packages) {
+        mOp = op;
+        mUser = user;
+        mPackages = packages;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+        final Context context = app.getContext();
+        final IconCache iconCache = app.getIconCache();
+
+        final String[] packages = mPackages;
+        final int N = packages.length;
+        FlagOp flagOp = FlagOp.NO_OP;
+        final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
+        switch (mOp) {
+            case OP_ADD: {
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+                    iconCache.updateIconsForPkg(packages[i], mUser);
+                    appsList.addPackage(context, packages[i], mUser);
+                }
+
+                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                if (heuristic != null) {
+                    heuristic.processPackageAdd(mPackages);
+                }
+                break;
+            }
+            case OP_UPDATE:
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                    iconCache.updateIconsForPkg(packages[i], mUser);
+                    appsList.updatePackage(context, packages[i], mUser);
+                    app.getWidgetCache().removePackage(packages[i], mUser);
+                }
+                // Since package was just updated, the target must be available now.
+                flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+                break;
+            case OP_REMOVE: {
+                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                if (heuristic != null) {
+                    heuristic.processPackageRemoved(mPackages);
+                }
+                for (int i = 0; i < N; i++) {
+                    iconCache.removeIconsForPkg(packages[i], mUser);
+                }
+                // Fall through
+            }
+            case OP_UNAVAILABLE:
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+                    appsList.removePackage(packages[i], mUser);
+                    app.getWidgetCache().removePackage(packages[i], mUser);
+                }
+                flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+                break;
+            case OP_SUSPEND:
+            case OP_UNSUSPEND:
+                flagOp = mOp == OP_SUSPEND ?
+                        FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
+                        FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
+                if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
+                appsList.updateDisabledFlags(
+                        ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp);
+                break;
+            case OP_USER_AVAILABILITY_CHANGE:
+                flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
+                        ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
+                        : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
+                // We want to update all packages for this user.
+                appsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp);
+                break;
+        }
+
+        ArrayList<AppInfo> added = null;
+        ArrayList<AppInfo> modified = null;
+        final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+        if (appsList.added.size() > 0) {
+            added = new ArrayList<>(appsList.added);
+            appsList.added.clear();
+        }
+        if (appsList.modified.size() > 0) {
+            modified = new ArrayList<>(appsList.modified);
+            appsList.modified.clear();
+        }
+        if (appsList.removed.size() > 0) {
+            removedApps.addAll(appsList.removed);
+            appsList.removed.clear();
+        }
+
+        final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
+
+        if (added != null) {
+            final ArrayList<AppInfo> addedApps = added;
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsAdded(null, null, null, addedApps);
+                }
+            });
+            for (AppInfo ai : added) {
+                addedOrUpdatedApps.put(ai.componentName, ai);
+            }
+        }
+
+        if (modified != null) {
+            final ArrayList<AppInfo> modifiedFinal = modified;
+            for (AppInfo ai : modified) {
+                addedOrUpdatedApps.put(ai.componentName, ai);
+            }
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsUpdated(modifiedFinal);
+                }
+            });
+        }
+
+        // Update shortcut infos
+        if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
+            final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+            final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+            final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+
+            synchronized (dataModel) {
+                for (ItemInfo info : dataModel.itemsIdMap) {
+                    if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+                        ShortcutInfo si = (ShortcutInfo) info;
+                        boolean infoUpdated = false;
+                        boolean shortcutUpdated = false;
+
+                        // Update shortcuts which use iconResource.
+                        if ((si.iconResource != null)
+                                && packageSet.contains(si.iconResource.packageName)) {
+                            Bitmap icon = LauncherIcons.createIconBitmap(
+                                    si.iconResource.packageName,
+                                    si.iconResource.resourceName, context);
+                            if (icon != null) {
+                                si.setIcon(icon);
+                                si.usingFallbackIcon = false;
+                                infoUpdated = true;
+                            }
+                        }
+
+                        ComponentName cn = si.getTargetComponent();
+                        if (cn != null && packageSet.contains(cn.getPackageName())) {
+                            AppInfo appInfo = addedOrUpdatedApps.get(cn);
+
+                            if (si.isPromise()) {
+                                if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+                                    // Auto install icon
+                                    PackageManager pm = context.getPackageManager();
+                                    ResolveInfo matched = pm.resolveActivity(
+                                            new Intent(Intent.ACTION_MAIN)
+                                                    .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
+                                            PackageManager.MATCH_DEFAULT_ONLY);
+                                    if (matched == null) {
+                                        // Try to find the best match activity.
+                                        Intent intent = pm.getLaunchIntentForPackage(
+                                                cn.getPackageName());
+                                        if (intent != null) {
+                                            cn = intent.getComponent();
+                                            appInfo = addedOrUpdatedApps.get(cn);
+                                        }
+
+                                        if ((intent == null) || (appInfo == null)) {
+                                            removedShortcuts.add(si);
+                                            continue;
+                                        }
+                                        si.promisedIntent = intent;
+                                    }
+                                }
+
+                                si.intent = si.promisedIntent;
+                                si.promisedIntent = null;
+                                si.status = ShortcutInfo.DEFAULT;
+                                infoUpdated = true;
+                                si.updateIcon(iconCache);
+                            }
+
+                            if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
+                                    && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                si.updateIcon(iconCache);
+                                si.title = Utilities.trim(appInfo.title);
+                                si.contentDescription = appInfo.contentDescription;
+                                infoUpdated = true;
+                            }
+
+                            int oldDisabledFlags = si.isDisabled;
+                            si.isDisabled = flagOp.apply(si.isDisabled);
+                            if (si.isDisabled != oldDisabledFlags) {
+                                shortcutUpdated = true;
+                            }
+                        }
+
+                        if (infoUpdated || shortcutUpdated) {
+                            updatedShortcuts.add(si);
+                        }
+                        if (infoUpdated) {
+                            LauncherModel.updateItemInDatabase(context, si);
+                        }
+                    } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
+                        LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
+                        if (mUser.equals(widgetInfo.user)
+                                && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+                                && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+                            widgetInfo.restoreStatus &=
+                                    ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+                                            ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+                            // adding this flag ensures that launcher shows 'click to setup'
+                            // if the widget has a config activity. In case there is no config
+                            // activity, it will be marked as 'restored' during bind.
+                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                            widgets.add(widgetInfo);
+                            LauncherModel.updateItemInDatabase(context, widgetInfo);
+                        }
+                    }
+                }
+            }
+
+            bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+            if (!removedShortcuts.isEmpty()) {
+                LauncherModel.deleteItemsFromDatabase(context, removedShortcuts);
+            }
+
+            if (!widgets.isEmpty()) {
+                scheduleCallbackTask(new CallbackTask() {
+                    @Override
+                    public void execute(Callbacks callbacks) {
+                        callbacks.bindWidgetsRestored(widgets);
+                    }
+                });
+            }
+        }
+
+        final HashSet<String> removedPackages = new HashSet<>();
+        final HashSet<ComponentName> removedComponents = new HashSet<>();
+        if (mOp == OP_REMOVE) {
+            // Mark all packages in the broadcast to be removed
+            Collections.addAll(removedPackages, packages);
+
+            // No need to update the removedComponents as
+            // removedPackages is a super-set of removedComponents
+        } else if (mOp == OP_UPDATE) {
+            // Mark disabled packages in the broadcast to be removed
+            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            for (int i=0; i<N; i++) {
+                if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+                    removedPackages.add(packages[i]);
+                }
+            }
+
+            // Update removedComponents as some components can get removed during package update
+            for (AppInfo info : removedApps) {
+                removedComponents.add(info.componentName);
+            }
+        }
+
+        if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
+            LauncherModel.deleteItemsFromDatabase(
+                    context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
+            LauncherModel.deleteItemsFromDatabase(
+                    context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
+
+            // Remove any queued items from the install queue
+            InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+
+            // Call the components-removed callback
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindWorkspaceComponentsRemoved(
+                            removedPackages, removedComponents, mUser);
+                }
+            });
+        }
+
+        if (!removedApps.isEmpty()) {
+            // Remove corresponding apps from All-Apps
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppInfosRemoved(removedApps);
+                }
+            });
+        }
+
+        // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
+        // get widget update signals.
+        if (!Utilities.ATLEAST_MARSHMALLOW &&
+                (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.notifyWidgetProvidersChanged();
+                }
+            });
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
new file mode 100644
index 0000000..8f7c21d
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,113 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles changes due to shortcut manager updates (deep shortcut changes)
+ */
+public class ShortcutsChangedTask extends ExtendedModelTask {
+
+    private final String mPackageName;
+    private final List<ShortcutInfoCompat> mShortcuts;
+    private final UserHandleCompat mUser;
+    private final boolean mUpdateIdMap;
+
+    public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+            UserHandleCompat user, boolean updateIdMap) {
+        mPackageName = packageName;
+        mShortcuts = shortcuts;
+        mUser = user;
+        mUpdateIdMap = updateIdMap;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        DeepShortcutManager deepShortcutManager = app.getShortcutManager();
+        deepShortcutManager.onShortcutsChanged(mShortcuts);
+
+        // Find ShortcutInfo's that have changed on the workspace.
+        final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
+        MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                ShortcutInfo si = (ShortcutInfo) itemInfo;
+                if (si.getPromisedIntent().getPackage().equals(mPackageName)
+                        && si.user.equals(mUser)) {
+                    idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
+                }
+            }
+        }
+
+        final Context context = LauncherAppState.getInstance().getContext();
+        final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+        if (!idsToWorkspaceShortcutInfos.isEmpty()) {
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails(
+                    mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+            for (ShortcutInfoCompat fullDetails : shortcuts) {
+                List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+                        .remove(fullDetails.getId());
+                if (!fullDetails.isPinned()) {
+                    // The shortcut was previously pinned but is no longer, so remove it from
+                    // the workspace and our pinned shortcut counts.
+                    // Note that we put this check here, after querying for full details,
+                    // because there's a possible race condition between pinning and
+                    // receiving this callback.
+                    removedShortcutInfos.addAll(shortcutInfos);
+                    continue;
+                }
+                for (ShortcutInfo shortcutInfo : shortcutInfos) {
+                    shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
+                    updatedShortcutInfos.add(shortcutInfo);
+                }
+            }
+        }
+
+        // If there are still entries in idsToWorkspaceShortcutInfos, that means that
+        // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
+        // means they were cleared, so we remove and unpin them now.
+        for (String id : idsToWorkspaceShortcutInfos.keySet()) {
+            removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
+        }
+
+        bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
+        if (!removedShortcutInfos.isEmpty()) {
+            LauncherModel.deleteItemsFromDatabase(context, removedShortcutInfos);
+        }
+
+        if (mUpdateIdMap) {
+            // Update the deep shortcut map if the list of ids has changed for an activity.
+            dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+            bindDeepShortcuts(dataModel);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
new file mode 100644
index 0000000..b7b52a4
--- /dev/null
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -0,0 +1,114 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Task to handle changing of lock state of the user
+ */
+public class UserLockStateChangedTask extends ExtendedModelTask {
+
+    private final UserHandleCompat mUser;
+
+    public UserLockStateChangedTask(UserHandleCompat user) {
+        mUser = user;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        Context context = app.getContext();
+        boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
+        DeepShortcutManager deepShortcutManager = app.getShortcutManager();
+
+        HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
+        if (isUserUnlocked) {
+            List<ShortcutInfoCompat> shortcuts =
+                    deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+            if (deepShortcutManager.wasLastCallSuccess()) {
+                for (ShortcutInfoCompat shortcut : shortcuts) {
+                    pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
+                }
+            } else {
+                // Shortcut manager can fail due to some race condition when the lock state
+                // changes too frequently. For the purpose of the update,
+                // consider it as still locked.
+                isUserUnlocked = false;
+            }
+        }
+
+        // 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 : dataModel.itemsIdMap) {
+            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                    && mUser.equals(itemInfo.user)) {
+                ShortcutInfo si = (ShortcutInfo) itemInfo;
+                if (isUserUnlocked) {
+                    ShortcutInfoCompat shortcut =
+                            pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(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()) {
+            LauncherModel.deleteItemsFromDatabase(context, deletedShortcutInfos);
+        }
+
+        // Remove shortcut id map for that user
+        Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator();
+        while (keysIter.hasNext()) {
+            if (keysIter.next().user.equals(mUser)) {
+                keysIter.remove();
+            }
+        }
+
+        if (isUserUnlocked) {
+            dataModel.updateDeepShortcutMap(
+                    null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+        }
+        bindDeepShortcuts(dataModel);
+    }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 6661429..78b7a3e 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -121,7 +121,7 @@
             // getting filled with the managed user apps, when it start with a fresh DB (or after
             // a very long time).
             if (userAppsExisted && !homescreenApps.isEmpty()) {
-                mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
+                mModel.addAndBindAddedWorkspaceItems(homescreenApps);
             }
         }
 
@@ -175,7 +175,7 @@
                 // Add the item to home screen and DB. This also generates an item id synchronously.
                 ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
                 itemList.add(workFolder);
-                mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+                mModel.addAndBindAddedWorkspaceItems(itemList);
                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
 
                 saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
@@ -200,7 +200,6 @@
         }
     }
 
-
     /**
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */