Merge "Simplifyling widget inflation and addition flow." into ub-launcher3-master
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/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..5c96dde 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -129,14 +129,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/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d2f25a4..46df0b2 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -32,6 +32,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 +43,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 +66,7 @@
     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 USER_HANDLE_KEY = "userHandle";
 
     // The set of shortcuts that are pending install
@@ -98,19 +102,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 +188,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;
@@ -192,6 +203,31 @@
         return info == null ? null : info.getShortcutInfo();
     }
 
+    public static void queueShortcut(ShortcutInfoCompat info, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, 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) {
         // Queue the item up for adding if launcher has not loaded properly yet
         LauncherAppState app = LauncherAppState.getInstance();
@@ -239,6 +275,7 @@
     private static class PendingInstallShortcutInfo {
 
         final LauncherActivityInfoCompat activityInfo;
+        final ShortcutInfoCompat shortcutInfo;
 
         final Intent data;
         final Context mContext;
@@ -249,14 +286,15 @@
         /**
          * Initializes a PendingInstallShortcutInfo received from a different app.
          */
-        public PendingInstallShortcutInfo(Intent data, Context context) {
+        public PendingInstallShortcutInfo(Intent data, UserHandleCompat user, Context context) {
             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;
+            shortcutInfo = null;
         }
 
         /**
@@ -266,15 +304,30 @@
             this.data = null;
             mContext = context;
             activityInfo = info;
+            shortcutInfo = null;
             user = info.getUser();
 
             launchIntent = AppInfo.makeLaunchIntent(context, info, user);
             label = info.getLabel().toString();
         }
 
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
+            this.data = null;
+            shortcutInfo = info;
+            mContext = context;
+            activityInfo = null;
+            user = info.getUserHandle();
+
+            launchIntent = info.makeIntent(context);
+            label = info.getShortLabel().toString();
+        }
+
         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 +337,35 @@
                         .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();
                 }
-            }
 
-            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 +384,56 @@
                 return json.endObject().toString();
             } catch (JSONException e) {
                 Log.d(TAG, "Exception when adding shortcut: " + e);
+                return null;
             }
-            return null;
         }
 
         public ShortcutInfo getShortcutInfo() {
             if (activityInfo != null) {
                 return new ShortcutInfo(activityInfo, mContext);
+            } else if (shortcutInfo != null) {
+                return new ShortcutInfo(shortcutInfo, mContext);
             } 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) {
-                    return null;
-                }
-
+            Decoder decoder = new Decoder(encoded, context);
+            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
                 LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
-                        .resolveActivity(launcherIntent, user);
+                        .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);
+                }
             }
 
             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 +446,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 +514,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: "
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8cc0fca..2f7a31f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -4018,24 +4018,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 eb1a763..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;
@@ -136,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();
@@ -152,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/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/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/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.
                 }