Converting IconShape to mainThreadInitializedObject instead of
keeping it a process static

Bug: 297950111
Flag: None
Test: Presubmit
Change-Id: Id66100e0cb300659c6017c287eb38ab04290477b
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 83236d1..a7284e5 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -631,7 +631,7 @@
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
             Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
-                    IconShape.getNormalizationScale());
+                    IconShape.INSTANCE.get(getContext()).getNormalizationScale());
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 72758f2..98dade5 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -123,7 +123,7 @@
     private OnTouchListener mInterceptTouchListener;
 
     private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
-    final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
+    final PreviewBackground mFolderLeaveBehind = new PreviewBackground(getContext());
 
     private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
     private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d2633e0..159e17f 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -257,7 +257,7 @@
 
         @Override
         public void onSystemIconStateChanged(String iconState) {
-            IconShape.init(mContext);
+            IconShape.INSTANCE.get(mContext).pickBestShape(mContext);
             refreshAndReloadLauncher();
             LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
         }
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 3d7e11e..9944ef6 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 
 import com.android.launcher3.graphics.BitmapCreationCheck;
-import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ResourceBasedOverride;
 
@@ -36,7 +35,6 @@
 
     protected void init(Context context) {
         FileLog.setDir(context.getApplicationContext().getFilesDir());
-        IconShape.init(context);
 
         if (BitmapCreationCheck.ENABLED) {
             BitmapCreationCheck.startTracking(context);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0fc3211..f03dcab 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1955,7 +1955,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 PreviewBackground();
+                mFolderCreateBg = new PreviewBackground(getContext());
                 fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
             } else {
                 fi.prepareCreateAnimation(v);
@@ -2670,7 +2670,7 @@
         boolean userFolderPending = willCreateUserFolder(info, mDragOverView, false);
         if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
 
-            mFolderCreateBg = new PreviewBackground();
+            mFolderCreateBg = new PreviewBackground(getContext());
             mFolderCreateBg.setup(mLauncher, mLauncher, null,
                     mDragOverView.getMeasuredWidth(), mDragOverView.getPaddingTop());
 
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 7a2ec97..7ef3209 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-import static com.android.launcher3.graphics.IconShape.getShape;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -45,6 +44,8 @@
 import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.IconShape.ShapeDelegate;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -233,8 +234,9 @@
         }
         play(a, getAnimator(mFolder.mFooter, ALPHA, 0, 1f), footerStartDelay, footerAlphaDuration);
 
+        ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getShape();
         // Create reveal animator for the folder background
-        play(a, getShape().createRevealAnimator(
+        play(a, shapeDelegate.createRevealAnimator(
                 mFolder, startRect, endRect, finalRadius, !mIsOpening));
 
         // Create reveal animator for the folder content (capture the top 4 icons 2x2)
@@ -246,10 +248,9 @@
         int left = mContent.getPaddingLeft() + page * lp.width;
         Rect contentStart = new Rect(left, 0, left + width, height);
         Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
-        play(a, getShape().createRevealAnimator(
+        play(a, shapeDelegate.createRevealAnimator(
                 mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
 
-
         // Fade in the folder name, as the text can overlap the icons when grid size is small.
         mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f);
         play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1),
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 4d88b68..00636a3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -112,7 +112,7 @@
 
     @Thunk BubbleTextView mFolderName;
 
-    PreviewBackground mBackground = new PreviewBackground();
+    PreviewBackground mBackground = new PreviewBackground(getContext());
     private boolean mBackgroundIsVisible = true;
 
     FolderGridOrganizer mPreviewVerifier;
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ec03803..df41d47 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -19,7 +19,6 @@
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
-import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.Animator;
@@ -49,6 +48,8 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
+import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.IconShape.ShapeDelegate;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -66,6 +67,7 @@
     @VisibleForTesting protected static final float HOVER_SCALE = 1.1f;
     @VisibleForTesting protected static final int HOVER_ANIMATION_DURATION = 300;
 
+    private final Context mContext;
     private final PorterDuffXfermode mShadowPorterDuffXfermode
             = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
     private RadialGradient mShadowShader = null;
@@ -138,6 +140,10 @@
                 }
             };
 
+    public PreviewBackground(Context context) {
+        mContext = context;
+    }
+
     /**
      * Draws folder background under cell layout
      */
@@ -254,6 +260,10 @@
         drawShadow(canvas);
     }
 
+    private ShapeDelegate getShape() {
+        return IconShape.INSTANCE.get(mContext).getShape();
+    }
+
     public void drawShadow(Canvas canvas) {
         if (!DRAW_SHADOW) {
             return;
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
index f82b07e..5f8f2dc 100644
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -22,7 +22,6 @@
 import android.animation.FloatArrayEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -35,7 +34,6 @@
 import android.graphics.Region.Op;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Xml;
 import android.view.View;
@@ -45,6 +43,8 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.views.ClipPathView;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -57,38 +57,94 @@
 /**
  * Abstract representation of the shape of an icon shape
  */
-public abstract class IconShape {
+public final class IconShape implements SafeCloseable {
 
-    private static IconShape sInstance = new Circle();
-    private static float sNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
+    public static final MainThreadInitializedObject<IconShape> INSTANCE =
+            new MainThreadInitializedObject<>(IconShape::new);
 
-    public static IconShape getShape() {
-        return sInstance;
+
+    private ShapeDelegate mDelegate = new Circle();
+    private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
+
+    private IconShape(Context context) {
+        pickBestShape(context);
     }
 
-    public static float getNormalizationScale() {
-        return sNormalizationScale;
+    public ShapeDelegate getShape() {
+        return mDelegate;
     }
 
-    public boolean enableShapeDetection(){
-        return false;
-    };
+    public float getNormalizationScale() {
+        return mNormalizationScale;
+    }
 
-    public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
-            Paint paint);
+    @Override
+    public void close() { }
 
-    public abstract void addToPath(Path path, float offsetX, float offsetY, float radius);
+    /**
+     * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
+     */
+    public void pickBestShape(Context context) {
+        // Pick any large size
+        final int size = 200;
 
-    public abstract <T extends View & ClipPathView> Animator createRevealAnimator(T target,
-            Rect startRect, Rect endRect, float endRadius, boolean isReversed);
+        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 extends IconShape {
+    private static abstract class SimpleRectShape implements ShapeDelegate {
 
         @Override
-        public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+        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) {
@@ -105,7 +161,7 @@
     /**
      * Abstract shape which draws using {@link Path}
      */
-    private static abstract class PathShape extends IconShape {
+    private static abstract class PathShape implements ShapeDelegate {
 
         private final Path mTmpPath = new Path();
 
@@ -121,7 +177,7 @@
                 Rect startRect, Rect endRect, float endRadius, Path outPath);
 
         @Override
-        public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+        public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
                 Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
             Path path = new Path();
             AnimatorUpdateListener listener =
@@ -203,7 +259,7 @@
         }
     }
 
-    public static class RoundedSquare extends SimpleRectShape {
+    private static class RoundedSquare extends SimpleRectShape {
 
         /**
          * Ratio of corner radius to half size.
@@ -237,7 +293,7 @@
         }
     }
 
-    public static class TearDrop extends PathShape {
+    private static class TearDrop extends PathShape {
 
         /**
          * Radio of short radius to large radius, based on the shape options defined in the config.
@@ -289,7 +345,7 @@
         }
     }
 
-    public static class Squircle extends PathShape {
+    private static class Squircle extends PathShape {
 
         /**
          * Radio of radius to circle radius, based on the shape options defined in the config.
@@ -375,14 +431,7 @@
         }
     }
 
-    /**
-     * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
-     */
-    public static void init(Context context) {
-        pickBestShape(context);
-    }
-
-    private static IconShape getShapeDefinition(String type, float radius) {
+    private static ShapeDelegate getShapeDefinition(String type, float radius) {
         switch (type) {
             case "Circle":
                 return new Circle();
@@ -397,8 +446,8 @@
         }
     }
 
-    private static List<IconShape> getAllShapes(Context context) {
-        ArrayList<IconShape> result = new ArrayList<>();
+    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
@@ -416,7 +465,7 @@
                 if (type == XmlPullParser.START_TAG) {
                     AttributeSet attrs = Xml.asAttributeSet(parser);
                     TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
-                    IconShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
+                    ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
                     a.recycle();
 
                     result.add(shape);
@@ -428,42 +477,4 @@
         return result;
     }
 
-    @TargetApi(Build.VERSION_CODES.O)
-    protected static 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;
-        IconShape closestShape = null;
-        for (IconShape 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) {
-            sInstance = closestShape;
-        }
-
-        // Initialize shape properties
-        sNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
-    }
 }
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 513377a..7331c6f 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -78,7 +78,8 @@
     private MonochromeIconFactory mMonochromeIconFactory;
 
     protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
-        super(context, fillResIconDpi, iconBitmapSize, IconShape.getShape().enableShapeDetection());
+        super(context, fillResIconDpi, iconBitmapSize,
+                IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
         mMonoIconEnabled = Themes.isThemedIconEnabled(context);
         mPoolId = poolId;
     }
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 5d3fa9b..172f968 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -18,12 +18,11 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 
 import static java.lang.Math.max;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -142,14 +141,10 @@
         if (mIsAdaptiveIcon) {
             if (!isOpening && progress >= shapeProgressStart) {
                 if (mRevealAnimator == null) {
-                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
-                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
-                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mRevealAnimator = null;
-                        }
-                    });
+                    mRevealAnimator = IconShape.INSTANCE.get(getContext()).getShape()
+                            .createRevealAnimator(this, mStartRevealRect,
+                                    mOutline, mTaskCornerRadius, !isOpening);
+                    mRevealAnimator.addListener(forEndCallback(() -> mRevealAnimator = null));
                     mRevealAnimator.start();
                     // We pause here so we can set the current fraction ourselves.
                     mRevealAnimator.pause();
@@ -230,7 +225,8 @@
             mStartRevealRect.set(0, 0, originalWidth, originalHeight);
 
             if (!isFolderIcon) {
-                Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
+                Utilities.scaleRectAboutCenter(mStartRevealRect,
+                        IconShape.INSTANCE.get(getContext()).getNormalizationScale());
             }
 
             if (dp.isLandscape) {