Fixing missing updates in package install sessions

> Ensure icon cache never returns null icon
> Enabling install shortuct receiver only after workspace has finished binding
> Making all the model changes for package installs on worker thread and only
posting the updaes on the UI
> Making shortcut exists check on the loaded items and not on the DB
> Explicitely using worker thread for PckageInstallListener
> Removing backward compatibility support from PackageInstallerCompat

Change-Id: I9592771b9670c1c1c84c8208cae8dafa7b393e65
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 48b38f1..fd45714 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -101,7 +101,7 @@
         mIconDpi = activityManager.getLauncherLargeIconDensity();
         mIconDb = new IconDB(context);
 
-        mWorkerHandler = new Handler(LauncherModel.sWorkerThread.getLooper());
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
     }
 
     private Drawable getFullResDefaultActivityIcon() {
@@ -388,16 +388,20 @@
         return new IconLoadRequest(request, mWorkerHandler);
     }
 
+    private Bitmap getNonNullIcon(CacheEntry entry, UserHandleCompat user) {
+        return entry.icon == null ? getDefaultIcon(user) : entry.icon;
+    }
+
     /**
      * Fill in "application" with the icon and label for "info."
      */
     public synchronized void getTitleAndIcon(AppInfo application,
             LauncherActivityInfoCompat info, boolean useLowResIcon) {
-        CacheEntry entry = cacheLocked(application.componentName, info,
-                info == null ? application.user : info.getUser(),
+        UserHandleCompat user = info == null ? application.user : info.getUser();
+        CacheEntry entry = cacheLocked(application.componentName, info, user,
                 false, useLowResIcon);
         application.title = entry.title;
-        application.iconBitmap = entry.icon;
+        application.iconBitmap = getNonNullIcon(entry, user);
         application.contentDescription = entry.contentDescription;
         application.usingLowResIcon = entry.isLowResIcon;
     }
@@ -445,7 +449,7 @@
             ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
             UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
-        shortcutInfo.setIcon(entry.icon);
+        shortcutInfo.setIcon(getNonNullIcon(entry, user));
         shortcutInfo.title = entry.title;
         shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
@@ -458,7 +462,7 @@
             String packageName, UserHandleCompat user, boolean useLowResIcon,
             PackageItemInfo infoOut) {
         CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
-        infoOut.iconBitmap = entry.icon;
+        infoOut.iconBitmap = getNonNullIcon(entry, user);
         infoOut.title = entry.title;
         infoOut.usingLowResIcon = entry.isLowResIcon;
         infoOut.contentDescription = entry.contentDescription;
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 0c69154..27dda64 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -199,12 +199,8 @@
                     }
                 }
 
-                final boolean exists = LauncherModel.shortcutExists(context, pendingInfo.label,
-                        intent, pendingInfo.user);
-                if (!exists) {
-                    // Generate a shortcut info to add into the model
-                    addShortcuts.add(pendingInfo.getShortcutInfo());
-                }
+                // Generate a shortcut info to add into the model
+                addShortcuts.add(pendingInfo.getShortcutInfo());
             }
 
             // Add the new apps to the model and bind them
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7364a9f..2084477 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -98,8 +98,6 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.Thunk;
@@ -1060,9 +1058,6 @@
         getWorkspace().reinflateWidgetsIfNecessary();
         reinflateQSBIfNecessary();
 
-        // Process any items that were added while Launcher was away.
-        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
-
         if (DEBUG_RESUME_TIME) {
             Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
         }
@@ -1078,7 +1073,10 @@
         mWorkspace.updateInteractionForState();
         mWorkspace.onResume();
 
-        PackageInstallerCompat.getInstance(this).onResume();
+        if (!isWorkspaceLoading()) {
+            // Process any items that were added while Launcher was away.
+            InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+        }
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
@@ -1089,7 +1087,6 @@
     protected void onPause() {
         // Ensure that items added to Launcher are queued until Launcher returns
         InstallShortcutReceiver.enableInstallQueue();
-        PackageInstallerCompat.getInstance(this).onPause();
 
         super.onPause();
         mPaused = true;
@@ -4084,7 +4081,7 @@
             sPendingAddItem = null;
         }
 
-        PackageInstallerCompat.getInstance(this).onFinishBind();
+        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.finishBindingItems(false);
@@ -4236,22 +4233,17 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
-    public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
-        if (mWorkspace != null) {
-            mWorkspace.updatePackageState(installInfo);
+    public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
+        Runnable r = new Runnable() {
+            public void run() {
+                bindRestoreItemsChange(updates);
+            }
+        };
+        if (waitUntilResume(r)) {
+            return;
         }
-    }
 
-    /**
-     * Update the label and icon of all the icons in a package
-     *
-     * Implementation of the method from LauncherModel.Callbacks.
-     */
-    @Override
-    public void updatePackageBadge(String packageName) {
-        if (mWorkspace != null) {
-            mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
-        }
+        mWorkspace.updateRestoreItems(updates);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 6e77d06..7f31e49 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -256,15 +256,4 @@
     public static boolean isDogfoodBuild() {
         return getInstance().mBuildInfo.isDogfoodBuild();
     }
-
-    public void setPackageState(ArrayList<PackageInstallInfo> installInfo) {
-        mModel.setPackageState(installInfo);
-    }
-
-    /**
-     * Updates the icons and label of all icons for the provided package name.
-     */
-    public void updatePackageBadge(String packageName) {
-        mModel.updatePackageBadge(packageName);
-    }
 }
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 9dd8dc5..03ec4bf 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -42,5 +42,6 @@
     // TODO: Delete these files on upgrade
     public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList(
             "launches.log",
-            "stats.log"));
+            "stats.log",
+            "com.android.launcher3.compat.PackageInstallerCompatV16.queue"));
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e5561e2..5a65cab 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -42,6 +42,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
@@ -199,8 +200,7 @@
         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
                 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
-        public void updatePackageState(ArrayList<PackageInstallInfo> installInfo);
-        public void updatePackageBadge(String packageName);
+        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
@@ -282,30 +282,110 @@
         return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
     }
 
-    public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) {
-        // Process the updated package state
-        Runnable r = new Runnable() {
+    public void setPackageState(final PackageInstallInfo installInfo) {
+        Runnable updateRunnable = new Runnable() {
+
+            @Override
             public void run() {
-                Callbacks callbacks = getCallback();
-                if (callbacks != null) {
-                    callbacks.updatePackageState(installInfo);
+                synchronized (sBgLock) {
+                    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 : sBgItemsIdMap.values()) {
+                        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 : sBgAppWidgets) {
+                        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);
+                    }
                 }
             }
         };
-        mHandler.post(r);
+        runOnWorkerThread(updateRunnable);
     }
 
-    public void updatePackageBadge(final String packageName) {
-        // Process the updated package badge
-        Runnable r = new Runnable() {
+    /**
+     * 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() {
-                Callbacks callbacks = getCallback();
-                if (callbacks != null) {
-                    callbacks.updatePackageBadge(packageName);
+                synchronized (sBgLock) {
+                    final ArrayList<ShortcutInfo> updates = new ArrayList<>();
+                    final UserHandleCompat user = UserHandleCompat.myUserHandle();
+
+                    for (ItemInfo info : sBgItemsIdMap.values()) {
+                        if (info instanceof ShortcutInfo) {
+                            ShortcutInfo si = (ShortcutInfo) info;
+                            ComponentName cn = si.getTargetComponent();
+                            if (si.isPromise() && (cn != null)
+                                    && packageName.equals(cn.getPackageName())) {
+                                if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+                                    // For auto install apps update the icon as well as label.
+                                    mIconCache.getTitleAndIcon(si,
+                                            si.promisedIntent, user,
+                                            si.shouldUseLowResIcon());
+                                } else {
+                                    // Only update the icon for restored apps.
+                                    si.updateIcon(mIconCache);
+                                }
+                                updates.add(si);
+                            }
+                        }
+                    }
+
+                    if (!updates.isEmpty()) {
+                        // Push changes to the callback.
+                        Runnable r = new Runnable() {
+                            public void run() {
+                                Callbacks callbacks = getCallback();
+                                if (callbacks != null) {
+                                    callbacks.bindShortcutsChanged(updates,
+                                            new ArrayList<ShortcutInfo>(), user);
+                                }
+                            }
+                        };
+                        mHandler.post(r);
+                    }
                 }
             }
         };
-        mHandler.post(r);
+        runOnWorkerThread(updateRunnable);
     }
 
     public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
@@ -537,8 +617,7 @@
                     for (ItemInfo item : workspaceApps) {
                         if (!allowDuplicate && item instanceof ShortcutInfo) {
                             // Short-circuit this logic if the icon exists somewhere on the workspace
-                            if (shortcutExists(context, item.title.toString(),
-                                    item.getIntent(), item.user)) {
+                            if (shortcutExists(context, item.getIntent(), item.user)) {
                                 continue;
                             }
                         }
@@ -904,41 +983,42 @@
     }
 
     /**
-     * Returns true if the shortcuts already exists in the database.
-     * we identify a shortcut by its title and intent.
+     * 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.
+     * TODO: Throw exception is above condition is not met.
      */
-    static boolean shortcutExists(Context context, String title, Intent intent,
-            UserHandleCompat user) {
-        final ContentResolver cr = context.getContentResolver();
+    @Thunk static boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
         final Intent intentWithPkg, intentWithoutPkg;
-
+        final String packageName;
         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.
+            packageName = intent.getComponent().getPackageName();
             if (intent.getPackage() != null) {
                 intentWithPkg = intent;
                 intentWithoutPkg = new Intent(intent).setPackage(null);
             } else {
-                intentWithPkg = new Intent(intent).setPackage(
-                        intent.getComponent().getPackageName());
+                intentWithPkg = new Intent(intent).setPackage(packageName);
                 intentWithoutPkg = intent;
             }
         } else {
             intentWithPkg = intent;
             intentWithoutPkg = intent;
+            packageName = intent.getPackage();
         }
-        String userSerial = Long.toString(UserManagerCompat.getInstance(context)
-                .getSerialNumberForUser(user));
-        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
-            new String[] { "title", "intent", "profileId" },
-            "title=? and (intent=? or intent=?) and profileId=?",
-            new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial },
-            null);
-        try {
-            return c.moveToFirst();
-        } finally {
-            c.close();
+
+        synchronized (sBgLock) {
+            for (ItemInfo item : sBgItemsIdMap.values()) {
+                if (item instanceof ShortcutInfo) {
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    if (intentWithPkg.equals(info.getIntent())
+                            || intentWithoutPkg.equals(info.getIntent())) {
+                        return true;
+                    }
+                }
+            }
         }
+        return false;
     }
 
     /**
@@ -1366,6 +1446,8 @@
     }
 
     public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
+        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
+        InstallShortcutReceiver.enableInstallQueue();
         synchronized (mLock) {
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
@@ -1812,7 +1894,7 @@
 
             synchronized (sBgLock) {
                 clearSBgDataStructures();
-                final HashSet<String> installingPkgs = PackageInstallerCompat
+                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                         .getInstance(mContext).updateAndGetActiveSessionCache();
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
@@ -1951,7 +2033,7 @@
 
                                             if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
                                                 // Restore has started once.
-                                            } else if (installingPkgs.contains(cn.getPackageName())) {
+                                            } else if (installingPkgs.containsKey(cn.getPackageName())) {
                                                 // App restore has started. Update the flag
                                                 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
                                                 ContentValues values = new ContentValues();
@@ -2093,6 +2175,18 @@
                                         break;
                                     }
 
+                                    if (restored) {
+                                        ComponentName cn = info.getTargetComponent();
+                                        if (cn != null) {
+                                            Integer progress = installingPkgs.get(cn.getPackageName());
+                                            if (progress != null) {
+                                                info.setInstallProgress(progress);
+                                            } else {
+                                                info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+                                            }
+                                        }
+                                    }
+
                                     switch (container) {
                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
@@ -2220,10 +2314,11 @@
                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
                                                 component);
                                         appWidgetInfo.restoreStatus = restoreStatus;
+                                        Integer installProgress = installingPkgs.get(component.getPackageName());
 
                                         if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
                                             // Restore has started once.
-                                        } else if (installingPkgs.contains(component.getPackageName())) {
+                                        } else if (installProgress != null) {
                                             // App restore has started. Update the flag
                                             appWidgetInfo.restoreStatus |=
                                                     LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
@@ -2233,6 +2328,9 @@
                                             itemsToRemove.add(id);
                                             continue;
                                         }
+
+                                        appWidgetInfo.installProgress =
+                                                installProgress == null ? 0 : installProgress;
                                     }
 
                                     appWidgetInfo.id = id;
@@ -3112,15 +3210,13 @@
                                     }
 
                                     // Restore the shortcut.
-                                    si.intent = si.promisedIntent;
-                                    si.promisedIntent = null;
-                                    si.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
-                                            & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
-                                            & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
                                     if (appInfo != null) {
                                         si.flags = appInfo.flags;
                                     }
 
+                                    si.intent = si.promisedIntent;
+                                    si.promisedIntent = null;
+                                    si.status = ShortcutInfo.DEFAULT;
                                     infoUpdated = true;
                                     si.updateIcon(mIconCache);
                                 }
@@ -3353,12 +3449,10 @@
             if (!TextUtils.isEmpty(title)) {
                 info.title = title;
             }
-            info.status = ShortcutInfo.FLAG_RESTORED_ICON;
         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
             if (TextUtils.isEmpty(info.title)) {
                 info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
             }
-            info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON;
         } else {
             throw new InvalidParameterException("Invalid restoreType " + promiseType);
         }
@@ -3367,6 +3461,7 @@
                 info.title.toString(), info.user);
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.promisedIntent = intent;
+        info.status = promiseType;
         return info;
     }
 
@@ -3669,4 +3764,11 @@
             return sBgFolders.get(folderId);
         }
     }
+
+    /**
+     * @return the looper for the worker thread which can be used to start background tasks.
+     */
+    public static Looper getWorkerLooper() {
+        return sWorkerThread.getLooper();
+    }
 }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 5bef845..6354fcd 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -214,6 +214,7 @@
         String uri = promisedIntent != null ? promisedIntent.toUri(0)
                 : (intent != null ? intent.toUri(0) : null);
         values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
+        values.put(LauncherSettings.Favorites.RESTORED, status);
 
         if (customIcon) {
             values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9e680fb..abb8489 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -62,8 +62,6 @@
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperUtils;
@@ -4358,32 +4356,17 @@
         removeItemsByPackageName(packages, user);
     }
 
-    public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
+    public void updateRestoreItems(final HashSet<ItemInfo> updates) {
         mapOverItems(MAP_RECURSE, new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v, View parent) {
-                if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
-                    ShortcutInfo shortcutInfo = (ShortcutInfo) info;
-                    ComponentName cn = shortcutInfo.getTargetComponent();
-                    if (user.equals(shortcutInfo.user) && cn != null
-                            && shortcutInfo.isPromise()
-                            && packageName.equals(cn.getPackageName())) {
-                        if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
-                            // For auto install apps update the icon as well as label.
-                            mIconCache.getTitleAndIcon(shortcutInfo,
-                                    shortcutInfo.promisedIntent, user,
-                                    shortcutInfo.shouldUseLowResIcon());
-                        } else {
-                            // Only update the icon for restored apps.
-                            shortcutInfo.updateIcon(mIconCache);
-                        }
-                        BubbleTextView shortcut = (BubbleTextView) v;
-                        shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
-
-                        if (parent != null) {
-                            parent.invalidate();
-                        }
-                    }
+                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
+                        && updates.contains(info)) {
+                    ((BubbleTextView) v).applyState(false);
+                } else if (v instanceof PendingAppWidgetHostView
+                        && info instanceof LauncherAppWidgetInfo
+                        && updates.contains(info)) {
+                    ((PendingAppWidgetHostView) v).applyState();
                 }
                 // process all the shortcuts
                 return false;
@@ -4391,42 +4374,6 @@
         });
     }
 
-    public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
-        for (final PackageInstallInfo installInfo : installInfos) {
-            if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
-                continue;
-            }
-
-            mapOverItems(MAP_RECURSE, new ItemOperator() {
-                @Override
-                public boolean evaluate(ItemInfo info, View v, View parent) {
-                    if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
-                        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;
-                            }
-                            ((BubbleTextView)v).applyState(false);
-                        }
-                    } else if (v instanceof PendingAppWidgetHostView
-                            && info instanceof LauncherAppWidgetInfo
-                            && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
-                                .equals(installInfo.packageName)) {
-                        ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
-                        ((PendingAppWidgetHostView) v).applyState();
-                    }
-
-                    // process all the shortcuts
-                    return false;
-                }
-            });
-        }
-    }
-
     void widgetsRestored(ArrayList<LauncherAppWidgetInfo> changedInfo) {
         if (!changedInfo.isEmpty()) {
             DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index 0eb8754..c499083 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -20,7 +20,7 @@
 
 import com.android.launcher3.Utilities;
 
-import java.util.HashSet;
+import java.util.HashMap;
 
 public abstract class PackageInstallerCompat {
 
@@ -37,25 +37,20 @@
                 if (Utilities.isLmpOrAbove()) {
                     sInstance = new PackageInstallerCompatVL(context);
                 } else {
-                    sInstance = new PackageInstallerCompatV16(context) { };
+                    sInstance = new PackageInstallerCompatV16();
                 }
             }
             return sInstance;
         }
     }
 
-    public abstract HashSet<String> updateAndGetActiveSessionCache();
-
-    public abstract void onPause();
-
-    public abstract void onResume();
-
-    public abstract void onFinishBind();
+    /**
+     * @return a map of active installs to their progress
+     */
+    public abstract HashMap<String, Integer> updateAndGetActiveSessionCache();
 
     public abstract void onStop();
 
-    public abstract void recordPackageUpdate(String packageName, int state, int progress);
-
     public static final class PackageInstallInfo {
         public final String packageName;
 
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
index 1910d22..654e349 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
@@ -16,160 +16,17 @@
 
 package com.android.launcher3.compat;
 
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.launcher3.LauncherAppState;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-import org.json.JSONTokener;
-
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.HashMap;
 
 public class PackageInstallerCompatV16 extends PackageInstallerCompat {
 
-    private static final String TAG = "PackageInstallerCompatV16";
-    private static final boolean DEBUG = false;
-
-    private static final String KEY_PROGRESS = "progress";
-    private static final String KEY_STATE = "state";
-
-    private static final String PREFS =
-            "com.android.launcher3.compat.PackageInstallerCompatV16.queue";
-
-    protected final SharedPreferences mPrefs;
-
-    boolean mUseQueue;
-    boolean mFinishedBind;
-    boolean mReplayPending;
-
-    PackageInstallerCompatV16(Context context) {
-        mPrefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
-    }
-
-    @Override
-    public void onPause() {
-        mUseQueue = true;
-        if (DEBUG) Log.d(TAG, "updates paused");
-    }
-
-    @Override
-    public void onResume() {
-        mUseQueue = false;
-        if (mFinishedBind) {
-            replayUpdates();
-        }
-    }
-
-    @Override
-    public void onFinishBind() {
-        mFinishedBind = true;
-        if (!mUseQueue) {
-            replayUpdates();
-        }
-    }
+    PackageInstallerCompatV16() { }
 
     @Override
     public void onStop() { }
 
-    private void replayUpdates() {
-        if (DEBUG) Log.d(TAG, "updates resumed");
-        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app == null) {
-            mReplayPending = true; // try again later
-            if (DEBUG) Log.d(TAG, "app is null, delaying send");
-            return;
-        }
-        mReplayPending = false;
-        ArrayList<PackageInstallInfo> updates = new ArrayList<PackageInstallInfo>();
-        for (String packageName: mPrefs.getAll().keySet()) {
-            final String json = mPrefs.getString(packageName, null);
-            if (!TextUtils.isEmpty(json)) {
-                updates.add(infoFromJson(packageName, json));
-            }
-        }
-        if (!updates.isEmpty()) {
-            sendUpdate(app, updates);
-        }
-    }
-
-    /**
-     * This should be called by the implementations to register a package update.
-     */
     @Override
-    public synchronized void recordPackageUpdate(String packageName, int state, int progress) {
-        SharedPreferences.Editor editor = mPrefs.edit();
-        PackageInstallInfo installInfo = new PackageInstallInfo(packageName);
-        installInfo.progress = progress;
-        installInfo.state = state;
-        if (state == STATUS_INSTALLED) {
-            // no longer necessary to track this package
-            editor.remove(packageName);
-            if (DEBUG) Log.d(TAG, "no longer tracking " + packageName);
-        } else {
-            editor.putString(packageName, infoToJson(installInfo));
-            if (DEBUG)
-                Log.d(TAG, "saved state: " + infoToJson(installInfo)
-                        + " for package: " + packageName);
-
-        }
-        editor.commit();
-
-        if (!mUseQueue) {
-            if (mReplayPending) {
-                replayUpdates();
-            } else if (state != STATUS_INSTALLED) {
-                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-                ArrayList<PackageInstallInfo> update = new ArrayList<PackageInstallInfo>();
-                update.add(installInfo);
-                sendUpdate(app, update);
-            }
-        }
-    }
-
-    private void sendUpdate(LauncherAppState app, ArrayList<PackageInstallInfo> updates) {
-        if (app == null) {
-            mReplayPending = true; // try again later
-            if (DEBUG) Log.d(TAG, "app is null, delaying send");
-        } else {
-            app.setPackageState(updates);
-        }
-    }
-
-    private static PackageInstallInfo infoFromJson(String packageName, String json) {
-        PackageInstallInfo info = new PackageInstallInfo(packageName);
-        try {
-            JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
-            info.state = object.getInt(KEY_STATE);
-            info.progress = object.getInt(KEY_PROGRESS);
-        } catch (JSONException e) {
-            Log.e(TAG, "failed to deserialize app state update", e);
-        }
-        return info;
-    }
-
-    private static String infoToJson(PackageInstallInfo info) {
-        String value = null;
-        try {
-            JSONStringer json = new JSONStringer()
-                    .object()
-                    .key(KEY_STATE).value(info.state)
-                    .key(KEY_PROGRESS).value(info.progress)
-                    .endObject();
-            value = json.toString();
-        } catch (JSONException e) {
-            Log.e(TAG, "failed to serialize app state update", e);
-        }
-        return value;
-    }
-
-    @Override
-    public HashSet<String> updateAndGetActiveSessionCache() {
-        return new HashSet<String>();
+    public HashMap<String, Integer> updateAndGetActiveSessionCache() {
+        return new HashMap<>();
     }
 }
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index d6d4b82..395d5f9 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -21,63 +21,41 @@
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.Handler;
-import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.util.Thunk;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.HashMap;
 
-public class PackageInstallerCompatVL extends PackageInstallerCompat implements Runnable {
+public class PackageInstallerCompatVL extends PackageInstallerCompat {
 
-    private static final String TAG = "PackageInstallerCompatVL";
-    private static final boolean DEBUG = false;
-
-    // All updates to these sets must happen on the {@link #mWorker} thread.
-    @Thunk final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>();
-    @Thunk final HashSet<String> mPendingBadgeUpdates = new HashSet<String>();
+    @Thunk final SparseArray<String> mActiveSessions = new SparseArray<>();
 
     @Thunk final PackageInstaller mInstaller;
     private final IconCache mCache;
     private final Handler mWorker;
 
-    private boolean mResumed;
-    private boolean mBound;
-
     PackageInstallerCompatVL(Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
         LauncherAppState.setApplicationContext(context.getApplicationContext());
         mCache = LauncherAppState.getInstance().getIconCache();
-        mWorker = new Handler();
-
-        mResumed = false;
-        mBound = false;
+        mWorker = new Handler(LauncherModel.getWorkerLooper());
 
         mInstaller.registerSessionCallback(mCallback, mWorker);
-
-        // On start, send updates for all active sessions
-        mWorker.post(new Runnable() {
-
-            @Override
-            public void run() {
-                for (SessionInfo info : mInstaller.getAllSessions()) {
-                    mPendingReplays.append(info.getSessionId(), info);
-                }
-            }
-        });
     }
 
     @Override
-    public HashSet<String> updateAndGetActiveSessionCache() {
-        HashSet<String> activePackages = new HashSet<String>();
+    public HashMap<String, Integer> updateAndGetActiveSessionCache() {
+        HashMap<String, Integer> activePackages = new HashMap<>();
         UserHandleCompat user = UserHandleCompat.myUserHandle();
         for (SessionInfo info : mInstaller.getAllSessions()) {
             addSessionInfoToCahce(info, user);
             if (info.getAppPackageName() != null) {
-                activePackages.add(info.getAppPackageName());
+                activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100));
+                mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
             }
         }
         return activePackages;
@@ -96,74 +74,10 @@
         mInstaller.unregisterSessionCallback(mCallback);
     }
 
-    @Override
-    public void onFinishBind() {
-        mBound = true;
-        mWorker.post(this);
-    }
-
-    @Override
-    public void onPause() {
-        mResumed = false;
-    }
-
-    @Override
-    public void onResume() {
-        mResumed = true;
-        mWorker.post(this);
-    }
-
-    @Override
-    public void recordPackageUpdate(String packageName, int state, int progress) {
-        // No op
-    }
-
-    @Override
-    public void run() {
-        // Called on mWorker thread.
-        replayUpdates(null);
-    }
-
-    @Thunk void replayUpdates(PackageInstallInfo newInfo) {
-        if (DEBUG) Log.d(TAG, "updates resumed");
-        if (!mResumed || !mBound) {
-            // Not yet ready
-            return;
-        }
-        if ((mPendingReplays.size() == 0) && (newInfo == null)) {
-            // Nothing to update
-            return;
-        }
-
+    @Thunk void sendUpdate(PackageInstallInfo info) {
         LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app == null) {
-            // Try again later
-            if (DEBUG) Log.d(TAG, "app is null, delaying send");
-            return;
-        }
-
-        ArrayList<PackageInstallInfo> updates = new ArrayList<PackageInstallInfo>();
-        if ((newInfo != null) && (newInfo.state != STATUS_INSTALLED)) {
-            updates.add(newInfo);
-        }
-        for (int i = mPendingReplays.size() - 1; i >= 0; i--) {
-            SessionInfo session = mPendingReplays.valueAt(i);
-            if (session.getAppPackageName() != null) {
-                updates.add(new PackageInstallInfo(session.getAppPackageName(),
-                        STATUS_INSTALLING,
-                        (int) (session.getProgress() * 100)));
-            }
-        }
-        mPendingReplays.clear();
-        if (!updates.isEmpty()) {
-            app.setPackageState(updates);
-        }
-
-        if (!mPendingBadgeUpdates.isEmpty()) {
-            for (String pkg : mPendingBadgeUpdates) {
-                app.updatePackageBadge(pkg);
-            }
-            mPendingBadgeUpdates.clear();
+        if (app != null) {
+            app.getModel().setPackageState(info);
         }
     }
 
@@ -171,19 +85,18 @@
 
         @Override
         public void onCreated(int sessionId) {
-            pushSessionBadgeToLauncher(sessionId);
+            pushSessionDisplayToLauncher(sessionId);
         }
 
         @Override
         public void onFinished(int sessionId, boolean success) {
-            mPendingReplays.remove(sessionId);
-            SessionInfo session = mInstaller.getSessionInfo(sessionId);
-            if ((session != null) && (session.getAppPackageName() != null)) {
-                mPendingBadgeUpdates.remove(session.getAppPackageName());
-                // Replay all updates with a one time update for this installed package. No
-                // need to store this record for future updates, as the app list will get
-                // refreshed on resume.
-                replayUpdates(new PackageInstallInfo(session.getAppPackageName(),
+            // For a finished session, we can't get the session info. So use the
+            // packageName from our local cache.
+            String packageName = mActiveSessions.get(sessionId);
+            mActiveSessions.remove(sessionId);
+
+            if (packageName != null) {
+                sendUpdate(new PackageInstallInfo(packageName,
                         success ? STATUS_INSTALLED : STATUS_FAILED, 0));
             }
         }
@@ -192,8 +105,9 @@
         public void onProgressChanged(int sessionId, float progress) {
             SessionInfo session = mInstaller.getSessionInfo(sessionId);
             if (session != null) {
-                mPendingReplays.put(sessionId, session);
-                replayUpdates(null);
+                sendUpdate(new PackageInstallInfo(session.getAppPackageName(),
+                        STATUS_INSTALLING,
+                        (int) (session.getProgress() * 100)));
             }
         }
 
@@ -202,18 +116,18 @@
 
         @Override
         public void onBadgingChanged(int sessionId) {
-            pushSessionBadgeToLauncher(sessionId);
+            pushSessionDisplayToLauncher(sessionId);
         }
 
-        private void pushSessionBadgeToLauncher(int sessionId) {
+        private void pushSessionDisplayToLauncher(int sessionId) {
             SessionInfo session = mInstaller.getSessionInfo(sessionId);
             if (session != null) {
                 addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
-                if (session.getAppPackageName() != null) {
-                    mPendingBadgeUpdates.add(session.getAppPackageName());
+                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+
+                if (app != null) {
+                    app.getModel().updateSessionDisplayInfo(session.getAppPackageName());
                 }
-                mPendingReplays.put(sessionId, session);
-                replayUpdates(null);
             }
         }
     };