Updating smart text selection animation

Now animates the highlight itself as opposed to an outline.

Bug: 70540865
Test: Manually tested it with single and multi-line - ltr and rtl
Change-Id: I8afee259c9952fcff0b713bca62c82a1022f2b0d
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7bb0db1..5ab579d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1745,16 +1745,19 @@
             highlight = null;
         }
 
+        if (mSelectionActionModeHelper != null) {
+            mSelectionActionModeHelper.onDraw(canvas);
+            if (mSelectionActionModeHelper.isDrawingHighlight()) {
+                highlight = null;
+            }
+        }
+
         if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
             drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
                     cursorOffsetVertical);
         } else {
             layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
         }
-
-        if (mSelectionActionModeHelper != null) {
-            mSelectionActionModeHelper.onDraw(canvas);
-        }
     }
 
     private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 2e354c1..e7a4c02 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -93,7 +93,7 @@
 
         if (SMART_SELECT_ANIMATION_ENABLED) {
             mSmartSelectSprite = new SmartSelectSprite(mTextView.getContext(),
-                    mTextView::invalidate);
+                    editor.getTextView().mHighlightColor, mTextView::invalidate);
         } else {
             mSmartSelectSprite = null;
         }
@@ -200,11 +200,15 @@
     }
 
     public void onDraw(final Canvas canvas) {
-        if (mSmartSelectSprite != null) {
+        if (isDrawingHighlight() && mSmartSelectSprite != null) {
             mSmartSelectSprite.draw(canvas);
         }
     }
 
+    public boolean isDrawingHighlight() {
+        return mSmartSelectSprite != null && mSmartSelectSprite.isAnimationActive();
+    }
+
     private void cancelAsyncTask() {
         if (mTextClassificationAsyncTask != null) {
             mTextClassificationAsyncTask.cancel(true);
diff --git a/core/java/android/widget/SmartSelectSprite.java b/core/java/android/widget/SmartSelectSprite.java
index a391c6e..9a84f69 100644
--- a/core/java/android/widget/SmartSelectSprite.java
+++ b/core/java/android/widget/SmartSelectSprite.java
@@ -26,7 +26,6 @@
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -36,7 +35,6 @@
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.Shape;
 import android.text.Layout;
-import android.util.TypedValue;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
@@ -54,21 +52,15 @@
 final class SmartSelectSprite {
 
     private static final int EXPAND_DURATION = 300;
-    private static final int CORNER_DURATION = 150;
-    private static final float STROKE_WIDTH_DP = 1.5F;
-
-    // GBLUE700
-    @ColorInt
-    private static final int DEFAULT_STROKE_COLOR = 0xFF3367D6;
+    private static final int CORNER_DURATION = 50;
 
     private final Interpolator mExpandInterpolator;
     private final Interpolator mCornerInterpolator;
-    private final float mStrokeWidth;
 
     private Animator mActiveAnimator = null;
     private final Runnable mInvalidator;
     @ColorInt
-    private final int mStrokeColor;
+    private final int mFillColor;
 
     static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator
             .<RectF>comparingDouble(e -> e.bottom)
@@ -124,26 +116,11 @@
             return expansionDirection * -1;
         }
 
-        @Retention(SOURCE)
-        @IntDef({RectangleBorderType.FIT, RectangleBorderType.OVERSHOOT})
-        private @interface RectangleBorderType {
-        /** A rectangle which, fully expanded, fits inside of its bounding rectangle. */
-        int FIT = 0;
-        /**
-         * A rectangle which, when fully expanded, clips outside of its bounding rectangle so that
-         * its edges no longer appear rounded.
-         */
-        int OVERSHOOT = 1;
-        }
-
-        private final float mStrokeWidth;
         private final RectF mBoundingRectangle;
         private float mRoundRatio = 1.0f;
         private final @ExpansionDirection int mExpansionDirection;
-        private final @RectangleBorderType int mRectangleBorderType;
 
         private final RectF mDrawRect = new RectF();
-        private final RectF mClipRect = new RectF();
         private final Path mClipPath = new Path();
 
         /** How offset the left edge of the rectangle is from the left side of the bounding box. */
@@ -159,13 +136,9 @@
         private RoundedRectangleShape(
                 final RectF boundingRectangle,
                 final @ExpansionDirection int expansionDirection,
-                final @RectangleBorderType int rectangleBorderType,
-                final boolean inverted,
-                final float strokeWidth) {
+                final boolean inverted) {
             mBoundingRectangle = new RectF(boundingRectangle);
             mBoundingWidth = boundingRectangle.width();
-            mRectangleBorderType = rectangleBorderType;
-            mStrokeWidth = strokeWidth;
             mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
 
             if (inverted) {
@@ -182,14 +155,8 @@
         }
 
         /*
-         * In order to achieve the "rounded rectangle hits the wall" effect, the drawing needs to be
-         * done in two passes. In this context, the wall is the bounding rectangle and in the first
-         * pass we need to draw the rounded rectangle (expanded and with a corner radius as per
-         * object properties) clipped by the bounding box. If the rounded rectangle expands outside
-         * of the bounding box, one more pass needs to be done, as there will now be a hole in the
-         * rounded rectangle where it "flattened" against the bounding box. In order to fill just
-         * this hole, we need to draw the bounding box, but clip it with the rounded rectangle and
-         * this will connect the missing pieces.
+         * In order to achieve the "rounded rectangle hits the wall" effect, we draw an expanding
+         * rounded rectangle that is clipped by the bounding box of the selected text.
          */
         @Override
         public void draw(Canvas canvas, Paint paint) {
@@ -201,31 +168,8 @@
             final float adjustedCornerRadius = getAdjustedCornerRadius();
 
             mDrawRect.set(mBoundingRectangle);
-            mDrawRect.left = mBoundingRectangle.left + mLeftBoundary;
-            mDrawRect.right = mBoundingRectangle.left + mRightBoundary;
-
-            if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
-                mDrawRect.left -= cornerRadius / 2;
-                mDrawRect.right += cornerRadius / 2;
-            } else {
-                switch (mExpansionDirection) {
-                    case ExpansionDirection.CENTER:
-                        break;
-                    case ExpansionDirection.LEFT:
-                        mDrawRect.right += cornerRadius;
-                        break;
-                    case ExpansionDirection.RIGHT:
-                        mDrawRect.left -= cornerRadius;
-                        break;
-                }
-            }
-
-            canvas.save();
-            mClipRect.set(mBoundingRectangle);
-            mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
-            canvas.clipRect(mClipRect);
-            canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
-            canvas.restore();
+            mDrawRect.left = mBoundingRectangle.left + mLeftBoundary - cornerRadius / 2;
+            mDrawRect.right = mBoundingRectangle.left + mRightBoundary + cornerRadius / 2;
 
             canvas.save();
             mClipPath.reset();
@@ -272,11 +216,7 @@
         }
 
         private float getBoundingWidth() {
-            if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
-                return (int) (mBoundingRectangle.width() + getCornerRadius());
-            } else {
-                return mBoundingRectangle.width();
-            }
+            return (int) (mBoundingRectangle.width() + getCornerRadius());
         }
 
     }
@@ -388,19 +328,20 @@
     }
 
     /**
-     * @param context     the {@link Context} in which the animation will run
+     * @param context the {@link Context} in which the animation will run
+     * @param highlightColor the highlight color of the underlying {@link TextView}
      * @param invalidator a {@link Runnable} which will be called every time the animation updates,
      *                    indicating that the view drawing the animation should invalidate itself
      */
-    SmartSelectSprite(final Context context, final Runnable invalidator) {
+    SmartSelectSprite(final Context context, @ColorInt int highlightColor,
+            final Runnable invalidator) {
         mExpandInterpolator = AnimationUtils.loadInterpolator(
                 context,
                 android.R.interpolator.fast_out_slow_in);
         mCornerInterpolator = AnimationUtils.loadInterpolator(
                 context,
                 android.R.interpolator.fast_out_linear_in);
-        mStrokeWidth = dpToPixel(context, STROKE_WIDTH_DP);
-        mStrokeColor = getStrokeColor(context);
+        mFillColor = highlightColor;
         mInvalidator = Preconditions.checkNotNull(invalidator);
     }
 
@@ -437,17 +378,14 @@
         RectangleWithTextSelectionLayout centerRectangle = null;
 
         int startingOffset = 0;
-        int startingRectangleIndex = 0;
-        for (int index = 0; index < rectangleCount; ++index) {
-            final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
-                    destinationRectangles.get(index);
+        for (RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout :
+                destinationRectangles) {
             final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
             if (contains(rectangle, start)) {
                 centerRectangle = rectangleWithTextSelectionLayout;
                 break;
             }
             startingOffset += rectangle.width();
-            ++startingRectangleIndex;
         }
 
         if (centerRectangle == null) {
@@ -459,9 +397,6 @@
         final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
                 generateDirections(centerRectangle, destinationRectangles);
 
-        final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
-                generateBorderTypes(rectangleCount);
-
         for (int index = 0; index < rectangleCount; ++index) {
             final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
                     destinationRectangles.get(index);
@@ -469,10 +404,8 @@
             final RoundedRectangleShape shape = new RoundedRectangleShape(
                     rectangle,
                     expansionDirections[index],
-                    rectangleBorderTypes[index],
                     rectangleWithTextSelectionLayout.getTextSelectionLayout()
-                            == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
-                    mStrokeWidth);
+                            == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
             cornerAnimators.add(createCornerAnimator(shape, updateListener));
             shapes.add(shape);
         }
@@ -480,44 +413,23 @@
         final RectangleList rectangleList = new RectangleList(shapes);
         final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
 
-        final float startingOffsetLeft;
-        final float startingOffsetRight;
-
-        final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
-        final float cornerRadius = startingRectangleShape.getCornerRadius();
-        if (startingRectangleShape.mRectangleBorderType
-                == RoundedRectangleShape.RectangleBorderType.FIT) {
-            switch (startingRectangleShape.mExpansionDirection) {
-                case RoundedRectangleShape.ExpansionDirection.LEFT:
-                    startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
-                    break;
-                case RoundedRectangleShape.ExpansionDirection.RIGHT:
-                    startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
-                    break;
-                case RoundedRectangleShape.ExpansionDirection.CENTER:  // fall through
-                default:
-                    startingOffsetLeft = startingOffset - cornerRadius / 2;
-                    startingOffsetRight = startingOffset + cornerRadius / 2;
-                    break;
-            }
-        } else {
-            startingOffsetLeft = startingOffsetRight = startingOffset;
-        }
-
         final Paint paint = shapeDrawable.getPaint();
-        paint.setColor(mStrokeColor);
-        paint.setStyle(Paint.Style.STROKE);
-        paint.setStrokeWidth(mStrokeWidth);
+        paint.setColor(mFillColor);
+        paint.setStyle(Paint.Style.FILL);
 
         mExistingRectangleList = rectangleList;
         mExistingDrawable = shapeDrawable;
 
-        mActiveAnimator = createAnimator(rectangleList, startingOffsetLeft, startingOffsetRight,
-                cornerAnimators, updateListener,
-                onAnimationEnd);
+        mActiveAnimator = createAnimator(rectangleList, startingOffset, startingOffset,
+                cornerAnimators, updateListener, onAnimationEnd);
         mActiveAnimator.start();
     }
 
+    /** Returns whether the sprite is currently animating. */
+    public boolean isAnimationActive() {
+        return mActiveAnimator != null && mActiveAnimator.isRunning();
+    }
+
     private Animator createAnimator(
             final RectangleList rectangleList,
             final float startingOffsetLeft,
@@ -625,36 +537,6 @@
         return result;
     }
 
-    private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
-            final int numberOfRectangles) {
-        final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
-
-        for (int i = 1; i < result.length - 1; ++i) {
-            result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
-        }
-
-        result[0] = RoundedRectangleShape.RectangleBorderType.FIT;
-        result[result.length - 1] = RoundedRectangleShape.RectangleBorderType.FIT;
-        return result;
-    }
-
-    private static float dpToPixel(final Context context, final float dp) {
-        return TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP,
-                dp,
-                context.getResources().getDisplayMetrics());
-    }
-
-    @ColorInt
-    private static int getStrokeColor(final Context context) {
-        final TypedValue typedValue = new TypedValue();
-        final TypedArray array = context.obtainStyledAttributes(typedValue.data, new int[]{
-                android.R.attr.colorControlActivated});
-        final int result = array.getColor(0, DEFAULT_STROKE_COLOR);
-        array.recycle();
-        return result;
-    }
-
     /**
      * A variant of {@link RectF#contains(float, float)} that also allows the point to reside on
      * the right boundary of the rectangle.