Merge "Move icon back to original cell if a quick drag would cause reorder." into ub-launcher3-master
diff --git a/res/layout/folder_page.xml b/res/layout/folder_page.xml
new file mode 100644
index 0000000..084e8fd
--- /dev/null
+++ b/res/layout/folder_page.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.launcher3.CellLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:hapticFeedbackEnabled="false"
+    android:importantForAccessibility="no"
+    launcher:containerType="folder" />
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 7bef889..f5b5bbf 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -20,5 +20,6 @@
         android:id="@+id/layout"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center" />
+        android:layout_gravity="center"
+        launcher:containerType="hotseat" />
 </com.android.launcher3.Hotseat>
diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
index faf6885..94bdedb 100644
--- a/res/layout/workspace_screen.xml
+++ b/res/layout/workspace_screen.xml
@@ -19,4 +19,5 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:hapticFeedbackEnabled="false" />
+    android:hapticFeedbackEnabled="false"
+    launcher:containerType="workspace" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 32bccb8..1e34c0f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -112,4 +112,12 @@
         <attr name="defaultLayoutId" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="CellLayout">
+        <attr name="containerType" format="integer">
+            <enum name="workspace" value="0" />
+            <enum name="hotseat" value="1" />
+            <enum name="folder" value="2" />
+        </attr>
+    </declare-styleable>
+
 </resources>
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/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 9eaef90..c0087c4 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -24,6 +24,7 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -35,6 +36,7 @@
 import android.graphics.drawable.TransitionDrawable;
 import android.os.Build;
 import android.os.Parcelable;
+import android.support.annotation.IntDef;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -60,6 +62,8 @@
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Thunk;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -145,8 +149,16 @@
     private TimeInterpolator mEaseOutInterpolator;
     private ShortcutAndWidgetContainer mShortcutsAndWidgets;
 
-    private boolean mIsHotseat = false;
-    private float mHotseatScale = 1f;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WORKSPACE, HOTSEAT, FOLDER})
+    public @interface ContainerType{}
+    public static final int WORKSPACE = 0;
+    public static final int HOTSEAT = 1;
+    public static final int FOLDER = 2;
+
+    @ContainerType private final int mContainerType;
+
+    private final float mChildScale;
 
     public static final int MODE_SHOW_REORDER_HINT = 0;
     public static final int MODE_DRAG_OVER = 1;
@@ -158,7 +170,7 @@
 
     private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
     private static final int REORDER_ANIMATION_DURATION = 150;
-    @Thunk float mReorderPreviewAnimationMagnitude;
+    @Thunk final float mReorderPreviewAnimationMagnitude;
 
     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
     private Rect mOccupiedRect = new Rect();
@@ -184,6 +196,9 @@
 
     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+        mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
+        a.recycle();
 
         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
         // the user where a dragged item will land when dropped.
@@ -207,9 +222,10 @@
         mFolderLeaveBehind.delegateCellX = -1;
         mFolderLeaveBehind.delegateCellY = -1;
 
+        mChildScale = mContainerType == HOTSEAT ? grid.inv.hotseatScale : 1f;
+
         setAlwaysDrawnWithCacheEnabled(false);
         final Resources res = getResources();
-        mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
 
         mBackground = (TransitionDrawable) res.getDrawable(
                 FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel
@@ -217,8 +233,7 @@
         mBackground.setCallback(this);
         mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
-        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
-                grid.iconSizePx);
+        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
 
         // Initialize the data structures used for the drag visualization.
         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
@@ -276,7 +291,7 @@
             mDragOutlineAnims[i] = anim;
         }
 
-        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
+        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
 
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
@@ -355,10 +370,6 @@
         mShortcutsAndWidgets.buildLayer();
     }
 
-    public float getChildrenScale() {
-        return mIsHotseat ? mHotseatScale : 1.0f;
-    }
-
     public void setCellDimensions(int width, int height) {
         mFixedCellWidth = mCellWidth = width;
         mFixedCellHeight = mCellHeight = height;
@@ -603,15 +614,8 @@
         return mCountY;
     }
 
-    public void setIsHotseat(boolean isHotseat) {
-        mIsHotseat = isHotseat;
-        mShortcutsAndWidgets.setContainerType(isHotseat
-                ? ShortcutAndWidgetContainer.HOTSEAT
-                : ShortcutAndWidgetContainer.DEFAULT);
-    }
-
-    public boolean isHotseat() {
-        return mIsHotseat;
+    public boolean acceptsWidget() {
+        return mContainerType == WORKSPACE;
     }
 
     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
@@ -621,11 +625,11 @@
         // Hotseat icons - remove text
         if (child instanceof BubbleTextView) {
             BubbleTextView bubbleChild = (BubbleTextView) child;
-            bubbleChild.setTextVisibility(!mIsHotseat);
+            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
         }
 
-        child.setScaleX(getChildrenScale());
-        child.setScaleY(getChildrenScale());
+        child.setScaleX(mChildScale);
+        child.setScaleY(mChildScale);
 
         // Generate an id for each view, this assumes we have at most 256x256 cells
         // per workspace screen
@@ -1041,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 {
@@ -1061,24 +1065,26 @@
                 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
             }
 
-            Utilities.scaleRectAboutCenter(r, getChildrenScale());
+            Utilities.scaleRectAboutCenter(r, mChildScale);
             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
 
             if (dragObject.stateAnnouncer != null) {
-                String msg;
-                if (isHotseat()) {
-                    msg = getContext().getString(R.string.move_to_hotseat_position,
-                            Math.max(cellX, cellY) + 1);
-                } else {
-                    msg = getContext().getString(R.string.move_to_empty_cell,
-                            cellY + 1, cellX + 1);
-                }
-                dragObject.stateAnnouncer.announce(msg);
+                dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
             }
         }
     }
 
+    public String getItemMoveDescription(int cellX, int cellY) {
+        if (mContainerType == HOTSEAT) {
+            return getContext().getString(R.string.move_to_hotseat_position,
+                    Math.max(cellX, cellY) + 1);
+        } else {
+            return getContext().getString(R.string.move_to_empty_cell,
+                    cellY + 1, cellX + 1);
+        }
+    }
+
     public void clearDragOutlines() {
         final int oldIndex = mDragOutlineCurrent;
         mDragOutlineAnims[oldIndex].animateOut();
@@ -2011,7 +2017,7 @@
             this.mode = mode;
             initDeltaX = child.getTranslationX();
             initDeltaY = child.getTranslationY();
-            finalScale = getChildrenScale() - 4.0f / child.getWidth();
+            finalScale = mChildScale - 4.0f / child.getWidth();
             initScale = child.getScaleX();
             this.child = child;
         }
@@ -2061,7 +2067,7 @@
                     // We make sure to end only after a full period
                     initDeltaX = 0;
                     initDeltaY = 0;
-                    initScale = getChildrenScale();
+                    initScale = mChildScale;
                     repeating = true;
                 }
             });
@@ -2081,8 +2087,8 @@
             }
 
             a = new LauncherViewPropertyAnimator(child)
-                .scaleX(getChildrenScale())
-                .scaleY(getChildrenScale())
+                .scaleX(mChildScale)
+                .scaleY(mChildScale)
                 .translationX(0)
                 .translationY(0)
                 .setDuration(REORDER_ANIMATION_DURATION);
@@ -2104,7 +2110,7 @@
         long screenId = mLauncher.getWorkspace().getIdForScreen(this);
         int container = Favorites.CONTAINER_DESKTOP;
 
-        if (mLauncher.isHotseatLayout(this)) {
+        if (mContainerType == HOTSEAT) {
             screenId = -1;
             container = Favorites.CONTAINER_HOTSEAT;
         }
@@ -2127,7 +2133,7 @@
                 info.spanY = lp.cellVSpan;
 
                 if (requiresDbUpdate) {
-                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
+                    LauncherModel.modifyItemInDatabase(getContext(), info, container, screenId,
                             info.cellX, info.cellY, info.spanX, info.spanY);
                 }
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index eb1db1c..fbff4eb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -30,6 +30,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.util.ArrayList;
@@ -620,6 +621,19 @@
                 : Math.max(widthPx, heightPx);
     }
 
+    public int getCellHeight(@ContainerType int containerType) {
+        switch (containerType) {
+            case CellLayout.WORKSPACE:
+                return cellHeightPx;
+            case CellLayout.FOLDER:
+                return folderCellHeightPx;
+            case CellLayout.HOTSEAT:
+                return hotseatCellHeightPx;
+            default:
+                // ??
+                return 0;
+        }
+    }
 
     /**
      * @return the left/right paddings for all containers.
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 270d539..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;
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b93c6df..3648fb7 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -113,12 +113,11 @@
         super.onFinishInflate();
         DeviceProfile grid = mLauncher.getDeviceProfile();
         mContent = (CellLayout) findViewById(R.id.layout);
-        if (grid.isLandscape && !grid.isLargeTablet) {
-            mContent.setGridSize(1, (int) grid.inv.numHotseatIcons);
+        if (grid.isVerticalBarLayout()) {
+            mContent.setGridSize(1, grid.inv.numHotseatIcons);
         } else {
-            mContent.setGridSize((int) grid.inv.numHotseatIcons, 1);
+            mContent.setGridSize(grid.inv.numHotseatIcons, 1);
         }
-        mContent.setIsHotseat(true);
 
         resetLayout();
     }
@@ -129,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,
@@ -167,7 +167,7 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // We don't want any clicks to go through to the hotseat unless the workspace is in
         // the normal state or an accessible drag is in progress.
-        return mLauncher.getWorkspace().workspaceInModalState() &&
+        return !mLauncher.getWorkspace().workspaceIconsCanBeDragged() &&
                 !mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
     }
 
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d2f25a4..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,6 +34,9 @@
 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;
@@ -40,10 +45,10 @@
 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;
@@ -63,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
@@ -98,19 +105,25 @@
                 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();
         }
     }
 
@@ -178,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;
@@ -189,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) {
@@ -239,6 +282,8 @@
     private static class PendingInstallShortcutInfo {
 
         final LauncherActivityInfoCompat activityInfo;
+        final ShortcutInfoCompat shortcutInfo;
+        final AppWidgetProviderInfo providerInfo;
 
         final Intent data;
         final Context mContext;
@@ -249,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()
@@ -284,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))
@@ -326,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);
@@ -389,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.
@@ -441,7 +580,7 @@
             LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
             for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
                 // If the intent specifies a package, make sure the package exists
-                String packageName = pendingInfo.getTargetPackage();
+                String packageName = getIntentPackage(pendingInfo.launchIntent);
                 if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
                         packageName, pendingInfo.user)) {
                     if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
@@ -450,7 +589,7 @@
                 }
 
                 // Generate a shortcut info to add into the model
-                installQueue.add(pendingInfo.getShortcutInfo());
+                installQueue.add(pendingInfo.getItemInfo());
             }
             return installQueue;
         }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 38545e2..1b0e5b3 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -85,6 +85,7 @@
      */
     public int numHotseatIcons;
     float hotseatIconSize;
+    public float hotseatScale;
     int defaultLayoutId;
 
     DeviceProfile landscapeProfile;
@@ -117,6 +118,8 @@
         numHotseatIcons = hs;
         hotseatIconSize = his;
         defaultLayoutId = dlId;
+
+        hotseatScale = hotseatIconSize / iconSize;
     }
 
     @TargetApi(23)
@@ -158,6 +161,8 @@
         // Supported overrides: numRows, numColumns, iconSize
         applyPartnerDeviceProfileOverrides(context, dm);
 
+        hotseatScale = hotseatIconSize / iconSize;
+
         Point realSize = new Point();
         display.getRealSize(realSize);
         // The real size never changes. smallSide and largeSide will remain the
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9160a01..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() {
@@ -2105,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);
@@ -2816,6 +2808,7 @@
     }
 
     boolean isHotseatLayout(View layout) {
+        // TODO: Remove this method
         return mHotseat != null && layout != null &&
                 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
     }
@@ -3303,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 &&
@@ -3333,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");
             }
@@ -3365,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() {
@@ -3408,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.
      *
@@ -3433,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;
         }
 
@@ -3521,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 "
@@ -3536,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) {
@@ -4035,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/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c7bb188..cc56d43 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -1776,9 +1776,12 @@
                 }
 
                 // 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.
                         shortcutManager.unpinShortcut(key);
                     }
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/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 342479f..6c73762 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -20,29 +20,19 @@
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.support.annotation.IntDef;
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import com.android.launcher3.CellLayout.ContainerType;
 
 public class ShortcutAndWidgetContainer extends ViewGroup {
     static final String TAG = "ShortcutAndWidgetContainer";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({DEFAULT, HOTSEAT, FOLDER})
-    public @interface ContainerType{}
-    public static final int DEFAULT = 0;
-    public static final int HOTSEAT = 1;
-    public static final int FOLDER = 2;
-
-    private int mContainerType = DEFAULT;
-
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     private final int[] mTmpCellXY = new int[2];
 
+    @ContainerType private final int mContainerType;
     private final WallpaperManager mWallpaperManager;
 
     private int mCellWidth;
@@ -51,13 +41,13 @@
     private int mCountX;
 
     private Launcher mLauncher;
-
     private boolean mInvertIfRtl = false;
 
-    public ShortcutAndWidgetContainer(Context context) {
+    public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
         super(context);
         mLauncher = Launcher.getLauncher(context);
         mWallpaperManager = WallpaperManager.getInstance(context);
+        mContainerType = containerType;
     }
 
     public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
@@ -105,19 +95,9 @@
         mInvertIfRtl = invert;
     }
 
-    public void setContainerType(@ContainerType int containerType) {
-        mContainerType = containerType;
-    }
-
     int getCellContentHeight() {
-        final DeviceProfile grid = mLauncher.getDeviceProfile();
-        int cellContentHeight = grid.cellHeightPx;
-        if (mContainerType == HOTSEAT) {
-            cellContentHeight = grid.hotseatCellHeightPx;
-        } else if (mContainerType == FOLDER) {
-            cellContentHeight = grid.folderCellHeightPx;
-        }
-        return Math.min(getMeasuredHeight(), cellContentHeight);
+        return Math.min(getMeasuredHeight(),
+                mLauncher.getDeviceProfile().getCellHeight(mContainerType));
     }
 
     public void measureChild(View child) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 85cb736..13bea20 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -100,6 +100,14 @@
         Insettable, DropTargetSource {
     private static final String TAG = "Launcher.Workspace";
 
+    /** The value that {@link #mTransitionProgress} must be greater than for
+     * {@link #transitionStateShouldAllowDrop()} to return true. */
+    private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
+
+    /** The value that {@link #mTransitionProgress} must be greater than for
+     * {@link #isFinishedSwitchingState()} ()} to return true. */
+    private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
+
     private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
 
     private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
@@ -1043,23 +1051,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);
     }
 
     /**
@@ -1072,13 +1085,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");
@@ -1101,13 +1110,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) {
@@ -1138,7 +1140,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?
@@ -1166,7 +1168,7 @@
     }
 
     private boolean shouldConsumeTouch(View v) {
-        return (workspaceInModalState() || !isFinishedSwitchingState())
+        return !workspaceIconsCanBeDragged()
                 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
     }
 
@@ -1177,7 +1179,8 @@
     /** This differs from isSwitchingState in that we take into account how far the transition
      *  has completed. */
     public boolean isFinishedSwitchingState() {
-        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+        return !mIsSwitchingState
+                || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
     }
 
     protected void onWindowVisibilityChanged (int visibility) {
@@ -1832,6 +1835,11 @@
         return mState != State.NORMAL;
     }
 
+    /** Returns whether a drag should be allowed to be started from the current workspace state. */
+    public boolean workspaceIconsCanBeDragged() {
+        return mState == State.NORMAL || mState == State.SPRING_LOADED;
+    }
+
     @Thunk void updateChildrenLayersEnabled(boolean force) {
         boolean small = mState == State.OVERVIEW || mIsSwitchingState;
         boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition();
@@ -2279,8 +2287,8 @@
         return dv;
     }
 
-    public boolean transitionStateShouldAllowDrop() {
-        return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
+    private boolean transitionStateShouldAllowDrop() {
+        return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
                 (mState == State.NORMAL || mState == State.SPRING_LOADED));
     }
 
@@ -2519,7 +2527,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;
@@ -3276,7 +3284,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() {
@@ -3426,8 +3434,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);
 
@@ -3478,14 +3486,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/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index c71307d..9a23aa8 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -57,7 +57,7 @@
         int y = id / mCountX;
         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
 
-        if (dragInfo.dragType == DragType.WIDGET && mView.isHotseat()) {
+        if (dragInfo.dragType == DragType.WIDGET && !mView.acceptsWidget()) {
             return INVALID_POSITION;
         }
 
@@ -161,11 +161,7 @@
 
         View child = mView.getChildAt(x, y);
         if (child == null || child == dragInfo.item) {
-            if (mView.isHotseat()) {
-                return mContext.getString(R.string.move_to_hotseat_position, id + 1);
-            } else {
-                return mContext.getString(R.string.move_to_empty_cell, y + 1, x + 1);
-            }
+            return mView.getItemMoveDescription(x, y);
         } else {
             return getDescriptionForDropOver(child, mContext);
         }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index e813bb4..a81b4ca 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1225,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/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index e71c5e9..8aaeb9e 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -253,11 +253,9 @@
 
     private CellLayout createAndAddNewPage() {
         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        CellLayout page = new CellLayout(getContext());
+        CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
-        page.getShortcutsAndWidgets().setContainerType(ShortcutAndWidgetContainer.FOLDER);
-        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         page.setInvertIfRtl(true);
         page.setGridSize(mGridCountX, mGridCountY);
 
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/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 97335cb..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;
@@ -60,8 +61,8 @@
         }
         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
@@ -77,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();
@@ -95,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 {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 29defdd..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;
@@ -123,9 +125,11 @@
                     // Decrement pinned shortcut count
                     ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
                     MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
-                    if (count == null || --count.value == 0) {
-                        DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
-                                .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.
                 }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 293585d..07f59a5 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -29,11 +29,9 @@
 import android.widget.TextView;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.R;
+import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
@@ -182,14 +180,6 @@
         ensurePreview();
     }
 
-    public int getActualItemWidth() {
-        ItemInfo info = (ItemInfo) getTag();
-        int[] size = getPreviewSize();
-        int cellWidth = mLauncher.getDeviceProfile().cellWidthPx;
-
-        return Math.min(size[0], info.spanX * cellWidth);
-    }
-
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean handled = super.onTouchEvent(ev);
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