Dominant color is part of icon cache

> Calculating extracted color during icon generation and storing it in model and DB
> Removing unused logic avoid various types of badge rendering
> Icons are badged with extracted colors, while folder is badged with theme color

Bug: 35428783
Change-Id: I93e30c52fbded7515c3ae1778422e84672eafb56
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 42fee80..4abdfd6 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -32,8 +32,6 @@
 
     <!-- Popup container -->
     <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
-    <color name="badge_color">#1DE9B6</color> <!-- Teal A400 -->
-    <color name="folder_badge_color">#1DE9B6</color> <!-- Teal A400 -->
 
     <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
     <color name="legacy_icon_background">#FFFFFF</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 36eb34b..e10719b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -212,10 +212,6 @@
     <!-- (touch_size - icon_size) / 2 -->
     <dimen name="system_shortcut_header_icon_padding">12dp</dimen>
 
-<!-- Icon badges (with notification counts) -->
-    <dimen name="badge_small_padding">0dp</dimen>
-    <dimen name="badge_large_padding">3dp</dimen>
-
 <!-- Notifications -->
     <dimen name="bg_round_rect_radius">12dp</dimen>
     <dimen name="notification_padding_start">16dp</dimen>
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 162aa08..9775955 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -29,6 +29,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.os.Process;
 import android.text.TextUtils;
@@ -438,7 +439,7 @@
             // Auto installs should always support the current platform version.
             mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(
                     LauncherIcons.createBadgedIconBitmap(
-                            icon, Process.myUserHandle(), mContext, Build.VERSION.SDK_INT)));
+                            icon, Process.myUserHandle(), mContext, VERSION.SDK_INT).icon));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a590504..46d7227 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -88,7 +88,7 @@
 
     private BadgeInfo mBadgeInfo;
     private BadgeRenderer mBadgeRenderer;
-    private IconPalette mBadgePalette;
+    private int mBadgeColor;
     private float mBadgeScale;
     private boolean mForceHideBadge;
     private Point mTempSpaceForBadgeOffset = new Point();
@@ -183,7 +183,7 @@
      */
     public void reset() {
         mBadgeInfo = null;
-        mBadgePalette = null;
+        mBadgeColor = Color.TRANSPARENT;
         mBadgeScale = 0f;
         mForceHideBadge = false;
     }
@@ -193,7 +193,7 @@
     }
 
     public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) {
-        applyIconAndLabel(info.iconBitmap, info);
+        applyIconAndLabel(info);
         setTag(info);
         if (promiseStateChanged || (info.hasPromiseIconUi())) {
             applyPromiseState(promiseStateChanged);
@@ -203,7 +203,7 @@
     }
 
     public void applyFromApplicationInfo(AppInfo info) {
-        applyIconAndLabel(info.iconBitmap, info);
+        applyIconAndLabel(info);
 
         // We don't need to check the info since it's not a ShortcutInfo
         super.setTag(info);
@@ -219,7 +219,7 @@
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
-        applyIconAndLabel(info.iconBitmap, info);
+        applyIconAndLabel(info);
         // We don't need to check the info since it's not a ShortcutInfo
         super.setTag(info);
 
@@ -227,8 +227,10 @@
         verifyHighRes();
     }
 
-    private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
-        FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info);
+    private void applyIconAndLabel(ItemInfoWithIcon info) {
+        FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(info);
+        mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f);
+
         iconDrawable.setIsDisabled(info.isDisabled());
         setIcon(iconDrawable);
         setText(info.title);
@@ -401,7 +403,7 @@
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
-            mBadgeRenderer.draw(canvas, mBadgePalette, mBadgeInfo, mTempIconBounds, mBadgeScale,
+            mBadgeRenderer.draw(canvas, mBadgeColor, mTempIconBounds, mBadgeScale,
                     mTempSpaceForBadgeOffset);
             canvas.translate(-scrollX, -scrollY);
         }
@@ -532,7 +534,7 @@
                     preloadDrawable.setLevel(progressLevel);
                 } else {
                     preloadDrawable = DrawableFactory.get(getContext())
-                            .newPendingIcon(info.iconBitmap, getContext());
+                            .newPendingIcon(info, getContext());
                     preloadDrawable.setLevel(progressLevel);
                     setIcon(preloadDrawable);
                 }
@@ -550,10 +552,6 @@
             float newBadgeScale = isBadged ? 1f : 0;
             mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
             if (wasBadged || isBadged) {
-                mBadgePalette = IconPalette.getBadgePalette(getResources());
-                if (mBadgePalette == null) {
-                    mBadgePalette = ((FastBitmapDrawable) mIcon).getIconPalette();
-                }
                 // Animate when a badge is first added or when it is removed.
                 if (animate && (wasBadged ^ isBadged) && isShown()) {
                     ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
@@ -565,10 +563,6 @@
         }
     }
 
-    public IconPalette getBadgePalette() {
-        return mBadgePalette;
-    }
-
     /**
      * Sets the icon for this view based on the layout direction.
      */
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7b3da67..3bfc41c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -248,7 +248,7 @@
         computeAllAppsButtonSize(context);
 
         // This is done last, after iconSizePx is calculated above.
-        mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
+        mBadgeRenderer = new BadgeRenderer(iconSizePx);
     }
 
     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 1272e0a..bd19dfa 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -32,7 +32,7 @@
 import android.util.Property;
 import android.util.SparseArray;
 
-import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.BitmapInfo;
 
 public class FastBitmapDrawable extends Drawable {
 
@@ -40,19 +40,9 @@
     private static final float DISABLED_DESATURATION = 1f;
     private static final float DISABLED_BRIGHTNESS = 0.5f;
 
-    public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
+    public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = (input) ->
+            (input < 0.05f) ? (input / 0.05f) : ((input < 0.3f) ? 1 : (1 - input) / 0.7f);
 
-        @Override
-        public float getInterpolation(float input) {
-            if (input < 0.05f) {
-                return input / 0.05f;
-            } else if (input < 0.3f){
-                return 1;
-            } else {
-                return (1 - input) / 0.7f;
-            }
-        }
-    };
     public static final int CLICK_FEEDBACK_DURATION = 2000;
 
     // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
@@ -69,12 +59,11 @@
 
     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
     private final Bitmap mBitmap;
+    protected final int mIconColor;
 
     private boolean mIsPressed;
     private boolean mIsDisabled;
 
-    private IconPalette mIconPalette;
-
     private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
             = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
         @Override
@@ -99,7 +88,20 @@
     private ObjectAnimator mBrightnessAnimator;
 
     public FastBitmapDrawable(Bitmap b) {
+        this(b, Color.TRANSPARENT);
+    }
+
+    public FastBitmapDrawable(BitmapInfo info) {
+        this(info.icon, info.color);
+    }
+
+    public FastBitmapDrawable(ItemInfoWithIcon info) {
+        this(info.iconBitmap, info.iconColor);
+    }
+
+    protected FastBitmapDrawable(Bitmap b, int iconColor) {
         mBitmap = b;
+        mIconColor = iconColor;
         setFilterBitmap(true);
     }
 
@@ -108,14 +110,6 @@
         canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
     }
 
-    public IconPalette getIconPalette() {
-        if (mIconPalette == null) {
-            mIconPalette = IconPalette.fromDominantColor(Utilities
-                    .findDominantColorByHue(mBitmap, 20), true /* desaturateBackground */);
-        }
-        return mIconPalette;
-    }
-
     @Override
     public void setColorFilter(ColorFilter cf) {
         // No op
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index ab853e5..baa60b0 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -33,18 +33,21 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
+import android.os.Build.VERSION;
 import android.os.Handler;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.support.annotation.NonNull;
+import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.ColorExtractor;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.uioverrides.UiFactory;
@@ -81,14 +84,13 @@
 
     @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
 
-    public static class CacheEntry {
-        public Bitmap icon;
+    public static class CacheEntry extends BitmapInfo {
         public CharSequence title = "";
         public CharSequence contentDescription = "";
         public boolean isLowResIcon;
     }
 
-    private final HashMap<UserHandle, Bitmap> mDefaultIcons = new HashMap<>();
+    private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
     @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
 
     private final Context mContext;
@@ -190,9 +192,9 @@
         return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
     }
 
-    protected Bitmap makeDefaultIcon(UserHandle user) {
+    protected BitmapInfo makeDefaultIcon(UserHandle user) {
         Drawable unbadged = getFullResDefaultActivityIcon();
-        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, Build.VERSION_CODES.O);
+        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, VERSION.SDK_INT);
     }
 
     /**
@@ -376,16 +378,16 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            entry.icon = LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
-                    mContext,  app.getApplicationInfo().targetSdkVersion);
+            LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+                    mContext,  app.getApplicationInfo().targetSdkVersion).applyTo(entry);
         }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
         mCache.put(key, entry);
 
         Bitmap lowResIcon = generateLowResIcon(entry.icon);
-        ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
-                app.getApplicationInfo().packageName);
+        ContentValues values = newContentValues(entry.icon, lowResIcon, entry.color,
+                entry.title.toString(), app.getApplicationInfo().packageName);
         addIconToDB(values, app.getComponentName(), info, userSerial);
     }
 
@@ -459,7 +461,7 @@
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
         if (info.getTargetComponent() == null) {
-            info.iconBitmap = getDefaultIcon(info.user);
+            getDefaultIcon(info.user).applyTo(info);
             info.title = "";
             info.contentDescription = "";
             info.usingLowResIcon = false;
@@ -494,11 +496,11 @@
     private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
         info.title = Utilities.trim(entry.title);
         info.contentDescription = entry.contentDescription;
-        info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon;
         info.usingLowResIcon = entry.isLowResIcon;
+        ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
     }
 
-    public synchronized Bitmap getDefaultIcon(UserHandle user) {
+    public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
         if (!mDefaultIcons.containsKey(user)) {
             mDefaultIcons.put(user, makeDefaultIcon(user));
         }
@@ -506,7 +508,7 @@
     }
 
     public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
-        return mDefaultIcons.get(user) == icon;
+        return getDefaultIcon(user).icon == icon;
     }
 
     /**
@@ -533,9 +535,9 @@
                 providerFetchedOnce = true;
 
                 if (info != null) {
-                    entry.icon = LauncherIcons.createBadgedIconBitmap(
+                    LauncherIcons.createBadgedIconBitmap(
                             getFullResIcon(info), info.getUser(), mContext,
-                            infoProvider.get().getApplicationInfo().targetSdkVersion);
+                            info.getApplicationInfo().targetSdkVersion).applyTo(entry);
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -543,7 +545,7 @@
                         if (packageEntry != null) {
                             if (DEBUG) Log.d(TAG, "using package default icon for " +
                                     componentName.toShortString());
-                            entry.icon = packageEntry.icon;
+                            packageEntry.applyTo(entry);
                             entry.title = packageEntry.title;
                             entry.contentDescription = packageEntry.contentDescription;
                         }
@@ -551,7 +553,7 @@
                     if (entry.icon == null) {
                         if (DEBUG) Log.d(TAG, "using default icon for " +
                                 componentName.toShortString());
-                        entry.icon = getDefaultIcon(user);
+                        getDefaultIcon(user).applyTo(entry);
                     }
                 }
             }
@@ -594,7 +596,7 @@
             entry.title = title;
         }
         if (icon != null) {
-            entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
+            LauncherIcons.createIconBitmap(icon, mContext).applyTo(entry);
         }
         if (!TextUtils.isEmpty(title) && entry.icon != null) {
             mCache.put(cacheKey, entry);
@@ -633,22 +635,23 @@
 
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
-                    Bitmap icon = LauncherIcons.createBadgedIconBitmap(
+                    BitmapInfo iconInfo = LauncherIcons.createBadgedIconBitmap(
                             appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
                     if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        LauncherIcons.badgeWithDrawable(icon,
+                        LauncherIcons.badgeWithDrawable(iconInfo.icon,
                                 mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
                     }
-                    Bitmap lowResIcon =  generateLowResIcon(icon);
+                    Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
-                    entry.icon = useLowResIcon ? lowResIcon : icon;
+                    entry.icon = useLowResIcon ? lowResIcon : iconInfo.icon;
+                    entry.color = iconInfo.color;
                     entry.isLowResIcon = useLowResIcon;
 
                     // Add the icon in the DB here, since these do not get written during
                     // package updates.
-                    ContentValues values =
-                            newContentValues(icon, lowResIcon, entry.title.toString(), packageName);
+                    ContentValues values = newContentValues(iconInfo.icon, lowResIcon, entry.color,
+                            entry.title.toString(), packageName);
                     addIconToDB(values, cacheKey.componentName, info,
                             mUserManager.getSerialNumberForUser(user));
 
@@ -671,14 +674,16 @@
         try {
             c = mIconDb.query(
                 new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
-                        IconDB.COLUMN_LABEL},
+                        IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL},
                 IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
                 new String[]{cacheKey.componentName.flattenToString(),
                         Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
             if (c.moveToNext()) {
                 entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : mHighResOptions);
+                // Set the alpha to be 255, so that we never have a wrong color
+                entry.color = ColorUtils.setAlphaComponent(c.getInt(1), 255);
                 entry.isLowResIcon = lowRes;
-                entry.title = c.getString(1);
+                entry.title = c.getString(2);
                 if (entry.title == null) {
                     entry.title = "";
                     entry.contentDescription = "";
@@ -771,7 +776,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int DB_VERSION = 17;
+        private final static int DB_VERSION = 18;
 
         private final static int RELEASE_VERSION = DB_VERSION +
                 (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
@@ -784,6 +789,7 @@
         private final static String COLUMN_VERSION = "version";
         private final static String COLUMN_ICON = "icon";
         private final static String COLUMN_ICON_LOW_RES = "icon_low_res";
+        private final static String COLUMN_ICON_COLOR = "icon_color";
         private final static String COLUMN_LABEL = "label";
         private final static String COLUMN_SYSTEM_STATE = "system_state";
 
@@ -802,6 +808,7 @@
                     COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
                     COLUMN_ICON + " BLOB, " +
                     COLUMN_ICON_LOW_RES + " BLOB, " +
+                    COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
                     COLUMN_LABEL + " TEXT, " +
                     COLUMN_SYSTEM_STATE + " TEXT, " +
                     "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
@@ -809,11 +816,12 @@
         }
     }
 
-    private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label,
-            String packageName) {
+    private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, int iconColor,
+            String label, String packageName) {
         ContentValues values = new ContentValues();
         values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
         values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon));
+        values.put(IconDB.COLUMN_ICON_COLOR, iconColor);
 
         values.put(IconDB.COLUMN_LABEL, label);
         values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index df1eec6..c476421 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -40,6 +40,7 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.BitmapInfo;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -480,7 +481,7 @@
                 final LauncherAppState app = LauncherAppState.getInstance(mContext);
                 // Set default values until proper values is loaded.
                 appInfo.title = "";
-                appInfo.iconBitmap = app.getIconCache().getDefaultIcon(user);
+                app.getIconCache().getDefaultIcon(user).applyTo(appInfo);
                 final ShortcutInfo si = appInfo.makeShortcut();
                 if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
                     app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
@@ -497,7 +498,7 @@
                 return Pair.create((ItemInfo) si, (Object) activityInfo);
             } else if (shortcutInfo != null) {
                 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
-                si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext);
+                LauncherIcons.createShortcutIcon(shortcutInfo, mContext).applyTo(si);
                 return Pair.create((ItemInfo) si, (Object) shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -641,18 +642,20 @@
         // users wouldn't get here without intent forwarding anyway.
         info.user = Process.myUserHandle();
 
+        BitmapInfo iconInfo = null;
         if (bitmap instanceof Bitmap) {
-            info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+            iconInfo = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
             if (extra instanceof Intent.ShortcutIconResource) {
                 info.iconResource = (Intent.ShortcutIconResource) extra;
-                info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+                iconInfo = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
             }
         }
-        if (info.iconBitmap == null) {
-            info.iconBitmap = app.getIconCache().getDefaultIcon(info.user);
+        if (iconInfo == null) {
+            iconInfo = app.getIconCache().getDefaultIcon(info.user);
         }
+        iconInfo.applyTo(info);
 
         info.title = Utilities.trim(name);
         info.contentDescription = UserManagerCompat.getInstance(app.getContext())
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index fea4dda..bf985c3 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -29,6 +29,11 @@
     public Bitmap iconBitmap;
 
     /**
+     * Dominant color in the {@link #iconBitmap}.
+     */
+    public int iconColor;
+
+    /**
      * Indicates whether we're using a low res icon
      */
     public boolean usingLowResIcon;
@@ -96,6 +101,7 @@
     protected ItemInfoWithIcon(ItemInfoWithIcon info) {
         super(info);
         iconBitmap = info.iconBitmap;
+        iconColor = info.iconColor;
         usingLowResIcon = info.usingLowResIcon;
         runtimeStatusFlags = info.runtimeStatusFlags;
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 35bf9e9..ea4b280 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -620,7 +620,7 @@
             @Override
             public ShortcutInfo get() {
                 si.updateFromDeepShortcutInfo(info, mApp.getContext());
-                si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
+                LauncherIcons.createShortcutIcon(info, mApp.getContext()).applyTo(si);
                 return si;
             }
         });
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index ca235eb..f8c6ee2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -28,7 +28,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
@@ -44,7 +43,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.View;
 
@@ -285,89 +283,6 @@
         }
     }
 
-    /**
-     * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
-     * @param bitmap The bitmap to scan
-     * @param samples The approximate max number of samples to use.
-     */
-    public static int findDominantColorByHue(Bitmap bitmap, int samples) {
-        final int height = bitmap.getHeight();
-        final int width = bitmap.getWidth();
-        int sampleStride = (int) Math.sqrt((height * width) / samples);
-        if (sampleStride < 1) {
-            sampleStride = 1;
-        }
-
-        // This is an out-param, for getting the hsv values for an rgb
-        float[] hsv = new float[3];
-
-        // First get the best hue, by creating a histogram over 360 hue buckets,
-        // where each pixel contributes a score weighted by saturation, value, and alpha.
-        float[] hueScoreHistogram = new float[360];
-        float highScore = -1;
-        int bestHue = -1;
-
-        int[] pixels = new int[samples];
-        int pixelCount = 0;
-
-        for (int y = 0; y < height; y += sampleStride) {
-            for (int x = 0; x < width; x += sampleStride) {
-                int argb = bitmap.getPixel(x, y);
-                int alpha = 0xFF & (argb >> 24);
-                if (alpha < 0x80) {
-                    // Drop mostly-transparent pixels.
-                    continue;
-                }
-                // Remove the alpha channel.
-                int rgb = argb | 0xFF000000;
-                Color.colorToHSV(rgb, hsv);
-                // Bucket colors by the 360 integer hues.
-                int hue = (int) hsv[0];
-                if (hue < 0 || hue >= hueScoreHistogram.length) {
-                    // Defensively avoid array bounds violations.
-                    continue;
-                }
-                if (pixelCount < samples) {
-                    pixels[pixelCount++] = rgb;
-                }
-                float score = hsv[1] * hsv[2];
-                hueScoreHistogram[hue] += score;
-                if (hueScoreHistogram[hue] > highScore) {
-                    highScore = hueScoreHistogram[hue];
-                    bestHue = hue;
-                }
-            }
-        }
-
-        SparseArray<Float> rgbScores = new SparseArray<>();
-        int bestColor = 0xff000000;
-        highScore = -1;
-        // Go back over the RGB colors that match the winning hue,
-        // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
-        // The highest-scoring RGB color wins.
-        for (int i = 0; i < pixelCount; i++) {
-            int rgb = pixels[i];
-            Color.colorToHSV(rgb, hsv);
-            int hue = (int) hsv[0];
-            if (hue == bestHue) {
-                float s = hsv[1];
-                float v = hsv[2];
-                int bucket = (int) (s * 100) + (int) (v * 10000);
-                // Score by cumulative saturation * value.
-                float score = s * v;
-                Float oldTotal = rgbScores.get(bucket);
-                float newTotal = oldTotal == null ? score : oldTotal + score;
-                rgbScores.put(bucket, newTotal);
-                if (newTotal > highScore) {
-                    highScore = newTotal;
-                    // All the colors in the winning bucket are very similar. Last in wins.
-                    bestColor = rgb;
-                }
-            }
-        }
-        return bestColor;
-    }
-
     /*
      * Finds a system apk which had a broadcast receiver listening to a particular action.
      * @param action intent action used to find the apk
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
index 08d8ad4..170f231 100644
--- a/src/com/android/launcher3/badge/BadgeInfo.java
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -16,12 +16,6 @@
 
 package com.android.launcher3.badge;
 
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.notification.NotificationInfo;
@@ -56,12 +50,6 @@
     /** This will only be initialized if the badge should display the notification icon. */
     private NotificationInfo mNotificationInfo;
 
-    /**
-     * When retrieving the notification icon, we draw it into this shader, which can be clipped
-     * as necessary when drawn in a badge.
-     */
-    private Shader mNotificationIcon;
-
     public BadgeInfo(PackageUserKey packageUserKey) {
         mPackageUserKey = packageUserKey;
         mNotificationKeys = new ArrayList<>();
@@ -112,7 +100,6 @@
 
     public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) {
         mNotificationInfo = notificationInfo;
-        mNotificationIcon = null;
     }
 
     public boolean hasNotificationToShow() {
@@ -120,35 +107,6 @@
     }
 
     /**
-     * Returns a shader to set on a Paint that will draw the notification icon in a badge.
-     *
-     * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called.
-     */
-    public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor,
-            int badgeSize, int badgePadding) {
-        if (mNotificationInfo == null) {
-            return null;
-        }
-        if (mNotificationIcon == null) {
-            Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor)
-                    .getConstantState().newDrawable();
-            int iconSize = badgeSize - badgePadding * 2;
-            icon.setBounds(0, 0, iconSize, iconSize);
-            Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(iconBitmap);
-            canvas.translate(badgePadding, badgePadding);
-            icon.draw(canvas);
-            mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP,
-                    Shader.TileMode.CLAMP);
-        }
-        return mNotificationIcon;
-    }
-
-    public boolean isIconLarge() {
-        return mNotificationInfo != null && mNotificationInfo.isIconLarge();
-    }
-
-    /**
      * Whether newBadge represents the same PackageUserKey as this badge, and icons with
      * this badge should be invalidated. So, for instance, if a badge has 3 notifications
      * and one of those notifications is updated, this method should return false because
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 6ce334e..02ec9f0 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -16,21 +16,17 @@
 
 package com.android.launcher3.badge;
 
-import android.content.Context;
-import android.content.res.Resources;
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Shader;
-import android.support.annotation.Nullable;
 import android.util.Log;
-import android.util.SparseArray;
 
-import com.android.launcher3.R;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.ShadowGenerator;
 
 /**
@@ -41,147 +37,58 @@
 
     private static final String TAG = "BadgeRenderer";
 
-    private static final boolean DOTS_ONLY = true;
-
     // The badge sizes are defined as percentages of the app icon size.
-    private static final float SIZE_PERCENTAGE = 0.38f;
+    private static final float SIZE_PERCENTAGE = 0.23f;
     // Used to expand the width of the badge for each additional digit.
-    private static final float CHAR_SIZE_PERCENTAGE = 0.12f;
-    private static final float TEXT_SIZE_PERCENTAGE = 0.26f;
     private static final float OFFSET_PERCENTAGE = 0.02f;
-    private static final float STACK_OFFSET_PERCENTAGE_X = 0.05f;
-    private static final float STACK_OFFSET_PERCENTAGE_Y = 0.06f;
-    private static final float DOT_SCALE = 0.6f;
 
-    private final Context mContext;
     private final int mSize;
-    private final int mCharSize;
-    private final int mTextHeight;
     private final int mOffset;
-    private final int mStackOffsetX;
-    private final int mStackOffsetY;
-    private final IconDrawer mLargeIconDrawer;
-    private final IconDrawer mSmallIconDrawer;
-    private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
-            | Paint.FILTER_BITMAP_FLAG);
-    private final SparseArray<Bitmap> mBackgroundsWithShadow;
+    private final float mCircleRadius;
+    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
 
-    public BadgeRenderer(Context context, int iconSizePx) {
-        mContext = context;
-        Resources res = context.getResources();
+    private final Bitmap mBackgroundWithShadow;
+    private final int mBitmapOffset;
+
+    public BadgeRenderer(int iconSizePx) {
         mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
-        mCharSize = (int) (CHAR_SIZE_PERCENTAGE * iconSizePx);
         mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
-        mStackOffsetX = (int) (STACK_OFFSET_PERCENTAGE_X * iconSizePx);
-        mStackOffsetY = (int) (STACK_OFFSET_PERCENTAGE_Y * iconSizePx);
-        mTextPaint.setTextSize(iconSizePx * TEXT_SIZE_PERCENTAGE);
-        mTextPaint.setTextAlign(Paint.Align.CENTER);
-        mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding));
-        mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
-        // Measure the text height.
-        Rect tempTextHeight = new Rect();
-        mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
-        mTextHeight = tempTextHeight.height();
 
-        mBackgroundsWithShadow = new SparseArray<>(3);
+        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
+        mBackgroundWithShadow = builder.setupBlurForSize(mSize).createPill(mSize, mSize);
+        mCircleRadius = builder.radius;
+
+        mBitmapOffset = -mBackgroundWithShadow.getHeight() / 2; // Same as width.
     }
 
     /**
      * Draw a circle in the top right corner of the given bounds, and draw
      * {@link BadgeInfo#getNotificationCount()} on top of the circle.
-     * @param palette The colors (based on the icon) to use for the badge.
-     * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out.
+     * @param color The color (based on the icon) to use for the badge.
      * @param iconBounds The bounds of the icon being badged.
      * @param badgeScale The progress of the animation, from 0 to 1.
      * @param spaceForOffset How much space is available to offset the badge up and to the right.
      */
-    public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
-            Rect iconBounds, float badgeScale, Point spaceForOffset) {
-        if (palette == null || iconBounds == null || spaceForOffset == null) {
+    public void draw(
+            Canvas canvas, int color, Rect iconBounds, float badgeScale, Point spaceForOffset) {
+        if (iconBounds == null || spaceForOffset == null) {
             Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
             return;
         }
-        mTextPaint.setColor(palette.textColor);
-        IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
-                ? mLargeIconDrawer : mSmallIconDrawer;
-        Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
-                mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
-        String notificationCount = badgeInfo == null ? "0"
-                : String.valueOf(badgeInfo.getNotificationCount());
-        int numChars = notificationCount.length();
-        int width = DOTS_ONLY ? mSize : mSize + mCharSize * (numChars - 1);
-        // Lazily load the background with shadow.
-        Bitmap backgroundWithShadow = mBackgroundsWithShadow.get(numChars);
-        if (backgroundWithShadow == null) {
-            backgroundWithShadow = new ShadowGenerator.Builder(Color.WHITE)
-                    .setupBlurForSize(mSize).createPill(width, mSize);
-            mBackgroundsWithShadow.put(numChars, backgroundWithShadow);
-        }
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         // We draw the badge relative to its center.
-        int badgeCenterX = iconBounds.right - width / 2;
+        int badgeCenterX = iconBounds.right - mSize / 2;
         int badgeCenterY = iconBounds.top + mSize / 2;
-        boolean isText = !DOTS_ONLY && badgeInfo != null && badgeInfo.getNotificationCount() != 0;
-        boolean isIcon = !DOTS_ONLY && icon != null;
-        boolean isDot = !(isText || isIcon);
-        if (isDot) {
-            badgeScale *= DOT_SCALE;
-        }
+
         int offsetX = Math.min(mOffset, spaceForOffset.x);
         int offsetY = Math.min(mOffset, spaceForOffset.y);
         canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
         canvas.scale(badgeScale, badgeScale);
-        // Prepare the background and shadow and possible stacking effect.
-        mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
-        int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width.
-        boolean shouldStack = !isDot && badgeInfo != null
-                && badgeInfo.getNotificationKeys().size() > 1;
-        if (shouldStack) {
-            int offsetDiffX = mStackOffsetX - mOffset;
-            int offsetDiffY = mStackOffsetY - mOffset;
-            canvas.translate(offsetDiffX, offsetDiffY);
-            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-                    -backgroundWithShadowSize / 2, mBackgroundPaint);
-            canvas.translate(-offsetDiffX, -offsetDiffY);
-        }
 
-        if (isText) {
-            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-                    -backgroundWithShadowSize / 2, mBackgroundPaint);
-            canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint);
-        } else if (isIcon) {
-            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-                    -backgroundWithShadowSize / 2, mBackgroundPaint);
-            iconDrawer.drawIcon(icon, canvas);
-        } else if (isDot) {
-            mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter);
-            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-                    -backgroundWithShadowSize / 2, mBackgroundPaint);
-        }
+        mCirclePaint.setColor(Color.BLACK);
+        canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
+        mCirclePaint.setColor(color);
+        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
         canvas.restore();
     }
-
-    /** Draws the notification icon with padding of a given size. */
-    private class IconDrawer {
-
-        private final int mPadding;
-        private final Bitmap mCircleClipBitmap;
-        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
-                Paint.FILTER_BITMAP_FLAG);
-
-        public IconDrawer(int padding) {
-            mPadding = padding;
-            mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8);
-            Canvas canvas = new Canvas();
-            canvas.setBitmap(mCircleClipBitmap);
-            canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint);
-        }
-
-        public void drawIcon(Shader icon, Canvas canvas) {
-            mPaint.setShader(icon);
-            canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint);
-            mPaint.setShader(null);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 3214b46..5cd90b1 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -137,8 +137,7 @@
             ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
             ShortcutInfo info = new ShortcutInfo(compat, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
-            info.iconBitmap = LauncherIcons
-                    .createShortcutIcon(compat, context, false /* badged */);
+            LauncherIcons.createShortcutIcon(compat, context, false /* badged */).applyTo(info);
             LauncherAppState.getInstance(context).getModel()
                     .updateAndBindShortcutInfo(info, compat);
             return info;
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
index 4cc70d3..299f090 100644
--- a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
@@ -18,6 +18,7 @@
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 
 import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.graphics.ColorExtractor.findDominantColorByHue;
 
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
@@ -257,7 +258,7 @@
             String value = VERSION_PREFIX + wallpaperId;
 
             if (bitmap != null) {
-                int color = Utilities.findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA);
+                int color = findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA);
                 value += "," + color;
             }
 
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
index ed5cfeb..26e5066 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -18,12 +18,14 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.graphics.Color;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.graphics.ColorExtractor;
 
 public class AppDiscoveryAppInfo extends AppInfo {
 
@@ -41,6 +43,8 @@
         this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
         this.title = item.title;
         this.iconBitmap = item.bitmap;
+        this.iconColor = iconBitmap == null ? Color.TRANSPARENT :
+                ColorExtractor.findDominantColorByHue(item.bitmap);
         this.usingLowResIcon = false;
         this.isInstantApp = item.isInstantApp;
         this.isRecent = item.isRecent;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 7c89df3..11ff88f 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -265,7 +265,7 @@
                             mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
 
                             if (info.isDisabled()) {
-                                FastBitmapDrawable d = new FastBitmapDrawable(null);
+                                FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null);
                                 d.setIsDisabled(true);
                                 mBaseFilter = (ColorMatrixColorFilter) d.getColorFilter();
                             }
@@ -367,7 +367,8 @@
                 return new FixedSizeEmptyDrawable(iconSize);
             }
             ShortcutInfoCompat si = (ShortcutInfoCompat) obj;
-            Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache());
+            Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache())
+                    .iconBitmap;
 
             float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size);
             float insetFraction = (iconSize - badgeSize) / iconSize;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 5983029..4d8d171 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,7 +63,6 @@
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -499,8 +498,7 @@
             // If we are animating to the accepting state, animate the badge out.
             float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
             mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
-            IconPalette badgePalette = IconPalette.getFolderBadgePalette(getResources());
-            mBadgeRenderer.draw(canvas, badgePalette, mBadgeInfo, mTempBounds,
+            mBadgeRenderer.draw(canvas, mBackground.getBadgeColor(), mTempBounds,
                     badgeScale, mTempSpaceForBadgeOffset);
         }
     }
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index eba5d98..285aef8 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -200,6 +200,10 @@
         return ColorUtils.setAlphaComponent(mBgColor, alpha);
     }
 
+    public int getBadgeColor() {
+        return mBgColor;
+    }
+
     public void drawBackground(Canvas canvas) {
         mPaint.setStyle(Paint.Style.FILL);
         mPaint.setColor(getBgColor());
diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java
new file mode 100644
index 0000000..ab906e2
--- /dev/null
+++ b/src/com/android/launcher3/graphics/BitmapInfo.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.graphics.Bitmap;
+
+import com.android.launcher3.ItemInfoWithIcon;
+
+public class BitmapInfo {
+
+    public Bitmap icon;
+    public int color;
+
+    public void applyTo(ItemInfoWithIcon info) {
+        info.iconBitmap = icon;
+        info.iconColor = color;
+    }
+
+    public void applyTo(BitmapInfo info) {
+        info.icon = icon;
+        info.color = color;
+    }
+
+    public static BitmapInfo fromBitmap(Bitmap bitmap) {
+        BitmapInfo info = new BitmapInfo();
+        info.icon = bitmap;
+        info.color = ColorExtractor.findDominantColorByHue(bitmap);
+        return info;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/graphics/ColorExtractor.java
new file mode 100644
index 0000000..e9d72b7
--- /dev/null
+++ b/src/com/android/launcher3/graphics/ColorExtractor.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.SparseArray;
+
+/**
+ * Utility class for extracting colors from a bitmap.
+ */
+public class ColorExtractor {
+
+    public static int findDominantColorByHue(Bitmap bitmap) {
+        return findDominantColorByHue(bitmap, 20);
+    }
+
+    /**
+     * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+     * @param bitmap The bitmap to scan
+     * @param samples The approximate max number of samples to use.
+     */
+    public static int findDominantColorByHue(Bitmap bitmap, int samples) {
+        final int height = bitmap.getHeight();
+        final int width = bitmap.getWidth();
+        int sampleStride = (int) Math.sqrt((height * width) / samples);
+        if (sampleStride < 1) {
+            sampleStride = 1;
+        }
+
+        // This is an out-param, for getting the hsv values for an rgb
+        float[] hsv = new float[3];
+
+        // First get the best hue, by creating a histogram over 360 hue buckets,
+        // where each pixel contributes a score weighted by saturation, value, and alpha.
+        float[] hueScoreHistogram = new float[360];
+        float highScore = -1;
+        int bestHue = -1;
+
+        int[] pixels = new int[samples];
+        int pixelCount = 0;
+
+        for (int y = 0; y < height; y += sampleStride) {
+            for (int x = 0; x < width; x += sampleStride) {
+                int argb = bitmap.getPixel(x, y);
+                int alpha = 0xFF & (argb >> 24);
+                if (alpha < 0x80) {
+                    // Drop mostly-transparent pixels.
+                    continue;
+                }
+                // Remove the alpha channel.
+                int rgb = argb | 0xFF000000;
+                Color.colorToHSV(rgb, hsv);
+                // Bucket colors by the 360 integer hues.
+                int hue = (int) hsv[0];
+                if (hue < 0 || hue >= hueScoreHistogram.length) {
+                    // Defensively avoid array bounds violations.
+                    continue;
+                }
+                if (pixelCount < samples) {
+                    pixels[pixelCount++] = rgb;
+                }
+                float score = hsv[1] * hsv[2];
+                hueScoreHistogram[hue] += score;
+                if (hueScoreHistogram[hue] > highScore) {
+                    highScore = hueScoreHistogram[hue];
+                    bestHue = hue;
+                }
+            }
+        }
+
+        SparseArray<Float> rgbScores = new SparseArray<>();
+        int bestColor = 0xff000000;
+        highScore = -1;
+        // Go back over the RGB colors that match the winning hue,
+        // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
+        // The highest-scoring RGB color wins.
+        for (int i = 0; i < pixelCount; i++) {
+            int rgb = pixels[i];
+            Color.colorToHSV(rgb, hsv);
+            int hue = (int) hsv[0];
+            if (hue == bestHue) {
+                float s = hsv[1];
+                float v = hsv[2];
+                int bucket = (int) (s * 100) + (int) (v * 10000);
+                // Score by cumulative saturation * value.
+                float score = s * v;
+                Float oldTotal = rgbScores.get(bucket);
+                float newTotal = oldTotal == null ? score : oldTotal + score;
+                rgbScores.put(bucket, newTotal);
+                if (newTotal > highScore) {
+                    highScore = newTotal;
+                    // All the colors in the winning bucket are very similar. Last in wins.
+                    bestColor = rgb;
+                }
+            }
+        }
+        return bestColor;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 371479b..32d9e41 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -31,7 +31,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
@@ -64,18 +64,18 @@
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
-    public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
-        return new FastBitmapDrawable(icon);
+    public FastBitmapDrawable newIcon(ItemInfoWithIcon info) {
+        return new FastBitmapDrawable(info);
     }
 
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
-    public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
+    public PreloadIconDrawable newPendingIcon(ItemInfoWithIcon info, Context context) {
         if (mPreloadProgressPath == null) {
             mPreloadProgressPath = getPreloadProgressPath(context);
         }
-        return new PreloadIconDrawable(icon, mPreloadProgressPath, context);
+        return new PreloadIconDrawable(info, mPreloadProgressPath, context);
     }
 
 
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 6e01ed5..9c3b77e 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -18,12 +18,7 @@
 
 import android.app.Notification;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 
@@ -41,37 +36,10 @@
     private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
     private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
 
-    private static IconPalette sBadgePalette;
-    private static IconPalette sFolderBadgePalette;
-
-    public final int dominantColor;
-    public final int backgroundColor;
-    public final ColorMatrixColorFilter backgroundColorMatrixFilter;
-    public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
-    public final int textColor;
-    public final int secondaryColor;
-
-    private IconPalette(int color, boolean desaturateBackground) {
-        dominantColor = color;
-        backgroundColor = desaturateBackground ? getMutedColor(dominantColor, 0.87f) : dominantColor;
-        ColorMatrix backgroundColorMatrix = new ColorMatrix();
-        Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
-        backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
-        if (!desaturateBackground) {
-            saturatedBackgroundColorMatrixFilter = backgroundColorMatrixFilter;
-        } else {
-            // Get slightly more saturated background color.
-            Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
-            saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
-        }
-        textColor = getTextColorForBackground(backgroundColor);
-        secondaryColor = getLowContrastColor(backgroundColor);
-    }
-
     /**
      * Returns a color suitable for the progress bar color of preload icon.
      */
-    public int getPreloadProgressColor(Context context) {
+    public static int getPreloadProgressColor(Context context, int dominantColor) {
         int result = dominantColor;
 
         // Make sure that the dominant color has enough saturation to be visible properly.
@@ -86,37 +54,6 @@
         return result;
     }
 
-    public static IconPalette fromDominantColor(int dominantColor, boolean desaturateBackground) {
-        return new IconPalette(dominantColor, desaturateBackground);
-    }
-
-    /**
-     * Returns an IconPalette based on the badge_color in colors.xml.
-     * If that color is Color.TRANSPARENT, then returns null instead.
-     */
-    public static @Nullable IconPalette getBadgePalette(Resources resources) {
-        int badgeColor = resources.getColor(R.color.badge_color);
-        if (badgeColor == Color.TRANSPARENT) {
-            // Colors will be extracted per app icon, so a static palette won't work.
-            return null;
-        }
-        if (sBadgePalette == null) {
-            sBadgePalette = fromDominantColor(badgeColor, false);
-        }
-        return sBadgePalette;
-    }
-
-    /**
-     * Returns an IconPalette based on the folder_badge_color in colors.xml.
-     */
-    public static @NonNull IconPalette getFolderBadgePalette(Resources resources) {
-        if (sFolderBadgePalette == null) {
-            int badgeColor = resources.getColor(R.color.folder_badge_color);
-            sFolderBadgePalette = fromDominantColor(badgeColor, false);
-        }
-        return sFolderBadgePalette;
-    }
-
     /**
      * Resolves a color such that it has enough contrast to be used as the
      * color of an icon or text on the given background color.
@@ -208,30 +145,8 @@
         return ColorUtils.LABToColor(low, a, b);
     }
 
-    private static int getMutedColor(int color, float whiteScrimAlpha) {
+    public static int getMutedColor(int color, float whiteScrimAlpha) {
         int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
         return ColorUtils.compositeColors(whiteScrim, color);
     }
-
-    private static int getTextColorForBackground(int backgroundColor) {
-        return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f);
-    }
-
-    private static int getLowContrastColor(int color) {
-        return getLighterOrDarkerVersionOfColor(color, 1.5f);
-    }
-
-    private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
-        int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
-        int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
-        int translucentWhiteOrBlack;
-        if (whiteMinAlpha >= 0) {
-            translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
-        } else if (blackMinAlpha >= 0) {
-            translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
-        } else {
-            translucentWhiteOrBlack = Color.WHITE;
-        }
-        return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
-    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 8c4738c..fdb6313 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -42,6 +41,7 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -51,6 +51,7 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Provider;
+import com.android.launcher3.util.Themes;
 
 /**
  * Helper methods for generating various launcher icons
@@ -69,7 +70,7 @@
      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
      * exist, it returns null.
      */
-    public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) {
+    public static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) {
         PackageManager packageManager = context.getPackageManager();
         // the resource
         try {
@@ -92,12 +93,13 @@
     /**
      * Returns a bitmap which is of the appropriate size to be displayed as an icon
      */
-    public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+    public static BitmapInfo createIconBitmap(Bitmap icon, Context context) {
         final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
         if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
-            return icon;
+            return BitmapInfo.fromBitmap(icon);
         }
-        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f);
+        return BitmapInfo.fromBitmap(
+                createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f));
     }
 
     /**
@@ -105,7 +107,7 @@
      * view or workspace. The icon is badged for {@param user}.
      * The bitmap is also visually normalized with other icons.
      */
-    public static Bitmap createBadgedIconBitmap(
+    public static BitmapInfo createBadgedIconBitmap(
             Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
 
         IconNormalizer normalizer;
@@ -141,18 +143,20 @@
             }
         }
 
+        final Bitmap result;
         if (user != null && !Process.myUserHandle().equals(user)) {
             BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
             Drawable badged = context.getPackageManager().getUserBadgedIcon(
                     drawable, user);
             if (badged instanceof BitmapDrawable) {
-                return ((BitmapDrawable) badged).getBitmap();
+                result = ((BitmapDrawable) badged).getBitmap();
             } else {
-                return createIconBitmap(badged, context, 1f);
+                result = createIconBitmap(badged, context, 1f);
             }
         } else {
-            return bitmap;
+            result = bitmap;
         }
+        return BitmapInfo.fromBitmap(result);
     }
 
     /**
@@ -302,29 +306,23 @@
         return drawable;
     }
 
-    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
+    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
         return createShortcutIcon(shortcutInfo, context, true /* badged */);
     }
 
-    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
             boolean badged) {
         return createShortcutIcon(shortcutInfo, context, badged, null);
     }
 
-    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
-            final Bitmap fallbackIcon) {
-        // If the shortcut is pinned but no longer has an icon in the system,
-        // keep the current icon instead of reverting to the default icon.
-        return createShortcutIcon(shortcutInfo, context, true, Provider.of(fallbackIcon));
-    }
-
-    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
             boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
         LauncherAppState app = LauncherAppState.getInstance(context);
         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
                 .getShortcutIconDrawable(shortcutInfo,
                         app.getInvariantDeviceProfile().fillResIconDpi);
         IconCache cache = app.getIconCache();
+
         Bitmap unbadgedBitmap = null;
         if (unbadgedDrawable != null) {
             unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
@@ -334,27 +332,32 @@
                 unbadgedBitmap = fallbackIconProvider.get();
             }
             if (unbadgedBitmap == null) {
-                unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle());
+                unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
             }
         }
 
+        BitmapInfo result = new BitmapInfo();
         if (!badged) {
-            return unbadgedBitmap;
+            result.color = Themes.getColorAccent(context);
+            result.icon = unbadgedBitmap;
+            return result;
         }
 
         int size = app.getInvariantDeviceProfile().iconBitmapSize;
 
         final Bitmap unbadgedfinal = unbadgedBitmap;
-        final Bitmap badge = getShortcutInfoBadge(shortcutInfo, cache);
+        final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
 
-        return UiFactory.createFromRenderer(size, size, false, (c) -> {
+        result.color = badge.iconColor;
+        result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> {
             ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
             badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
         });
+        return result;
     }
 
-    public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
-        final Bitmap badgeBitmap;
+    public static ItemInfoWithIcon getShortcutInfoBadge(
+            ShortcutInfoCompat shortcutInfo, IconCache cache) {
         ComponentName cn = shortcutInfo.getActivity();
         if (cn != null) {
             // Get the app info for the source activity.
@@ -365,13 +368,12 @@
                     .addCategory(Intent.CATEGORY_LAUNCHER)
                     .setComponent(cn);
             cache.getTitleAndIcon(appInfo, false);
-            badgeBitmap = appInfo.iconBitmap;
+            return appInfo;
         } else {
             PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
             cache.getTitleAndIconForApp(pkgInfo, false);
-            badgeBitmap = pkgInfo.iconBitmap;
+            return pkgInfo;
         }
-        return badgeBitmap;
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 6d486ee..a40b6df 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -32,6 +32,7 @@
 import android.util.SparseArray;
 
 import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.anim.Interpolators;
 
 import java.lang.ref.WeakReference;
@@ -86,7 +87,7 @@
     private final Paint mProgressPaint;
 
     private Bitmap mShadowBitmap;
-    private int mIndicatorColor = 0;
+    private final int mIndicatorColor;
 
     private int mTrackAlpha;
     private float mTrackLength;
@@ -103,8 +104,8 @@
     /**
      * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
      */
-    public PreloadIconDrawable(Bitmap b, Path progressPath, Context context) {
-        super(b);
+    public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
+        super(info);
         mContext = context;
         mProgressPath = progressPath;
         mScaledTrackPath = new Path();
@@ -113,6 +114,7 @@
         mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mProgressPaint.setStyle(Paint.Style.STROKE);
         mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+        mIndicatorColor = IconPalette.getPreloadProgressColor(context, mIconColor);
 
         setInternalProgress(0);
     }
@@ -266,9 +268,6 @@
             mScaledTrackPath.reset();
             mTrackAlpha = MAX_PAINT_ALPHA;
             setIsDisabled(true);
-        } else if (mIndicatorColor == 0) {
-            // Update the indicator color
-            mIndicatorColor = getIconPalette().getPreloadProgressColor(mContext);
         }
 
         if (progress < 1 && progress > 0) {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index ccef9b7..b1d07f1 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapInfo;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ContentWriter;
@@ -151,10 +152,9 @@
         info.user = user;
         info.itemType = itemType;
         info.title = getTitle();
-        info.iconBitmap = loadIcon(info);
         // the fallback icon
-        if (info.iconBitmap == null) {
-            info.iconBitmap = mIconCache.getDefaultIcon(info.user);
+        if (!loadIcon(info)) {
+            mIconCache.getDefaultIcon(info.user).applyTo(info);
         }
 
         // TODO: If there's an explicit component and we can't install that, delete it.
@@ -165,8 +165,7 @@
     /**
      * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
      */
-    protected Bitmap loadIcon(ShortcutInfo info) {
-        Bitmap icon = null;
+    protected boolean loadIcon(ShortcutInfo info) {
         if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
             String packageName = getString(iconPackageIndex);
             String resourceName = getString(iconResourceIndex);
@@ -174,24 +173,24 @@
                 info.iconResource = new ShortcutIconResource();
                 info.iconResource.packageName = packageName;
                 info.iconResource.resourceName = resourceName;
-                icon = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+                BitmapInfo iconInfo = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+                if (iconInfo != null) {
+                    iconInfo.applyTo(info);
+                    return true;
+                }
             }
         }
-        if (icon == null) {
-            // Failed to load from resource, try loading from DB.
-            byte[] data = getBlob(iconIndex);
-            try {
-                icon = LauncherIcons.createIconBitmap(
-                        BitmapFactory.decodeByteArray(data, 0, data.length), mContext);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to load icon for info " + info, e);
-                return null;
-            }
+
+        // Failed to load from resource, try loading from DB.
+        byte[] data = getBlob(iconIndex);
+        try {
+            LauncherIcons.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length),
+                    mContext).applyTo(info);
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to load icon for info " + info, e);
+            return false;
         }
-        if (icon == null) {
-            Log.e(TAG, "Failed to load icon for info " + info);
-        }
-        return icon;
     }
 
     /**
@@ -211,9 +210,8 @@
         info.user = user;
         info.intent = intent;
 
-        info.iconBitmap = loadIcon(info);
         // the fallback icon
-        if (info.iconBitmap == null) {
+        if (!loadIcon(info)) {
             mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
         }
 
@@ -269,8 +267,7 @@
 
         mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
         if (mIconCache.isDefaultIcon(info.iconBitmap, user)) {
-            Bitmap icon = loadIcon(info);
-            info.iconBitmap = icon != null ? icon : info.iconBitmap;
+            loadIcon(info);
         }
 
         if (lai != null) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 310416f..b13b48a 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -472,12 +472,12 @@
                                         public Bitmap get() {
                                             // If the pinned deep shortcut is no longer published,
                                             // use the last saved icon instead of the default.
-                                            return c.loadIcon(finalInfo);
+                                            return c.loadIcon(finalInfo)
+                                                    ? finalInfo.iconBitmap : null;
                                         }
                                     };
-                                    info.iconBitmap = LauncherIcons
-                                            .createShortcutIcon(pinnedShortcut, context,
-                                                    true /* badged */, fallbackIconProvider);
+                                    LauncherIcons.createShortcutIcon(pinnedShortcut, context,
+                                            true /* badged */, fallbackIconProvider).applyTo(info);
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 470dadf..18ae61b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapInfo;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -191,9 +192,10 @@
                         // Update shortcuts which use iconResource.
                         if ((si.iconResource != null)
                                 && packageSet.contains(si.iconResource.packageName)) {
-                            Bitmap icon = LauncherIcons.createIconBitmap(si.iconResource, context);
-                            if (icon != null) {
-                                si.iconBitmap = icon;
+                            BitmapInfo iconInfo =
+                                    LauncherIcons.createIconBitmap(si.iconResource, context);
+                            if (iconInfo != null) {
+                                iconInfo.applyTo(si);
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index c1f33a6..0b75e2c 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -92,8 +93,10 @@
                 }
                 for (final ShortcutInfo shortcutInfo : shortcutInfos) {
                     shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                    shortcutInfo.iconBitmap = LauncherIcons.createShortcutIcon(fullDetails, context,
-                            shortcutInfo.iconBitmap);
+                    // If the shortcut is pinned but no longer has an icon in the system,
+                    // keep the current icon instead of reverting to the default icon.
+                    LauncherIcons.createShortcutIcon(fullDetails, context, true,
+                            Provider.of(shortcutInfo.iconBitmap)).applyTo(shortcutInfo);
                     updatedShortcutInfos.add(shortcutInfo);
                 }
             }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 2e9ac72..b033405 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -91,8 +92,10 @@
                     }
                     si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
                     si.updateFromDeepShortcutInfo(shortcut, context);
-                    si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context,
-                            si.iconBitmap);
+                    // If the shortcut is pinned but no longer has an icon in the system,
+                    // keep the current icon instead of reverting to the default icon.
+                    LauncherIcons.createShortcutIcon(shortcut, context, true,
+                            Provider.of(si.iconBitmap)).applyTo(si);
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index 120de04..6918935 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -83,7 +83,7 @@
         if (mIconDrawable == null) {
             mIconDrawable = new BitmapDrawable(context.getResources(), LauncherAppState
                     .getInstance(context).getIconCache()
-                    .getDefaultIcon(statusBarNotification.getUser()));
+                    .getDefaultIcon(statusBarNotification.getUser()).icon);
             mBadgeIcon = Notification.BADGE_ICON_NONE;
         }
         intent = notification.contentIntent;
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 5bbd19c..2fefa85 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -18,6 +18,7 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.support.annotation.Nullable;
 import android.view.MotionEvent;
@@ -114,12 +115,12 @@
         }
     }
 
-    public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
+    public void updateHeader(int notificationCount, int iconColor) {
         mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
-        if (palette != null) {
+        if (Color.alpha(iconColor) > 0) {
             if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
                 mNotificationHeaderTextColor =
-                        IconPalette.resolveContrastColor(mContext, palette.dominantColor,
+                        IconPalette.resolveContrastColor(mContext, iconColor,
                                 Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
             }
             mHeaderText.setTextColor(mNotificationHeaderTextColor);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 6481183..cedf291 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -54,6 +54,7 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherModel;
@@ -742,11 +743,11 @@
     }
 
     private void updateNotificationHeader() {
-        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+        ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
         BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
         if (mNotificationItemView != null && badgeInfo != null) {
-            IconPalette palette = mOriginalIcon.getBadgePalette();
-            mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette);
+            mNotificationItemView.updateHeader(
+                    badgeInfo.getNotificationCount(), itemInfo.iconColor);
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 6c83d12..4adfb7c 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -148,8 +148,8 @@
                 final ShortcutInfoCompat shortcut = shortcuts.get(i);
                 final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
                 // Use unbadged icon for the menu.
-                si.iconBitmap = LauncherIcons.createShortcutIcon(
-                        shortcut, launcher, false /* badged */);
+                LauncherIcons.createShortcutIcon(shortcut, launcher, false /* badged */)
+                        .applyTo(si);
                 si.rank = i;
 
                 final DeepShortcutView view = shortcutViews.get(i);
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 24bcebb..6970833 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -58,8 +58,6 @@
     private final int mStartState;
     private final boolean mDisabledForSafeMode;
 
-    private Bitmap mIcon;
-
     private Drawable mCenterDrawable;
     private Drawable mSettingIconDrawable;
 
@@ -129,53 +127,44 @@
 
     @Override
     public void reapplyItemInfo(ItemInfoWithIcon info) {
-        Bitmap icon = info.iconBitmap;
-        if (mIcon == icon) {
-            return;
-        }
-        mIcon = icon;
         if (mCenterDrawable != null) {
             mCenterDrawable.setCallback(null);
             mCenterDrawable = null;
         }
-        if (mIcon != null) {
+        if (info.iconBitmap != null) {
             // The view displays three modes,
             //   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 = drawableFactory.newIcon(mIcon, mInfo);
+                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(info);
                 disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = drawableFactory.newIcon(mIcon, mInfo);
+                mCenterDrawable = drawableFactory.newIcon(info);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
-
-                updateSettingColor();
+                updateSettingColor(info.iconColor);
             } else {
                 mCenterDrawable = DrawableFactory.get(getContext())
-                        .newPendingIcon(mIcon, getContext());
-                mCenterDrawable.setCallback(this);
+                        .newPendingIcon(info, getContext());
                 mSettingIconDrawable = null;
                 applyState();
             }
+            mCenterDrawable.setCallback(this);
             mDrawableSizeChanged = true;
         }
         invalidate();
     }
 
-    private void updateSettingColor() {
-        int color = Utilities.findDominantColorByHue(mIcon, 20);
+    private void updateSettingColor(int dominantColor) {
         // Make the dominant color bright.
         float[] hsv = new float[3];
-        Color.colorToHSV(color, hsv);
+        Color.colorToHSV(dominantColor, hsv);
         hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
         hsv[2] = 1;
-        color = Color.HSVToColor(hsv);
-
-        mSettingIconDrawable.setColorFilter(color,  PorterDuff.Mode.SRC_IN);
+        mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv),  PorterDuff.Mode.SRC_IN);
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index bbb6772..cf90afd 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -9,12 +9,12 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
+import android.graphics.Color;
 import android.os.Process;
 import android.os.UserHandle;
 import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.provider.ProviderTestRule;
-import android.support.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppFilter;
@@ -27,6 +27,7 @@
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.graphics.BitmapInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.TestLauncherProvider;
@@ -208,7 +209,7 @@
             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
             if (entry == null) {
                 entry = new CacheEntry();
-                entry.icon = getDefaultIcon(user);
+                getDefaultIcon(user).applyTo(entry);
             }
             return entry;
         }
@@ -216,6 +217,7 @@
         public void addCache(ComponentName key, String title) {
             CacheEntry entry = new CacheEntry();
             entry.icon = newIcon();
+            entry.color = Color.RED;
             entry.title = title;
             mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
         }
@@ -225,8 +227,8 @@
         }
 
         @Override
-        protected Bitmap makeDefaultIcon(UserHandle user) {
-            return newIcon();
+        protected BitmapInfo makeDefaultIcon(UserHandle user) {
+            return BitmapInfo.fromBitmap(newIcon());
         }
     }
 }
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 173c556..dfefa31 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -17,6 +17,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.graphics.BitmapInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -138,7 +139,8 @@
         assertTrue(mLoaderCursor.moveToNext());
 
         Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
-        when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon);
+        when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user)))
+                .thenReturn(BitmapInfo.fromBitmap(icon));
         ShortcutInfo info = mLoaderCursor.loadSimpleShortcut();
         assertEquals(icon, info.iconBitmap);
         assertEquals("my-shortcut", info.title);