Merge "Defining various modes for CellLayout: Workspace, Hotseat & Folder" into ub-launcher3-master
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 6b27559..33041db 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -63,6 +63,7 @@
   FOLDER_ICON = 4;
   DEEPSHORTCUT = 5;
   SEARCHBOX = 6;
+  EDITTEXT = 7;
 }
 
 // Used to define what type of container a Target would represent.
@@ -91,7 +92,9 @@
   APPINFO_TARGET = 7;
   RESIZE_HANDLE = 8;
   VERTICAL_SCROLL = 9;
-  // HOME, BACK, GO_TO_PLAYSTORE
+  HOME_INTENT = 10; // Deprecated, use enum Command instead
+  BACK_BUTTON = 11; // Deprecated, use enum Command instead
+  // GO_TO_PLAYSTORE
 }
 
 // Used to define the action component of the LauncherEvent.
@@ -99,6 +102,7 @@
   enum Type {
     TOUCH = 0;
     AUTOMATED = 1;
+    COMMAND = 2;
     // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
   }
   enum Touch {
@@ -116,9 +120,16 @@
     LEFT = 3;
     RIGHT = 4;
   }
+  enum Command {
+    HOME_INTENT = 0;
+    BACK = 1;
+  }
   optional Type type = 1;
   optional Touch touch = 2;
   optional Direction dir = 3;
+  optional Command command = 4;
+  // Log if the action was performed on outside of the container
+  optional bool is_outside = 5;
 }
 
 //
diff --git a/res/values/config.xml b/res/values/config.xml
index 5b3ee46..d270def 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -79,6 +79,9 @@
     <!-- Name of an icon provider class. -->
     <string name="icon_provider_class" translatable="false"></string>
 
+    <!-- Name of a drawable factory class. -->
+    <string name="drawable_factory_class" translatable="false"></string>
+
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 0380923..c45ff7b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -355,10 +355,11 @@
 
     public void snapToWidget(boolean animate) {
         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding
-                - mWidgetPadding.left - mWidgetPadding.right;
-        int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding
-                - mWidgetPadding.top - mWidgetPadding.bottom;
+        DeviceProfile profile = mLauncher.getDeviceProfile();
+        int newWidth = (int) (mWidgetView.getWidth() * profile.appWidgetScale.x)
+                + 2 * mBackgroundPadding - mWidgetPadding.left - mWidgetPadding.right;
+        int newHeight = (int) (mWidgetView.getHeight() * profile.appWidgetScale.y)
+                + 2 * mBackgroundPadding - mWidgetPadding.top - mWidgetPadding.bottom;
 
         mTmpPt[0] = mWidgetView.getLeft();
         mTmpPt[1] = mWidgetView.getTop();
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 8b5a8a8..2a4212a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -437,7 +436,8 @@
                 return -1;
             }
 
-            ItemInfo.writeBitmap(mValues, LauncherIcons.createIconBitmap(icon, mContext));
+            mValues.put(LauncherSettings.Favorites.ICON,
+                    Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index dbb797d..51cd052 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -26,9 +25,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Region;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.util.TypedValue;
@@ -42,6 +39,7 @@
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.model.PackageItemInfo;
 
@@ -190,7 +188,7 @@
     }
 
     private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
-        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
+        FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info);
         iconDrawable.setIsDisabled(info.isDisabled());
         setIcon(iconDrawable);
         setText(info.title);
@@ -202,15 +200,6 @@
     }
 
     /**
-     * Used for measurement only, sets some dummy values on this view.
-     */
-    public void applyDummyInfo() {
-        ColorDrawable d = new ColorDrawable();
-        setIcon(mLauncher.resizeIconDrawable(d));
-        setText("");
-    }
-
-    /**
      * Overrides the default long press timeout.
      */
     public void setLongPressTimeout(int longPressTimeout) {
@@ -528,12 +517,9 @@
     /**
      * Sets the icon for this view based on the layout direction.
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     private void setIcon(Drawable icon) {
         mIcon = icon;
-        if (mIconSize != -1) {
-            mIcon.setBounds(0, 0, mIconSize, mIconSize);
-        }
+        mIcon.setBounds(0, 0, mIconSize, mIconSize);
         applyCompoundDrawables(mIcon);
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index af22cfa..c0087c4 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1045,7 +1045,7 @@
                     // Offsets due to the size difference between the View and the dragOutline.
                     // There is a size difference to account for the outer blur, which may lie
                     // outside the bounds of the view.
-                    top += (v.getHeight() - dragOutline.getHeight()) / 2;
+                    top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
                     // We center about the x axis
                     left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
                 } else {
@@ -2696,6 +2696,18 @@
         }
 
         public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
+            setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
+        }
+
+        /**
+         * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
+         * to be scaled.
+         *
+         * ie. In multi-window mode, we setup widgets so that they are measured and laid out
+         * using their full/invariant device profile sizes.
+         */
+        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+                float cellScaleX, float cellScaleY) {
             if (isLockedToGrid) {
                 final int myCellHSpan = cellHSpan;
                 final int myCellVSpan = cellVSpan;
@@ -2706,8 +2718,8 @@
                     myCellX = colCount - myCellX - cellHSpan;
                 }
 
-                width = myCellHSpan * cellWidth - leftMargin - rightMargin;
-                height = myCellVSpan * cellHeight - topMargin - bottomMargin;
+                width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
+                height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
                 x = (myCellX * cellWidth + leftMargin);
                 y = (myCellY * cellHeight + topMargin);
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b38109c..fbff4eb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Gravity;
@@ -124,6 +125,9 @@
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
 
+    // Widgets
+    public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
+
     // Drop Target
     public int dropTargetBarSizePx;
 
@@ -221,6 +225,12 @@
         // The nav bar is black so we add bottom padding to visually center hotseat icons.
         profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
 
+        // We use these scales to measure and layout the widgets using their full invariant profile
+        // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
+        float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
+        float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
+        profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+
         return profile;
     }
 
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 7eaae5a..0cefc57 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -94,7 +94,7 @@
     private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
 
-    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+    protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
     private final Bitmap mBitmap;
     private State mState = State.NORMAL;
     private boolean mIsDisabled;
@@ -116,6 +116,17 @@
 
     @Override
     public void draw(Canvas canvas) {
+        drawInternal(canvas);
+    }
+
+    public void drawWithBrightness(Canvas canvas, float brightness) {
+        float oldBrightness = getBrightness();
+        setBrightness(brightness);
+        drawInternal(canvas);
+        setBrightness(oldBrightness);
+    }
+
+    protected void drawInternal(Canvas canvas) {
         canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
     }
 
@@ -278,7 +289,7 @@
     /**
      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
      */
-    public void setDesaturation(float desaturation) {
+    private void setDesaturation(float desaturation) {
         int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
         if (mDesaturation != newDesaturation) {
             mDesaturation = newDesaturation;
@@ -293,7 +304,7 @@
     /**
      * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
      */
-    public void setBrightness(float brightness) {
+    private void setBrightness(float brightness) {
         int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
         if (mBrightness != newBrightness) {
             mBrightness = newBrightness;
@@ -301,7 +312,7 @@
         }
     }
 
-    public float getBrightness() {
+    private float getBrightness() {
         return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
     }
 
@@ -364,7 +375,6 @@
 
     private AnimatorSet cancelAnimator(AnimatorSet animator) {
         if (animator != null) {
-            animator.removeAllListeners();
             animator.cancel();
         }
         return null;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 8b70d1c..c244235 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
 
 package com.android.launcher3;
 
-import android.content.ContentValues;
 import android.content.Context;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
 
@@ -93,10 +93,10 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.TITLE, title.toString());
-        values.put(LauncherSettings.Favorites.OPTIONS, options);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.TITLE, title)
+                .put(LauncherSettings.Favorites.OPTIONS, options);
 
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 7cddabb..2c9ebba 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -128,14 +128,15 @@
         if (!FeatureFlags.NO_ALL_APPS_ICON) {
             // Add the Apps button
             Context context = getContext();
-            int allAppsButtonRank = mLauncher.getDeviceProfile().inv.getAllAppsButtonRank();
+            DeviceProfile grid = mLauncher.getDeviceProfile();
+            int allAppsButtonRank = grid.inv.getAllAppsButtonRank();
 
             LayoutInflater inflater = LayoutInflater.from(context);
             TextView allAppsButton = (TextView)
                     inflater.inflate(R.layout.all_apps_button, mContent, false);
             Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+            d.setBounds(0, 0, grid.iconSizePx, grid.iconSizePx);
 
-            mLauncher.resizeIconDrawable(d);
             int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
             Rect bounds = d.getBounds();
             d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 04d0c8c..db72b2f 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -494,7 +494,6 @@
             shortcutInfo.setIcon(getDefaultIcon(user));
             shortcutInfo.title = "";
             shortcutInfo.contentDescription = "";
-            shortcutInfo.usingFallbackIcon = true;
             shortcutInfo.usingLowResIcon = false;
         } else {
             LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
@@ -512,7 +511,6 @@
         shortcutInfo.setIcon(getNonNullIcon(entry, user));
         shortcutInfo.title = Utilities.trim(entry.title);
         shortcutInfo.contentDescription = entry.contentDescription;
-        shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
     }
 
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 056facb..46bc3b3 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,18 +34,24 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONStringer;
-import org.json.JSONTokener;
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
@@ -60,6 +68,8 @@
     private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
 
     private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
+    private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
+    private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
     private static final String USER_HANDLE_KEY = "userHandle";
 
     // The set of shortcuts that are pending install
@@ -76,11 +86,7 @@
             String encoded = info.encodeToString();
             if (encoded != null) {
                 Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-                if (strings == null) {
-                    strings = new HashSet<String>(1);
-                } else {
-                    strings = new HashSet<String>(strings);
-                }
+                strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
                 strings.add(encoded);
                 sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
             }
@@ -99,32 +105,37 @@
                 Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
                         + ", removing packages: " + packageNames);
             }
-            if (strings != null) {
-                Set<String> newStrings = new HashSet<String>(strings);
-                Iterator<String> newStringsIter = newStrings.iterator();
-                while (newStringsIter.hasNext()) {
-                    String encoded = newStringsIter.next();
-                    PendingInstallShortcutInfo info = decode(encoded, context);
-                    if (info == null || (packageNames.contains(info.getTargetPackage())
-                            && user.equals(info.user))) {
+            if (Utilities.isEmpty(strings)) {
+                return;
+            }
+            Set<String> newStrings = new HashSet<>(strings);
+            Iterator<String> newStringsIter = newStrings.iterator();
+            while (newStringsIter.hasNext()) {
+                String encoded = newStringsIter.next();
+                try {
+                    Decoder decoder = new Decoder(encoded, context);
+                    if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
+                            user.equals(decoder.user)) {
                         newStringsIter.remove();
                     }
+                } catch (JSONException | URISyntaxException e) {
+                    Log.d(TAG, "Exception reading shortcut to add: " + e);
+                    newStringsIter.remove();
                 }
-                sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
             }
+            sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
         }
     }
 
-    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
-            SharedPreferences sharedPrefs, Context context) {
+    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
+        SharedPreferences sharedPrefs = Utilities.getPrefs(context);
         synchronized(sLock) {
+            ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
             if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
             if (strings == null) {
-                return new ArrayList<PendingInstallShortcutInfo>();
+                return infos;
             }
-            ArrayList<PendingInstallShortcutInfo> infos =
-                new ArrayList<PendingInstallShortcutInfo>();
             for (String encoded : strings) {
                 PendingInstallShortcutInfo info = decode(encoded, context);
                 if (info != null) {
@@ -180,7 +191,8 @@
             return null;
         }
 
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
+                data, UserHandleCompat.myUserHandle(), context);
         if (info.launchIntent == null || info.label == null) {
             if (DBG) Log.e(TAG, "Invalid install shortcut intent");
             return null;
@@ -191,7 +203,36 @@
 
     public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
         PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : info.getShortcutInfo();
+        return info == null ? null : (ShortcutInfo) info.getItemInfo();
+    }
+
+    public static void queueShortcut(ShortcutInfoCompat info, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+    }
+
+    public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
+    }
+
+    public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
+        HashSet<ShortcutKey> result = new HashSet<>();
+
+        Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null);
+        if (Utilities.isEmpty(strings)) {
+            return result;
+        }
+
+        for (String encoded : strings) {
+            try {
+                Decoder decoder = new Decoder(encoded, context);
+                if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+                    result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user));
+                }
+            } catch (JSONException | URISyntaxException e) {
+                Log.d(TAG, "Exception reading shortcut to add: " + e);
+            }
+        }
+        return result;
     }
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
@@ -212,36 +253,12 @@
         mUseInstallQueue = false;
         flushInstallQueue(context);
     }
+
     static void flushInstallQueue(Context context) {
-        SharedPreferences sp = Utilities.getPrefs(context);
-        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context);
-        if (!installQueue.isEmpty()) {
-            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
-            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
-            while (iter.hasNext()) {
-                final PendingInstallShortcutInfo pendingInfo = iter.next();
-
-                // If the intent specifies a package, make sure the package exists
-                String packageName = pendingInfo.getTargetPackage();
-                if (!TextUtils.isEmpty(packageName)) {
-                    UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
-                    if (!LauncherAppsCompat.getInstance(context)
-                            .isPackageEnabledForProfile(packageName, myUserHandle)) {
-                        if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                                + pendingInfo.launchIntent);
-                        continue;
-                    }
-                }
-
-                // Generate a shortcut info to add into the model
-                addShortcuts.add(pendingInfo.getShortcutInfo());
-            }
-
-            // Add the new apps to the model and bind them
-            if (!addShortcuts.isEmpty()) {
-                LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
-            }
+        ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
+        if (!items.isEmpty()) {
+            LauncherAppState.getInstance().getModel().addAndBindAddedWorkspaceItems(
+                    new LazyShortcutsProvider(context.getApplicationContext(), items));
         }
     }
 
@@ -265,6 +282,8 @@
     private static class PendingInstallShortcutInfo {
 
         final LauncherActivityInfoCompat activityInfo;
+        final ShortcutInfoCompat shortcutInfo;
+        final AppWidgetProviderInfo providerInfo;
 
         final Intent data;
         final Context mContext;
@@ -275,32 +294,73 @@
         /**
          * Initializes a PendingInstallShortcutInfo received from a different app.
          */
-        public PendingInstallShortcutInfo(Intent data, Context context) {
+        public PendingInstallShortcutInfo(Intent data, UserHandleCompat user, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = null;
+
             this.data = data;
+            this.user = user;
             mContext = context;
 
             launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
             label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-            user = UserHandleCompat.myUserHandle();
-            activityInfo = null;
+
         }
 
         /**
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
         public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) {
-            this.data = null;
-            mContext = context;
             activityInfo = info;
+            shortcutInfo = null;
+            providerInfo = null;
+
+            data = null;
             user = info.getUser();
+            mContext = context;
 
             launchIntent = AppInfo.makeLaunchIntent(context, info, user);
             label = info.getLabel().toString();
         }
 
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
+            activityInfo = null;
+            shortcutInfo = info;
+            providerInfo = null;
+
+            data = null;
+            mContext = context;
+            user = info.getUserHandle();
+
+            launchIntent = info.makeIntent(context);
+            label = info.getShortLabel().toString();
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(
+                AppWidgetProviderInfo info, int widgetId, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = info;
+
+            data = null;
+            mContext = context;
+            user = UserHandleCompat.fromUser(info.getProfile());
+
+            launchIntent = new Intent().setComponent(info.provider)
+                    .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+            label = info.label;
+        }
+
         public String encodeToString() {
-            if (activityInfo != null) {
-                try {
+            try {
+                if (activityInfo != null) {
                     // If it a launcher target, we only need component name, and user to
                     // recreate this.
                     return new JSONStringer()
@@ -310,30 +370,45 @@
                         .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
                                 .getSerialNumberForUser(user))
                         .endObject().toString();
-                } catch (JSONException e) {
-                    Log.d(TAG, "Exception when adding shortcut: " + e);
-                    return null;
+                } else if (shortcutInfo != null) {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                            .object()
+                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                            .key(DEEPSHORTCUT_TYPE_KEY).value(true)
+                            .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                    .getSerialNumberForUser(user))
+                            .endObject().toString();
+                } else if (providerInfo != null) {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                            .object()
+                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                            .key(APP_WIDGET_TYPE_KEY).value(true)
+                            .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                    .getSerialNumberForUser(user))
+                            .endObject().toString();
                 }
-            }
 
-            if (launchIntent.getAction() == null) {
-                launchIntent.setAction(Intent.ACTION_VIEW);
-            } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
-                    launchIntent.getCategories() != null &&
-                    launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-                launchIntent.addFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-            }
+                if (launchIntent.getAction() == null) {
+                    launchIntent.setAction(Intent.ACTION_VIEW);
+                } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+                        launchIntent.getCategories() != null &&
+                        launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                    launchIntent.addFlags(
+                            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                }
 
-            // This name is only used for comparisons and notifications, so fall back to activity
-            // name if not supplied
-            String name = ensureValidName(mContext, launchIntent, label).toString();
-            Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-            Intent.ShortcutIconResource iconResource =
-                data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                // This name is only used for comparisons and notifications, so fall back to activity
+                // name if not supplied
+                String name = ensureValidName(mContext, launchIntent, label).toString();
+                Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+                Intent.ShortcutIconResource iconResource =
+                    data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
 
-            // Only encode the parameters which are supported by the API.
-            try {
+                // Only encode the parameters which are supported by the API.
                 JSONStringer json = new JSONStringer()
                     .object()
                     .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
@@ -352,57 +427,79 @@
                 return json.endObject().toString();
             } catch (JSONException e) {
                 Log.d(TAG, "Exception when adding shortcut: " + e);
+                return null;
             }
-            return null;
         }
 
-        public ShortcutInfo getShortcutInfo() {
+        public ItemInfo getItemInfo() {
             if (activityInfo != null) {
                 return new ShortcutInfo(activityInfo, mContext);
+            } else if (shortcutInfo != null) {
+                return new ShortcutInfo(shortcutInfo, mContext);
+            } else if (providerInfo != null) {
+                LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+                        .fromProviderInfo(mContext, providerInfo);
+                LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+                        launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+                        info.provider);
+                InvariantDeviceProfile idp = LauncherAppState.getInstance()
+                        .getInvariantDeviceProfile();
+                widgetInfo.minSpanX = info.minSpanX;
+                widgetInfo.minSpanY = info.minSpanY;
+                widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+                widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+                return widgetInfo;
             } else {
                 return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
             }
         }
 
-        public String getTargetPackage() {
-            String packageName = launchIntent.getPackage();
-            if (packageName == null) {
-                packageName = launchIntent.getComponent() == null ? null :
-                    launchIntent.getComponent().getPackageName();
-            }
-            return packageName;
-        }
-
         public boolean isLauncherActivity() {
             return activityInfo != null;
         }
     }
 
+    private static String getIntentPackage(Intent intent) {
+        return intent.getComponent() == null
+                ? intent.getPackage() : intent.getComponent().getPackageName();
+    }
+
     private static PendingInstallShortcutInfo decode(String encoded, Context context) {
         try {
-            JSONObject object = (JSONObject) new JSONTokener(encoded).nextValue();
-            Intent launcherIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
-
-            if (object.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                // The is an internal launcher target shortcut.
-                UserHandleCompat user = UserManagerCompat.getInstance(context)
-                        .getUserForSerialNumber(object.getLong(USER_HANDLE_KEY));
-                if (user == null) {
+            Decoder decoder = new Decoder(encoded, context);
+            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+                LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
+                        .resolveActivity(decoder.launcherIntent, decoder.user);
+                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+            } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+                DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
+                List<ShortcutInfoCompat> si = sm.queryForFullDetails(
+                        decoder.launcherIntent.getPackage(),
+                        Arrays.asList(ShortcutInfoCompat.EXTRA_SHORTCUT_ID), decoder.user);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0), context);
+                }
+            } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
+                int widgetId = decoder.launcherIntent
+                        .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
+                        .getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
+                        !info.getProfile().equals(decoder.user.getUser())) {
                     return null;
                 }
-
-                LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
-                        .resolveActivity(launcherIntent, user);
-                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+                return new PendingInstallShortcutInfo(info, widgetId, context);
             }
 
             Intent data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent);
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, object.getString(NAME_KEY));
+            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
+            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
 
-            String iconBase64 = object.optString(ICON_KEY);
-            String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
-            String iconResourcePackageName = object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+            String iconBase64 = decoder.optString(ICON_KEY);
+            String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
+            String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
             if (iconBase64 != null && !iconBase64.isEmpty()) {
                 byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
                 Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
@@ -415,13 +512,29 @@
                 data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
             }
 
-            return new PendingInstallShortcutInfo(data, context);
+            return new PendingInstallShortcutInfo(data, decoder.user, context);
         } catch (JSONException | URISyntaxException e) {
             Log.d(TAG, "Exception reading shortcut to add: " + e);
         }
         return null;
     }
 
+    private static class Decoder extends JSONObject {
+        public final Intent launcherIntent;
+        public final UserHandleCompat user;
+
+        private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
+            super(encoded);
+            launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
+            user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context)
+                    .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
+                    : UserHandleCompat.myUserHandle();
+            if (user == null) {
+                throw new JSONException("Invalid user");
+            }
+        }
+    }
+
     /**
      * Tries to create a new PendingInstallShortcutInfo which represents the same target,
      * but is an app target and not a shortcut.
@@ -445,4 +558,40 @@
         // Ignore any conflicts in the label name, as that can change based on locale.
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
+
+    private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> {
+
+        private final Context mContext;
+        private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
+
+        public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> items) {
+            mContext = context;
+            mPendingItems = items;
+        }
+
+        /**
+         * This must be called on the background thread as this requires multiple calls to
+         * packageManager and icon cache.
+         */
+        @Override
+        public ArrayList<ItemInfo> get() {
+            Preconditions.assertNonUiThread();
+            ArrayList<ItemInfo> installQueue = new ArrayList<>();
+            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
+            for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
+                // If the intent specifies a package, make sure the package exists
+                String packageName = getIntentPackage(pendingInfo.launchIntent);
+                if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
+                        packageName, pendingInfo.user)) {
+                    if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+                            + pendingInfo.launchIntent);
+                    continue;
+                }
+
+                // Generate a shortcut info to add into the model
+                installQueue.add(pendingInfo.getItemInfo());
+            }
+            return installQueue;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 2043772..3e0ae4f 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,12 +18,10 @@
 
 import android.content.ComponentName;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 
 import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents an item in the launcher.
@@ -142,15 +140,15 @@
         return getIntent() == null ? null : getIntent().getComponent();
     }
 
-    public void writeToValues(ContentValues values) {
-        values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
-        values.put(LauncherSettings.Favorites.CONTAINER, container);
-        values.put(LauncherSettings.Favorites.SCREEN, screenId);
-        values.put(LauncherSettings.Favorites.CELLX, cellX);
-        values.put(LauncherSettings.Favorites.CELLY, cellY);
-        values.put(LauncherSettings.Favorites.SPANX, spanX);
-        values.put(LauncherSettings.Favorites.SPANY, spanY);
-        values.put(LauncherSettings.Favorites.RANK, rank);
+    public void writeToValues(ContentWriter writer) {
+        writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+                .put(LauncherSettings.Favorites.CONTAINER, container)
+                .put(LauncherSettings.Favorites.SCREEN, screenId)
+                .put(LauncherSettings.Favorites.CELLX, cellX)
+                .put(LauncherSettings.Favorites.CELLY, cellY)
+                .put(LauncherSettings.Favorites.SPANX, spanX)
+                .put(LauncherSettings.Favorites.SPANY, spanY)
+                .put(LauncherSettings.Favorites.RANK, rank);
     }
 
     public void readFromValues(ContentValues values) {
@@ -166,26 +164,15 @@
 
     /**
      * Write the fields of this item to the DB
-     *
-     * @param context A context object to use for getting UserManagerCompat
-     * @param values
      */
-    void onAddToDatabase(Context context, ContentValues values) {
-        writeToValues(values);
-        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
-        values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
-
+    void onAddToDatabase(ContentWriter writer) {
         if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
             // We should never persist an item on the extra empty screen.
             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
         }
-    }
 
-    static void writeBitmap(ContentValues values, Bitmap bitmap) {
-        if (bitmap != null) {
-            byte[] data = Utilities.flattenBitmap(bitmap);
-            values.put(LauncherSettings.Favorites.ICON, data);
-        }
+        writeToValues(writer);
+        writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2f51054..9215024 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1470,9 +1470,7 @@
         }
 
         LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
-
-        mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
-                isWorkspaceLocked());
+        mWorkspace.addInScreen(view, info);
     }
 
     /**
@@ -1507,20 +1505,15 @@
             hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
         }
         hostView.setVisibility(View.VISIBLE);
-        addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
+        prepareAppWidget(hostView, launcherInfo);
+        mWorkspace.addInScreen(hostView, launcherInfo);
     }
 
-    private void addAppWidgetToWorkspace(
-            AppWidgetHostView hostView, LauncherAppWidgetInfo item,
-            LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
+    private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
         hostView.setTag(item);
         item.onBindAppWidget(this, hostView);
-
         hostView.setFocusable(true);
         hostView.setOnFocusChangeListener(mFocusHandler);
-
-        mWorkspace.addInScreen(hostView, item.container, item.screenId,
-                item.cellX, item.cellY, item.spanX, item.spanY, insert);
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1681,9 +1674,27 @@
                 // Can be cases where mWorkspace is null, this prevents a NPE
                 return;
             }
-            // In all these cases, only animate if we're already on home
+
+            // Note: There should be at most one log per method call. This is enforced implicitly
+            // by using if-else statements.
+            UserEventDispatcher ued = getUserEventDispatcher();
+
+            // TODO: Log this case.
             mWorkspace.exitWidgetResizeMode();
 
+            AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
+            if (topOpenView instanceof DeepShortcutsContainer) {
+                ued.logActionCommand(LauncherLogProto.Action.HOME_INTENT,
+                        topOpenView.getExtendedTouchView(), LauncherLogProto.DEEPSHORTCUTS);
+            } else if (topOpenView instanceof Folder) {
+                ued.logActionCommand(LauncherLogProto.Action.HOME_INTENT,
+                            ((Folder) topOpenView).getFolderIcon(), LauncherLogProto.FOLDER);
+            } else if (alreadyOnHome) {
+                ued.logActionCommand(LauncherLogProto.Action.HOME_INTENT,
+                        mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
+            }
+
+            // In all these cases, only animate if we're already on home
             AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
             exitSpringLoadedDragMode();
 
@@ -2087,8 +2098,7 @@
         // Create the view
         FolderIcon newFolder =
             FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
-        mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
-                isWorkspaceLocked());
+        mWorkspace.addInScreen(newFolder, folderInfo);
         // Force measure the new folder icon
         CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
         parent.getShortcutsAndWidgets().measureChild(newFolder);
@@ -2187,20 +2197,34 @@
             return;
         }
 
+        // Note: There should be at most one log per method call. This is enforced implicitly
+        // by using if-else statements.
+        UserEventDispatcher ued = getUserEventDispatcher();
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null) {
             if (topView.getActiveTextView() != null) {
                 topView.getActiveTextView().dispatchBackKey();
             } else {
+                if (topView instanceof DeepShortcutsContainer) {
+                    ued.logActionCommand(LauncherLogProto.Action.BACK,
+                            topView.getExtendedTouchView(), LauncherLogProto.DEEPSHORTCUTS);
+                } else if (topView instanceof Folder) {
+                    ued.logActionCommand(LauncherLogProto.Action.BACK,
+                            ((Folder) topView).getFolderIcon(), LauncherLogProto.FOLDER);
+                }
                 topView.close(true);
             }
         } else if (isAppsViewVisible()) {
+            ued.logActionCommand(LauncherLogProto.Action.BACK, LauncherLogProto.ALLAPPS);
             showWorkspace(true);
         } else if (isWidgetsViewVisible())  {
+            ued.logActionCommand(LauncherLogProto.Action.BACK, LauncherLogProto.WIDGETS);
             showOverviewMode(true);
         } else if (mWorkspace.isInOverviewMode()) {
+            ued.logActionCommand(LauncherLogProto.Action.BACK, LauncherLogProto.OVERVIEW);
             showWorkspace(true);
         } else {
+            // TODO: Log this case.
             mWorkspace.exitWidgetResizeMode();
 
             // Back button is a no-op here, but give at least some feedback for the button press
@@ -2594,7 +2618,7 @@
                 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                     String id = ((ShortcutInfo) info).getDeepShortcutId();
                     String packageName = intent.getPackage();
-                    LauncherAppState.getInstance().getShortcutManager().startShortcut(
+                    DeepShortcutManager.getInstance(this).startShortcut(
                             packageName, id, intent.getSourceBounds(), optsBundle, info.user);
                 } else {
                     // Could be launching some bookkeeping activity
@@ -3272,25 +3296,25 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
-    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+    public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end,
                           final boolean forceAnimateIcons) {
         Runnable r = new Runnable() {
             public void run() {
-                bindItems(shortcuts, start, end, forceAnimateIcons);
+                bindItems(items, start, end, forceAnimateIcons);
             }
         };
         if (waitUntilResume(r)) {
             return;
         }
 
-        // Get the list of added shortcuts and intersect them with the set of shortcuts here
+        // Get the list of added items and intersect them with the set of items here
         final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<Animator>();
         final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
-        long newShortcutsScreenId = -1;
+        long newItemsScreenId = -1;
         for (int i = start; i < end; i++) {
-            final ItemInfo item = shortcuts.get(i);
+            final ItemInfo item = items.get(i);
 
             // Short circuit if we are loading dock items for a configuration which has no dock
             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
@@ -3302,15 +3326,33 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                     ShortcutInfo info = (ShortcutInfo) item;
                     view = createShortcut(info);
                     break;
-                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                     view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                             (FolderInfo) item, mIconCache);
                     break;
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+                    if (mIsSafeModeEnabled) {
+                        view = new PendingAppWidgetHostView(this, info, mIconCache, true);
+                    } else {
+                        LauncherAppWidgetProviderInfo providerInfo =
+                                mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
+                        if (providerInfo == null) {
+                            deleteWidgetInfo(info);
+                            continue;
+                        }
+                        view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
+                    }
+                    prepareAppWidget((AppWidgetHostView) view, info);
+                    break;
+                }
                 default:
                     throw new RuntimeException("Invalid Item Type");
             }
@@ -3334,30 +3376,29 @@
                     }
                 }
             }
-            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
-                    item.cellY, 1, 1);
+            workspace.addInScreenFromBind(view, item);
             if (animateIcons) {
                 // Animate all the applications up now
                 view.setAlpha(0f);
                 view.setScaleX(0f);
                 view.setScaleY(0f);
                 bounceAnims.add(createNewAppBounceAnimation(view, i));
-                newShortcutsScreenId = item.screenId;
+                newItemsScreenId = item.screenId;
             }
         }
 
         if (animateIcons) {
             // Animate to the correct page
-            if (newShortcutsScreenId > -1) {
+            if (newItemsScreenId > -1) {
                 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
-                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
                 final Runnable startBounceAnimRunnable = new Runnable() {
                     public void run() {
                         anim.playTogether(bounceAnims);
                         anim.start();
                     }
                 };
-                if (newShortcutsScreenId != currentScreenId) {
+                if (newItemsScreenId != currentScreenId) {
                     // We post the animation slightly delayed to prevent slowdowns
                     // when we are loading right after we return to launcher.
                     mWorkspace.postDelayed(new Runnable() {
@@ -3377,15 +3418,6 @@
         workspace.requestLayout();
     }
 
-    private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
-        PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
-        view.updateIcon(mIconCache);
-        view.updateAppWidget(null);
-        view.setOnClickListener(this);
-        addAppWidgetToWorkspace(view, item, null, false);
-        mWorkspace.requestLayout();
-    }
-
     /**
      * Add the views for a widget to the workspace.
      *
@@ -3402,7 +3434,11 @@
         }
 
         if (mIsSafeModeEnabled) {
-            bindSafeModeWidget(item);
+            PendingAppWidgetHostView view =
+                    new PendingAppWidgetHostView(this, item, mIconCache, true);
+            prepareAppWidget(view, item);
+            mWorkspace.addInScreen(view, item);
+            mWorkspace.requestLayout();
             return;
         }
 
@@ -3490,6 +3526,7 @@
             }
         }
 
+        final AppWidgetHostView view;
         if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
             if (DEBUG_WIDGETS) {
                 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
@@ -3505,16 +3542,12 @@
 
             item.minSpanX = appWidgetInfo.minSpanX;
             item.minSpanY = appWidgetInfo.minSpanY;
-            addAppWidgetToWorkspace(
-                    mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
-                    item, appWidgetInfo, false);
+            view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
         } else {
-            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
-            view.updateIcon(mIconCache);
-            view.updateAppWidget(null);
-            view.setOnClickListener(this);
-            addAppWidgetToWorkspace(view, item, null, false);
+            view = new PendingAppWidgetHostView(this, item, mIconCache, false);
         }
+        prepareAppWidget(view, item);
+        mWorkspace.addInScreen(view, item);
         mWorkspace.requestLayout();
 
         if (DEBUG_WIDGETS) {
@@ -4004,24 +4037,6 @@
     }
 
     /**
-     * Returns a FastBitmapDrawable with the icon, accurately sized.
-     */
-    public FastBitmapDrawable createIconDrawable(Bitmap icon) {
-        FastBitmapDrawable d = new FastBitmapDrawable(icon);
-        d.setFilterBitmap(true);
-        resizeIconDrawable(d);
-        return d;
-    }
-
-    /**
-     * Resizes an icon drawable to the correct icon size.
-     */
-    public Drawable resizeIconDrawable(Drawable icon) {
-        icon.setBounds(0, 0, mDeviceProfile.iconSizePx, mDeviceProfile.iconSizePx);
-        return icon;
-    }
-
-    /**
      * Prints out out state for debugging.
      */
     public void dumpState() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 2a43aad..5937d78 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,8 +28,6 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutCache;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -44,7 +42,6 @@
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
-    private final DeepShortcutManager mDeepShortcutManager;
 
     @Thunk boolean mWallpaperChangedSinceLastCheck;
 
@@ -98,10 +95,9 @@
         mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
         mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
         mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
-        mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
+        mModel = new LauncherModel(this, mIconCache, mAppFilter);
 
         LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
 
@@ -173,10 +169,6 @@
         return mWidgetCache;
     }
 
-    public DeepShortcutManager getShortcutManager() {
-        return mDeepShortcutManager;
-    }
-
     public boolean hasWallpaperChangedSinceLastCheck() {
         boolean result = mWallpaperChangedSinceLastCheck;
         mWallpaperChangedSinceLastCheck = false;
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 78f5b8e..2218767 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -18,11 +18,10 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a widget (either instantiated or about to be) in the Launcher.
@@ -127,13 +126,12 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
-        values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
-        values.put(LauncherSettings.Favorites.RESTORED, restoreStatus);
-        values.put(LauncherSettings.Favorites.INTENT,
-                bindOptions == null ? null : bindOptions.toUri(0));
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+                .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
+                .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+                .put(LauncherSettings.Favorites.INTENT, bindOptions);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c70a475..cc56d43 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -76,6 +76,7 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -84,6 +85,7 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -155,7 +157,8 @@
         @Override
         public void run() {
             if (mDeepShortcutsLoaded) {
-                boolean hasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+                boolean hasShortcutHostPermission =
+                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
                 if (hasShortcutHostPermission != mHasShortcutHostPermission) {
                     mApp.reloadWorkspace();
                 }
@@ -172,7 +175,6 @@
     // </ only access in worker thread >
 
     private final IconCache mIconCache;
-    private final DeepShortcutManager mDeepShortcutManager;
 
     private final LauncherAppsCompat mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -209,14 +211,12 @@
         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
     }
 
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
-            DeepShortcutManager deepShortcutManager) {
+    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         Context context = app.getContext();
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
         mIconCache = iconCache;
-        mDeepShortcutManager = deepShortcutManager;
 
         mLauncherApps = LauncherAppsCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
@@ -261,9 +261,16 @@
     /**
      * Adds the provided items to the workspace.
      */
+    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
+        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
+    }
+
+    /**
+     * Adds the provided items to the workspace.
+     */
     public void addAndBindAddedWorkspaceItems(
-            final ArrayList<? extends ItemInfo> workspaceApps) {
-        enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps));
+            Provider<List<ItemInfo>> appsProvider) {
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
     }
 
     /**
@@ -332,7 +339,7 @@
         runOnWorkerThread(r);
     }
 
-    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+    static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
             final ItemInfo item, final String callingFunction) {
         final long itemId = item.id;
         final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
@@ -341,7 +348,7 @@
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.update(uri, values, null, null);
+                cr.update(uri, writer.getValues(), null, null);
                 updateItemArrays(item, itemId, stackTrace);
             }
         };
@@ -439,14 +446,14 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase");
     }
 
     /**
@@ -506,25 +513,25 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
-        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SPANX, item.spanX)
+                .put(LauncherSettings.Favorites.SPANY, item.spanY)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase");
     }
 
     /**
      * Update an item to the database in a specified container.
      */
     public static void updateItemInDatabase(Context context, final ItemInfo item) {
-        final ContentValues values = new ContentValues();
-        item.onAddToDatabase(context, values);
-        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
+        ContentWriter writer = new ContentWriter(context);
+        item.onAddToDatabase(writer);
+        updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase");
     }
 
     /**
@@ -546,19 +553,19 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
+        final ContentWriter writer = new ContentWriter(context);
         final ContentResolver cr = context.getContentResolver();
-        item.onAddToDatabase(context, values);
+        item.onAddToDatabase(writer);
 
         item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
 
-        values.put(LauncherSettings.Favorites._ID, item.id);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
 
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
+                cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
 
                 synchronized (sBgDataModel) {
                     checkItemInfoLocked(item.id, item, stackTrace);
@@ -1190,6 +1197,7 @@
             final PackageManager manager = context.getPackageManager();
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
             final boolean isSdCardReady = Utilities.isBootCompleted();
             final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>();
 
@@ -1287,8 +1295,8 @@
                         // We can only query for shortcuts when the user is unlocked.
                         if (userUnlocked) {
                             List<ShortcutInfoCompat> pinnedShortcuts =
-                                    mDeepShortcutManager.queryForPinnedShortcuts(null, user);
-                            if (mDeepShortcutManager.wasLastCallSuccess()) {
+                                    shortcutManager.queryForPinnedShortcuts(null, user);
+                            if (shortcutManager.wasLastCallSuccess()) {
                                 for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
                                     shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
                                             shortcut);
@@ -1768,11 +1776,14 @@
                 }
 
                 // Unpin shortcuts that don't exist on the workspace.
+                HashSet<ShortcutKey> pendingShortcuts =
+                        InstallShortcutReceiver.getPendingShortcuts(context);
                 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
                     MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
-                    if (numTimesPinned == null || numTimesPinned.value == 0) {
+                    if ((numTimesPinned == null || numTimesPinned.value == 0)
+                            && !pendingShortcuts.contains(key)) {
                         // Shortcut is pinned but doesn't exist on the workspace; unpin it.
-                        mDeepShortcutManager.unpinShortcut(key);
+                        shortcutManager.unpinShortcut(key);
                     }
                 }
 
@@ -2325,12 +2336,13 @@
             }
             if (!mDeepShortcutsLoaded) {
                 sBgDataModel.deepShortcutMap.clear();
-                mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
+                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
                 if (mHasShortcutHostPermission) {
                     for (UserHandleCompat user : mUserManager.getUserProfiles()) {
                         if (mUserManager.isUserUnlocked(user)) {
-                            List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
-                                    .queryForAllShortcuts(user);
+                            List<ShortcutInfoCompat> shortcuts =
+                                    shortcutManager.queryForAllShortcuts(user);
                             sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
                         }
                     }
@@ -2632,7 +2644,6 @@
         // the fallback icon
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
     }
@@ -2669,7 +2680,6 @@
         info.user = UserHandleCompat.myUserHandle();
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 349f094..b30c3f3 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -650,7 +650,7 @@
             if (mWidgetHostResetHandler != null) {
                 new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost();
                 mWidgetHostResetHandler.sendEmptyMessage(
-                        ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED);
+                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
             }
 
             // Set the flag for empty DB
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index bf39774..7c92f80 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -36,6 +36,8 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
+import com.android.launcher3.graphics.DrawableFactory;
+
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
@@ -63,7 +65,7 @@
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
-            boolean disabledForSafeMode) {
+            IconCache cache, boolean disabledForSafeMode) {
         super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
 
         mLauncher = Launcher.getLauncher(context);
@@ -82,6 +84,10 @@
         if (Utilities.ATLEAST_LOLLIPOP) {
             setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
         }
+
+        updateIcon(cache);
+        updateAppWidget(null);
+        setOnClickListener(mLauncher);
     }
 
     @Override
@@ -117,7 +123,7 @@
         mDrawableSizeChanged = true;
     }
 
-    public void updateIcon(IconCache cache) {
+    private void updateIcon(IconCache cache) {
         Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
         if (mIcon == icon) {
             return;
@@ -132,13 +138,14 @@
             //   1) App icon in the center
             //   2) Preload icon in the center
             //   3) Setup icon in the center and app icon in the top right corner.
+            DrawableFactory drawableFactory = DrawableFactory.get(getContext());
             if (mDisabledForSafeMode) {
-                FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
+                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(mIcon, mInfo);
                 disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = new FastBitmapDrawable(mIcon);
+                mCenterDrawable = drawableFactory.newIcon(mIcon, mInfo);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
 
                 updateSettingColor();
@@ -148,7 +155,7 @@
                     sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
                 }
 
-                FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
+                FastBitmapDrawable drawable = drawableFactory.newIcon(mIcon, mInfo);
                 mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
                 mCenterDrawable.setCallback(this);
                 mSettingIconDrawable = null;
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index c1d60fd..41074be 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -215,9 +215,18 @@
             view.setVisibility(View.VISIBLE);
         } else {
             animator.addListener(new AnimatorListenerAdapter() {
+                private boolean mCancelled = false;
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mCancelled = true;
+                }
+
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    view.setVisibility(View.INVISIBLE);
+                    if (!mCancelled) {
+                        view.setVisibility(View.INVISIBLE);
+                    }
                 }
             });
         }
@@ -226,7 +235,6 @@
 
     private void startAnimator(int index, Animator animator, long duration) {
         if (mAnimators[index] != null) {
-            mAnimators[index].removeAllListeners();
             mAnimators[index].cancel();
         }
         mAnimators[index] = animator;
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index efc0eac..973e688 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -177,8 +177,9 @@
             // Set the paint color only when the level changes, so that the dominant color
             // is only calculated when needed.
             mPaint.setColor(getIndicatorColor());
-        } else if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).setIsDisabled(true);
+        }
+        if (mIcon instanceof FastBitmapDrawable) {
+            ((FastBitmapDrawable) mIcon).setIsDisabled(level < 100);
         }
 
         invalidateSelf();
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 9568d57..6c73762 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,6 +18,7 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
@@ -100,20 +101,20 @@
     }
 
     public void measureChild(View child) {
-        final DeviceProfile grid = mLauncher.getDeviceProfile();
-        final int cellWidth = mCellWidth;
-        final int cellHeight = mCellHeight;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (!lp.isFullscreen) {
-            lp.setup(cellWidth, cellHeight, invertLayoutHorizontally(), mCountX);
+            final DeviceProfile profile = mLauncher.getDeviceProfile();
 
             if (child instanceof LauncherAppWidgetHostView) {
-                // Widgets have their own padding, so skip
+                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+                        profile.appWidgetScale.x, profile.appWidgetScale.y);
+                // Widgets have their own padding
             } else {
-                // Otherwise, center the icon/folder
+                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+                // Center the icon/folder
                 int cHeight = getCellContentHeight();
                 int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
-                int cellPaddingX = (int) (grid.edgeMarginPx / 2f);
+                int cellPaddingX = (int) (profile.edgeMarginPx / 2f);
                 child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
             }
         } else {
@@ -138,6 +139,21 @@
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+                if (child instanceof LauncherAppWidgetHostView) {
+                    // Scale and center the widget to fit within its cells.
+                    DeviceProfile profile = mLauncher.getDeviceProfile();
+                    float scaleX = profile.appWidgetScale.x;
+                    float scaleY = profile.appWidgetScale.y;
+
+                    float scale = Math.min(scaleX, scaleY);
+                    child.setScaleX(scale);
+                    child.setScaleY(scale);
+
+                    child.setTranslationX(-(lp.width - (lp.width * scaleX)) / 2.0f);
+                    child.setTranslationY(-(lp.height - (lp.height * scaleY)) / 2.0f);
+                }
+
                 int childLeft = lp.x;
                 int childTop = lp.y;
                 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fc08736..d6d03d3 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -18,7 +18,6 @@
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -32,7 +31,9 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -77,12 +78,6 @@
     public Intent intent;
 
     /**
-     * Indicates whether we're using the default fallback icon instead of something from the
-     * app.
-     */
-    public boolean usingFallbackIcon;
-
-    /**
      * Indicates whether we're using a low res icon
      */
     boolean usingLowResIcon;
@@ -187,7 +182,6 @@
         status = info.status;
         mInstallProgress = info.mInstallProgress;
         isDisabled = info.isDisabled;
-        usingFallbackIcon = info.usingFallbackIcon;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -240,25 +234,19 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+                .put(LauncherSettings.BaseLauncherColumns.INTENT, getPromisedIntent())
+                .put(LauncherSettings.Favorites.RESTORED, status);
 
-        String titleStr = title != null ? title.toString() : null;
-        values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
-
-        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 (!usingFallbackIcon && !usingLowResIcon) {
-            writeBitmap(values, mIcon);
+        if (!usingLowResIcon) {
+            writer.putIcon(mIcon, user);
         }
         if (iconResource != null) {
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
-                    iconResource.packageName);
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
-                    iconResource.resourceName);
+            writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+                    .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                            iconResource.resourceName);
         }
     }
 
@@ -308,7 +296,7 @@
 
         // TODO: Use cache for this
         LauncherAppState launcherAppState = LauncherAppState.getInstance();
-        Drawable unbadgedDrawable = launcherAppState.getShortcutManager()
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
                 .getShortcutIconDrawable(shortcutInfo,
                         launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 01d87f4..10680b4 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -48,7 +48,6 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.Animation.AnimationListener;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
@@ -183,18 +182,20 @@
     // in all apps or customize mode)
 
     public enum State {
-        NORMAL          (false, false),
-        NORMAL_HIDDEN   (false, false),
-        SPRING_LOADED   (false, true),
-        OVERVIEW        (true, true),
-        OVERVIEW_HIDDEN (true, false);
+        NORMAL          (false, false, LauncherLogProto.WORKSPACE),
+        NORMAL_HIDDEN   (false, false, LauncherLogProto.ALLAPPS),
+        SPRING_LOADED   (false, true, LauncherLogProto.WORKSPACE),
+        OVERVIEW        (true, true, LauncherLogProto.OVERVIEW),
+        OVERVIEW_HIDDEN (true, false, LauncherLogProto.WIDGETS);
 
         public final boolean shouldUpdateWidget;
         public final boolean hasMultipleVisiblePages;
+        public final int containerType;
 
-        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages) {
+        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) {
             this.shouldUpdateWidget = shouldUpdateWidget;
             this.hasMultipleVisiblePages = hasMultipleVisiblePages;
+            this.containerType = containerType;
         }
     }
 
@@ -1042,23 +1043,28 @@
         }
     }
 
-    // See implementation for parameter definition.
-    void addInScreen(View child, long container, long screenId,
-            int x, int y, int spanX, int spanY) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
+    /**
+     * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+     * See {@link #addInScreen}.
+     */
+    public void addInScreenFromBind(View child, ItemInfo info) {
+        int x = info.cellX;
+        int y = info.cellY;
+        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            int screenId = (int) info.screenId;
+            x = mLauncher.getHotseat().getCellXFromOrder(screenId);
+            y = mLauncher.getHotseat().getCellYFromOrder(screenId);
+        }
+        addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
     }
 
-    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
-    // See implementation for parameter definition.
-    public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
-            int spanX, int spanY) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
-    }
-
-    // See implementation for parameter definition.
-    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
-            boolean insert) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
+    /**
+     * Adds the specified child in the specified screen based on the {@param info}
+     * See {@link #addInScreen}.
+     */
+    public void addInScreen(View child, ItemInfo info) {
+        addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
+                info.spanX, info.spanY);
     }
 
     /**
@@ -1071,13 +1077,9 @@
      * @param y The Y position of the child in the screen's grid.
      * @param spanX The number of cells spanned horizontally by the child.
      * @param spanY The number of cells spanned vertically by the child.
-     * @param insert When true, the child is inserted at the beginning of the children list.
-     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
-     *                          the x and y position in which to place hotseat items. Otherwise
-     *                          we use the x and y position to compute the rank.
      */
-    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
-            boolean insert, boolean computeXYFromRank) {
+    private void addInScreen(View child, long container, long screenId, int x, int y,
+            int spanX, int spanY) {
         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
             if (getScreenWithId(screenId) == null) {
                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -1100,13 +1102,6 @@
             if (child instanceof FolderIcon) {
                 ((FolderIcon) child).setTextVisible(false);
             }
-
-            if (computeXYFromRank) {
-                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
-                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
-            } else {
-                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
-            }
         } else {
             // Show folder title if not in the hotseat
             if (child instanceof FolderIcon) {
@@ -1137,7 +1132,7 @@
         int childId = mLauncher.getViewIdForItem(info);
 
         boolean markCellsAsOccupied = !(child instanceof Folder);
-        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
             // TODO: This branch occurs when the workspace is adding views
             // outside of the defined grid
             // maybe we should be deleting these items from the LauncherModel?
@@ -1969,7 +1964,7 @@
             CellLayout cl = ((CellLayout) getChildAt(i));
             mScreenOrder.add(getIdForScreen(cl));
         }
-
+        mLauncher.getUserEventDispatcher().logOverviewReorder();
         mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
 
         // Re-enable auto layout transitions for page deletion.
@@ -2054,7 +2049,7 @@
         StateTransitionListener listener = new StateTransitionListener();
         if (animated) {
             ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
-            stepAnimator.addListener(listener);
+            stepAnimator.addUpdateListener(listener);
 
             workspaceAnim.play(stepAnimator);
             workspaceAnim.addListener(listener);
@@ -2518,7 +2513,7 @@
         if (d.dragSource != this) {
             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                     (int) mDragViewVisualCenter[1] };
-            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, d);
         } else if (mDragInfo != null) {
             final View cell = mDragInfo.cell;
             boolean droppedOnOriginalCellDuringTransition = false;
@@ -3264,7 +3259,7 @@
      * to add an item to one of the workspace screens.
      */
     private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
-            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+            final CellLayout cellLayout, DragObject d) {
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
             public void run() {
@@ -3414,8 +3409,8 @@
             LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
                     mTargetCell[0], mTargetCell[1]);
 
-            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
-                    info.spanY, insertAtFirst);
+            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
+                    info.spanX, info.spanY);
             cellLayout.onDropChild(view);
             cellLayout.getShortcutsAndWidgets().measureChild(view);
 
@@ -3466,14 +3461,14 @@
                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
         resetTransitionTransform(layout);
 
-        float dragViewScaleX;
-        float dragViewScaleY;
+        float dragViewScaleX = 1f;
+        float dragViewScaleY = 1f;
         if (scale) {
-            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
-            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
-        } else {
-            dragViewScaleX = 1f;
-            dragViewScaleY = 1f;
+            float width = info.spanX * layout.mCellWidth;
+            float height = info.spanY * layout.mCellHeight;
+
+            dragViewScaleX = r.width() / width;
+            dragViewScaleY = r.height() / height;
         }
 
         // The animation will scale the dragView about its center, so we need to center about
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 1f36468..6a71bef 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -356,7 +356,8 @@
                 cl.setShortcutAndWidgetAlpha(finalAlpha);
             }
 
-            if (Workspace.isQsbContainerPage(i)) {
+            if (Workspace.isQsbContainerPage(i) &&
+                    states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
                 if (animated) {
                     Animator anim = mWorkspace.mQsbAlphaController
                             .animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
@@ -372,8 +373,6 @@
 
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
 
-        final View qsbContainer = mLauncher.getQsbContainer();
-
         Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController
                 .animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE);
 
@@ -395,7 +394,7 @@
             // For animation optimization, we may need to provide the Launcher transition
             // with a set of views on which to force build and manage layers in certain scenarios.
             layerViews.addView(overviewPanel);
-            layerViews.addView(qsbContainer);
+            layerViews.addView(mLauncher.getQsbContainer());
             layerViews.addView(mLauncher.getHotseat());
             layerViews.addView(mWorkspace.getPageIndicator());
 
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
index cfd07e6..bc602f3 100644
--- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -188,7 +188,6 @@
 
     private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
         if (animator != null) {
-            animator.removeAllListeners();
             animator.cancel();
         }
         return null;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 53c12b5..a81b4ca 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -79,7 +79,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.CircleRevealOutlineProvider;
@@ -388,6 +387,10 @@
         return isEditingName() ? mFolderName : null;
     }
 
+    public FolderIcon getFolderIcon() {
+        return mFolderIcon;
+    }
+
     /**
      * We need to handle touch events to prevent them from falling through to the workspace below.
      */
@@ -1222,8 +1225,7 @@
                         // We add the child after removing the folder to prevent both from existing
                         // at the same time in the CellLayout.  We need to add the new item with
                         // addInScreenFromBind() to ensure that hotseat items are placed correctly.
-                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container,
-                                mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo);
 
                         // Focus the newly created child
                         newIcon.requestFocus();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index a29a946..3745323 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -459,10 +459,7 @@
             d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
             if (d instanceof FastBitmapDrawable) {
                 FastBitmapDrawable fd = (FastBitmapDrawable) d;
-                float oldBrightness = fd.getBrightness();
-                fd.setBrightness(params.overlayAlpha);
-                d.draw(canvas);
-                fd.setBrightness(oldBrightness);
+                fd.drawWithBrightness(canvas, params.overlayAlpha);
             } else {
                 d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
                         PorterDuff.Mode.SRC_ATOP);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index a7d4c63..e205c42 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,7 +24,9 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.ProviderConfig;
@@ -100,20 +102,31 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap(Canvas canvas) {
-        Bitmap b;
+        float scale = 1f;
+        int width = mView.getWidth();
+        int height = mView.getHeight();
 
         if (mView instanceof TextView) {
             Drawable d = Workspace.getTextViewIcon((TextView) mView);
             Rect bounds = getDrawableBounds(d);
-            b = Bitmap.createBitmap(bounds.width() + DRAG_BITMAP_PADDING,
-                    bounds.height() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
-        } else {
-            b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
-                    mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
+            width = bounds.width();
+            height = bounds.height();
+        } else if (mView instanceof LauncherAppWidgetHostView) {
+            DeviceProfile profile = Launcher.getLauncher(mView.getContext()).getDeviceProfile();
+            scale = Math.min(profile.appWidgetScale.x, profile.appWidgetScale.y);
+            width = (int) (mView.getWidth() * scale);
+            height = (int) (mView.getHeight() * scale);
         }
 
+        Bitmap b = Bitmap.createBitmap(width + DRAG_BITMAP_PADDING, height + DRAG_BITMAP_PADDING,
+                Bitmap.Config.ARGB_8888);
         canvas.setBitmap(b);
+
+        canvas.save();
+        canvas.scale(scale, scale);
         drawDragView(canvas);
+        canvas.restore();
+
         canvas.setBitmap(null);
 
         return b;
@@ -132,12 +145,29 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragOutline(Canvas canvas) {
-        final Bitmap b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
-                mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
+        float scale = 1f;
+        int width = mView.getWidth();
+        int height = mView.getHeight();
+
+        if (mView instanceof LauncherAppWidgetHostView) {
+            DeviceProfile profile = Launcher.getLauncher(mView.getContext()).getDeviceProfile();
+            scale = Math.min(profile.appWidgetScale.x, profile.appWidgetScale.y);
+            width = (int) Math.floor(mView.getWidth() * scale);
+            height = (int) Math.floor(mView.getHeight() * scale);
+        }
+
+        Bitmap b = Bitmap.createBitmap(width + DRAG_BITMAP_PADDING, height + DRAG_BITMAP_PADDING,
+                Bitmap.Config.ALPHA_8);
         canvas.setBitmap(b);
+
+        canvas.save();
+        canvas.scale(scale, scale);
         drawDragView(canvas);
+        canvas.restore();
+
         HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
+
         canvas.setBitmap(null);
         return b;
     }
@@ -160,8 +190,17 @@
     public float getScaleAndPosition(Bitmap preview, int[] outPos) {
         float scale = Launcher.getLauncher(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
-        outPos[0] = Math.round(outPos[0] - (preview.getWidth() - scale * mView.getWidth()) / 2);
-        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 - previewPadding / 2);
+        DeviceProfile profile = Launcher.getLauncher(mView.getContext()).getDeviceProfile();
+        if (mView instanceof LauncherAppWidgetHostView) {
+            // App widgets are technically scaled, but are drawn at their expected size -- so the
+            // app widget scale should not affect the scale of the preview.
+            scale /= Math.min(profile.appWidgetScale.x, profile.appWidgetScale.y);
+        }
+
+        outPos[0] = Math.round(outPos[0] -
+                (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+                - previewPadding / 2);
         return scale;
     }
 }
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
new file mode 100644
index 0000000..2926a29
--- /dev/null
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.graphics;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Factory for creating new drawables.
+ */
+public class DrawableFactory {
+
+    private static DrawableFactory sInstance;
+    private static final Object LOCK = new Object();
+
+    public static DrawableFactory get(Context context) {
+        synchronized (LOCK) {
+            if (sInstance == null) {
+                context = context.getApplicationContext();
+                sInstance = loadByName(context.getString(R.string.drawable_factory_class), context);
+            }
+            return sInstance;
+        }
+    }
+
+    public static DrawableFactory loadByName(String className, Context context) {
+        if (!TextUtils.isEmpty(className)) {
+            try {
+                Class<?> cls = Class.forName(className);
+                return (DrawableFactory)
+                        cls.getDeclaredConstructor(Context.class).newInstance(context);
+            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                    | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+                return new DrawableFactory();
+            }
+        }
+        return new DrawableFactory();
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
+        FastBitmapDrawable d = new FastBitmapDrawable(icon);
+        d.setFilterBitmap(true);
+        return d;
+    }
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c2b97eb..395daa5 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -20,8 +20,16 @@
 public class LoggerUtils {
     private static final String TAG = "LoggerUtils";
 
-    public static String getActionStr(LauncherLogProto.Action action) {
-        switch(action.touch) {
+    private static String getCommandStr(Action action) {
+        switch (action.command) {
+            case Action.HOME_INTENT: return "HOME_INTENT";
+            case Action.BACK: return "BACK";
+            default: return "UNKNOWN";
+        }
+    }
+
+    private static String getTouchStr(Action action) {
+        switch (action.touch) {
             case Action.TAP: return "TAP";
             case Action.LONGPRESS: return "LONGPRESS";
             case Action.DRAGDROP: return "DRAGDROP";
@@ -32,6 +40,14 @@
         }
     }
 
+    public static String getActionStr(LauncherLogProto.Action action) {
+        switch (action.type) {
+            case Action.TOUCH: return getTouchStr(action);
+            case Action.COMMAND: return getCommandStr(action);
+            default: return "UNKNOWN";
+        }
+    }
+
     public static String getTargetStr(Target t) {
         String typeStr = "";
         if (t == null){
@@ -61,6 +77,7 @@
             case LauncherLogProto.DEEPSHORTCUT: typeStr = "DEEPSHORTCUT"; break;
             case LauncherLogProto.FOLDER_ICON: typeStr = "FOLDERICON"; break;
             case LauncherLogProto.SEARCHBOX: typeStr = "SEARCHBOX"; break;
+            case LauncherLogProto.EDITTEXT: typeStr = "EDITTEXT"; break;
 
             default: typeStr = "UNKNOWN";
         }
@@ -173,23 +190,41 @@
     }
 
     /**
+     * Used for commands.
+     */
+    public static LauncherLogProto.LauncherEvent initLauncherEvent(int command,
+            boolean createSrcTarget) {
+        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
+        event.action = new LauncherLogProto.Action();
+        event.action.type = Action.COMMAND;
+        event.action.command = command;
+        event.srcTarget = null;
+
+        if (createSrcTarget) {
+            event.srcTarget = new LauncherLogProto.Target[1];
+            event.srcTarget[0] = new LauncherLogProto.Target();
+            event.srcTarget[0].type = Target.CONTAINER;
+        }
+        return event;
+    }
+
+    /**
      * Used for drag and drop interaction.
      */
     public static LauncherLogProto.LauncherEvent initLauncherEvent(
             int actionType,
-            View v,
             ItemInfo info,
             int parentSrcTargetType,
             View parentDestTargetType){
         LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
 
         event.srcTarget = new LauncherLogProto.Target[2];
-        event.srcTarget[0] = initTarget(v, info);
+        event.srcTarget[0] = initTarget(info);
         event.srcTarget[1] = new LauncherLogProto.Target();
         event.srcTarget[1].type = parentSrcTargetType;
 
         event.destTarget = new LauncherLogProto.Target[2];
-        event.destTarget[0] = initTarget(v, info);
+        event.destTarget[0] = initTarget(info);
         event.destTarget[1] = initDropTarget(parentDestTargetType);
 
         event.action = new LauncherLogProto.Action();
@@ -197,7 +232,7 @@
         return event;
     }
 
-    private static Target initTarget(View v, ItemInfo info) {
+    private static Target initTarget(ItemInfo info) {
         Target t = new LauncherLogProto.Target();
         t.type = Target.ITEM;
         switch (info.itemType) {
@@ -243,6 +278,6 @@
         if (!(v.getTag() instanceof ItemInfo)) {
             return t;
         }
-        return initTarget(v, (ItemInfo) v.getTag());
+        return initTarget((ItemInfo) v.getTag());
     }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 441d8e5..2fcdd39 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -116,29 +117,35 @@
                 Action.TOUCH, v, Target.CONTAINER);
         event.action.touch = Action.TAP;
 
-        // Fill in grid(x,y), pageIndex of the child and container type of the parent
-        // TODO: make this percolate up the view hierarchy if needed.
+        // TODO: make idx percolate up the view hierarchy if needed.
         int idx = 0;
-        LogContainerProvider provider = getLaunchProviderRecursive(v);
-        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
-            return null;
-        }
-        ItemInfo itemInfo = (ItemInfo) v.getTag();
-        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
-
-        event.srcTarget[idx].intentHash = intent.hashCode();
-        ComponentName cn = intent.getComponent();
-        if (cn != null) {
-            event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
-            event.srcTarget[idx].componentHash = cn.hashCode();
-            if (mPredictedApps != null) {
-                event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
-                        new ComponentKey(cn, itemInfo.user));
+        if (fillInLogContainerData(event, v)) {
+            ItemInfo itemInfo = (ItemInfo) v.getTag();
+            event.srcTarget[idx].intentHash = intent.hashCode();
+            ComponentName cn = intent.getComponent();
+            if (cn != null) {
+                event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
+                event.srcTarget[idx].componentHash = cn.hashCode();
+                if (mPredictedApps != null) {
+                    event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
+                            new ComponentKey(cn, itemInfo.user));
+                }
             }
         }
         return event;
     }
 
+    public boolean fillInLogContainerData(LauncherEvent event, View v) {
+        // Fill in grid(x,y), pageIndex of the child and container type of the parent
+        LogContainerProvider provider = getLaunchProviderRecursive(v);
+        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+            return false;
+        }
+        ItemInfo itemInfo = (ItemInfo) v.getTag();
+        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+        return true;
+    }
+
     public void logAppLaunch(View v, Intent intent) {
         LauncherEvent ev = createLauncherEvent(v, intent);
         if (ev == null) {
@@ -147,10 +154,30 @@
         dispatchUserEvent(ev, intent);
     }
 
-    public void logActionOnItem(int action, int itemType) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.ITEM);
-        event.action.touch = action;
-        event.srcTarget[0].itemType = itemType;
+    public void logActionCommand(int command, int containerType) {
+        logActionCommand(command, containerType, 0);
+    }
+
+    public void logActionCommand(int command, int containerType, int pageIndex) {
+        LauncherEvent event = LoggerUtils.initLauncherEvent(command, true);
+        event.srcTarget[0].containerType = containerType;
+        event.srcTarget[0].pageIndex = pageIndex;
+        dispatchUserEvent(event, null);
+    }
+
+    /**
+     * TODO: Make this function work when a container view is passed as the 2nd param.
+     */
+    public void logActionCommand(int command, View itemView, int containerType) {
+        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.COMMAND, itemView,
+                Target.CONTAINER);
+        event.action.command = command;
+        if (fillInLogContainerData(event, itemView)) {
+            // TODO: Remove the following two lines once fillInLogContainerData can take in a
+            // container view.
+            event.srcTarget[0].type = Target.CONTAINER;
+            event.srcTarget[0].containerType = containerType;
+        }
         dispatchUserEvent(event, null);
     }
 
@@ -193,9 +220,27 @@
         mPredictedApps = predictedApps;
     }
 
+    /* Currently we are only interested in whether this event happens or not and don't
+    * care about which screen moves to where. */
+    public void logOverviewReorder() {
+        LauncherEvent event = new LauncherLogProto.LauncherEvent();
+
+        event.srcTarget = new LauncherLogProto.Target[2];
+        event.srcTarget[0] = new LauncherLogProto.Target();
+        event.srcTarget[0].type = Target.CONTAINER;
+        event.srcTarget[0].containerType = LauncherLogProto.WORKSPACE;
+        event.srcTarget[1] = new LauncherLogProto.Target();
+        event.srcTarget[1].type = Target.CONTAINER;
+        event.srcTarget[1].containerType = LauncherLogProto.OVERVIEW;
+
+        event.action = new LauncherLogProto.Action();
+        event.action.type = Action.TOUCH;
+        event.action.touch = Action.DRAGDROP;
+        dispatchUserEvent(event, null);
+
+    }
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH,
-                dragObj.dragView,
                 dragObj.originalDragInfo,
                 Target.CONTAINER,
                 dropTargetAsView);
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 986e163..4cbb087 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherModel.Callbacks;
@@ -33,39 +34,42 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Task to add auto-created workspace items.
  */
 public class AddWorkspaceItemsTask extends ExtendedModelTask {
 
-    private final ArrayList<? extends ItemInfo> mWorkspaceApps;
+    private final Provider<List<ItemInfo>> mAppsProvider;
 
     /**
-     * @param workspaceApps items to add on the workspace
+     * @param appsProvider items to add on the workspace
      */
-    public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
-        mWorkspaceApps = workspaceApps;
+    public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
+        mAppsProvider = appsProvider;
     }
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        if (mWorkspaceApps.isEmpty()) {
+        List<ItemInfo> workspaceApps = mAppsProvider.get();
+        if (workspaceApps.isEmpty()) {
             return;
         }
         Context context = app.getContext();
 
-        final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
-        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+        final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
+        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
 
         // Get the list of workspace screens.  We need to append to this list and
         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
         // called.
         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
         synchronized(dataModel) {
-            for (ItemInfo item : mWorkspaceApps) {
+            for (ItemInfo item : workspaceApps) {
                 if (item instanceof ShortcutInfo) {
                     // Short-circuit this logic if the icon exists somewhere on the workspace
                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
@@ -74,13 +78,14 @@
                 }
 
                 // Find appropriate space for the item.
-                Pair<Long, int[]> coords = findSpaceForItem(
-                        app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
+                Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
+                        addedWorkspaceScreensFinal, item.spanX, item.spanY);
                 long screenId = coords.first;
                 int[] cordinates = coords.second;
 
                 ItemInfo itemInfo;
-                if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
+                        item instanceof LauncherAppWidgetInfo) {
                     itemInfo = item;
                 } else if (item instanceof AppInfo) {
                     itemInfo = ((AppInfo) item).makeShortcut();
@@ -92,23 +97,23 @@
                 addItemToDatabase(context, itemInfo, screenId, cordinates);
 
                 // Save the ShortcutInfo for binding in the workspace
-                addedShortcutsFinal.add(itemInfo);
+                addedItemsFinal.add(itemInfo);
             }
         }
 
         // Update the workspace screens
         updateScreens(context, workspaceScreens);
 
-        if (!addedShortcutsFinal.isEmpty()) {
+        if (!addedItemsFinal.isEmpty()) {
             scheduleCallbackTask(new CallbackTask() {
                 @Override
                 public void execute(Callbacks callbacks) {
                     final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
-                    if (!addedShortcutsFinal.isEmpty()) {
-                        ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                    if (!addedItemsFinal.isEmpty()) {
+                        ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
                         long lastScreenId = info.screenId;
-                        for (ItemInfo i : addedShortcutsFinal) {
+                        for (ItemInfo i : addedItemsFinal) {
                             if (i.screenId == lastScreenId) {
                                 addAnimated.add(i);
                             } else {
@@ -258,5 +263,4 @@
         }
         return occupied.findVacantCell(xy, spanX, spanY);
     }
-
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index c18eeef..3d54637 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.model;
 
+import android.content.Context;
 import android.util.Log;
 import android.util.MutableInt;
 
 import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
@@ -26,6 +28,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
@@ -122,9 +125,11 @@
                     // Decrement pinned shortcut count
                     ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
                     MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
-                    if (count == null || --count.value == 0) {
-                        LauncherAppState.getInstance()
-                                .getShortcutManager().unpinShortcut(pinnedShortcut);
+                    Context context = LauncherAppState.getInstance().getContext();
+                    if ((count == null || --count.value == 0)
+                            && !InstallShortcutReceiver.getPendingShortcuts(context)
+                                .contains(pinnedShortcut)) {
+                        DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut);
                     }
                     // Fall through.
                 }
@@ -161,7 +166,7 @@
 
                 // Since this is a new item, pin the shortcut in the system server.
                 if (newItem && count.value == 1) {
-                    LauncherAppState.getInstance().getShortcutManager()
+                    DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
                             .pinShortcut(pinnedShortcut);
                 }
                 // Fall through
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index e3a2b24..176e8ea 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -214,7 +214,6 @@
                                     si.iconResource.resourceName, context);
                             if (icon != null) {
                                 si.setIcon(icon);
-                                si.usingFallbackIcon = false;
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 8f7c21d..3314353 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -51,7 +51,8 @@
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        DeepShortcutManager deepShortcutManager = app.getShortcutManager();
+        final Context context = app.getContext();
+        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
         deepShortcutManager.onShortcutsChanged(mShortcuts);
 
         // Find ShortcutInfo's that have changed on the workspace.
@@ -67,7 +68,6 @@
             }
         }
 
-        final Context context = LauncherAppState.getInstance().getContext();
         final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
         if (!idsToWorkspaceShortcutInfos.isEmpty()) {
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index b7b52a4..a89fe0b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -50,7 +50,7 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         Context context = app.getContext();
         boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
-        DeepShortcutManager deepShortcutManager = app.getShortcutManager();
+        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
 
         HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
         if (isUserUnlocked) {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 12a6701..4f5edc9 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -72,18 +72,6 @@
         }
     };
 
-    /**
-     * Listener for keep running the animation until the final state is reached.
-     */
-    private final AnimatorListenerAdapter mAnimCycleListener = new AnimatorListenerAdapter() {
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mAnimator = null;
-            animateToPosition(mFinalPosition);
-        }
-    };
-
     private final Paint mCirclePaint;
     private final float mDotRadius;
     private final int mActiveColor;
@@ -163,7 +151,7 @@
             float positionForThisAnim = mCurrentPosition > mFinalPosition ?
                     mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
             mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
-            mAnimator.addListener(mAnimCycleListener);
+            mAnimator.addListener(new AnimationCycleListener());
             mAnimator.setDuration(ANIMATION_DURATION);
             mAnimator.start();
         }
@@ -171,7 +159,6 @@
 
     public void stopAllAnimations() {
         if (mAnimator != null) {
-            mAnimator.removeAllListeners();
             mAnimator.cancel();
             mAnimator = null;
         }
@@ -326,4 +313,25 @@
             }
         }
     }
+
+    /**
+     * Listener for keep running the animation until the final state is reached.
+     */
+    private class AnimationCycleListener extends AnimatorListenerAdapter {
+
+        private boolean mCancelled = false;
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCancelled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCancelled) {
+                mAnimator = null;
+                animateToPosition(mFinalPosition);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index c2c7c17..41f1a47 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -45,10 +45,22 @@
     private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
             | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
 
+    private static DeepShortcutManager sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    public static DeepShortcutManager getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new DeepShortcutManager(context.getApplicationContext());
+            }
+            return sInstance;
+        }
+    }
+
     private final LauncherApps mLauncherApps;
     private boolean mWasLastCallSuccess;
 
-    public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+    private DeepShortcutManager(Context context) {
         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 08ca242..314a862 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherViewPropertyAnimator;
@@ -103,7 +102,7 @@
     public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
+        mDeepShortcutsManager = DeepShortcutManager.getInstance(context);
 
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..33d979c
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.util;
+
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * A wrapper around {@link ContentValues} with some utility methods.
+ */
+public class ContentWriter {
+
+    private final ContentValues mValues;
+    private final Context mContext;
+
+    private Bitmap mIcon;
+    private UserHandleCompat mUser;
+
+    public ContentWriter(Context context) {
+        this(new ContentValues(), context);
+    }
+
+    public ContentWriter(ContentValues values, Context context) {
+        mValues = values;
+        mContext = context;
+    }
+
+    public ContentWriter put(String key, Integer value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, Long value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, String value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, CharSequence value) {
+        mValues.put(key, value == null ? null : value.toString());
+        return this;
+    }
+
+    public ContentWriter put(String key, Intent value) {
+        mValues.put(key, value == null ? null : value.toUri(0));
+        return this;
+    }
+
+    public ContentWriter putIcon(Bitmap value, UserHandleCompat user) {
+        mIcon = value;
+        mUser = user;
+        return this;
+    }
+
+    public ContentWriter put(String key, UserHandleCompat user) {
+        return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+    }
+
+    /**
+     * Commits any pending validation and returns the final values.
+     * Must not be called on UI thread.
+     */
+    public ContentValues getValues() {
+        Preconditions.assertNonUiThread();
+        if (mIcon != null && !LauncherAppState.getInstance().getIconCache()
+                .isDefaultIcon(mIcon, mUser)) {
+            mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+            mIcon = null;
+        }
+        return mValues;
+    }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 78b7a3e..817a38a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -121,7 +121,7 @@
             // getting filled with the managed user apps, when it start with a fresh DB (or after
             // a very long time).
             if (userAppsExisted && !homescreenApps.isEmpty()) {
-                mModel.addAndBindAddedWorkspaceItems(homescreenApps);
+                mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
             }
         }
 
@@ -173,7 +173,7 @@
                 }
 
                 // Add the item to home screen and DB. This also generates an item id synchronously.
-                ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+                ArrayList<ItemInfo> itemList = new ArrayList<>(1);
                 itemList.add(workFolder);
                 mModel.addAndBindAddedWorkspaceItems(itemList);
                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..8eea28b 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -73,7 +73,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         ContentValues itemValues = new ContentValues();
-        writeToValues(itemValues);
+        writeToValues(new ContentWriter(itemValues, null));
         itemValues.writeToParcel(dest, flags);
 
         dest.writeInt(mArg1);
diff --git a/src/com/android/launcher3/util/Provider.java b/src/com/android/launcher3/util/Provider.java
new file mode 100644
index 0000000..1cdd8d6
--- /dev/null
+++ b/src/com/android/launcher3/util/Provider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Utility class to allow lazy initialization of objects.
+ */
+public abstract class Provider<T> {
+
+    /**
+     * Initializes and returns the object. This may contain expensive operations not suitable
+     * to UI thread.
+     */
+    public abstract T get();
+
+    public static <T> Provider<T> of (final T value) {
+        return new Provider<T>() {
+            @Override
+            public T get() {
+                return value;
+            }
+        };
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 5103ced..466146e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,9 +17,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
+#LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+#LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 21
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java
index c133bf6..6be2522 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/BindWidgetTest.java
@@ -17,6 +17,7 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -218,14 +219,14 @@
         mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
 
         // Insert the item
-        v = new ContentValues();
+        ContentWriter writer = new ContentWriter(mTargetContext);
         item.id = LauncherSettings.Settings.call(
                 mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
         item.screenId = screenId;
-        item.onAddToDatabase(mTargetContext, v);
-        v.put(LauncherSettings.Favorites._ID, item.id);
-        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
+        item.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
+        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
 
         // Reset loader
         try {
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index ecb3782..b2f0cbb 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Provider;
 
 import org.mockito.ArgumentCaptor;
 
@@ -48,8 +49,8 @@
         idp.numRows = 5;
     }
 
-    private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
-        return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
+    private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+        return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
 
             @Override
             protected void addItemToDatabase(Context context, ItemInfo item,