Fix folder morphing animation

  - Also splits interpolator between open / closing anim to setup future folder motion work
  - Also fixed bug if no shape selected in picker

Bug: 393979768
Bug: 387543793
Test: Manual verify animation and picker bug
Flag: com.android.launcher3.enable_launcher_icon_shapes

Change-Id: Ieb7355b9128cd1ded46af6787f5c14baf9be6774
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index d2ff2cb..2157610 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,6 +35,7 @@
 import android.util.Property;
 import android.view.View;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
@@ -79,9 +80,10 @@
     private final int mDuration;
     private final int mDelay;
 
-    private final TimeInterpolator mFolderInterpolator;
-    private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator;
-    private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
+    private final Interpolator mFolderOpenInterpolator;
+    private final Interpolator mFolderCloseInterpolator;
+    private final Interpolator mLargeFolderPreviewItemOpenInterpolator;
+    private final Interpolator mLargeFolderPreviewItemCloseInterpolator;
 
     private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
     private final FolderGridOrganizer mPreviewVerifier;
@@ -108,7 +110,9 @@
         mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
         mDelay = res.getInteger(R.integer.config_folderDelay);
 
-        mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
+        mFolderOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
+                R.interpolator.standard_interpolator);
+        mFolderCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
                 R.interpolator.standard_interpolator);
         mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
                 R.interpolator.large_folder_preview_item_open_interpolator);
@@ -252,6 +256,7 @@
                 (int) (left + (startRect.right / initialScale)) + extraRadius,
                 (int) (startRect.bottom / initialScale) + extraRadius);
         Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
+        // animated contents of folder with the folder background
         play(a, shapeDelegate.createRevealAnimator(
                 mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
 
@@ -332,7 +337,11 @@
         // We set the interpolator on all current child animators here, because the preview item
         // animators may use a different interpolator.
         for (Animator animator : a.getChildAnimations()) {
-            animator.setInterpolator(mFolderInterpolator);
+            animator.setInterpolator(
+                    mIsOpening
+                    ? mFolderOpenInterpolator
+                    : mFolderCloseInterpolator
+            );
         }
 
         int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
@@ -467,7 +476,7 @@
         return mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
-    private TimeInterpolator getPreviewItemInterpolator() {
+    private Interpolator getPreviewItemInterpolator() {
         if (isLargeFolder()) {
             // With larger folders, we want the preview items to reach their final positions faster
             // (when opening) and later (when closing) so that they appear aligned with the rest of
@@ -476,7 +485,7 @@
                     ? mLargeFolderPreviewItemOpenInterpolator
                     : mLargeFolderPreviewItemCloseInterpolator;
         }
-        return mFolderInterpolator;
+        return mIsOpening ? mFolderOpenInterpolator : mFolderCloseInterpolator;
     }
 
     private Animator getAnimator(View view, Property property, float v1, float v2) {
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 9edc386..80743af 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -55,6 +55,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
@@ -148,13 +149,23 @@
                             KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
                     String currentShapePath =
                             ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
+                    Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
+                            .values()
+                            .stream()
+                            .filter(shape -> shape.getPathString().equals(currentShapePath))
+                            .findFirst();
+                    // Handle default for when current shape doesn't match new shapes.
+                    if (selectedShape.isEmpty()) {
+                        selectedShape = Optional.ofNullable(ShapesProvider.INSTANCE.getIconShapes()
+                                .get("circle"));
+                    }
+
                     for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
                         cursor.newRow()
                                 .add(KEY_SHAPE_KEY, shape.getKey())
                                 .add(KEY_SHAPE_TITLE, shape.getTitle())
                                 .add(KEY_PATH, shape.getPathString())
-                                .add(KEY_IS_DEFAULT,
-                                        shape.getPathString().equals(currentShapePath));
+                                .add(KEY_IS_DEFAULT, shape.equals(selectedShape.get()));
                     }
                     return cursor;
                 } else  {
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
index e62936c..eac3440 100644
--- a/src/com/android/launcher3/graphics/IconShape.kt
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -38,6 +38,7 @@
 import androidx.graphics.shapes.Morph
 import androidx.graphics.shapes.RoundedPolygon
 import androidx.graphics.shapes.SvgPathParser
+import androidx.graphics.shapes.rectangle
 import androidx.graphics.shapes.toPath
 import androidx.graphics.shapes.transformed
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
@@ -362,8 +363,8 @@
         }
 
         /**
-         * Creates a rounded rect with the start point at the center of the top edge. This ensures a
-         * better animation since our shape paths also start at top-center of the bounding box.
+         * Create RoundedRect using RoundedPolygon API. Ensures smoother animation morphing between
+         * generic polygon by using [RoundedPolygon.Companion.rectangle] directly.
          */
         fun createRoundedRect(
             left: Float,
@@ -372,27 +373,11 @@
             bottom: Float,
             cornerR: Float,
         ) =
-            RoundedPolygon(
-                vertices =
-                    floatArrayOf(
-                        // x1, y1
-                        (left + right) / 2,
-                        top,
-                        // x2, y2
-                        right,
-                        top,
-                        // x3, y3
-                        right,
-                        bottom,
-                        // x4, y4
-                        left,
-                        bottom,
-                        // x5, y5
-                        left,
-                        top,
-                    ),
-                centerX = (left + right) / 2,
-                centerY = (top + bottom) / 2,
+            RoundedPolygon.rectangle(
+                width = right - left,
+                height = bottom - top,
+                centerX = (right - left) / 2,
+                centerY = (bottom - top) / 2,
                 rounding = CornerRounding(cornerR),
             )
     }
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index dfb7793..f1ea3a0 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -202,9 +202,9 @@
             )
         } else {
             mapOf(
-                "default" to
+                "circle" to
                     IconShapeModel(
-                        key = "default",
+                        key = "circle",
                         title = "circle",
                         pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
                     )