Merge "Add drop animation / Toast to widgettray" into ub-launcher3-burnaby
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml
index 9b0da1d..fe18edc 100644
--- a/res/layout/search_drop_target_bar.xml
+++ b/res/layout/search_drop_target_bar.xml
@@ -45,19 +45,6 @@
             style="@style/DropTargetButtonContainer"
             android:layout_weight="1" >
 
-            <!-- Uninstall target -->
-
-            <com.android.launcher3.UninstallDropTarget
-                android:id="@+id/uninstall_target_text"
-                style="@style/DropTargetButton"
-                android:drawableStart="@drawable/uninstall_target_selector"
-                android:text="@string/delete_target_uninstall_label" />
-        </FrameLayout>
-
-        <FrameLayout
-            style="@style/DropTargetButtonContainer"
-            android:layout_weight="1" >
-
             <!-- Info target -->
 
             <com.android.launcher3.InfoDropTarget
@@ -66,6 +53,19 @@
                 android:drawableStart="@drawable/info_target_selector"
                 android:text="@string/info_target_label" />
         </FrameLayout>
+
+        <FrameLayout
+            style="@style/DropTargetButtonContainer"
+            android:layout_weight="1" >
+
+            <!-- Uninstall target -->
+
+            <com.android.launcher3.UninstallDropTarget
+                android:id="@+id/uninstall_target_text"
+                style="@style/DropTargetButton"
+                android:drawableStart="@drawable/uninstall_target_selector"
+                android:text="@string/delete_target_uninstall_label" />
+        </FrameLayout>
     </LinearLayout>
 
 </com.android.launcher3.SearchDropTargetBar>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 52306e3..bfe7e36 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -309,13 +309,13 @@
 
 <!-- Strings for accessibility actions -->
     <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
-    <string name="action_add_to_workspace">Add To Workspace</string>
+    <string name="action_add_to_workspace">Add to workspace</string>
 
     <!-- Accessibility confirmation for item added to workspace [DO NOT TRANSLATE] -->
     <string name="item_added_to_workspace">Item added to workspace</string>
 
     <!-- Accessibility confirmation for item removed [DO NOT TRANSLATE] -->
-    <string name="item_removed_from_workspace">Item removed from workspace</string>
+    <string name="item_removed">Item removed</string>
 
     <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
     <string name="action_move">Move Item</string>
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 5b39908..fb49df5 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -26,18 +26,19 @@
 import android.graphics.drawable.TransitionDrawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 import android.widget.TextView;
 
-import com.android.launcher3.R;
 import com.android.launcher3.util.Thunk;
 
 /**
  * Implements a DropTarget.
  */
-public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener {
+public abstract class ButtonDropTarget extends TextView
+        implements DropTarget, DragController.DragListener, OnClickListener {
 
     private static int DRAG_VIEW_DROP_DURATION = 285;
 
@@ -256,4 +257,18 @@
     public void getLocationInDragLayer(int[] loc) {
         mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
     }
+
+    public void enableAccessibleDrag(boolean enable) {
+        setOnClickListener(enable ? this : null);
+    }
+
+    protected String getAccessibilityDropConfirmation() {
+        return null;
+    }
+
+    @Override
+    public void onClick(View v) {
+        LauncherAppState.getInstance().getAccessibilityDelegate()
+            .handleAccessibleDrop(this, null, getAccessibilityDropConfirmation());
+    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index f4afb95..f08f25f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -557,7 +557,7 @@
             Resources res = getContext().getResources();
             View child = getChildAt(x, y);
             if (child == null || child == dragInfo.item) {
-                return res.getString(R.string.move_to_empty_cell, x, y);
+                return res.getString(R.string.move_to_empty_cell, x + 1, y + 1);
             } else {
                 ItemInfo info = (ItemInfo) child.getTag();
                 if (info instanceof AppInfo || info instanceof ShortcutInfo) {
diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
index eb7c26a..a43ab67 100644
--- a/src/com/android/launcher3/DeferredHandler.java
+++ b/src/com/android/launcher3/DeferredHandler.java
@@ -20,12 +20,10 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
-import android.util.Pair;
 
 import com.android.launcher3.util.Thunk;
 
 import java.util.LinkedList;
-import java.util.ListIterator;
 
 /**
  * Queue of things to run on a looper thread.  Items posted with {@link #post} will not
@@ -35,20 +33,18 @@
  * This class is fifo.
  */
 public class DeferredHandler {
-    @Thunk LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
+    @Thunk LinkedList<Runnable> mQueue = new LinkedList<>();
     private MessageQueue mMessageQueue = Looper.myQueue();
     private Impl mHandler = new Impl();
 
     @Thunk class Impl extends Handler implements MessageQueue.IdleHandler {
         public void handleMessage(Message msg) {
-            Pair<Runnable, Integer> p;
             Runnable r;
             synchronized (mQueue) {
                 if (mQueue.size() == 0) {
                     return;
                 }
-                p = mQueue.removeFirst();
-                r = p.first;
+                r = mQueue.removeFirst();
             }
             r.run();
             synchronized (mQueue) {
@@ -79,11 +75,8 @@
 
     /** Schedule runnable to run after everything that's on the queue right now. */
     public void post(Runnable runnable) {
-        post(runnable, 0);
-    }
-    public void post(Runnable runnable, int type) {
         synchronized (mQueue) {
-            mQueue.add(new Pair<Runnable, Integer>(runnable, type));
+            mQueue.add(runnable);
             if (mQueue.size() == 1) {
                 scheduleNextLocked();
             }
@@ -92,31 +85,10 @@
 
     /** Schedule runnable to run when the queue goes idle. */
     public void postIdle(final Runnable runnable) {
-        postIdle(runnable, 0);
-    }
-    public void postIdle(final Runnable runnable, int type) {
-        post(new IdleRunnable(runnable), type);
+        post(new IdleRunnable(runnable));
     }
 
-    public void cancelRunnable(Runnable runnable) {
-        synchronized (mQueue) {
-            while (mQueue.remove(runnable)) { }
-        }
-    }
-    public void cancelAllRunnablesOfType(int type) {
-        synchronized (mQueue) {
-            ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator();
-            Pair<Runnable, Integer> p;
-            while (iter.hasNext()) {
-                p = iter.next();
-                if (p.second == type) {
-                    iter.remove();
-                }
-            }
-        }
-    }
-
-    public void cancel() {
+    public void cancelAll() {
         synchronized (mQueue) {
             mQueue.clear();
         }
@@ -124,20 +96,19 @@
 
     /** Runs all queued Runnables from the calling thread. */
     public void flush() {
-        LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>();
+        LinkedList<Runnable> queue = new LinkedList<>();
         synchronized (mQueue) {
             queue.addAll(mQueue);
             mQueue.clear();
         }
-        for (Pair<Runnable, Integer> p : queue) {
-            p.first.run();
+        for (Runnable r : queue) {
+            r.run();
         }
     }
 
     void scheduleNextLocked() {
         if (mQueue.size() > 0) {
-            Pair<Runnable, Integer> p = mQueue.getFirst();
-            Runnable peek = p.first;
+            Runnable peek = mQueue.getFirst();
             if (peek instanceof IdleRunnable) {
                 mMessageQueue.addIdleHandler(mHandler);
             } else {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index aa3e66c..e741b97 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -29,7 +29,6 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 
-import com.android.launcher3.R;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.WidgetsContainerView;
 
@@ -59,13 +58,15 @@
         setDrawable(R.drawable.remove_target_selector);
     }
 
-    public static boolean willAcceptDrop(DragSource source, Object info) {
-        return (info instanceof ItemInfo) && source.supportsDeleteDropTarget();
+    public static boolean supportsDrop(Object info) {
+        return (info instanceof ShortcutInfo)
+                || (info instanceof LauncherAppWidgetInfo)
+                || (info instanceof FolderInfo);
     }
 
     @Override
     protected boolean supportsDrop(DragSource source, Object info) {
-        return willAcceptDrop(source, info);
+        return source.supportsDeleteDropTarget() && supportsDrop(info);
     }
 
     @Override
@@ -304,4 +305,9 @@
         dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
+
+    @Override
+    protected String getAccessibilityDropConfirmation() {
+        return getResources().getString(R.string.item_removed);
+    }
 }
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index b24608c..1968868 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -462,8 +462,7 @@
                 mLastTouchUpTime = System.currentTimeMillis();
                 if (mDragging) {
                     PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                    if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource,
-                            mDragObject.dragInfo)) {
+                    if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
                         vec = null;
                     }
                     if (vec != null) {
@@ -617,7 +616,7 @@
 
             if (mDragging) {
                 PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, mDragObject.dragInfo)) {
+                if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
                     vec = null;
                 }
                 if (vec != null) {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index c35ce94..dff47c2 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -745,9 +745,18 @@
                 replaceFolderWithFinalItem();
             }
         } else {
-            rearrangeChildren();
             // The drag failed, we need to return the item to the folder
+            ShortcutInfo info = (ShortcutInfo) d.dragInfo;
+            View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
+                    ? mCurrentDragView : mContent.createNewView(info);
+            ArrayList<View> views = getItemsInReadingOrder();
+            views.add(info.rank, icon);
+            mContent.arrangeChildren(views, views.size());
+            mItemsInvalidated = true;
+
+            mSuppressOnAdd = true;
             mFolderIcon.onDrop(d);
+            mSuppressOnAdd = false;
         }
 
         if (target != this) {
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index 6174892..3f08f43 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -363,7 +363,7 @@
     }
 
     @SuppressLint("InflateParams")
-    private View createNewView(ShortcutInfo item) {
+    public View createNewView(ShortcutInfo item) {
         final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
                 R.layout.folder_application, null, false);
         textView.applyFromShortcutInfo(item, mIconCache, false);
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/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index e48640c..f1ff48d 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -21,7 +21,6 @@
 import android.provider.Settings;
 import android.util.AttributeSet;
 
-import com.android.launcher3.R;
 import com.android.launcher3.compat.UserHandleCompat;
 
 public class InfoDropTarget extends ButtonDropTarget {
@@ -66,9 +65,13 @@
 
     @Override
     protected boolean supportsDrop(DragSource source, Object info) {
-        return source.supportsAppInfoDropTarget() &&
-                Settings.Global.getInt(getContext().getContentResolver(),
-                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
+        return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
+    }
+
+    public static boolean supportsDrop(Context context, Object info) {
+        return (Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) &&
+                (info instanceof AppInfo || info instanceof PendingAddItemInfo);
     }
 
     @Override
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 c708b3c..8c920f0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -98,10 +98,9 @@
 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.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -305,7 +304,7 @@
 
     @Thunk static LocaleConfiguration sLocaleConfiguration = null;
 
-    private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+    private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
@@ -1061,9 +1060,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));
         }
@@ -1079,7 +1075,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();
@@ -1090,7 +1089,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;
@@ -2051,12 +2049,6 @@
 
         TextKeyListener.getInstance().release();
 
-        // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
-        // to prevent leaking Launcher activities on orientation change.
-        if (mModel != null) {
-            mModel.unbindItemInfosAndClearQueuedBindRunnables();
-        }
-
         getContentResolver().unregisterContentObserver(mWidgetObserver);
         unregisterReceiver(mCloseSystemDialogsReceiver);
 
@@ -3896,7 +3888,7 @@
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindFolders(final HashMap<Long, FolderInfo> folders) {
+    public void bindFolders(final LongArrayMap<FolderInfo> folders) {
         Runnable r = new Runnable() {
             public void run() {
                 bindFolders(folders);
@@ -3905,8 +3897,7 @@
         if (waitUntilResume(r)) {
             return;
         }
-        sFolders.clear();
-        sFolders.putAll(folders);
+        sFolders = folders.clone();
     }
 
     /**
@@ -4085,7 +4076,7 @@
             sPendingAddItem = null;
         }
 
-        PackageInstallerCompat.getInstance(this).onFinishBind();
+        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.finishBindingItems(false);
@@ -4237,22 +4228,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/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index 8ba02ea..a60e160 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -1,16 +1,13 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.text.TextUtils;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -68,18 +65,21 @@
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
+        if (DeleteDropTarget.supportsDrop(item)) {
+            info.addAction(mActions.get(REMOVE));
+        }
+        if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
+            info.addAction(mActions.get(UNINSTALL));
+        }
+        if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
+            info.addAction(mActions.get(INFO));
+        }
+
         if ((item instanceof ShortcutInfo)
                 || (item instanceof LauncherAppWidgetInfo)
                 || (item instanceof FolderInfo)) {
-            // Workspace shortcut / widget
-            info.addAction(mActions.get(REMOVE));
             info.addAction(mActions.get(MOVE));
-        } else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
-            // App or Widget from customization tray
-            if (item instanceof AppInfo) {
-                info.addAction(mActions.get(UNINSTALL));
-            }
-            info.addAction(mActions.get(INFO));
+        } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
             info.addAction(mActions.get(ADD_TO_WORKSPACE));
         }
     }
@@ -94,10 +94,9 @@
     }
 
     public boolean performAction(View host, ItemInfo item, int action) {
-        Resources res = mLauncher.getResources();
         if (action == REMOVE) {
             if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
-                announceConfirmation(R.string.item_removed_from_workspace);
+                announceConfirmation(R.string.item_removed);
                 return true;
             }
             return false;
@@ -105,9 +104,7 @@
             InfoDropTarget.startDetailsActivityForInfo(item, mLauncher);
             return true;
         } else if (action == UNINSTALL) {
-            AppInfo info = (AppInfo) item;
-            mLauncher.startApplicationUninstallActivity(info.componentName, info.flags, info.user);
-            return true;
+            return UninstallDropTarget.startUninstallActivity(mLauncher, item);
         } else if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
@@ -158,19 +155,31 @@
         return mDragInfo;
     }
 
-    public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation,
+    /**
+     * @param clickedTarget the actual view that was clicked
+     * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
+     * as the actual drop location otherwise the views center is used.
+     */
+    public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
             String confirmation) {
         if (!isInAccessibleDrag()) return;
 
         int[] loc = new int[2];
-        loc[0] = dropLocation.centerX();
-        loc[1] = dropLocation.centerY();
+        if (dropLocation == null) {
+            loc[0] = clickedTarget.getWidth() / 2;
+            loc[1] = clickedTarget.getHeight() / 2;
+        } else {
+            loc[0] = dropLocation.centerX();
+            loc[1] = dropLocation.centerY();
+        }
 
-        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc);
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
         mLauncher.getDragController().completeAccessibleDrag(loc);
 
         endAccessibleDrag();
-        announceConfirmation(confirmation);
+        if (!TextUtils.isEmpty(confirmation)) {
+            announceConfirmation(confirmation);
+        }
     }
 
     public void beginAccessibleDrag(View item, ItemInfo info) {
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..d5dce51 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -28,7 +28,6 @@
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -42,6 +41,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;
@@ -60,6 +60,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
 
@@ -108,11 +109,6 @@
     @Thunk LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
 
-    // Specific runnable types that are run on the main thread deferred handler, this allows us to
-    // clear all queued binding runnables when the Launcher activity is destroyed.
-    private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
-    private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
-
     private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
 
     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
@@ -146,7 +142,7 @@
 
     // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
     // LauncherModel to their ids
-    static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
+    static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
 
     // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
     //       created by LauncherModel that are directly on the home screen (however, no widgets or
@@ -158,7 +154,7 @@
         new ArrayList<LauncherAppWidgetInfo>();
 
     // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
-    static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
+    static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
 
     // sBgWorkspaceScreens is the ordered set of workspace screens.
     static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
@@ -187,7 +183,7 @@
                               boolean forceAnimateIcons);
         public void bindScreens(ArrayList<Long> orderedScreenIds);
         public void bindAddScreens(ArrayList<Long> orderedScreenIds);
-        public void bindFolders(HashMap<Long,FolderInfo> folders);
+        public void bindFolders(LongArrayMap<FolderInfo> folders);
         public void finishBindingItems();
         public void bindAppWidget(LauncherAppWidgetInfo info);
         public void bindAllApplications(ArrayList<AppInfo> apps);
@@ -199,8 +195,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);
@@ -256,9 +251,6 @@
     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
      * posted on the main thread handler. */
     @Thunk void runOnMainThread(Runnable r) {
-        runOnMainThread(r, 0);
-    }
-    @Thunk void runOnMainThread(Runnable r, int type) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             // If we are on the worker thread, post onto the main handler
             mHandler.post(r);
@@ -282,30 +274,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) {
+                        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) {
+                        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 +609,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;
                             }
                         }
@@ -600,7 +671,7 @@
         runOnWorkerThread(r);
     }
 
-    public void unbindItemInfosAndClearQueuedBindRunnables() {
+    private void unbindItemInfosAndClearQueuedBindRunnables() {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
                     "main thread");
@@ -610,8 +681,9 @@
         synchronized (mDeferredBindRunnables) {
             mDeferredBindRunnables.clear();
         }
-        // Remove any queued bind runnables
-        mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
+
+        // Remove any queued UI runnables
+        mHandler.cancelAll();
         // Unbind all the workspace items
         unbindWorkspaceItemsOnMainThread();
     }
@@ -620,19 +692,15 @@
     void unbindWorkspaceItemsOnMainThread() {
         // Ensure that we don't use the same workspace items data structure on the main thread
         // by making a copy of workspace items first.
-        final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
-        final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
+        final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
         synchronized (sBgLock) {
-            tmpWorkspaceItems.addAll(sBgWorkspaceItems);
-            tmpAppWidgets.addAll(sBgAppWidgets);
+            tmpItems.addAll(sBgWorkspaceItems);
+            tmpItems.addAll(sBgAppWidgets);
         }
         Runnable r = new Runnable() {
                 @Override
                 public void run() {
-                   for (ItemInfo item : tmpWorkspaceItems) {
-                       item.unbind();
-                   }
-                   for (ItemInfo item : tmpAppWidgets) {
+                   for (ItemInfo item : tmpItems) {
                        item.unbind();
                    }
                 }
@@ -904,47 +972,48 @@
     }
 
     /**
-     * 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) {
+                if (item instanceof ShortcutInfo) {
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    if (intentWithPkg.equals(info.getIntent())
+                            || intentWithoutPkg.equals(info.getIntent())) {
+                        return true;
+                    }
+                }
+            }
         }
+        return false;
     }
 
     /**
      * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
      */
-    FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
+    FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
         final ContentResolver cr = context.getContentResolver();
         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
                 "_id=? and (itemType=? or itemType=?)",
@@ -1064,7 +1133,7 @@
                 return cn.getPackageName().equals(pn) && info.user.equals(user);
             }
         };
-        return filterItemInfos(sBgItemsIdMap.values(), filter);
+        return filterItemInfos(sBgItemsIdMap, filter);
     }
 
     /**
@@ -1104,7 +1173,7 @@
                         switch (item.itemType) {
                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                                 sBgFolders.remove(item.id);
-                                for (ItemInfo info: sBgItemsIdMap.values()) {
+                                for (ItemInfo info: sBgItemsIdMap) {
                                     if (info.container == item.id) {
                                         // We are deleting a folder which still contains items that
                                         // think they are contained by that folder.
@@ -1217,6 +1286,9 @@
      */
     public void initialize(Callbacks callbacks) {
         synchronized (mLock) {
+            // Disconnect any of the callbacks and drawables associated with ItemInfos on the
+            // workspace to prevent leaking Launcher activities on orientation change.
+            unbindItemInfosAndClearQueuedBindRunnables();
             mCallbacks = new WeakReference<Callbacks>(callbacks);
         }
     }
@@ -1366,6 +1438,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);
@@ -1404,7 +1478,7 @@
                 mDeferredBindRunnables.clear();
             }
             for (final Runnable r : deferredBindRunnables) {
-                mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
+                mHandler.post(r);
             }
         }
     }
@@ -1676,7 +1750,7 @@
         }
 
         // check & update map of what's occupied; used to discard overlapping/invalid items
-        private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
+        private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
             LauncherAppState app = LauncherAppState.getInstance();
             DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
             final int countX = (int) grid.numColumns;
@@ -1812,7 +1886,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>();
@@ -1824,7 +1898,7 @@
                 // +1 for the hotseat (it can be larger than the workspace)
                 // Load workspace in reverse order to ensure that latest items are loaded first (and
                 // before any earlier duplicates)
-                final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
+                final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
 
                 try {
                     final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
@@ -1951,7 +2025,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 +2167,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 +2306,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 +2320,9 @@
                                             itemsToRemove.add(id);
                                             continue;
                                         }
+
+                                        appWidgetInfo.installProgress =
+                                                installProgress == null ? 0 : installProgress;
                                     }
 
                                     appWidgetInfo.id = id;
@@ -2347,7 +2437,7 @@
 
                 // Remove any empty screens
                 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
-                for (ItemInfo item: sBgItemsIdMap.values()) {
+                for (ItemInfo item: sBgItemsIdMap) {
                     long screenId = item.screenId;
                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                             unusedScreens.contains(screenId)) {
@@ -2372,14 +2462,13 @@
                     for (int y = 0; y < countY; y++) {
                         String line = "";
 
-                        Iterator<Long> iter = occupied.keySet().iterator();
-                        while (iter.hasNext()) {
-                            long screenId = iter.next();
+                        for (int i = 0; i < nScreens; i++) {
+                            long screenId = occupied.keyAt(i);
                             if (screenId > 0) {
                                 line += " | ";
                             }
+                            ItemInfo[][] screen = occupied.valueAt(i);
                             for (int x = 0; x < countX; x++) {
-                                ItemInfo[][] screen = occupied.get(screenId);
                                 if (x < screen.length && y < screen[x].length) {
                                     line += (screen[x][y] != null) ? "#" : ".";
                                 } else {
@@ -2470,14 +2559,17 @@
 
         /** Filters the set of folders which are on the specified screen. */
         private void filterCurrentFolders(long currentScreenId,
-                HashMap<Long, ItemInfo> itemsIdMap,
-                HashMap<Long, FolderInfo> folders,
-                HashMap<Long, FolderInfo> currentScreenFolders,
-                HashMap<Long, FolderInfo> otherScreenFolders) {
+                LongArrayMap<ItemInfo> itemsIdMap,
+                LongArrayMap<FolderInfo> folders,
+                LongArrayMap<FolderInfo> currentScreenFolders,
+                LongArrayMap<FolderInfo> otherScreenFolders) {
 
-            for (long id : folders.keySet()) {
+            int total = folders.size();
+            for (int i = 0; i < total; i++) {
+                long id = folders.keyAt(i);
+                FolderInfo folder = folders.valueAt(i);
+
                 ItemInfo info = itemsIdMap.get(id);
-                FolderInfo folder = folders.get(id);
                 if (info == null || folder == null) continue;
                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                         info.screenId == currentScreenId) {
@@ -2521,13 +2613,13 @@
                     }
                 }
             };
-            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+            runOnMainThread(r);
         }
 
         private void bindWorkspaceItems(final Callbacks oldCallbacks,
                 final ArrayList<ItemInfo> workspaceItems,
                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
-                final HashMap<Long, FolderInfo> folders,
+                final LongArrayMap<FolderInfo> folders,
                 ArrayList<Runnable> deferredBindRunnables) {
 
             final boolean postOnMainThread = (deferredBindRunnables != null);
@@ -2552,7 +2644,7 @@
                         deferredBindRunnables.add(r);
                     }
                 } else {
-                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                    runOnMainThread(r);
                 }
             }
 
@@ -2571,7 +2663,7 @@
                         deferredBindRunnables.add(r);
                     }
                 } else {
-                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                    runOnMainThread(r);
                 }
             }
 
@@ -2590,7 +2682,7 @@
                 if (postOnMainThread) {
                     deferredBindRunnables.add(r);
                 } else {
-                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                    runOnMainThread(r);
                 }
             }
         }
@@ -2615,15 +2707,18 @@
             ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
             ArrayList<LauncherAppWidgetInfo> appWidgets =
                     new ArrayList<LauncherAppWidgetInfo>();
-            HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
-            HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
             ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
+
+            final LongArrayMap<FolderInfo> folders;
+            final LongArrayMap<ItemInfo> itemsIdMap;
+
             synchronized (sBgLock) {
                 workspaceItems.addAll(sBgWorkspaceItems);
                 appWidgets.addAll(sBgAppWidgets);
-                folders.putAll(sBgFolders);
-                itemsIdMap.putAll(sBgItemsIdMap);
                 orderedScreenIds.addAll(sBgWorkspaceScreens);
+
+                folders = sBgFolders.clone();
+                itemsIdMap = sBgItemsIdMap.clone();
             }
 
             final boolean isLoadingSynchronously =
@@ -2649,8 +2744,8 @@
                     new ArrayList<LauncherAppWidgetInfo>();
             ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
                     new ArrayList<LauncherAppWidgetInfo>();
-            HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
-            HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
+            LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
+            LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
 
             filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                     otherWorkspaceItems);
@@ -2670,7 +2765,7 @@
                     }
                 }
             };
-            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+            runOnMainThread(r);
 
             bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
 
@@ -2686,7 +2781,7 @@
                         }
                     }
                 };
-                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                runOnMainThread(r);
             }
 
             // Load all the remaining pages (if we are loading synchronously, we want to defer this
@@ -2719,7 +2814,7 @@
                     mDeferredBindRunnables.add(r);
                 }
             } else {
-                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+                runOnMainThread(r);
             }
         }
 
@@ -2790,8 +2885,6 @@
 
             // Clear the list of apps
             mBgAllAppsList.clear();
-            SharedPreferences prefs = mContext.getSharedPreferences(
-                    LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
             for (UserHandleCompat user : profiles) {
                 // Query for the set of apps
                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
@@ -2815,7 +2908,7 @@
                 if (!updatedPackages.isEmpty()) {
                     final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
                     synchronized (sBgLock) {
-                        for (ItemInfo info : sBgItemsIdMap.values()) {
+                        for (ItemInfo info : sBgItemsIdMap) {
                             if (info instanceof ShortcutInfo && user.equals(info.user)
                                     && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                 ShortcutInfo si = (ShortcutInfo) info;
@@ -3064,7 +3157,7 @@
 
                 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
                 synchronized (sBgLock) {
-                    for (ItemInfo info : sBgItemsIdMap.values()) {
+                    for (ItemInfo info : sBgItemsIdMap) {
                         if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
                             ShortcutInfo si = (ShortcutInfo) info;
                             boolean infoUpdated = false;
@@ -3112,15 +3205,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 +3444,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 +3456,7 @@
                 info.title.toString(), info.user);
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.promisedIntent = intent;
+        info.status = promiseType;
         return info;
     }
 
@@ -3443,7 +3533,7 @@
         return info;
     }
 
-    static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
+    static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
             ItemInfoFilter f) {
         HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
         for (ItemInfo i : infos) {
@@ -3484,7 +3574,7 @@
                 }
             }
         };
-        return filterItemInfos(sBgItemsIdMap.values(), filter);
+        return filterItemInfos(sBgItemsIdMap, filter);
     }
 
     /**
@@ -3594,7 +3684,7 @@
      * Return an existing FolderInfo object if we have encountered this ID previously,
      * or make a new one.
      */
-    @Thunk static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
+    @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
         // See if a placeholder was created for us already
         FolderInfo folderInfo = folders.get(id);
         if (folderInfo == null) {
@@ -3669,4 +3759,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/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index af8bc75..44a76b7 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -180,6 +180,9 @@
         prepareStartAnimation(mDropTargetBar);
         mShowDropTargetBarAnim.start();
         hideSearchBar(true);
+        if (mQSBSearchBar != null) {
+            mQSBSearchBar.setVisibility(View.GONE);
+        }
     }
 
     /**
@@ -190,6 +193,9 @@
         prepareStartAnimation(mDropTargetBar);
         mShowDropTargetBarAnim.reverse();
         showSearchBar(true);
+        if (mQSBSearchBar != null) {
+            mQSBSearchBar.setVisibility(View.VISIBLE);
+        }
     }
 
     /*
@@ -228,4 +234,13 @@
             return null;
         }
     }
+
+    public void enableAccessibleDrag(boolean enable) {
+        if (mQSBSearchBar != null) {
+            mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE);
+        }
+        mInfoDropTarget.enableAccessibleDrag(enable);
+        mDeleteDropTarget.enableAccessibleDrag(enable);
+        mUninstallDropTarget.enableAccessibleDrag(enable);
+    }
 }
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/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 4a7fffe..4c52d7e 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -33,9 +33,12 @@
 
     @Override
     protected boolean supportsDrop(DragSource source, Object info) {
+        return supportsDrop(getContext(), info);
+    }
+
+    public static boolean supportsDrop(Context context, Object info) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-            UserManager userManager = (UserManager)
-                    getContext().getSystemService(Context.USER_SERVICE);
+            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = userManager.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
                     || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
@@ -78,8 +81,7 @@
     void completeDrop(final DragObject d) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
         final UserHandleCompat user = ((ItemInfo) d.dragInfo).user;
-        if (mLauncher.startApplicationUninstallActivity(
-                componentInfo.first, componentInfo.second, user)) {
+        if (startUninstallActivity(mLauncher, d.dragInfo)) {
 
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
                 @Override
@@ -96,6 +98,13 @@
         }
     }
 
+    public static boolean startUninstallActivity(Launcher launcher, Object info) {
+        final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
+        final UserHandleCompat user = ((ItemInfo) info).user;
+        return launcher.startApplicationUninstallActivity(
+                componentInfo.first, componentInfo.second, user);
+    }
+
     @Thunk void sendUninstallResult(DragSource target, boolean result) {
         if (target instanceof UninstallSource) {
             ((UninstallSource) target).onUninstallActivityReturned(result);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9e680fb..2efd207 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -62,9 +62,8 @@
 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.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperUtils;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -73,7 +72,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -122,7 +120,7 @@
     final static long EXTRA_EMPTY_SCREEN_ID = -201;
     private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
 
-    @Thunk HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
+    @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>();
     @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>();
 
     @Thunk Runnable mRemoveEmptyScreenRunnable;
@@ -836,12 +834,9 @@
     }
 
     public long getIdForScreen(CellLayout layout) {
-        Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
-        while (iter.hasNext()) {
-            long id = iter.next();
-            if (mWorkspaceScreens.get(id) == layout) {
-                return id;
-            }
+        int index = mWorkspaceScreens.indexOfValue(layout);
+        if (index != -1) {
+            return mWorkspaceScreens.keyAt(index);
         }
         return -1;
     }
@@ -876,8 +871,10 @@
 
         int currentPage = getNextPage();
         ArrayList<Long> removeScreens = new ArrayList<Long>();
-        for (Long id: mWorkspaceScreens.keySet()) {
-            CellLayout cl = mWorkspaceScreens.get(id);
+        int total = mWorkspaceScreens.size();
+        for (int i = 0; i < total; i++) {
+            long id = mWorkspaceScreens.keyAt(i);
+            CellLayout cl = mWorkspaceScreens.valueAt(i);
             if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
@@ -1617,6 +1614,7 @@
             // Reset our click listener
             setOnClickListener(mLauncher);
         }
+        mLauncher.getSearchBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable);
     }
 
@@ -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);
             }
         }
     };
diff --git a/src/com/android/launcher3/util/LongArrayMap.java b/src/com/android/launcher3/util/LongArrayMap.java
new file mode 100644
index 0000000..e3c96cd
--- /dev/null
+++ b/src/com/android/launcher3/util/LongArrayMap.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.util.LongSparseArray;
+
+import java.util.Iterator;
+
+/**
+ * Extension of {@link LongSparseArray} with some utility methods.
+ */
+public class LongArrayMap<E> extends LongSparseArray<E> implements Iterable<E> {
+
+    public boolean containsKey(long key) {
+        return indexOfKey(key) >= 0;
+    }
+
+    public boolean isEmpty() {
+        return size() <= 0;
+    }
+
+    @Override
+    public LongArrayMap<E> clone() {
+        return (LongArrayMap<E>) super.clone();
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new ValueIterator();
+    }
+
+    private class ValueIterator implements Iterator<E> {
+
+        private int mNextIndex = 0;
+
+        @Override
+        public boolean hasNext() {
+            return mNextIndex < size();
+        }
+
+        @Override
+        public E next() {
+            return valueAt(mNextIndex ++);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}