Merge "Add override method to fix building against master support lib." 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/layout/widget_cell.xml b/res/layout/widget_cell.xml
index f53b74e..64ddea1 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -19,9 +19,11 @@
     android:layout_width="@dimen/widget_preview_container_width"
     android:layout_height="wrap_content"
     android:layout_weight="1"
-    android:paddingTop="@dimen/widget_preview_padding_top"
+    android:layout_marginTop="@dimen/widget_preview_padding_top"
+    android:layout_marginLeft="8dp"
+    android:layout_marginBottom="8dp"
     android:orientation="vertical"
-    android:background="@drawable/focusable_view_bg"
+    android:background="@color/bubble_dark_background"
     android:focusable="true">
 
     <LinearLayout
@@ -45,7 +47,7 @@
             android:fadingEdge="horizontal"
 
             android:textColor="#FFFFFFFF"
-            android:textSize="12sp"
+            android:textSize="16sp"
             android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
             android:shadowRadius="2.0"
@@ -61,9 +63,8 @@
             android:layout_marginLeft="5dp"
             android:layout_weight="0"
             android:gravity="start"
-
             android:textColor="#FFFFFFFF"
-            android:textSize="12sp"
+            android:textSize="16sp"
             android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
             android:shadowRadius="2.0"
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 017b450..f94d023 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -19,10 +19,6 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:layout_marginLeft="8dp"
-    android:layout_marginRight="8dp"
-    android:layout_marginTop="8dp"
-    android:layout_marginBottom="8dp"
     android:focusable="true"
     android:background="@drawable/focusable_view_bg"
     android:descendantFocusability="afterDescendants">
@@ -49,7 +45,6 @@
             android:id="@+id/section"
             android:layout_width="match_parent"
             android:layout_height="@dimen/widget_section_height"
-            android:paddingTop="8dp"
             android:paddingLeft="16dp"
             android:paddingRight="16dp"
             android:singleLine="true"
@@ -80,6 +75,7 @@
             android:id="@+id/widgets_scroll_container"
             android:layout_width="match_parent"
             android:layout_height="@dimen/widget_cell_height"
+            android:paddingLeft="40dp"
             android:scrollbars="none" >
             <LinearLayout
                 android:id="@+id/widgets_cell_list"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index cad60fb..da11082 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -80,7 +80,7 @@
 
 <!-- Widget tray -->
     <dimen name="widget_container_inset">8dp</dimen>
-    <dimen name="widget_preview_size">140dp</dimen>
+    <dimen name="widget_preview_size">120dp</dimen>
     <dimen name="widget_preview_padding_top">8dp</dimen>
     <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
     <dimen name="widget_preview_label_horizontal_padding">8dp</dimen>
@@ -90,8 +90,8 @@
     <dimen name="widget_section_icon_padding">8dp</dimen>
 
     <!-- Equation: widget_preview_size + 2 * widget_preview_padding_horizontal -->
-    <dimen name="widget_preview_container_width">156dp</dimen>
-    <dimen name="widget_cell_height">160dp</dimen>
+    <dimen name="widget_preview_container_width">136dp</dimen>
+    <dimen name="widget_cell_height">150dp</dimen>
 
     <!-- Padding applied to shortcut previews -->
     <dimen name="shortcut_preview_padding_left">0dp</dimen>
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/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 2402507..3c698c0 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -349,7 +349,7 @@
                 mTmpRect.right, mTmpRect.bottom);
     }
 
-    static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
+    public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
         if (rect == null) {
             rect = new Rect();
         }
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 7364a9f..8c920f0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -98,12 +98,12 @@
 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;
 import com.android.launcher3.widget.WidgetsContainerView;
 
 import java.io.DataInputStream;
@@ -304,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;
 
@@ -1060,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));
         }
@@ -1078,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();
@@ -1089,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;
@@ -2050,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);
 
@@ -3895,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);
@@ -3904,8 +3897,7 @@
         if (waitUntilResume(r)) {
             return;
         }
-        sFolders.clear();
-        sFolders.putAll(folders);
+        sFolders = folders.clone();
     }
 
     /**
@@ -3953,7 +3945,7 @@
             pendingInfo.minSpanX = item.minSpanX;
             pendingInfo.minSpanY = item.minSpanY;
             Bundle options = null;
-            //        AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
+                    WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
 
             int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
             boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -4084,7 +4076,7 @@
             sPendingAddItem = null;
         }
 
-        PackageInstallerCompat.getInstance(this).onFinishBind();
+        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.finishBindingItems(false);
@@ -4236,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();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 1ae75c3..0bc7333 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -24,8 +24,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.TypedValue;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
 import android.widget.ImageView;
@@ -43,7 +41,7 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
- * The linear layout used strictly for the widget tray.
+ * Represents the individual cell of the widget inside the widget tray.
  */
 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
 
@@ -53,14 +51,12 @@
     private static final int FADE_IN_DURATION_MS = 70;
     private int mPresetPreviewSize;
 
-    private static WidgetCell sShortpressTarget = null;
-
+    private ImageView mWidgetImage;
+    private TextView mWidgetName;
+    private TextView mWidgetDims;
     private final Rect mOriginalImagePadding = new Rect();
 
     private String mDimensionsFormatString;
-    private CheckForShortPress mPendingCheckForShortPress = null;
-    private ShortPressListener mShortPressListener = null;
-    private boolean mShortPressTriggered = false;
     private boolean mIsAppWidget;
     private Object mInfo;
 
@@ -92,57 +88,27 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        final ImageView image = (ImageView) findViewById(R.id.widget_preview);
-        mOriginalImagePadding.left = image.getPaddingLeft();
-        mOriginalImagePadding.top = image.getPaddingTop();
-        mOriginalImagePadding.right = image.getPaddingRight();
-        mOriginalImagePadding.bottom = image.getPaddingBottom();
+        mWidgetImage = (ImageView) findViewById(R.id.widget_preview);
+        mOriginalImagePadding.left = mWidgetImage.getPaddingLeft();
+        mOriginalImagePadding.top = mWidgetImage.getPaddingTop();
+        mOriginalImagePadding.right = mWidgetImage.getPaddingRight();
+        mOriginalImagePadding.bottom = mWidgetImage.getPaddingBottom();
 
         // Ensure we are using the right text size
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-        TextView name = (TextView) findViewById(R.id.widget_name);
-        if (name != null) {
-            name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
-        }
-        TextView dims = (TextView) findViewById(R.id.widget_dims);
-        if (dims != null) {
-            dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (DEBUG) {
-            Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString()));
-        }
-        super.onDetachedFromWindow();
-        deletePreview(false);
+        DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+        mWidgetName = ((TextView) findViewById(R.id.widget_name));
+        mWidgetDims = ((TextView) findViewById(R.id.widget_dims));
     }
 
     public void reset() {
-        ImageView image = (ImageView) findViewById(R.id.widget_preview);
-        final TextView name = (TextView) findViewById(R.id.widget_name);
-        final TextView dims = (TextView) findViewById(R.id.widget_dims);
-        image.setImageDrawable(null);
-        name.setText(null);
-        dims.setText(null);
+        mWidgetImage.setImageDrawable(null);
+        mWidgetName.setText(null);
+        mWidgetDims.setText(null);
     }
 
-    public void deletePreview(boolean recycleImage) {
-        if (recycleImage) {
-            final ImageView image = (ImageView) findViewById(R.id.widget_preview);
-            if (image != null) {
-                image.setImageDrawable(null);
-            }
-        }
-
-        if (mActiveRequest != null) {
-            mActiveRequest.cancel(recycleImage);
-            mActiveRequest = null;
-        }
-    }
-
+    /**
+     * Apply the widget provider info to the view.
+     */
     public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
             int maxWidth, WidgetPreviewLoader loader) {
         LauncherAppState app = LauncherAppState.getInstance();
@@ -150,37 +116,41 @@
 
         mIsAppWidget = true;
         mInfo = info;
-        final ImageView image = (ImageView) findViewById(R.id.widget_preview);
         if (maxWidth > -1) {
-            image.setMaxWidth(maxWidth);
+            mWidgetImage.setMaxWidth(maxWidth);
         }
-        final TextView name = (TextView) findViewById(R.id.widget_name);
-        name.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
-        final TextView dims = (TextView) findViewById(R.id.widget_dims);
-        if (dims != null) {
-            int hSpan = Math.min(info.spanX, (int) grid.numColumns);
-            int vSpan = Math.min(info.spanY, (int) grid.numRows);
-            dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
-        }
+        // TODO(hyunyoungs): setup a cache for these labels.
+        mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
+        int hSpan = Math.min(info.spanX, (int) grid.numColumns);
+        int vSpan = Math.min(info.spanY, (int) grid.numRows);
+        mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
         mWidgetPreviewLoader = loader;
     }
 
+    /**
+     * Apply the resolve info to the view.
+     */
     public void applyFromResolveInfo(
             PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
         mIsAppWidget = false;
         mInfo = info;
         CharSequence label = info.loadLabel(pm);
-        final TextView name = (TextView) findViewById(R.id.widget_name);
-        name.setText(label);
-        final TextView dims = (TextView) findViewById(R.id.widget_dims);
-        if (dims != null) {
-            dims.setText(String.format(mDimensionsFormatString, 1, 1));
-        }
+        mWidgetName.setText(label);
+        mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1));
         mWidgetPreviewLoader = loader;
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        deletePreview(false);
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString()));
+        }
+    }
+
     public int[] getPreviewSize() {
-        final ImageView i = (ImageView) findViewById(R.id.widget_preview);
         int[] maxSize = new int[2];
         maxSize[0] = mPresetPreviewSize;
         maxSize[1] = mPresetPreviewSize;
@@ -189,110 +159,28 @@
 
     public void applyPreview(Bitmap bitmap) {
         FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
-        final WidgetImageView image =
-            (WidgetImageView) findViewById(R.id.widget_preview);
         if (DEBUG) {
             Log.d(TAG, String.format("[tag=%s] applyPreview preview: %s",
                     getTagToString(), preview));
         }
         if (preview != null) {
-            image.mAllowRequestLayout = false;
-            image.setImageDrawable(preview);
+            mWidgetImage.setImageDrawable(preview);
             if (mIsAppWidget) {
                 // center horizontally
                 int[] imageSize = getPreviewSize();
                 int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
-                image.setPadding(mOriginalImagePadding.left + centerAmount,
+                mWidgetImage.setPadding(mOriginalImagePadding.left + centerAmount,
                         mOriginalImagePadding.top,
                         mOriginalImagePadding.right,
                         mOriginalImagePadding.bottom);
             }
-            image.setAlpha(0f);
-            image.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
-            image.mAllowRequestLayout = true;
-            image.requestLayout();
+            mWidgetImage.setAlpha(0f);
+            mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+            // TODO(hyunyoungs): figure out why this has to be called explicitly.
+            mWidgetImage.requestLayout();
         }
     }
 
-    void setShortPressListener(ShortPressListener listener) {
-        mShortPressListener = listener;
-    }
-
-    interface ShortPressListener {
-        void onShortPress(View v);
-        void cleanUpShortPress(View v);
-    }
-
-    class CheckForShortPress implements Runnable {
-        public void run() {
-            if (sShortpressTarget != null) return;
-            if (mShortPressListener != null) {
-                mShortPressListener.onShortPress(WidgetCell.this);
-                sShortpressTarget = WidgetCell.this;
-            }
-            mShortPressTriggered = true;
-        }
-    }
-
-    private void checkForShortPress() {
-        if (sShortpressTarget != null) return;
-        if (mPendingCheckForShortPress == null) {
-            mPendingCheckForShortPress = new CheckForShortPress();
-        }
-        postDelayed(mPendingCheckForShortPress, 120);
-    }
-
-    /**
-     * Remove the longpress detection timer.
-     */
-    private void removeShortPressCallback() {
-        if (mPendingCheckForShortPress != null) {
-          removeCallbacks(mPendingCheckForShortPress);
-        }
-    }
-
-    private void cleanUpShortPress() {
-        removeShortPressCallback();
-        if (mShortPressTriggered) {
-            if (mShortPressListener != null) {
-                mShortPressListener.cleanUpShortPress(WidgetCell.this);
-            }
-            mShortPressTriggered = false;
-        }
-    }
-
-    static void resetShortPressTarget() {
-        sShortpressTarget = null;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        super.onTouchEvent(event);
-
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_UP:
-                cleanUpShortPress();
-                break;
-            case MotionEvent.ACTION_DOWN:
-                checkForShortPress();
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cleanUpShortPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                break;
-        }
-
-        // We eat up the touch events here, since the PagedView (which uses the same swiping
-        // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
-        // the user is scrolling between pages.  This means that if the pages themselves don't
-        // handle touch events, it gets forwarded up to PagedView itself, and it's own
-        // onTouchEvent() handling will prevent further intercept touch events from being called
-        // (it's the same view in that case).  This is not ideal, but to prevent more changes,
-        // we just always mark the touch event as handled.
-        return true;
-    }
-
     public void ensurePreview() {
         if (mActiveRequest != null) {
             return;
@@ -331,6 +219,16 @@
         return Math.min(size[0], info.spanX * cellWidth);
     }
 
+
+    private void deletePreview(boolean recycleImage) {
+        mWidgetImage.setImageDrawable(null);
+
+        if (mActiveRequest != null) {
+            mActiveRequest.cancel(recycleImage);
+            mActiveRequest = null;
+        }
+    }
+
     /**
      * Helper method to get the string info of the tag.
      */
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
new file mode 100644
index 0000000..d654550
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -0,0 +1,197 @@
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.DragLayer;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+
+public class WidgetHostViewLoader {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WidgetHostViewLoader";
+
+    /* constants used for widget loading state. */
+    private static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
+    private static final int WIDGET_PRELOAD_PENDING = 0;
+    private static final int WIDGET_BOUND = 1;
+    private static final int WIDGET_INFLATED = 2;
+
+    int mState = WIDGET_NO_CLEANUP_REQUIRED;
+
+    /* Runnables to handle inflation and binding. */
+    private Runnable mInflateWidgetRunnable = null;
+    private Runnable mBindWidgetRunnable = null;
+
+    /* Id of the widget being handled. */
+    int mWidgetLoadingId = -1;
+    PendingAddWidgetInfo mCreateWidgetInfo = null;
+
+    // TODO: technically, this class should not have to know the existence of the launcher.
+    private Launcher mLauncher;
+    private Handler mHandler;
+
+    public WidgetHostViewLoader(Launcher launcher) {
+        mLauncher = launcher;
+        mHandler = new Handler();
+    }
+
+    /**
+     * Start loading the widget.
+     */
+    public void load(View v) {
+        if (mCreateWidgetInfo != null) {
+            // Just in case the cleanup process wasn't properly executed.
+            finish(false);
+        }
+        boolean status = false;
+        if (v.getTag() instanceof PendingAddWidgetInfo) {
+            mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
+            status = preloadWidget(v, mCreateWidgetInfo);
+        }
+        if (DEBUG) {
+            Log.d(TAG, String.format("load started on [state=%d, status=%s]", mState, status));
+        }
+    }
+
+
+    /**
+     * Clean up according to what the last known state was.
+     * @param widgetIdUsed   {@code true} if the widgetId was consumed which can happen only
+     *                       when view is fully inflated
+     */
+    public void finish(boolean widgetIdUsed) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("cancel on state [%d] widgetId=[%d]",
+                    mState, mWidgetLoadingId));
+        }
+
+        // If the widget was not added, we may need to do further cleanup.
+        PendingAddWidgetInfo info = mCreateWidgetInfo;
+        mCreateWidgetInfo = null;
+
+        if (mState == WIDGET_PRELOAD_PENDING) {
+            // We never did any preloading, so just remove pending callbacks to do so
+            mHandler.removeCallbacks(mBindWidgetRunnable);
+            mHandler.removeCallbacks(mInflateWidgetRunnable);
+        } else if (mState == WIDGET_BOUND) {
+             // Delete the widget id which was allocated
+            if (mWidgetLoadingId != -1 && !info.isCustomWidget()) {
+                mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+            }
+
+            // We never got around to inflating the widget, so remove the callback to do so.
+            mHandler.removeCallbacks(mInflateWidgetRunnable);
+        } else if (mState == WIDGET_INFLATED && !widgetIdUsed) {
+            // Delete the widget id which was allocated
+            if (mWidgetLoadingId != -1 && !info.isCustomWidget()) {
+                mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+            }
+
+            // The widget was inflated and added to the DragLayer -- remove it.
+            AppWidgetHostView widget = info.boundWidget;
+            mLauncher.getDragLayer().removeView(widget);
+        }
+        setState(WIDGET_NO_CLEANUP_REQUIRED);
+        mWidgetLoadingId = -1;
+    }
+
+    private boolean preloadWidget(final View v, final PendingAddWidgetInfo info) {
+        final LauncherAppWidgetProviderInfo pInfo = info.info;
+
+        final Bundle options = pInfo.isCustomWidget ? null :
+                getDefaultOptionsForWidget(mLauncher, info);
+
+        // If there is a configuration activity, do not follow thru bound and inflate.
+        if (pInfo.configure != null) {
+            info.bindOptions = options;
+            return false;
+        }
+        setState(WIDGET_PRELOAD_PENDING);
+        mBindWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (pInfo.isCustomWidget) {
+                    setState(WIDGET_BOUND);
+                    return;
+                }
+
+                mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+                if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+                        mWidgetLoadingId, pInfo, options)) {
+                    setState(WIDGET_BOUND);
+                }
+            }
+        };
+        mHandler.post(mBindWidgetRunnable);
+
+        mInflateWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mState != WIDGET_BOUND) {
+                    return;
+                }
+                AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView(
+                        (Context) mLauncher, mWidgetLoadingId, pInfo);
+                info.boundWidget = hostView;
+                setState(WIDGET_INFLATED);
+                hostView.setVisibility(View.INVISIBLE);
+                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false);
+
+                // We want the first widget layout to be the correct size. This will be important
+                // for width size reporting to the AppWidgetManager.
+                DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
+                        unScaledSize[1]);
+                lp.x = lp.y = 0;
+                lp.customPosition = true;
+                hostView.setLayoutParams(lp);
+                mLauncher.getDragLayer().addView(hostView);
+                v.setTag(info);
+            }
+        };
+        mHandler.post(mInflateWidgetRunnable);
+        return true;
+    }
+
+    public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
+        Bundle options = null;
+        Rect rect = new Rect();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
+            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
+                    info.componentName, null);
+
+            float density = launcher.getResources().getDisplayMetrics().density;
+            int xPaddingDips = (int) ((padding.left + padding.right) / density);
+            int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+
+            options = new Bundle();
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+                    rect.left - xPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+                    rect.top - yPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+                    rect.right - xPaddingDips);
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+                    rect.bottom - yPaddingDips);
+        }
+        return options;
+    }
+
+    private void setState(int state) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("     state [%d -> %d]", mState, state));
+        }
+        mState = state;
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 292a5de..27a3ea1 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -30,6 +30,7 @@
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.Toast;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
@@ -54,15 +55,17 @@
 /**
  * The widgets list view container.
  */
-public class WidgetsContainerView extends FrameLayout implements Insettable, View.OnTouchListener,
-        View.OnLongClickListener, DragSource{
+public class WidgetsContainerView extends FrameLayout implements Insettable,
+        View.OnLongClickListener, View.OnClickListener, DragSource{
 
-    private static final String TAG = "WidgetContainerView";
+    private static final String TAG = "WidgetsContainerView";
     private static final boolean DEBUG = false;
 
     /* {@link RecyclerView} will keep following # of views in cache, before recycling. */
     private static final int WIDGET_CACHE_SIZE = 2;
 
+    private static final int SPRING_MODE_DELAY_MS = 150;
+
     /* Global instances that are used inside this container. */
     private Launcher mLauncher;
     private DragController mDragController;
@@ -75,12 +78,13 @@
     private RecyclerView mView;
     private WidgetsListAdapter mAdapter;
 
-    /* Dragging related. */
-    private boolean mDraggingWidget = false;    // TODO(hyunyoungs): seems not needed? check!
-    private Point mLastTouchDownPos = new Point();
+    /* Touch handling related member variables. */
+    private Toast mWidgetInstructionToast;
 
     /* Rendering related. */
     private WidgetPreviewLoader mWidgetPreviewLoader;
+    private WidgetHostViewLoader mWidgetHostViewLoader;
+
     private Rect mPadding = new Rect();
 
     public WidgetsContainerView(Context context) {
@@ -95,8 +99,8 @@
         super(context, attrs, defStyleAttr);
         mLauncher = (Launcher) context;
         mDragController = mLauncher.getDragController();
-
-        mAdapter = new WidgetsListAdapter(context, this, mLauncher, this, mLauncher);
+        mWidgetHostViewLoader = new WidgetHostViewLoader(mLauncher);
+        mAdapter = new WidgetsListAdapter(context, this, this, mLauncher);
         mWidgets = new WidgetsModel(context, mAdapter);
         mAdapter.setWidgetsModel(mWidgets);
         mIconCache = (LauncherAppState.getInstance()).getIconCache();
@@ -147,11 +151,26 @@
     //
 
     @Override
+    public void onClick(View v) {
+        // When we have exited widget tray or are in transition, disregard clicks
+        if (!mLauncher.isWidgetsViewVisible()
+                || mLauncher.getWorkspace().isSwitchingState()
+                || !(v instanceof WidgetCell)) return;
+
+        // Let the user know that they have to long press to add a widget
+        if (mWidgetInstructionToast != null) {
+            mWidgetInstructionToast.cancel();
+        }
+        mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
+                Toast.LENGTH_SHORT);
+        mWidgetInstructionToast.show();
+    }
+
+    @Override
     public boolean onLongClick(View v) {
         if (DEBUG) {
             Log.d(TAG, String.format("onLonglick [v=%s]", v));
         }
-
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
         // When we have exited all apps or are in transition, disregard long clicks
@@ -161,7 +180,11 @@
         Log.d(TAG, String.format("onLonglick dragging enabled?.", v));
         if (!mLauncher.isDraggingEnabled()) return false;
 
-        return beginDragging(v);
+        boolean status = beginDragging(v);
+        if (status) {
+            mWidgetHostViewLoader.load(v);
+        }
+        return status;
     }
 
     private boolean beginDragging(View v) {
@@ -174,7 +197,7 @@
         }
 
         // We delay entering spring-loaded mode slightly to make sure the UI
-        // thready is free of any work.
+        // thread is free of any work.
         postDelayed(new Runnable() {
             @Override
             public void run() {
@@ -184,13 +207,12 @@
                     mLauncher.enterSpringLoadedDragMode();
                 }
             }
-        }, 150);
+        }, SPRING_MODE_DELAY_MS);
 
         return true;
     }
 
     private boolean beginDraggingWidget(WidgetCell v) {
-        mDraggingWidget = true;
         // Get the widget preview as the drag representation
         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
@@ -198,7 +220,6 @@
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
         if (image.getDrawable() == null) {
-            mDraggingWidget = false;
             return false;
         }
 
@@ -259,19 +280,6 @@
         return true;
     }
 
-    /*
-     * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
-     */
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        Log.d(TAG, String.format("onTouch [MotionEvent=%s]", ev));
-        if (ev.getAction() == MotionEvent.ACTION_DOWN ||
-                ev.getAction() == MotionEvent.ACTION_MOVE) {
-            mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
-        }
-        return false;
-    }
-
     //
     // Drag related handling methods that implement {@link DragSource} interface.
     //
@@ -340,6 +348,10 @@
             }
             d.deferDragViewCleanupPostAnimation = false;
         }
+        //TODO(hyunyoungs): if drop fails, this call cleans up correctly.
+        // However, in rare corner case where drop succeeds but doesn't end up using the widget
+        // id created by the loader, this finish will leave dangling widget id.
+        mWidgetHostViewLoader.finish(success);
     }
 
     //
@@ -368,5 +380,4 @@
         }
         return mWidgetPreviewLoader;
     }
-
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index afeb2d3..f6ab21e 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -56,20 +56,16 @@
     private WidgetsModel mWidgetsModel;
     private WidgetPreviewLoader mWidgetPreviewLoader;
 
-    private View.OnTouchListener mTouchListener;
     private View.OnClickListener mIconClickListener;
     private View.OnLongClickListener mIconLongClickListener;
 
-
     public WidgetsListAdapter(Context context,
-            View.OnTouchListener touchListener,
             View.OnClickListener iconClickListener,
             View.OnLongClickListener iconLongClickListener,
             Launcher launcher) {
         mLayoutInflater = LayoutInflater.from(context);
         mContext = context;
 
-        mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
 
@@ -109,7 +105,6 @@
                 // set up touch.
                 widget.setOnClickListener(mIconClickListener);
                 widget.setOnLongClickListener(mIconLongClickListener);
-                widget.setOnTouchListener(mTouchListener);
                 row.addView(widget);
             }
         } else if (diff < 0) {