Updating the virtual preloader UX.

> No click feedback when in preloader mode
> No preloader UI when drawn in drag layer
> The preloader consists of a background 9 patch image and a circular progress
is drawn in the content region of the background.
> The preloader is drawn in a slightly larget area than the actual bounds to
make the circular progress more prominent compared to the icon.

issue: 15835307
Change-Id: Ifec3d93ecf1fac994d1128b517da3797247e7ed6
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index d9365cc..2972c4f 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -1,96 +1,143 @@
 package com.android.launcher3;
 
 import android.animation.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+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.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 
 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 ICON_SCALE_FACTOR = 0.6f;
+    private static final float MIN_SATUNATION = 0.2f;
+    private static final float MIN_LIGHTNESS = 0.6f;
 
-    private static Bitmap sProgressBg, sProgressFill;
+    private static final float ICON_SCALE_FACTOR = 0.5f;
+    private static final int DEFAULT_COLOR = 0xFF009688;
 
-    private final Rect mCanvasClipRect = new Rect();
-    private final RectF mRect = new RectF();
-    private final Path mProgressPath = new Path();
-    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+    private static final Rect sTempRect = new Rect();
 
+    private final RectF mIndicatorRect = new RectF();
+    private boolean mIndicatorRectDirty;
+
+    private final Paint mPaint;
     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 boolean mPathChanged;
 
     private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
     private ObjectAnimator mAnimator;
 
-    public PreloadIconDrawable(Drawable icon, Resources res) {
+    public PreloadIconDrawable(Drawable icon, Theme theme) {
         mIcon = icon;
 
-        setBounds(icon.getBounds());
-        mPathChanged = false;
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
 
-        if (sProgressBg == null) {
-            sProgressBg = BitmapFactory.decodeResource(res, R.drawable.bg_preloader);
+        setBounds(icon.getBounds());
+        applyTheme(theme);
+        onLevelChange(0);
+    }
+
+    @Override
+    public void applyTheme(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);
         }
-        if (sProgressFill == null) {
-            sProgressFill = BitmapFactory.decodeResource(res, R.drawable.bg_preloader_progress);
-        }
+        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 = getBounds();
-        if (canvas.getClipBounds(mCanvasClipRect) && !Rect.intersects(mCanvasClipRect, r)) {
+        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));
-            canvas.drawBitmap(sProgressBg, null, r, mPaint);
-            canvas.drawBitmap(sProgressFill, null, r, mPaint);
-            iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
+            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;
-            canvas.drawBitmap(sProgressBg, null, r, mPaint);
+            mBgDrawable.setAlpha(255);
+            mBgDrawable.draw(canvas);
 
             if (mProgress >= 100) {
-                canvas.drawBitmap(sProgressFill, null, r, mPaint);
+                canvas.drawOval(mIndicatorRect, mPaint);
             } else if (mProgress > 0) {
-                if (mPathChanged) {
-                    mProgressPath.reset();
-                    mProgressPath.moveTo(r.exactCenterX(), r.centerY());
-
-                    mRect.set(r);
-                    mProgressPath.arcTo(mRect, -90, mProgress * 3.6f);
-                    mProgressPath.close();
-                    mPathChanged = false;
-                }
-
-                canvas.save();
-                canvas.clipPath(mProgressPath);
-                canvas.drawBitmap(sProgressFill, null, r, mPaint);
-                canvas.restore();
+                canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
             }
         } else {
             iconScale = 1;
@@ -103,12 +150,6 @@
     }
 
     @Override
-    protected void onBoundsChange(Rect bounds) {
-        mIcon.setBounds(bounds);
-        mPathChanged = true;
-    }
-
-    @Override
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
@@ -126,7 +167,6 @@
     @Override
     protected boolean onLevelChange(int level) {
         mProgress = level;
-        mPathChanged = true;
 
         // Stop Animation
         if (mAnimator != null) {
@@ -134,6 +174,14 @@
             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).setGhostModeEnabled(level <= 0);
+        }
 
         invalidateSelf();
         return true;
@@ -165,4 +213,37 @@
     public float getAnimationProgress() {
         return mAnimationProgress;
     }
+
+    @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_SATUNATION) {
+            mIndicatorColor = DEFAULT_COLOR;
+            return mIndicatorColor;
+        }
+        hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
+        mIndicatorColor = Color.HSVToColor(hsv);
+        return mIndicatorColor;
+    }
 }