Merge "Update BorderAnimator to work with layout updates" into udc-dev am: b323851e01 am: 56ae9055d6

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/23320817

Change-Id: I6fd06cab51198d0ba440eea5cb235ecebb6ee1b4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 08857b7..8a11b57 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -27,11 +27,13 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.BorderAnimator;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -43,7 +45,9 @@
  */
 public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
 
-    @NonNull private final BorderAnimator mBorderAnimator;
+    @ColorInt private final int mBorderColor;
+
+    @Nullable private BorderAnimator mBorderAnimator;
 
     @Nullable private ImageView mThumbnailView1;
     @Nullable private ImageView mThumbnailView2;
@@ -74,29 +78,9 @@
                 attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
 
         setWillNotDraw(false);
-        Resources resources = context.getResources();
-        mBorderAnimator = new BorderAnimator(
-                /* borderBoundsBuilder= */ bounds -> bounds.set(0, 0, getWidth(), getHeight()),
-                /* borderWidthPx= */ resources.getDimensionPixelSize(
-                        R.dimen.keyboard_quick_switch_border_width),
-                /* borderRadiusPx= */ resources.getDimensionPixelSize(
-                        R.dimen.keyboard_quick_switch_task_view_radius),
-                /* borderColor= */ ta.getColor(
-                        R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
-                /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate,
-                /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() {
-                    @NonNull
-                    @Override
-                    public View getContainerView() {
-                        return KeyboardQuickSwitchTaskView.this;
-                    }
 
-                    @NonNull
-                    @Override
-                    public View getContentView() {
-                        return mContent;
-                    }
-                });
+        mBorderColor = ta.getColor(
+                R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR);
         ta.recycle();
     }
 
@@ -108,17 +92,34 @@
         mIcon1 = findViewById(R.id.icon1);
         mIcon2 = findViewById(R.id.icon2);
         mContent = findViewById(R.id.content);
+
+        Resources resources = mContext.getResources();
+
+        Preconditions.assertNotNull(mContent);
+        mBorderAnimator = new BorderAnimator(
+                /* borderRadiusPx= */ resources.getDimensionPixelSize(
+                        R.dimen.keyboard_quick_switch_task_view_radius),
+                /* borderColor= */ mBorderColor,
+                /* borderAnimationParams= */ new BorderAnimator.ScalingParams(
+                        /* borderWidthPx= */ resources.getDimensionPixelSize(
+                                R.dimen.keyboard_quick_switch_border_width),
+                        /* boundsBuilder= */ bounds -> bounds.set(
+                                0, 0, getWidth(), getHeight()),
+                        /* targetView= */ this,
+                        /* contentView= */ mContent));
     }
 
-    @NonNull
+    @Nullable
     protected Animator getFocusAnimator(boolean focused) {
-        return mBorderAnimator.buildAnimator(focused);
+        return mBorderAnimator == null ? null : mBorderAnimator.buildAnimator(focused);
     }
 
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
-        mBorderAnimator.drawBorder(canvas);
+        if (mBorderAnimator != null) {
+            mBorderAnimator.drawBorder(canvas);
+        }
     }
 
     protected void setThumbnails(
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
index c43fb27..011d45c 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -20,6 +20,7 @@
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.View;
@@ -37,8 +38,8 @@
  * <p>
  * To use this class:
  * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
- *      provided border bounds. If the border will not be visible outside of those bounds, then a
- *      {@link ViewScaleTargetProvider} must be provided in the constructor.
+ *      provided border bounds. See {@link SimpleParams} and {@link ScalingParams} to determine
+ *      which would be best for your target view.
  * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
  *      {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
  * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
@@ -46,7 +47,7 @@
  */
 public final class BorderAnimator {
 
-    public static final int DEFAULT_BORDER_COLOR = 0xffffffff;
+    public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
 
     private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
     private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
@@ -54,68 +55,44 @@
 
     @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
             this::updateOutline);
-    @NonNull private final Rect mBorderBounds = new Rect();
-    @NonNull private final BorderBoundsBuilder mBorderBoundsBuilder;
-    @Px private final int mBorderWidthPx;
     @Px private final int mBorderRadiusPx;
-    @NonNull private final Runnable mInvalidateViewCallback;
-    @Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider;
+    @NonNull private final BorderAnimationParams mBorderAnimationParams;
     private final long mAppearanceDurationMs;
     private final long mDisappearanceDurationMs;
     @NonNull private final Interpolator mInterpolator;
     @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
-    private float mAlignmentAdjustment;
-
     @Nullable private Animator mRunningBorderAnimation;
 
     public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
+            @Px int borderRadiusPx,
             @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback) {
-        this(borderBoundsBuilder,
-                borderWidthPx,
-                borderRadiusPx,
+            @NonNull BorderAnimationParams borderAnimationParams) {
+        this(borderRadiusPx,
                 borderColor,
-                invalidateViewCallback,
-                /* viewScaleTargetProvider= */ null);
-    }
-
-    public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
-            @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback,
-            @Nullable ViewScaleTargetProvider viewScaleTargetProvider) {
-        this(borderBoundsBuilder,
-                borderWidthPx,
-                borderRadiusPx,
-                borderColor,
-                invalidateViewCallback,
-                viewScaleTargetProvider,
+                borderAnimationParams,
                 DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_INTERPOLATOR);
     }
 
+    /**
+     * @param borderRadiusPx the radius of the border's corners, in pixels
+     * @param borderColor the border's color
+     * @param borderAnimationParams params for handling different target view layout situation.
+     * @param appearanceDurationMs appearance animation duration, in milliseconds
+     * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+     * @param interpolator animation interpolator
+     */
     public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
+            @Px int borderRadiusPx,
             @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback,
-            @Nullable ViewScaleTargetProvider viewScaleTargetProvider,
+            @NonNull BorderAnimationParams borderAnimationParams,
             long appearanceDurationMs,
             long disappearanceDurationMs,
             @NonNull Interpolator interpolator) {
-        mBorderBoundsBuilder = borderBoundsBuilder;
-        mBorderWidthPx = borderWidthPx;
         mBorderRadiusPx = borderRadiusPx;
-        mInvalidateViewCallback = invalidateViewCallback;
-        mViewScaleTargetProvider = viewScaleTargetProvider;
+        mBorderAnimationParams = borderAnimationParams;
         mAppearanceDurationMs = appearanceDurationMs;
         mDisappearanceDurationMs = disappearanceDurationMs;
         mInterpolator = interpolator;
@@ -128,15 +105,11 @@
     private void updateOutline() {
         float interpolatedProgress = mInterpolator.getInterpolation(
                 mBorderAnimationProgress.value);
-        float borderWidth = mBorderWidthPx * interpolatedProgress;
-        // Outset the border by half the width to create an outwards-growth animation
-        mAlignmentAdjustment = (-borderWidth / 2f)
-                // Inset the border if we are scaling the container up
-                + (mViewScaleTargetProvider == null ? 0 : mBorderWidthPx);
 
+        mBorderAnimationParams.setProgress(interpolatedProgress);
         mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
-        mBorderPaint.setStrokeWidth(borderWidth);
-        mInvalidateViewCallback.run();
+        mBorderPaint.setStrokeWidth(mBorderAnimationParams.getBorderWidth());
+        mBorderAnimationParams.mTargetView.invalidate();
     }
 
     /**
@@ -146,16 +119,14 @@
      * calling super.
      */
     public void drawBorder(Canvas canvas) {
-        // Increase the radius if we are scaling the container up
-        float radiusAdjustment = mViewScaleTargetProvider == null
-                ? -mAlignmentAdjustment : mAlignmentAdjustment;
+        float alignmentAdjustment = mBorderAnimationParams.getAlignmentAdjustment();
         canvas.drawRoundRect(
-                /* left= */ mBorderBounds.left + mAlignmentAdjustment,
-                /* top= */ mBorderBounds.top + mAlignmentAdjustment,
-                /* right= */ mBorderBounds.right - mAlignmentAdjustment,
-                /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
-                /* rx= */ mBorderRadiusPx + radiusAdjustment,
-                /* ry= */ mBorderRadiusPx + radiusAdjustment,
+                /* left= */ mBorderAnimationParams.mBorderBounds.left + alignmentAdjustment,
+                /* top= */ mBorderAnimationParams.mBorderBounds.top + alignmentAdjustment,
+                /* right= */ mBorderAnimationParams.mBorderBounds.right - alignmentAdjustment,
+                /* bottom= */ mBorderAnimationParams.mBorderBounds.bottom - alignmentAdjustment,
+                /* rx= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
+                /* ry= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
                 /* paint= */ mBorderPaint);
     }
 
@@ -164,7 +135,6 @@
      */
     @NonNull
     public Animator buildAnimator(boolean isAppearing) {
-        mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
         mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
         mRunningBorderAnimation.setDuration(
                 isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
@@ -172,7 +142,7 @@
         mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                setViewScales();
+                mBorderAnimationParams.onShowBorder();
             }
         });
         mRunningBorderAnimation.addListener(
@@ -181,7 +151,7 @@
                     if (isAppearing) {
                         return;
                     }
-                    resetViewScales();
+                    mBorderAnimationParams.onHideBorder();
                 }));
 
         return mRunningBorderAnimation;
@@ -196,56 +166,15 @@
         if (mRunningBorderAnimation != null) {
             mRunningBorderAnimation.end();
         }
-        mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
         if (visible) {
-            setViewScales();
+            mBorderAnimationParams.onShowBorder();
         }
         mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
         if (!visible) {
-            resetViewScales();
+            mBorderAnimationParams.onHideBorder();
         }
     }
 
-    private void setViewScales() {
-        if (mViewScaleTargetProvider == null) {
-            return;
-        }
-        View container = mViewScaleTargetProvider.getContainerView();
-        float width = container.getWidth();
-        float height = container.getHeight();
-        // scale up just enough to make room for the border
-        float scaleX = 1f + ((2 * mBorderWidthPx) / width);
-        float scaleY = 1f + ((2 * mBorderWidthPx) / height);
-
-        container.setPivotX(width / 2);
-        container.setPivotY(height / 2);
-        container.setScaleX(scaleX);
-        container.setScaleY(scaleY);
-
-        View contentView = mViewScaleTargetProvider.getContentView();
-        contentView.setPivotX(contentView.getWidth() / 2f);
-        contentView.setPivotY(contentView.getHeight() / 2f);
-        contentView.setScaleX(1f / scaleX);
-        contentView.setScaleY(1f / scaleY);
-    }
-
-    private void resetViewScales() {
-        if (mViewScaleTargetProvider == null) {
-            return;
-        }
-        View container = mViewScaleTargetProvider.getContainerView();
-        container.setPivotX(container.getWidth());
-        container.setPivotY(container.getHeight());
-        container.setScaleX(1f);
-        container.setScaleY(1f);
-
-        View contentView = mViewScaleTargetProvider.getContentView();
-        contentView.setPivotX(contentView.getWidth() / 2f);
-        contentView.setPivotY(contentView.getHeight() / 2f);
-        contentView.setScaleX(1f);
-        contentView.setScaleY(1f);
-    }
-
     /**
      * Callback to update the border bounds when building this animation.
      */
@@ -258,23 +187,166 @@
     }
 
     /**
-     * Provider for scaling target views for the beginning and end of this animation.
+     * Params for handling different target view layout situation.
      */
-    public interface ViewScaleTargetProvider {
+    private abstract static class BorderAnimationParams {
+
+        @NonNull private final Rect mBorderBounds = new Rect();
+        @NonNull private final BorderBoundsBuilder mBoundsBuilder;
+
+        @NonNull final View mTargetView;
+        @Px final int mBorderWidthPx;
+
+        private float mAnimationProgress = 0f;
+        @Nullable private View.OnLayoutChangeListener mLayoutChangeListener;
 
         /**
-         * Returns the content view's container. This view will be scaled up to make room for the
-         * border.
+         * @param borderWidthPx the width of the border, in pixels
+         * @param boundsBuilder callback to update the border bounds
+         * @param targetView the view that will be drawing the border
          */
-        @NonNull
-        View getContainerView();
+        private BorderAnimationParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView) {
+            mBorderWidthPx = borderWidthPx;
+            mBoundsBuilder = boundsBuilder;
+            mTargetView = targetView;
+        }
+
+        private void setProgress(float progress) {
+            mAnimationProgress = progress;
+        }
+
+        private float getBorderWidth() {
+            return mBorderWidthPx * mAnimationProgress;
+        }
+
+        float getAlignmentAdjustment() {
+            // Outset the border by half the width to create an outwards-growth animation
+            return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset();
+        }
+
+
+        void onShowBorder() {
+            if (mLayoutChangeListener == null) {
+                mLayoutChangeListener =
+                        (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                            onShowBorder();
+                            mTargetView.invalidate();
+                        };
+                mTargetView.addOnLayoutChangeListener(mLayoutChangeListener);
+            }
+            mBoundsBuilder.updateBorderBounds(mBorderBounds);
+        }
+
+        void onHideBorder() {
+            if (mLayoutChangeListener != null) {
+                mTargetView.removeOnLayoutChangeListener(mLayoutChangeListener);
+                mLayoutChangeListener = null;
+            }
+        }
+
+        abstract int getAlignmentAdjustmentInset();
+
+        abstract float getRadiusAdjustment();
+    }
+
+    /**
+     * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the
+     * target view's bounds without any additional logic.
+     */
+    public static final class SimpleParams extends BorderAnimationParams {
+
+        public SimpleParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView) {
+            super(borderWidthPx, boundsBuilder, targetView);
+        }
+
+        @Override
+        int getAlignmentAdjustmentInset() {
+            return 0;
+        }
+
+        @Override
+        float getRadiusAdjustment() {
+            return -getAlignmentAdjustment();
+        }
+    }
+
+    /**
+     * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by
+     * the target view's bound.
+     * <p>
+     * Note: using these params will set the scales and pivots of the
+     * container and content views, however will only reset the scales back to 1.
+     */
+    public static final class ScalingParams extends BorderAnimationParams {
+
+        @NonNull private final View mContentView;
 
         /**
-         * Returns the content view. This view will be scaled down reciprocally to the container's
-         * up-scaling to maintain its original size. This should be the view containing all of the
-         * content being surrounded by the border.
+         * @param targetView the view that will be drawing the border. this view will be scaled up
+         *                   to make room for the border
+         * @param contentView the view around which the border will be drawn. this view will be
+         *                    scaled down reciprocally to keep its original size and location.
          */
-        @NonNull
-        View getContentView();
+        public ScalingParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView,
+                @NonNull View contentView) {
+            super(borderWidthPx, boundsBuilder, targetView);
+            mContentView = contentView;
+        }
+
+        @Override
+        void onShowBorder() {
+            super.onShowBorder();
+            float width = mTargetView.getWidth();
+            float height = mTargetView.getHeight();
+            // Scale up just enough to make room for the border. Fail fast and fix the scaling
+            // onLayout.
+            float scaleX = width == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / width);
+            float scaleY = height == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / height);
+
+            mTargetView.setPivotX(width / 2);
+            mTargetView.setPivotY(height / 2);
+            mTargetView.setScaleX(scaleX);
+            mTargetView.setScaleY(scaleY);
+
+            mContentView.setPivotX(mContentView.getWidth() / 2f);
+            mContentView.setPivotY(mContentView.getHeight() / 2f);
+            mContentView.setScaleX(1f / scaleX);
+            mContentView.setScaleY(1f / scaleY);
+        }
+
+        @Override
+        void onHideBorder() {
+            super.onHideBorder();
+            mTargetView.setPivotX(mTargetView.getWidth());
+            mTargetView.setPivotY(mTargetView.getHeight());
+            mTargetView.setScaleX(1f);
+            mTargetView.setScaleY(1f);
+
+            mContentView.setPivotX(mContentView.getWidth() / 2f);
+            mContentView.setPivotY(mContentView.getHeight() / 2f);
+            mContentView.setScaleX(1f);
+            mContentView.setScaleY(1f);
+        }
+
+        @Override
+        int getAlignmentAdjustmentInset() {
+            // Inset the border since we are scaling the container up
+            return mBorderWidthPx;
+        }
+
+        @Override
+        float getRadiusAdjustment() {
+            // Increase the radius since we are scaling the container up
+            return getAlignmentAdjustment();
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c47c946..6e7b6dc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -445,13 +445,14 @@
         mBorderAnimator = !keyboardFocusHighlightEnabled
                 ? null
                 : new BorderAnimator(
-                        /* borderBoundsBuilder= */ this::updateBorderBounds,
-                        /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                R.dimen.keyboard_quick_switch_border_width),
                         /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
                         /* borderColor= */ ta.getColor(
                                 R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
-                        /* invalidateViewCallback= */ TaskView.this::invalidate);
+                        /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
+                                /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+                                        R.dimen.keyboard_quick_switch_border_width),
+                                /* boundsBuilder= */ this::updateBorderBounds,
+                                /* targetView= */ this));
         ta.recycle();
     }