Updating the PreloadIconDrawable

> The drawable gets the path from MaskIconDrawable path, instead of
  using a circle
> The progress changes are animated as well

Bug: 34831873
Change-Id: I4e7f0b610f4fd94de8e0cfcf8b179b775cf0b4d8
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 2efe31f..8043eac 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -27,7 +26,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -45,6 +43,7 @@
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
@@ -57,8 +56,6 @@
 public class BubbleTextView extends TextView
         implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver {
 
-    private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
-
     // Dimensions in DP
     private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
     private static final float KEY_SHADOW_RADIUS = 1f;
@@ -423,10 +420,6 @@
         super.onAttachedToWindow();
 
         if (mBackground != null) mBackground.setCallback(this);
-
-        if (mIcon instanceof PreloadIconDrawable) {
-            ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
-        }
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
@@ -495,7 +488,8 @@
                 if (mIcon instanceof PreloadIconDrawable) {
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                 } else {
-                    preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
+                    preloadDrawable = DrawableFactory.get(getContext())
+                            .newPendingIcon(info.iconBitmap, getContext());
                     setIcon(preloadDrawable);
                 }
 
@@ -520,20 +514,6 @@
                 : null;
     }
 
-    private Theme getPreloaderTheme() {
-        Object tag = getTag();
-        int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
-                (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
-                        : R.style.PreloadIcon;
-        Theme theme = sPreloaderThemes.get(style);
-        if (theme == null) {
-            theme = getResources().newTheme();
-            theme.applyStyle(style, true);
-            sPreloaderThemes.put(style, theme);
-        }
-        return theme;
-    }
-
     /**
      * Sets the icon for this view based on the layout direction.
      */
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index df19547..95d2daf 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -120,7 +120,7 @@
 
     public FastBitmapDrawable(Bitmap b) {
         mBitmap = b;
-        setBounds(0, 0, b.getWidth(), b.getHeight());
+        setFilterBitmap(true);
     }
 
     public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 3256df6..354cf20 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -33,8 +32,8 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.PackageItemInfo;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
@@ -42,8 +41,6 @@
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
 
-    private static Theme sPreloaderTheme;
-
     private final Rect mRect = new Rect();
     private View mDefaultView;
     private OnClickListener mClickListener;
@@ -149,13 +146,8 @@
 
                 updateSettingColor();
             } else {
-                if (sPreloaderTheme == null) {
-                    sPreloaderTheme = getResources().newTheme();
-                    sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
-                }
-
-                FastBitmapDrawable drawable = drawableFactory.newIcon(mIcon, mInfo);
-                mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
+                mCenterDrawable = DrawableFactory.get(getContext())
+                        .newPendingIcon(mIcon, getContext());
                 mCenterDrawable.setCallback(this);
                 mSettingIconDrawable = null;
                 applyState();
@@ -226,13 +218,10 @@
         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
 
         if (mSettingIconDrawable == null) {
-            int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
-                    ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
-            int maxSize = grid.iconSizePx + 2 * outset;
+            int maxSize = grid.iconSizePx;
             int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
 
             mRect.set(0, 0, size, size);
-            mRect.inset(outset, outset);
             mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
             mCenterDrawable.setBounds(mRect);
         } else  {
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
deleted file mode 100644
index 973e688..0000000
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package com.android.launcher3;
-
-import android.animation.ObjectAnimator;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-public class PreloadIconDrawable extends Drawable {
-
-    private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
-    private static final float ANIMATION_PROGRESS_STARTED = 0f;
-    private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
-
-    private static final float MIN_SATURATION = 0.2f;
-    private static final float MIN_LIGHTNESS = 0.6f;
-
-    private static final float ICON_SCALE_FACTOR = 0.5f;
-    private static final int DEFAULT_COLOR = 0xFF009688;
-
-    private static final Rect sTempRect = new Rect();
-
-    private final RectF mIndicatorRect = new RectF();
-    private boolean mIndicatorRectDirty;
-
-    private final Paint mPaint;
-    public final Drawable mIcon;
-
-    private Drawable mBgDrawable;
-    private int mRingOutset;
-
-    private int mIndicatorColor = 0;
-
-    /**
-     * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
-     * is shown with no progress bar.
-     */
-    private int mProgress = 0;
-
-    private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-    private ObjectAnimator mAnimator;
-
-    public PreloadIconDrawable(Drawable icon, Theme theme) {
-        mIcon = icon;
-
-        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        setBounds(icon.getBounds());
-        applyPreloaderTheme(theme);
-        onLevelChange(0);
-    }
-
-    public void applyPreloaderTheme(Theme t) {
-        TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
-        mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
-        mBgDrawable.setFilterBitmap(true);
-        mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
-        mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
-        ta.recycle();
-        onBoundsChange(getBounds());
-        invalidateSelf();
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        mIcon.setBounds(bounds);
-        if (mBgDrawable != null) {
-            sTempRect.set(bounds);
-            sTempRect.inset(-mRingOutset, -mRingOutset);
-            mBgDrawable.setBounds(sTempRect);
-        }
-        mIndicatorRectDirty = true;
-    }
-
-    public int getOutset() {
-        return mRingOutset;
-    }
-
-    /**
-     * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
-     * half the stroke size to accommodate the indicator.
-     */
-    private void initIndicatorRect() {
-        Drawable d = mBgDrawable;
-        Rect bounds = d.getBounds();
-
-        d.getPadding(sTempRect);
-        // Amount by which padding has to be scaled
-        float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
-        float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
-        mIndicatorRect.set(
-                bounds.left + sTempRect.left * paddingScaleX,
-                bounds.top + sTempRect.top * paddingScaleY,
-                bounds.right - sTempRect.right * paddingScaleX,
-                bounds.bottom - sTempRect.bottom * paddingScaleY);
-
-        float inset = mPaint.getStrokeWidth() / 2;
-        mIndicatorRect.inset(inset, inset);
-        mIndicatorRectDirty = false;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        final Rect r = new Rect(getBounds());
-        if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
-            // The draw region has been clipped.
-            return;
-        }
-        if (mIndicatorRectDirty) {
-            initIndicatorRect();
-        }
-        final float iconScale;
-
-        if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
-                && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
-            mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
-            mBgDrawable.setAlpha(mPaint.getAlpha());
-            mBgDrawable.draw(canvas);
-            canvas.drawOval(mIndicatorRect, mPaint);
-
-            iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
-        } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
-            mPaint.setAlpha(255);
-            iconScale = ICON_SCALE_FACTOR;
-            mBgDrawable.setAlpha(255);
-            mBgDrawable.draw(canvas);
-
-            if (mProgress >= 100) {
-                canvas.drawOval(mIndicatorRect, mPaint);
-            } else if (mProgress > 0) {
-                canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
-            }
-        } else {
-            iconScale = 1;
-        }
-
-        canvas.save();
-        canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
-        mIcon.draw(canvas);
-        canvas.restore();
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mIcon.setAlpha(alpha);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mIcon.setColorFilter(cf);
-    }
-
-    @Override
-    protected boolean onLevelChange(int level) {
-        mProgress = level;
-
-        // Stop Animation
-        if (mAnimator != null) {
-            mAnimator.cancel();
-            mAnimator = null;
-        }
-        mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-        if (level > 0) {
-            // Set the paint color only when the level changes, so that the dominant color
-            // is only calculated when needed.
-            mPaint.setColor(getIndicatorColor());
-        }
-        if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).setIsDisabled(level < 100);
-        }
-
-        invalidateSelf();
-        return true;
-    }
-
-    /**
-     * Runs the finish animation if it is has not been run after last level change.
-     */
-    public void maybePerformFinishedAnimation() {
-        if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
-            return;
-        }
-        if (mAnimator != null) {
-            mAnimator.cancel();
-        }
-        setAnimationProgress(ANIMATION_PROGRESS_STARTED);
-        mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
-                ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
-        mAnimator.start();
-    }
-
-    public void setAnimationProgress(float progress) {
-        if (progress != mAnimationProgress) {
-            mAnimationProgress = progress;
-            invalidateSelf();
-        }
-    }
-
-    public float getAnimationProgress() {
-        return mAnimationProgress;
-    }
-
-    public boolean hasNotCompleted() {
-        return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mIcon.getIntrinsicHeight();
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mIcon.getIntrinsicWidth();
-    }
-
-    private int getIndicatorColor() {
-        if (mIndicatorColor != 0) {
-            return mIndicatorColor;
-        }
-        if (!(mIcon instanceof FastBitmapDrawable)) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        mIndicatorColor = Utilities.findDominantColorByHue(
-                ((FastBitmapDrawable) mIcon).getBitmap(), 20);
-
-        // Make sure that the dominant color has enough saturation to be visible properly.
-        float[] hsv = new float[3];
-        Color.colorToHSV(mIndicatorColor, hsv);
-        if (hsv[1] < MIN_SATURATION) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
-        mIndicatorColor = Color.HSVToColor(hsv);
-        return mIndicatorColor;
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9819418..c80d4a8 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -72,6 +72,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 973245b..5dc9633 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -53,13 +53,11 @@
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.SimpleOnStylusPressListener;
@@ -245,7 +243,7 @@
     };
 
     public Drawable prepareCreate(final View destView) {
-        Drawable animateDrawable = getTopDrawable((TextView) destView);
+        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 destView.getMeasuredWidth());
         return animateDrawable;
@@ -270,7 +268,7 @@
     }
 
     public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
-        Drawable animateDrawable = getTopDrawable((TextView) finalView);
+        Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 finalView.getMeasuredWidth());
 
@@ -771,11 +769,6 @@
         }
     }
 
-    private Drawable getTopDrawable(TextView v) {
-        Drawable d = v.getCompoundDrawables()[1];
-        return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
-    }
-
     class FolderPreviewItemAnim {
         ValueAnimator mValueAnimator;
         float finalScale;
@@ -892,7 +885,7 @@
 
         for (int i = 0; i < mDrawingParams.size(); i++) {
             PreviewItemDrawingParams p = mDrawingParams.get(i);
-            p.drawable = getTopDrawable((TextView) items.get(i));
+            p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
 
             if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
                 computePreviewItemDrawingParams(i, nItemsInPreview, p);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 1a470ff..bb136f7 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -27,7 +27,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
-import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.ProviderConfig;
@@ -185,10 +184,6 @@
         } else {
             bounds.offsetTo(0, 0);
         }
-        if (d instanceof PreloadIconDrawable) {
-            int inset = -((PreloadIconDrawable) d).getOutset();
-            bounds.inset(inset, inset);
-        }
         return bounds;
     }
 
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 4d4d508..2493447 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -21,12 +21,14 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
 import android.os.UserHandle;
 import android.support.annotation.UiThread;
+import android.util.Log;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
@@ -41,9 +43,13 @@
  */
 public class DrawableFactory {
 
+    private static final String TAG = "DrawableFactory";
+
     private static DrawableFactory sInstance;
     private static final Object LOCK = new Object();
 
+    private Path mPreloadProgressPath;
+
     public static DrawableFactory get(Context context) {
         synchronized (LOCK) {
             if (sInstance == null) {
@@ -61,9 +67,38 @@
      * Returns a FastBitmapDrawable with the icon.
      */
     public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
-        FastBitmapDrawable d = new FastBitmapDrawable(icon);
-        d.setFilterBitmap(true);
-        return d;
+        return new FastBitmapDrawable(icon);
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
+        if (mPreloadProgressPath == null) {
+            mPreloadProgressPath = getPreloadProgressPath(context);
+        }
+        return new PreloadIconDrawable(icon, mPreloadProgressPath);
+    }
+
+
+    protected Path getPreloadProgressPath(Context context) {
+        if (Utilities.isAtLeastO()) {
+            try {
+                // Try to load the path from Mask Icon
+                Drawable maskIcon = context.getDrawable(R.drawable.mask_drawable_wrapper);
+                maskIcon.setBounds(0, 0,
+                        PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
+                return (Path) maskIcon.getClass().getMethod("getIconMask").invoke(maskIcon);
+            } catch (Exception e) {
+                Log.e(TAG, "Error loading mask icon", e);
+            }
+        }
+
+        // Create a circle static from top center and going clockwise.
+        Path p = new Path();
+        p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
+        p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
+        return p;
     }
 
     public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
new file mode 100644
index 0000000..4be4bd5
--- /dev/null
+++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
@@ -0,0 +1,41 @@
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class FixedScaleDrawable extends DrawableWrapper {
+
+    // TODO b/33553066 use the constant defined in MaskableIconDrawable
+    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+
+    public FixedScaleDrawable() {
+        super(new ColorDrawable());
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE,
+                getBounds().exactCenterX(), getBounds().exactCenterY());
+        super.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 27aeaba..991038c 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -32,16 +32,42 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "IconPalette";
 
-    public int backgroundColor;
-    public int textColor;
-    public int secondaryColor;
+    private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
+    private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
+    private static final int DEFAULT_PRELOAD_COLOR = 0xFF009688;
+
+    public final int dominantColor;
+    public final int backgroundColor;
+    public final int textColor;
+    public final int secondaryColor;
+
+    private IconPalette(int color) {
+        dominantColor = color;
+        backgroundColor = getMutedColor(dominantColor);
+        textColor = getTextColorForBackground(backgroundColor);
+        secondaryColor = getLowContrastColor(backgroundColor);
+    }
+
+    /**
+     * Returns a color suitable for the progress bar color of preload icon.
+     */
+    public int getPreloadProgressColor() {
+        int result = dominantColor;
+
+        // Make sure that the dominant color has enough saturation to be visible properly.
+        float[] hsv = new float[3];
+        Color.colorToHSV(result, hsv);
+        if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
+            result = DEFAULT_PRELOAD_COLOR;
+        } else {
+            hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
+            result = Color.HSVToColor(hsv);
+        }
+        return result;
+    }
 
     public static IconPalette fromDominantColor(int dominantColor) {
-        IconPalette palette = new IconPalette();
-        palette.backgroundColor = getMutedColor(dominantColor);
-        palette.textColor = getTextColorForBackground(palette.backgroundColor);
-        palette.secondaryColor = getLowContrastColor(palette.backgroundColor);
-        return palette;
+        return new IconPalette(dominantColor);
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 472b913..3fffb5b 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -29,13 +29,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
-import android.graphics.drawable.ScaleDrawable;
 import android.os.Process;
 import android.os.UserHandle;
-import android.view.Gravity;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.IconCache;
@@ -52,8 +49,6 @@
  * Helper methods for generating various launcher icons
  */
 public class LauncherIcons {
-    // TODO b/33553066 use the constant defined in MaskableIconDrawable
-    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
 
     private static final Rect sOldBounds = new Rect();
     private static final Canvas sCanvas = new Canvas();
@@ -236,17 +231,16 @@
         if (!(ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isAtLeastO())) {
             return drawable;
         }
-        int color = context.getResources().getColor(R.color.legacy_icon_background);
-        ColorDrawable colorDrawable = new ColorDrawable(color);
-        ScaleDrawable scaleDrawable = new ScaleDrawable(drawable,
-                Gravity.CENTER, LEGACY_ICON_SCALE, LEGACY_ICON_SCALE);
-        scaleDrawable.setLevel(1);
+
         try {
             Class clazz = Class.forName("android.graphics.drawable.MaskableIconDrawable");
-            if (!clazz.isAssignableFrom(drawable.getClass())){
+            if (!clazz.isAssignableFrom(drawable.getClass())) {
+                Drawable maskWrapper =
+                        context.getDrawable(R.drawable.mask_drawable_wrapper).mutate();
+                ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(maskWrapper))
+                        .setDrawable(drawable);
 
-                return (Drawable) clazz.getConstructor(Drawable.class, Drawable.class)
-                        .newInstance(colorDrawable, scaleDrawable);
+                return maskWrapper;
             }
         } catch (Exception e) {
             return drawable;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
new file mode 100644
index 0000000..bc07ce1
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -0,0 +1,289 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Rect;
+import android.util.Property;
+import android.util.SparseArray;
+import android.view.animation.LinearInterpolator;
+
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
+ */
+public class PreloadIconDrawable extends FastBitmapDrawable {
+
+    private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
+            new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
+                @Override
+                public Float get(PreloadIconDrawable object) {
+                    return object.mInternalStateProgress;
+                }
+
+                @Override
+                public void set(PreloadIconDrawable object, Float value) {
+                    object.setInternalProgress(value);
+                }
+            };
+
+    public static final int PATH_SIZE = 100;
+
+    private static final float PROGRESS_WIDTH = 7;
+    private static final float PROGRESS_GAP = 2;
+    private static final int MAX_PAINT_ALPHA = 255;
+
+    private static final long DURATION_SCALE = 500;
+
+    // The smaller the number, the faster the animation would be.
+    // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
+    private static final float COMPLETE_ANIM_FRACTION = 0.3f;
+
+    private static final int COLOR_TRACK = 0x77EEEEEE;
+    private static final int COLOR_SHADOW = 0x55000000;
+
+    private static final float SMALL_SCALE = 0.75f;
+
+    private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+
+    private final Matrix mTmpMatrix = new Matrix();
+    private final PathMeasure mPathMeasure = new PathMeasure();
+
+    // Path in [0, 100] bounds.
+    private final Path mProgressPath;
+
+    private final Path mScaledTrackPath;
+    private final Path mScaledProgressPath;
+    private final Paint mProgressPaint;
+
+    private Bitmap mShadowBitmap;
+    private int mIndicatorColor = 0;
+
+    private int mTrackAlpha;
+    private float mTrackLength;
+    private float mIconScale;
+
+    private boolean mRanFinishAnimation;
+
+    // Progress of the internal state. [0, 1] indicates the fraction of completed progress,
+    // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
+    private float mInternalStateProgress;
+
+    private ObjectAnimator mCurrentAnim;
+
+    /**
+     * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
+     */
+    public PreloadIconDrawable(Bitmap b, Path progressPath) {
+        super(b);
+        mProgressPath = progressPath;
+        mScaledTrackPath = new Path();
+        mScaledProgressPath = new Path();
+
+        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        mProgressPaint.setStyle(Paint.Style.STROKE);
+        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        setInternalProgress(0);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mTmpMatrix.setScale(
+                (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
+                (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
+        mTmpMatrix.postTranslate(
+                bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
+                bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
+
+        mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+        float scale = bounds.width() / PATH_SIZE;
+        mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
+
+        mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
+                (PROGRESS_GAP ) * scale);
+        mPathMeasure.setPath(mScaledTrackPath, true);
+        mTrackLength = mPathMeasure.getLength();
+
+        setInternalProgress(mInternalStateProgress);
+    }
+
+    private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
+        int key = (width << 16) | height;
+        WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
+        Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+        if (shadow != null) {
+            return shadow;
+        }
+        shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(shadow);
+        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
+        mProgressPaint.setColor(COLOR_TRACK);
+        mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
+        c.drawPath(mScaledTrackPath, mProgressPaint);
+        mProgressPaint.clearShadowLayer();
+        c.setBitmap(null);
+
+        sShadowCache.put(key, new WeakReference<>(shadow));
+        return shadow;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mRanFinishAnimation) {
+            super.draw(canvas);
+            return;
+        }
+
+        // Draw track.
+        mProgressPaint.setColor(mIndicatorColor);
+        mProgressPaint.setAlpha(mTrackAlpha);
+        if (mShadowBitmap != null) {
+            canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
+        }
+        canvas.drawPath(mScaledProgressPath, mProgressPaint);
+
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        Rect bounds = getBounds();
+
+        canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
+        drawInternal(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Updates the install progress based on the level
+     */
+    @Override
+    protected boolean onLevelChange(int level) {
+        // Run the animation if we have already been bound.
+        updateInternalState(level * 0.01f,  getBounds().width() > 0, false);
+        return true;
+    }
+
+    /**
+     * Runs the finish animation if it is has not been run after last call to
+     * {@link #onLevelChange}
+     */
+    public void maybePerformFinishedAnimation() {
+        // If the drawable was recently initialized, skip the progress animation.
+        if (mInternalStateProgress == 0) {
+            mInternalStateProgress = 1;
+        }
+        updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
+    }
+
+    public boolean hasNotCompleted() {
+        return !mRanFinishAnimation;
+    }
+
+    private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
+        if (mCurrentAnim != null) {
+            mCurrentAnim.cancel();
+            mCurrentAnim = null;
+        }
+
+        if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
+            return;
+        }
+        if (!shouldAnimate || mRanFinishAnimation) {
+            setInternalProgress(finalProgress);
+        } else {
+            mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
+            mCurrentAnim.setDuration(
+                    (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
+            mCurrentAnim.setInterpolator(new LinearInterpolator());
+            if (isFinish) {
+                mCurrentAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mRanFinishAnimation = true;
+                    }
+                });
+            }
+            mCurrentAnim.start();
+        }
+
+    }
+
+    /**
+     * Sets the internal progress and updates the UI accordingly
+     *   for progress <= 0:
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is not visible
+     *   for 0 < progress < 1
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
+     *       {@link #mScaledTrackPath}.
+     *       @see PathMeasure#getSegment(float, float, Path, boolean)
+     *   for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
+     *     - we calculate fraction of progress in the above range
+     *     - progress track is drawn with alpha based on fraction
+     *     - progress bar is drawn at 100% with alpha based on fraction
+     *     - icon is scaled up based on fraction and is drawn in enabled state
+     *   for progress >= (1 + COMPLETE_ANIM_FRACTION)
+     *     - only icon is drawn in normal state
+     */
+    private void setInternalProgress(float progress) {
+        mInternalStateProgress = progress;
+        if (progress <= 0) {
+            mIconScale = SMALL_SCALE;
+            mScaledTrackPath.reset();
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (mIndicatorColor == 0) {
+            // Update the indicator color
+            mIndicatorColor = getIconPalette().getPreloadProgressColor();
+        }
+
+        if (progress < 1 && progress > 0) {
+            mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
+            mIconScale = SMALL_SCALE;
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (progress >= 1) {
+            setIsDisabled(false);
+            mScaledTrackPath.set(mScaledProgressPath);
+            float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
+
+            if (fraction >= 1) {
+                // Animation has completed
+                mIconScale = 1;
+                mTrackAlpha = 0;
+            } else {
+                mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
+                mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
+            }
+        }
+        invalidateSelf();
+    }
+}