Merge "Updating FolderIcon drawing to avoid dependency on software layer." into ub-launcher3-dorval
am: 3ba48fa019

Change-Id: Id18f5a34264411582115cde8cddd0dc8ecded8df
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e0fcbf0..8179dad 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -105,7 +105,6 @@
 
     private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
     FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
-    Paint mFolderBgPaint = new Paint();
 
     private float mBackgroundAlpha;
 
@@ -501,9 +500,9 @@
             cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
-            bg.drawBackground(canvas, mFolderBgPaint);
+            bg.drawBackground(canvas);
             if (!bg.isClipping) {
-                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+                bg.drawBackgroundStroke(canvas);
             }
             canvas.restore();
         }
@@ -513,7 +512,7 @@
                     mFolderLeaveBehind.delegateCellY, mTempLocation);
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
-            mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint);
+            mFolderLeaveBehind.drawLeaveBehind(canvas);
             canvas.restore();
         }
     }
@@ -528,7 +527,7 @@
                 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
                 canvas.save();
                 canvas.translate(mTempLocation[0], mTempLocation[1]);
-                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+                bg.drawBackgroundStroke(canvas);
                 canvas.restore();
             }
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d84a9d2..92577b7 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -24,11 +24,15 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -126,8 +130,6 @@
     private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
     private Drawable mReferenceDrawable = null;
 
-    Paint mBgPaint = new Paint();
-
     private Alarm mOpenAlarm = new Alarm();
 
     private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
@@ -180,11 +182,6 @@
         DeviceProfile grid = launcher.getDeviceProfile();
         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
 
-        // For performance and compatibility reasons we render the preview using a software layer.
-        // In particular, hardware path clipping has spotty ecosystem support and bad performance.
-        // Software rendering also allows us to use shadow layers.
-        icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
-
         icon.setClipToPadding(false);
         icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
@@ -495,7 +492,7 @@
     }
 
     private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
-        canvas.save();
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
         canvas.translate(params.transX, params.transY);
         canvas.scale(params.scale, params.scale);
         Drawable d = params.drawable;
@@ -522,10 +519,29 @@
      * information, handles drawing, and animation (accept state <--> rest state).
      */
     public static class PreviewBackground {
+
+        private final PorterDuffXfermode mClipPorterDuffXfermode
+                = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+        // Create a RadialGradient such that it draws a black circle and then extends with
+        // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
+        // just at the edge quickly change it to transparent.
+        private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
+                new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
+                new float[] {0, 0.999f, 1},
+                Shader.TileMode.CLAMP);
+
+        private final PorterDuffXfermode mShadowPorterDuffXfermode
+                = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
+        private RadialGradient mShadowShader = null;
+
+        private final Matrix mShaderMatrix = new Matrix();
+        private final Path mPath = new Path();
+
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
         private float mScale = 1f;
         private float mColorMultiplier = 1f;
-        private Path mClipPath = new Path();
-        private int mStrokeWidth;
+        private float mStrokeWidth;
         private View mInvalidateDelegate;
 
         public int previewSize;
@@ -548,7 +564,7 @@
         private static final int BG_OPACITY = 160;
         private static final int MAX_BG_OPACITY = 225;
         private static final int BG_INTENSITY = 245;
-        private static final int SHADOW_OPACITY = 80;
+        private static final int SHADOW_OPACITY = 40;
 
         ValueAnimator mScaleAnimator;
 
@@ -564,7 +580,16 @@
             basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
             basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
 
-            mStrokeWidth = Utilities.pxFromDp(1, dm);
+            // Stroke width is 1dp
+            mStrokeWidth = dm.density;
+
+            float radius = getScaledRadius();
+            float shadowRadius = radius + mStrokeWidth;
+            int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
+            mShadowShader = new RadialGradient(0, 0, 1,
+                    new int[] {shadowColor, Color.TRANSPARENT},
+                    new float[] {radius / shadowRadius, 1},
+                    Shader.TileMode.CLAMP);
 
             invalidate();
         }
@@ -594,10 +619,6 @@
         }
 
         void invalidate() {
-            int radius = getScaledRadius();
-            mClipPath.reset();
-            mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
-
             if (mInvalidateDelegate != null) {
                 mInvalidateDelegate.invalidate();
             }
@@ -612,70 +633,94 @@
             invalidate();
         }
 
-        public void drawBackground(Canvas canvas, Paint paint) {
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
-
-            paint.reset();
-            paint.setStyle(Paint.Style.FILL);
-            paint.setXfermode(null);
-            paint.setAntiAlias(true);
-
+        public void drawBackground(Canvas canvas) {
+            mPaint.setStyle(Paint.Style.FILL);
             int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
-            paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+            mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
 
+            drawCircle(canvas, 0 /* deltaRadius */);
+
+            // Draw shadow.
+            if (mShadowShader == null) {
+                return;
+            }
             float radius = getScaledRadius();
+            float shadowRadius = radius + mStrokeWidth;
+            mPaint.setColor(Color.BLACK);
+            int offsetX = getOffsetX();
+            int offsetY = getOffsetY();
+            final int saveCount;
+            if (canvas.isHardwareAccelerated()) {
+                saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
+                        offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
+                        null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
 
-            canvas.drawCircle(radius, radius, radius, paint);
-            canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
+            } else {
+                saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+                clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
+            }
 
-            paint.setStyle(Paint.Style.STROKE);
-            paint.setColor(Color.TRANSPARENT);
-            paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
-            canvas.drawCircle(radius, radius, radius, paint);
+            mShaderMatrix.setScale(shadowRadius, shadowRadius);
+            mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
+            mShadowShader.setLocalMatrix(mShaderMatrix);
+            mPaint.setShader(mShadowShader);
+            canvas.drawPaint(mPaint);
+            mPaint.setShader(null);
 
-            canvas.restore();
+            if (canvas.isHardwareAccelerated()) {
+                mPaint.setXfermode(mShadowPorterDuffXfermode);
+                canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+                mPaint.setXfermode(null);
+            }
+
+            canvas.restoreToCount(saveCount);
         }
 
-        public void drawBackgroundStroke(Canvas canvas, Paint paint) {
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
-
-            paint.reset();
-            paint.setAntiAlias(true);
-            paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
-            paint.setStyle(Paint.Style.STROKE);
-            paint.setStrokeWidth(mStrokeWidth);
-
-            float radius = getScaledRadius();
-            canvas.drawCircle(radius, radius, radius - 1, paint);
-
-            canvas.restore();
+        public void drawBackgroundStroke(Canvas canvas) {
+            mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+            mPaint.setStyle(Paint.Style.STROKE);
+            mPaint.setStrokeWidth(mStrokeWidth);
+            drawCircle(canvas, 1 /* deltaRadius */);
         }
 
-        public void drawLeaveBehind(Canvas canvas, Paint paint) {
+        public void drawLeaveBehind(Canvas canvas) {
             float originalScale = mScale;
             mScale = 0.5f;
 
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
+            mPaint.setStyle(Paint.Style.FILL);
+            mPaint.setColor(Color.argb(160, 245, 245, 245));
+            drawCircle(canvas, 0 /* deltaRadius */);
 
-            paint.reset();
-            paint.setAntiAlias(true);
-            paint.setColor(Color.argb(160, 245, 245, 245));
-
-            float radius = getScaledRadius();
-            canvas.drawCircle(radius, radius, radius, paint);
-
-            canvas.restore();
             mScale = originalScale;
         }
 
-        // It is the callers responsibility to save and restore the canvas.
-        private void clipCanvas(Canvas canvas) {
-            canvas.translate(getOffsetX(), getOffsetY());
-            canvas.clipPath(mClipPath);
-            canvas.translate(-getOffsetX(), -getOffsetY());
+        private void drawCircle(Canvas canvas,float deltaRadius) {
+            float radius = getScaledRadius();
+            canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
+                    radius - deltaRadius, mPaint);
+        }
+
+        // It is the callers responsibility to save and restore the canvas layers.
+        private void clipCanvasSoftware(Canvas canvas, Region.Op op) {
+            mPath.reset();
+            float r = getScaledRadius();
+            mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+            canvas.clipPath(mPath, op);
+        }
+
+        // It is the callers responsibility to save and restore the canvas layers.
+        private void clipCanvasHardware(Canvas canvas) {
+            mPaint.setColor(Color.BLACK);
+            mPaint.setXfermode(mClipPorterDuffXfermode);
+
+            float radius = getScaledRadius();
+            mShaderMatrix.setScale(radius, radius);
+            mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
+            mClipShader.setLocalMatrix(mShaderMatrix);
+            mPaint.setShader(mClipShader);
+            canvas.drawPaint(mPaint);
+            mPaint.setXfermode(null);
+            mPaint.setShader(null);
         }
 
         private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
@@ -795,17 +840,22 @@
         }
 
         if (!mBackground.drawingDelegated()) {
-            mBackground.drawBackground(canvas, mBgPaint);
+            mBackground.drawBackground(canvas);
         }
 
         if (mFolder == null) return;
         if (mFolder.getItemCount() == 0 && !mAnimating) return;
 
-        canvas.save();
+        final int saveCount;
 
-
-        if (mPreviewLayoutRule.clipToBackground()) {
-            mBackground.clipCanvas(canvas);
+        if (canvas.isHardwareAccelerated()) {
+            saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+        } else {
+            saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            if (mPreviewLayoutRule.clipToBackground()) {
+                mBackground.clipCanvasSoftware(canvas, Region.Op.INTERSECT);
+            }
         }
 
         // The items are drawn in coordinates relative to the preview offset
@@ -818,18 +868,24 @@
                 drawPreviewItem(canvas, p);
             }
         }
-        canvas.restore();
+        canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY);
+
+        if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
+            mBackground.clipCanvasHardware(canvas);
+        }
+        canvas.restoreToCount(saveCount);
 
         if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
-            mBackground.drawBackgroundStroke(canvas, mBgPaint);
+            mBackground.drawBackgroundStroke(canvas);
         }
 
-        int offsetX = mBackground.getOffsetX();
-        int offsetY = mBackground.getOffsetY();
-        int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
-        Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
         if ((mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0) {
             // If we are animating to the accepting state, animate the badge out.
+            int offsetX = mBackground.getOffsetX();
+            int offsetY = mBackground.getOffsetY();
+            int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
+            Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+
             float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
             mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds, badgeScale);
         }