Merge "Convert IconShape to Kotlin" into main
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
deleted file mode 100644
index cb14587..0000000
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright (C) 2018 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 static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.util.AttributeSet;
-import android.util.Xml;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.launcher3.views.ClipPathView;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Abstract representation of the shape of an icon shape
- */
-@LauncherAppSingleton
-public final class IconShape {
-
-    public static DaggerSingletonObject<IconShape> INSTANCE =
-            new DaggerSingletonObject<>(LauncherBaseAppComponent::getIconShape);
-
-    private ShapeDelegate mDelegate = new Circle();
-    private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
-
-    @Inject
-    public IconShape(@ApplicationContext Context context) {
-        pickBestShape(context);
-    }
-
-    public ShapeDelegate getShape() {
-        return mDelegate;
-    }
-
-    public float getNormalizationScale() {
-        return mNormalizationScale;
-    }
-
-    /**
-     * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
-     */
-    public void pickBestShape(Context context) {
-        // Pick any large size
-        final int size = 200;
-
-        Region full = new Region(0, 0, size, size);
-        Region iconR = new Region();
-        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
-                new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
-        drawable.setBounds(0, 0, size, size);
-        iconR.setPath(drawable.getIconMask(), full);
-
-        Path shapePath = new Path();
-        Region shapeR = new Region();
-
-        // Find the shape with minimum area of divergent region.
-        int minArea = Integer.MAX_VALUE;
-        ShapeDelegate closestShape = null;
-        for (ShapeDelegate shape : getAllShapes(context)) {
-            shapePath.reset();
-            shape.addToPath(shapePath, 0, 0, size / 2f);
-            shapeR.setPath(shapePath, full);
-            shapeR.op(iconR, Op.XOR);
-
-            int area = GraphicsUtils.getArea(shapeR);
-            if (area < minArea) {
-                minArea = area;
-                closestShape = shape;
-            }
-        }
-
-        if (closestShape != null) {
-            mDelegate = closestShape;
-        }
-
-        // Initialize shape properties
-        mNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
-    }
-
-
-
-    public interface ShapeDelegate {
-
-        default boolean enableShapeDetection() {
-            return false;
-        }
-
-        void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint paint);
-
-        void addToPath(Path path, float offsetX, float offsetY, float radius);
-
-        <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed);
-    }
-
-    /**
-     * Abstract shape where the reveal animation is a derivative of a round rect animation
-     */
-    private static abstract class SimpleRectShape implements ShapeDelegate {
-
-        @Override
-        public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
-            return new RoundedRectRevealOutlineProvider(
-                    getStartRadius(startRect), endRadius, startRect, endRect) {
-                @Override
-                public boolean shouldRemoveElevationDuringAnimation() {
-                    return true;
-                }
-            }.createRevealAnimator(target, isReversed);
-        }
-
-        protected abstract float getStartRadius(Rect startRect);
-    }
-
-    /**
-     * Abstract shape which draws using {@link Path}
-     */
-    private static abstract class PathShape implements ShapeDelegate {
-
-        private final Path mTmpPath = new Path();
-
-        @Override
-        public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
-                Paint paint) {
-            mTmpPath.reset();
-            addToPath(mTmpPath, offsetX, offsetY, radius);
-            canvas.drawPath(mTmpPath, paint);
-        }
-
-        protected abstract AnimatorUpdateListener newUpdateListener(
-                Rect startRect, Rect endRect, float endRadius, Path outPath);
-
-        @Override
-        public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
-            Path path = new Path();
-            AnimatorUpdateListener listener =
-                    newUpdateListener(startRect, endRect, endRadius, path);
-
-            ValueAnimator va =
-                    isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
-            va.addListener(new AnimatorListenerAdapter() {
-                private ViewOutlineProvider mOldOutlineProvider;
-
-                public void onAnimationStart(Animator animation) {
-                    mOldOutlineProvider = target.getOutlineProvider();
-                    target.setOutlineProvider(null);
-
-                    target.setTranslationZ(-target.getElevation());
-                }
-
-                public void onAnimationEnd(Animator animation) {
-                    target.setTranslationZ(0);
-                    target.setClipPath(null);
-                    target.setOutlineProvider(mOldOutlineProvider);
-                }
-            });
-
-            va.addUpdateListener((anim) -> {
-                path.reset();
-                listener.onAnimationUpdate(anim);
-                target.setClipPath(path);
-            });
-
-            return va;
-        }
-    }
-
-    public static final class Circle extends PathShape {
-
-        private final float[] mTempRadii = new float[8];
-
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endRadius, Path outPath) {
-            float r1 = getStartRadius(startRect);
-
-            float[] startValues = new float[] {
-                    startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r1};
-            float[] endValues = new float[] {
-                    endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
-            FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-                float[] values = evaluator.evaluate(progress, startValues, endValues);
-                outPath.addRoundRect(
-                        values[0], values[1], values[2], values[3],
-                        getRadiiArray(values[4], values[5]), Path.Direction.CW);
-            };
-        }
-
-        private float[] getRadiiArray(float r1, float r2) {
-            mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
-                    mTempRadii[6] = mTempRadii[7] = r1;
-            mTempRadii[4] = mTempRadii[5] = r2;
-            return mTempRadii;
-        }
-
-
-        @Override
-        public void addToPath(Path path, float offsetX, float offsetY, float radius) {
-            path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
-        }
-
-        protected float getStartRadius(Rect startRect) {
-            return startRect.width() / 2f;
-        }
-
-        @Override
-        public boolean enableShapeDetection() {
-            return true;
-        }
-    }
-
-    private static class RoundedSquare extends SimpleRectShape {
-
-        /**
-         * Ratio of corner radius to half size.
-         */
-        private final float mRadiusRatio;
-
-        public RoundedSquare(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            float cr = radius * mRadiusRatio;
-            canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
-        }
-
-        @Override
-        public void addToPath(Path path, float offsetX, float offsetY, float radius) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            float cr = radius * mRadiusRatio;
-            path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
-                    Path.Direction.CW);
-        }
-
-        @Override
-        protected float getStartRadius(Rect startRect) {
-            return (startRect.width() / 2f) * mRadiusRatio;
-        }
-    }
-
-    private static class TearDrop extends PathShape {
-
-        /**
-         * Radio of short radius to large radius, based on the shape options defined in the config.
-         */
-        private final float mRadiusRatio;
-        private final float[] mTempRadii = new float[8];
-
-        public TearDrop(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void addToPath(Path p, float offsetX, float offsetY, float r1) {
-            float r2 = r1 * mRadiusRatio;
-            float cx = r1 + offsetX;
-            float cy = r1 + offsetY;
-
-            p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
-                    Path.Direction.CW);
-        }
-
-        private float[] getRadiiArray(float r1, float r2) {
-            mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
-                    mTempRadii[6] = mTempRadii[7] = r1;
-            mTempRadii[4] = mTempRadii[5] = r2;
-            return mTempRadii;
-        }
-
-        @Override
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endRadius, Path outPath) {
-            float r1 = startRect.width() / 2f;
-            float r2 = r1 * mRadiusRatio;
-
-            float[] startValues = new float[] {
-                    startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
-            float[] endValues = new float[] {
-                    endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
-            FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-                float[] values = evaluator.evaluate(progress, startValues, endValues);
-                outPath.addRoundRect(
-                        values[0], values[1], values[2], values[3],
-                        getRadiiArray(values[4], values[5]), Path.Direction.CW);
-            };
-        }
-    }
-
-    private static class Squircle extends PathShape {
-
-        /**
-         * Radio of radius to circle radius, based on the shape options defined in the config.
-         */
-        private final float mRadiusRatio;
-
-        public Squircle(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void addToPath(Path p, float offsetX, float offsetY, float r) {
-            float cx = r + offsetX;
-            float cy = r + offsetY;
-            float control = r - r * mRadiusRatio;
-
-            p.moveTo(cx, cy - r);
-            addLeftCurve(cx, cy, r, control, p);
-            addRightCurve(cx, cy, r, control, p);
-            addLeftCurve(cx, cy, -r, -control, p);
-            addRightCurve(cx, cy, -r, -control, p);
-            p.close();
-        }
-
-        private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
-            path.cubicTo(
-                    cx - control, cy - r,
-                    cx - r, cy - control,
-                    cx - r, cy);
-        }
-
-        private void addRightCurve(float cx, float cy, float r, float control, Path path) {
-            path.cubicTo(
-                    cx - r, cy + control,
-                    cx - control, cy + r,
-                    cx, cy + r);
-        }
-
-        @Override
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endR, Path outPath) {
-
-            float startCX = startRect.exactCenterX();
-            float startCY = startRect.exactCenterY();
-            float startR = startRect.width() / 2f;
-            float startControl = startR - startR * mRadiusRatio;
-            float startHShift = 0;
-            float startVShift = 0;
-
-            float endCX = endRect.exactCenterX();
-            float endCY = endRect.exactCenterY();
-            // Approximate corner circle using bezier curves
-            // http://spencermortensen.com/articles/bezier-circle/
-            float endControl = endR * 0.551915024494f;
-            float endHShift = endRect.width() / 2f - endR;
-            float endVShift = endRect.height() / 2f - endR;
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-
-                float cx = (1 - progress) * startCX + progress * endCX;
-                float cy = (1 - progress) * startCY + progress * endCY;
-                float r = (1 - progress) * startR + progress * endR;
-                float control = (1 - progress) * startControl + progress * endControl;
-                float hShift = (1 - progress) * startHShift + progress * endHShift;
-                float vShift = (1 - progress) * startVShift + progress * endVShift;
-
-                outPath.moveTo(cx, cy - vShift - r);
-                outPath.rLineTo(-hShift, 0);
-
-                addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
-                outPath.rLineTo(0, vShift + vShift);
-
-                addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
-                outPath.rLineTo(hShift + hShift, 0);
-
-                addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
-                outPath.rLineTo(0, -vShift - vShift);
-
-                addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
-                outPath.close();
-            };
-        }
-    }
-
-    private static ShapeDelegate getShapeDefinition(String type, float radius) {
-        switch (type) {
-            case "Circle":
-                return new Circle();
-            case "RoundedSquare":
-                return new RoundedSquare(radius);
-            case "TearDrop":
-                return new TearDrop(radius);
-            case "Squircle":
-                return new Squircle(radius);
-            default:
-                throw new IllegalArgumentException("Invalid shape type: " + type);
-        }
-    }
-
-    private static List<ShapeDelegate> getAllShapes(Context context) {
-        ArrayList<ShapeDelegate> result = new ArrayList<>();
-        try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
-
-            // Find the root tag
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_TAG
-                    && type != XmlPullParser.END_DOCUMENT
-                    && !"shapes".equals(parser.getName()));
-
-            final int depth = parser.getDepth();
-            int[] radiusAttr = new int[] {R.attr.folderIconRadius};
-
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
-                if (type == XmlPullParser.START_TAG) {
-                    AttributeSet attrs = Xml.asAttributeSet(parser);
-                    TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
-                    ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
-                    a.recycle();
-
-                    result.add(shape);
-                }
-            }
-        } catch (IOException | XmlPullParserException e) {
-            throw new RuntimeException(e);
-        }
-        return result;
-    }
-
-}
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
new file mode 100644
index 0000000..22d3f3d
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2018 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.FloatArrayEvaluator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.Region
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.util.Xml
+import android.view.View
+import android.view.ViewOutlineProvider
+import com.android.launcher3.R
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.icons.GraphicsUtils
+import com.android.launcher3.icons.IconNormalizer
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.views.ClipPathView
+import java.io.IOException
+import javax.inject.Inject
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+/** Abstract representation of the shape of an icon shape */
+@LauncherAppSingleton
+class IconShape @Inject constructor(@ApplicationContext context: Context) {
+    var shape: ShapeDelegate = Circle()
+        private set
+
+    var normalizationScale: Float = IconNormalizer.ICON_VISIBLE_AREA_FACTOR
+        private set
+
+    init {
+        pickBestShape(context)
+    }
+
+    /** Initializes the shape which is closest to the [AdaptiveIconDrawable] */
+    fun pickBestShape(context: Context) {
+        // Pick any large size
+        val size = 200
+        val full = Region(0, 0, size, size)
+        val shapePath = Path()
+        val shapeR = Region()
+        val iconR = Region()
+        val drawable = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), ColorDrawable(Color.BLACK))
+        drawable.setBounds(0, 0, size, size)
+        iconR.setPath(drawable.iconMask, full)
+
+        // Find the shape with minimum area of divergent region.
+        var minArea = Int.MAX_VALUE
+        var closestShape: ShapeDelegate? = null
+        for (shape in getAllShapes(context)) {
+            shapePath.reset()
+            shape.addToPath(shapePath, 0f, 0f, size / 2f)
+            shapeR.setPath(shapePath, full)
+            shapeR.op(iconR, Region.Op.XOR)
+
+            val area = GraphicsUtils.getArea(shapeR)
+            if (area < minArea) {
+                minArea = area
+                closestShape = shape
+            }
+        }
+
+        if (closestShape != null) {
+            shape = closestShape
+        }
+
+        // Initialize shape properties
+        normalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null)
+    }
+
+    interface ShapeDelegate {
+        fun enableShapeDetection(): Boolean {
+            return false
+        }
+
+        fun drawShape(canvas: Canvas, offsetX: Float, offsetY: Float, radius: Float, paint: Paint)
+
+        fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float)
+
+        fun <T> createRevealAnimator(
+            target: T,
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            isReversed: Boolean,
+        ): ValueAnimator where T : View?, T : ClipPathView?
+    }
+
+    /** Abstract shape where the reveal animation is a derivative of a round rect animation */
+    private abstract class SimpleRectShape : ShapeDelegate {
+        override fun <T> createRevealAnimator(
+            target: T,
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            isReversed: Boolean,
+        ): ValueAnimator where T : View?, T : ClipPathView? {
+            return object :
+                    RoundedRectRevealOutlineProvider(
+                        getStartRadius(startRect),
+                        endRadius,
+                        startRect,
+                        endRect,
+                    ) {
+                    override fun shouldRemoveElevationDuringAnimation(): Boolean {
+                        return true
+                    }
+                }
+                .createRevealAnimator(target, isReversed)
+        }
+
+        protected abstract fun getStartRadius(startRect: Rect): Float
+    }
+
+    /** Abstract shape which draws using [Path] */
+    abstract class PathShape : ShapeDelegate {
+        private val mTmpPath = Path()
+
+        override fun drawShape(
+            canvas: Canvas,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            paint: Paint,
+        ) {
+            mTmpPath.reset()
+            addToPath(mTmpPath, offsetX, offsetY, radius)
+            canvas.drawPath(mTmpPath, paint)
+        }
+
+        protected abstract fun newUpdateListener(
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            outPath: Path,
+        ): ValueAnimator.AnimatorUpdateListener
+
+        override fun <T> createRevealAnimator(
+            target: T,
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            isReversed: Boolean,
+        ): ValueAnimator where T : View?, T : ClipPathView? {
+            val path = Path()
+            val listener = newUpdateListener(startRect, endRect, endRadius, path)
+
+            val va =
+                if (isReversed) ValueAnimator.ofFloat(1f, 0f) else ValueAnimator.ofFloat(0f, 1f)
+            va.addListener(
+                object : AnimatorListenerAdapter() {
+                    private var mOldOutlineProvider: ViewOutlineProvider? = null
+
+                    override fun onAnimationStart(animation: Animator) {
+                        target?.apply {
+                            mOldOutlineProvider = outlineProvider
+                            outlineProvider = null
+                            translationZ = -target.elevation
+                        }
+                    }
+
+                    override fun onAnimationEnd(animation: Animator) {
+                        target?.apply {
+                            translationZ = 0f
+                            setClipPath(null)
+                            outlineProvider = mOldOutlineProvider
+                        }
+                    }
+                }
+            )
+
+            va.addUpdateListener { anim: ValueAnimator ->
+                path.reset()
+                listener.onAnimationUpdate(anim)
+                target?.setClipPath(path)
+            }
+
+            return va
+        }
+    }
+
+    open class Circle : PathShape() {
+        private val mTempRadii = FloatArray(8)
+
+        override fun newUpdateListener(
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            outPath: Path,
+        ): ValueAnimator.AnimatorUpdateListener {
+            val r1 = getStartRadius(startRect)
+
+            val startValues =
+                floatArrayOf(
+                    startRect.left.toFloat(),
+                    startRect.top.toFloat(),
+                    startRect.right.toFloat(),
+                    startRect.bottom.toFloat(),
+                    r1,
+                    r1,
+                )
+            val endValues =
+                floatArrayOf(
+                    endRect.left.toFloat(),
+                    endRect.top.toFloat(),
+                    endRect.right.toFloat(),
+                    endRect.bottom.toFloat(),
+                    endRadius,
+                    endRadius,
+                )
+
+            val evaluator = FloatArrayEvaluator(FloatArray(6))
+
+            return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+                val progress = anim.animatedValue as Float
+                val values = evaluator.evaluate(progress, startValues, endValues)
+                outPath.addRoundRect(
+                    values[0],
+                    values[1],
+                    values[2],
+                    values[3],
+                    getRadiiArray(values[4], values[5]),
+                    Path.Direction.CW,
+                )
+            }
+        }
+
+        private fun getRadiiArray(r1: Float, r2: Float): FloatArray {
+            mTempRadii[7] = r1
+            mTempRadii[6] = mTempRadii[7]
+            mTempRadii[3] = mTempRadii[6]
+            mTempRadii[2] = mTempRadii[3]
+            mTempRadii[1] = mTempRadii[2]
+            mTempRadii[0] = mTempRadii[1]
+            mTempRadii[5] = r2
+            mTempRadii[4] = mTempRadii[5]
+            return mTempRadii
+        }
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW)
+        }
+
+        private fun getStartRadius(startRect: Rect): Float {
+            return startRect.width() / 2f
+        }
+
+        override fun enableShapeDetection(): Boolean {
+            return true
+        }
+    }
+
+    private class RoundedSquare(
+        /** Ratio of corner radius to half size. */
+        private val mRadiusRatio: Float
+    ) : SimpleRectShape() {
+        override fun drawShape(
+            canvas: Canvas,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            paint: Paint,
+        ) {
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+            val cr = radius * mRadiusRatio
+            canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, paint)
+        }
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+            val cr = radius * mRadiusRatio
+            path.addRoundRect(
+                cx - radius,
+                cy - radius,
+                cx + radius,
+                cy + radius,
+                cr,
+                cr,
+                Path.Direction.CW,
+            )
+        }
+
+        override fun getStartRadius(startRect: Rect): Float {
+            return (startRect.width() / 2f) * mRadiusRatio
+        }
+    }
+
+    private class TearDrop(
+        /**
+         * Radio of short radius to large radius, based on the shape options defined in the config.
+         */
+        private val mRadiusRatio: Float
+    ) : PathShape() {
+        private val mTempRadii = FloatArray(8)
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            val r2 = radius * mRadiusRatio
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+
+            path.addRoundRect(
+                cx - radius,
+                cy - radius,
+                cx + radius,
+                cy + radius,
+                getRadiiArray(radius, r2),
+                Path.Direction.CW,
+            )
+        }
+
+        fun getRadiiArray(r1: Float, r2: Float): FloatArray {
+            mTempRadii[7] = r1
+            mTempRadii[6] = mTempRadii[7]
+            mTempRadii[3] = mTempRadii[6]
+            mTempRadii[2] = mTempRadii[3]
+            mTempRadii[1] = mTempRadii[2]
+            mTempRadii[0] = mTempRadii[1]
+            mTempRadii[5] = r2
+            mTempRadii[4] = mTempRadii[5]
+            return mTempRadii
+        }
+
+        override fun newUpdateListener(
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            outPath: Path,
+        ): ValueAnimator.AnimatorUpdateListener {
+            val r1 = startRect.width() / 2f
+            val r2 = r1 * mRadiusRatio
+
+            val startValues =
+                floatArrayOf(
+                    startRect.left.toFloat(),
+                    startRect.top.toFloat(),
+                    startRect.right.toFloat(),
+                    startRect.bottom.toFloat(),
+                    r1,
+                    r2,
+                )
+            val endValues =
+                floatArrayOf(
+                    endRect.left.toFloat(),
+                    endRect.top.toFloat(),
+                    endRect.right.toFloat(),
+                    endRect.bottom.toFloat(),
+                    endRadius,
+                    endRadius,
+                )
+
+            val evaluator = FloatArrayEvaluator(FloatArray(6))
+
+            return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+                val progress = anim.animatedValue as Float
+                val values = evaluator.evaluate(progress, startValues, endValues)
+                outPath.addRoundRect(
+                    values[0],
+                    values[1],
+                    values[2],
+                    values[3],
+                    getRadiiArray(values[4], values[5]),
+                    Path.Direction.CW,
+                )
+            }
+        }
+    }
+
+    private class Squircle(
+        /** Radio of radius to circle radius, based on the shape options defined in the config. */
+        private val mRadiusRatio: Float
+    ) : PathShape() {
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+            val control = radius - radius * mRadiusRatio
+
+            path.moveTo(cx, cy - radius)
+            addLeftCurve(cx, cy, radius, control, path)
+            addRightCurve(cx, cy, radius, control, path)
+            addLeftCurve(cx, cy, -radius, -control, path)
+            addRightCurve(cx, cy, -radius, -control, path)
+            path.close()
+        }
+
+        fun addLeftCurve(cx: Float, cy: Float, r: Float, control: Float, path: Path) {
+            path.cubicTo(cx - control, cy - r, cx - r, cy - control, cx - r, cy)
+        }
+
+        fun addRightCurve(cx: Float, cy: Float, r: Float, control: Float, path: Path) {
+            path.cubicTo(cx - r, cy + control, cx - control, cy + r, cx, cy + r)
+        }
+
+        override fun newUpdateListener(
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            outPath: Path,
+        ): ValueAnimator.AnimatorUpdateListener {
+            val startCX = startRect.exactCenterX()
+            val startCY = startRect.exactCenterY()
+            val startR = startRect.width() / 2f
+            val startControl = startR - startR * mRadiusRatio
+            val startHShift = 0f
+            val startVShift = 0f
+
+            val endCX = endRect.exactCenterX()
+            val endCY = endRect.exactCenterY()
+            // Approximate corner circle using bezier curves
+            // http://spencermortensen.com/articles/bezier-circle/
+            val endControl = endRadius * 0.551915024494f
+            val endHShift = endRect.width() / 2f - endRadius
+            val endVShift = endRect.height() / 2f - endRadius
+
+            return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+                val progress = anim.animatedValue as Float
+                val cx = (1 - progress) * startCX + progress * endCX
+                val cy = (1 - progress) * startCY + progress * endCY
+                val r = (1 - progress) * startR + progress * endRadius
+                val control = (1 - progress) * startControl + progress * endControl
+                val hShift = (1 - progress) * startHShift + progress * endHShift
+                val vShift = (1 - progress) * startVShift + progress * endVShift
+
+                outPath.moveTo(cx, cy - vShift - r)
+                outPath.rLineTo(-hShift, 0f)
+
+                addLeftCurve(cx - hShift, cy - vShift, r, control, outPath)
+                outPath.rLineTo(0f, vShift + vShift)
+
+                addRightCurve(cx - hShift, cy + vShift, r, control, outPath)
+                outPath.rLineTo(hShift + hShift, 0f)
+
+                addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath)
+                outPath.rLineTo(0f, -vShift - vShift)
+
+                addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath)
+                outPath.close()
+            }
+        }
+    }
+
+    companion object {
+        @JvmField var INSTANCE = DaggerSingletonObject(LauncherAppComponent::getIconShape)
+
+        private fun getShapeDefinition(type: String, radius: Float): ShapeDelegate {
+            return when (type) {
+                "Circle" -> Circle()
+                "RoundedSquare" -> RoundedSquare(radius)
+                "TearDrop" -> TearDrop(radius)
+                "Squircle" -> Squircle(radius)
+                else -> throw IllegalArgumentException("Invalid shape type: $type")
+            }
+        }
+
+        private fun getAllShapes(context: Context): List<ShapeDelegate> {
+            val result = ArrayList<ShapeDelegate>()
+            try {
+                context.resources.getXml(R.xml.folder_shapes).use { parser ->
+                    // Find the root tag
+                    var type: Int = parser.next()
+                    while (
+                        type != XmlPullParser.END_TAG &&
+                            type != XmlPullParser.END_DOCUMENT &&
+                            "shapes" != parser.name
+                    ) {
+                        type = parser.next()
+                    }
+                    val depth = parser.depth
+                    val radiusAttr = intArrayOf(R.attr.folderIconRadius)
+                    type = parser.next()
+                    while (
+                        (type != XmlPullParser.END_TAG || parser.depth > depth) &&
+                            type != XmlPullParser.END_DOCUMENT
+                    ) {
+                        if (type == XmlPullParser.START_TAG) {
+                            val attrs = Xml.asAttributeSet(parser)
+                            val arr = context.obtainStyledAttributes(attrs, radiusAttr)
+                            val shape = getShapeDefinition(parser.name, arr.getFloat(0, 1f))
+                            arr.recycle()
+                            result.add(shape)
+                        }
+                        type = parser.next()
+                    }
+                }
+            } catch (e: IOException) {
+                throw RuntimeException(e)
+            } catch (e: XmlPullParserException) {
+                throw RuntimeException(e)
+            }
+            return result
+        }
+    }
+}