Merge "1. Replace use of java.util.HashSet with android.util.ArraySet in NotificationListener. 2. Make few instance variable final. 3. Fix an unchecked cast." into ub-launcher3-dorval-polish
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index d0d33a0..d07e330 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
@@ -102,8 +103,8 @@
     private OnTouchListener mInterceptTouchListener;
     private final StylusEventHelper mStylusEventHelper;
 
-    private final ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<>();
-    final FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
+    private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
+    final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
 
     private float mBackgroundAlpha;
 
@@ -495,7 +496,7 @@
         }
 
         for (int i = 0; i < mFolderBackgrounds.size(); i++) {
-            FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
+            PreviewBackground bg = mFolderBackgrounds.get(i);
             cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
@@ -521,7 +522,7 @@
         super.dispatchDraw(canvas);
 
         for (int i = 0; i < mFolderBackgrounds.size(); i++) {
-            FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i);
+            PreviewBackground bg = mFolderBackgrounds.get(i);
             if (bg.isClipping) {
                 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
                 canvas.save();
@@ -532,10 +533,10 @@
         }
     }
 
-    public void addFolderBackground(FolderIcon.PreviewBackground bg) {
+    public void addFolderBackground(PreviewBackground bg) {
         mFolderBackgrounds.add(bg);
     }
-    public void removeFolderBackground(FolderIcon.PreviewBackground bg) {
+    public void removeFolderBackground(PreviewBackground bg) {
         mFolderBackgrounds.remove(bg);
     }
 
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 09ca5c5..ec30de8 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -170,7 +170,11 @@
     }
 
     public Drawable getFullResIcon(LauncherActivityInfo info) {
-        return mIconProvider.getIcon(info, mIconDpi);
+        return getFullResIcon(info, true);
+    }
+
+    public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
+        return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
     }
 
     protected Bitmap makeDefaultIcon(UserHandle user) {
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index a5d3990..6872515 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -24,8 +24,11 @@
         return mSystemState;
     }
 
-
-    public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
+    /**
+     * @param flattenDrawable true if the caller does not care about the specification of the
+     *                        original icon as long as the flattened version looks the same.
+     */
+    public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) {
         return info.getIcon(iconDpi);
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 517073a..0fabeeb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -71,6 +71,7 @@
 import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -254,7 +255,7 @@
     public static final int REORDER_TIMEOUT = 350;
     private final Alarm mFolderCreationAlarm = new Alarm();
     private final Alarm mReorderAlarm = new Alarm();
-    private FolderIcon.PreviewBackground mFolderCreateBg;
+    private PreviewBackground mFolderCreateBg;
     private FolderIcon mDragOverFolderIcon = null;
     private boolean mCreateUserFolderOnDrop = false;
     private boolean mAddToExistingFolderOnDrop = false;
@@ -2374,7 +2375,7 @@
                 // In order to keep everything continuous, we hand off the currently rendered
                 // folder background to the newly created icon. This preserves animation state.
                 fi.setFolderBackground(mFolderCreateBg);
-                mFolderCreateBg = new FolderIcon.PreviewBackground();
+                mFolderCreateBg = new PreviewBackground();
                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
                         postAnimationRunnable);
             } else {
@@ -3055,7 +3056,7 @@
         final int cellX;
         final int cellY;
 
-        final FolderIcon.PreviewBackground bg = new FolderIcon.PreviewBackground();
+        final PreviewBackground bg = new PreviewBackground();
 
         public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
             this.layout = layout;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index ab2f100..022b3b8 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -272,7 +272,7 @@
                     .resolveActivity(info.getIntent(), info.user);
             outObj[0] = activityInfo;
             return (activityInfo != null) ? appState.getIconCache()
-                    .getFullResIcon(activityInfo) : null;
+                    .getFullResIcon(activityInfo, false) : null;
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             if (info instanceof PendingAddShortcutInfo) {
                 ShortcutConfigActivityInfo activityInfo =
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 74e8d3b..cb86b59 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -57,7 +57,7 @@
     private GradientDrawable mFolderBackground;
 
     private FolderIcon mFolderIcon;
-    private FolderIcon.PreviewBackground mPreviewBackground;
+    private PreviewBackground mPreviewBackground;
 
     private Context mContext;
     private Launcher mLauncher;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index fa148c8..215a31c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -23,21 +23,12 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 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.Point;
-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.support.annotation.NonNull;
-import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.LayoutInflater;
@@ -57,7 +48,6 @@
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
 import com.android.launcher3.ItemInfo;
@@ -76,8 +66,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -101,8 +89,6 @@
     private CheckLongPressHelper mLongPressHelper;
     private StylusEventHelper mStylusEventHelper;
 
-    // The number of icons to display in the
-    private static final int CONSUMPTION_ANIMATION_DURATION = 100;
     private static final int DROP_IN_ANIMATION_DURATION = 400;
     private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
     private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
@@ -521,393 +507,6 @@
         canvas.restore();
     }
 
-    /**
-     * This object represents a FolderIcon preview background. It stores drawing / measurement
-     * 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 int mBgColor;
-        private float mStrokeWidth;
-        private int mStrokeAlpha = MAX_BG_OPACITY;
-        private int mShadowAlpha = 255;
-        private View mInvalidateDelegate;
-
-        public int previewSize;
-        private int basePreviewOffsetX;
-        private int basePreviewOffsetY;
-
-        private CellLayout mDrawingDelegate;
-        public int delegateCellX;
-        public int delegateCellY;
-
-        // When the PreviewBackground is drawn under an icon (for creating a folder) the border
-        // should not occlude the icon
-        public boolean isClipping = true;
-
-        // Drawing / animation configurations
-        private static final float ACCEPT_SCALE_FACTOR = 1.25f;
-        private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
-
-        // Expressed on a scale from 0 to 255.
-        private static final int BG_OPACITY = 160;
-        private static final int MAX_BG_OPACITY = 225;
-        private static final int SHADOW_OPACITY = 40;
-
-        ValueAnimator mScaleAnimator;
-        ObjectAnimator mStrokeAlphaAnimator;
-        ObjectAnimator mShadowAnimator;
-
-        private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
-                new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
-                    @Override
-                    public Integer get(PreviewBackground previewBackground) {
-                        return previewBackground.mStrokeAlpha;
-                    }
-
-                    @Override
-                    public void set(PreviewBackground previewBackground, Integer alpha) {
-                        previewBackground.mStrokeAlpha = alpha;
-                        previewBackground.invalidate();
-                    }
-                };
-
-        private static final Property<PreviewBackground, Integer> SHADOW_ALPHA =
-                new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") {
-                    @Override
-                    public Integer get(PreviewBackground previewBackground) {
-                        return previewBackground.mShadowAlpha;
-                    }
-
-                    @Override
-                    public void set(PreviewBackground previewBackground, Integer alpha) {
-                        previewBackground.mShadowAlpha = alpha;
-                        previewBackground.invalidate();
-                    }
-                };
-
-        public void setup(Launcher launcher, View invalidateDelegate,
-                   int availableSpace, int topPadding) {
-            mInvalidateDelegate = invalidateDelegate;
-            mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary);
-
-            DeviceProfile grid = launcher.getDeviceProfile();
-            final int previewSize = grid.folderIconSizePx;
-            final int previewPadding = grid.folderIconPreviewPadding;
-
-            this.previewSize = (previewSize - 2 * previewPadding);
-
-            basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
-            basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
-
-            // Stroke width is 1dp
-            mStrokeWidth = launcher.getResources().getDisplayMetrics().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();
-        }
-
-        int getRadius() {
-            return previewSize / 2;
-        }
-
-        int getScaledRadius() {
-            return (int) (mScale * getRadius());
-        }
-
-        int getOffsetX() {
-            return basePreviewOffsetX - (getScaledRadius() - getRadius());
-        }
-
-        int getOffsetY() {
-            return basePreviewOffsetY - (getScaledRadius() - getRadius());
-        }
-
-        /**
-         * Returns the progress of the scale animation, where 0 means the scale is at 1f
-         * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
-         */
-        float getScaleProgress() {
-            return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
-        }
-
-        void invalidate() {
-            if (mInvalidateDelegate != null) {
-                mInvalidateDelegate.invalidate();
-            }
-
-            if (mDrawingDelegate != null) {
-                mDrawingDelegate.invalidate();
-            }
-        }
-
-        void setInvalidateDelegate(View invalidateDelegate) {
-            mInvalidateDelegate = invalidateDelegate;
-            invalidate();
-        }
-
-        public void drawBackground(Canvas canvas) {
-            mPaint.setStyle(Paint.Style.FILL);
-            int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
-            mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, alpha));
-
-            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);
-
-            } else {
-                saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-                clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
-            }
-
-            mShaderMatrix.setScale(shadowRadius, shadowRadius);
-            mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
-            mShadowShader.setLocalMatrix(mShaderMatrix);
-            mPaint.setAlpha(mShadowAlpha);
-            mPaint.setShader(mShadowShader);
-            canvas.drawPaint(mPaint);
-            mPaint.setAlpha(255);
-            mPaint.setShader(null);
-            if (canvas.isHardwareAccelerated()) {
-                mPaint.setXfermode(mShadowPorterDuffXfermode);
-                canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
-                mPaint.setXfermode(null);
-            }
-
-            canvas.restoreToCount(saveCount);
-        }
-
-        public void fadeInBackgroundShadow() {
-            if (mShadowAnimator != null) {
-                mShadowAnimator.cancel();
-            }
-            mShadowAnimator = ObjectAnimator
-                    .ofInt(this, SHADOW_ALPHA, 0, 255)
-                    .setDuration(100);
-            mShadowAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mShadowAnimator = null;
-                }
-            });
-            mShadowAnimator.start();
-        }
-
-        public void animateBackgroundStroke() {
-            if (mStrokeAlphaAnimator != null) {
-                mStrokeAlphaAnimator.cancel();
-            }
-            mStrokeAlphaAnimator = ObjectAnimator
-                    .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY)
-                    .setDuration(100);
-            mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mStrokeAlphaAnimator = null;
-                }
-            });
-            mStrokeAlphaAnimator.start();
-        }
-
-        public void drawBackgroundStroke(Canvas canvas) {
-            mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
-            mPaint.setStyle(Paint.Style.STROKE);
-            mPaint.setStrokeWidth(mStrokeWidth);
-            drawCircle(canvas, 1 /* deltaRadius */);
-        }
-
-        public void drawLeaveBehind(Canvas canvas) {
-            float originalScale = mScale;
-            mScale = 0.5f;
-
-            mPaint.setStyle(Paint.Style.FILL);
-            mPaint.setColor(Color.argb(160, 245, 245, 245));
-            drawCircle(canvas, 0 /* deltaRadius */);
-
-            mScale = originalScale;
-        }
-
-        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) {
-            if (mDrawingDelegate != delegate) {
-                delegate.addFolderBackground(this);
-            }
-
-            mDrawingDelegate = delegate;
-            delegateCellX = cellX;
-            delegateCellY = cellY;
-
-            invalidate();
-        }
-
-        private void clearDrawingDelegate() {
-            if (mDrawingDelegate != null) {
-                mDrawingDelegate.removeFolderBackground(this);
-            }
-
-            mDrawingDelegate = null;
-            invalidate();
-        }
-
-        private boolean drawingDelegated() {
-            return mDrawingDelegate != null;
-        }
-
-        private void animateScale(float finalScale, float finalMultiplier,
-                final Runnable onStart, final Runnable onEnd) {
-            final float scale0 = mScale;
-            final float scale1 = finalScale;
-
-            final float bgMultiplier0 = mColorMultiplier;
-            final float bgMultiplier1 = finalMultiplier;
-
-            if (mScaleAnimator != null) {
-                mScaleAnimator.cancel();
-            }
-
-            mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
-
-            mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float prog = animation.getAnimatedFraction();
-                    mScale = prog * scale1 + (1 - prog) * scale0;
-                    mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
-                    invalidate();
-                }
-            });
-            mScaleAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    if (onStart != null) {
-                        onStart.run();
-                    }
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (onEnd != null) {
-                        onEnd.run();
-                    }
-                    mScaleAnimator = null;
-                }
-            });
-
-            mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
-            mScaleAnimator.start();
-        }
-
-        public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
-            Runnable onStart = new Runnable() {
-                @Override
-                public void run() {
-                    delegateDrawing(cl, cellX, cellY);
-                }
-            };
-            animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
-        }
-
-        public void animateToRest() {
-            // This can be called multiple times -- we need to make sure the drawing delegate
-            // is saved and restored at the beginning of the animation, since cancelling the
-            // existing animation can clear the delgate.
-            final CellLayout cl = mDrawingDelegate;
-            final int cellX = delegateCellX;
-            final int cellY = delegateCellY;
-
-            Runnable onStart = new Runnable() {
-                @Override
-                public void run() {
-                    delegateDrawing(cl, cellX, cellY);
-                }
-            };
-            Runnable onEnd = new Runnable() {
-                @Override
-                public void run() {
-                    clearDrawingDelegate();
-                }
-            };
-            animateScale(1f, 1f, onStart, onEnd);
-        }
-
-        public int getBackgroundAlpha() {
-            return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
-        }
-
-        public float getStrokeWidth() {
-            return mStrokeWidth;
-        }
-    }
-
     public void setFolderBackground(PreviewBackground bg) {
         mBackground = bg;
         mBackground.setInvalidateDelegate(this);
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
new file mode 100644
index 0000000..44ebbcd
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -0,0 +1,430 @@
+/*
+ * 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.folder;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+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.Region;
+import android.graphics.Shader;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.util.Themes;
+
+/**
+ * This object represents a FolderIcon preview background. It stores drawing / measurement
+ * information, handles drawing, and animation (accept state <--> rest state).
+ */
+public class PreviewBackground {
+
+    private static final int CONSUMPTION_ANIMATION_DURATION = 100;
+
+    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);
+
+    float mScale = 1f;
+    private float mColorMultiplier = 1f;
+    private int mBgColor;
+    private float mStrokeWidth;
+    private int mStrokeAlpha = MAX_BG_OPACITY;
+    private int mShadowAlpha = 255;
+    private View mInvalidateDelegate;
+
+    int previewSize;
+    int basePreviewOffsetX;
+    int basePreviewOffsetY;
+
+    private CellLayout mDrawingDelegate;
+    public int delegateCellX;
+    public int delegateCellY;
+
+    // When the PreviewBackground is drawn under an icon (for creating a folder) the border
+    // should not occlude the icon
+    public boolean isClipping = true;
+
+    // Drawing / animation configurations
+    private static final float ACCEPT_SCALE_FACTOR = 1.25f;
+    private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
+
+    // Expressed on a scale from 0 to 255.
+    private static final int BG_OPACITY = 160;
+    private static final int MAX_BG_OPACITY = 225;
+    private static final int SHADOW_OPACITY = 40;
+
+    private ValueAnimator mScaleAnimator;
+    private ObjectAnimator mStrokeAlphaAnimator;
+    private ObjectAnimator mShadowAnimator;
+
+    private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
+            new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
+                @Override
+                public Integer get(PreviewBackground previewBackground) {
+                    return previewBackground.mStrokeAlpha;
+                }
+
+                @Override
+                public void set(PreviewBackground previewBackground, Integer alpha) {
+                    previewBackground.mStrokeAlpha = alpha;
+                    previewBackground.invalidate();
+                }
+            };
+
+    private static final Property<PreviewBackground, Integer> SHADOW_ALPHA =
+            new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") {
+                @Override
+                public Integer get(PreviewBackground previewBackground) {
+                    return previewBackground.mShadowAlpha;
+                }
+
+                @Override
+                public void set(PreviewBackground previewBackground, Integer alpha) {
+                    previewBackground.mShadowAlpha = alpha;
+                    previewBackground.invalidate();
+                }
+            };
+
+    public void setup(Launcher launcher, View invalidateDelegate,
+                      int availableSpace, int topPadding) {
+        mInvalidateDelegate = invalidateDelegate;
+        mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary);
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        final int previewSize = grid.folderIconSizePx;
+        final int previewPadding = grid.folderIconPreviewPadding;
+
+        this.previewSize = (previewSize - 2 * previewPadding);
+
+        basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
+        basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
+
+        // Stroke width is 1dp
+        mStrokeWidth = launcher.getResources().getDisplayMetrics().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();
+    }
+
+    int getRadius() {
+        return previewSize / 2;
+    }
+
+    int getScaledRadius() {
+        return (int) (mScale * getRadius());
+    }
+
+    int getOffsetX() {
+        return basePreviewOffsetX - (getScaledRadius() - getRadius());
+    }
+
+    int getOffsetY() {
+        return basePreviewOffsetY - (getScaledRadius() - getRadius());
+    }
+
+    /**
+     * Returns the progress of the scale animation, where 0 means the scale is at 1f
+     * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
+     */
+    float getScaleProgress() {
+        return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
+    }
+
+    void invalidate() {
+        if (mInvalidateDelegate != null) {
+            mInvalidateDelegate.invalidate();
+        }
+
+        if (mDrawingDelegate != null) {
+            mDrawingDelegate.invalidate();
+        }
+    }
+
+    void setInvalidateDelegate(View invalidateDelegate) {
+        mInvalidateDelegate = invalidateDelegate;
+        invalidate();
+    }
+
+    public void drawBackground(Canvas canvas) {
+        mPaint.setStyle(Paint.Style.FILL);
+        int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
+        mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, alpha));
+
+        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);
+
+        } else {
+            saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
+        }
+
+        mShaderMatrix.setScale(shadowRadius, shadowRadius);
+        mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
+        mShadowShader.setLocalMatrix(mShaderMatrix);
+        mPaint.setAlpha(mShadowAlpha);
+        mPaint.setShader(mShadowShader);
+        canvas.drawPaint(mPaint);
+        mPaint.setAlpha(255);
+        mPaint.setShader(null);
+        if (canvas.isHardwareAccelerated()) {
+            mPaint.setXfermode(mShadowPorterDuffXfermode);
+            canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+            mPaint.setXfermode(null);
+        }
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    public void fadeInBackgroundShadow() {
+        if (mShadowAnimator != null) {
+            mShadowAnimator.cancel();
+        }
+        mShadowAnimator = ObjectAnimator
+                .ofInt(this, SHADOW_ALPHA, 0, 255)
+                .setDuration(100);
+        mShadowAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mShadowAnimator = null;
+            }
+        });
+        mShadowAnimator.start();
+    }
+
+    public void animateBackgroundStroke() {
+        if (mStrokeAlphaAnimator != null) {
+            mStrokeAlphaAnimator.cancel();
+        }
+        mStrokeAlphaAnimator = ObjectAnimator
+                .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY)
+                .setDuration(100);
+        mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStrokeAlphaAnimator = null;
+            }
+        });
+        mStrokeAlphaAnimator.start();
+    }
+
+    public void drawBackgroundStroke(Canvas canvas) {
+        mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeWidth(mStrokeWidth);
+        drawCircle(canvas, 1 /* deltaRadius */);
+    }
+
+    public void drawLeaveBehind(Canvas canvas) {
+        float originalScale = mScale;
+        mScale = 0.5f;
+
+        mPaint.setStyle(Paint.Style.FILL);
+        mPaint.setColor(Color.argb(160, 245, 245, 245));
+        drawCircle(canvas, 0 /* deltaRadius */);
+
+        mScale = originalScale;
+    }
+
+    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.
+    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.
+    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) {
+        if (mDrawingDelegate != delegate) {
+            delegate.addFolderBackground(this);
+        }
+
+        mDrawingDelegate = delegate;
+        delegateCellX = cellX;
+        delegateCellY = cellY;
+
+        invalidate();
+    }
+
+    private void clearDrawingDelegate() {
+        if (mDrawingDelegate != null) {
+            mDrawingDelegate.removeFolderBackground(this);
+        }
+
+        mDrawingDelegate = null;
+        invalidate();
+    }
+
+    boolean drawingDelegated() {
+        return mDrawingDelegate != null;
+    }
+
+    private void animateScale(float finalScale, float finalMultiplier,
+                              final Runnable onStart, final Runnable onEnd) {
+        final float scale0 = mScale;
+        final float scale1 = finalScale;
+
+        final float bgMultiplier0 = mColorMultiplier;
+        final float bgMultiplier1 = finalMultiplier;
+
+        if (mScaleAnimator != null) {
+            mScaleAnimator.cancel();
+        }
+
+        mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
+
+        mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float prog = animation.getAnimatedFraction();
+                mScale = prog * scale1 + (1 - prog) * scale0;
+                mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
+                invalidate();
+            }
+        });
+        mScaleAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (onStart != null) {
+                    onStart.run();
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (onEnd != null) {
+                    onEnd.run();
+                }
+                mScaleAnimator = null;
+            }
+        });
+
+        mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+        mScaleAnimator.start();
+    }
+
+    public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
+        Runnable onStart = new Runnable() {
+            @Override
+            public void run() {
+                delegateDrawing(cl, cellX, cellY);
+            }
+        };
+        animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
+    }
+
+    public void animateToRest() {
+        // This can be called multiple times -- we need to make sure the drawing delegate
+        // is saved and restored at the beginning of the animation, since cancelling the
+        // existing animation can clear the delgate.
+        final CellLayout cl = mDrawingDelegate;
+        final int cellX = delegateCellX;
+        final int cellY = delegateCellY;
+
+        Runnable onStart = new Runnable() {
+            @Override
+            public void run() {
+                delegateDrawing(cl, cellX, cellY);
+            }
+        };
+        Runnable onEnd = new Runnable() {
+            @Override
+            public void run() {
+                clearDrawingDelegate();
+            }
+        };
+        animateScale(1f, 1f, onStart, onEnd);
+    }
+
+    public int getBackgroundAlpha() {
+        return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
+    }
+
+    public float getStrokeWidth() {
+        return mStrokeWidth;
+    }
+}