Merge "Update the BorderAnimator" into udc-dev
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index ebcbdcd..04e87be 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -25,28 +25,40 @@
     android:clipToOutline="true"
     launcher:borderColor="?androidprv:attr/materialColorOutline">
 
-    <include
-        layout="@layout/keyboard_quick_switch_thumbnail"
-        android:id="@+id/thumbnail1"
-        android:layout_width="0dp"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
+        app:layout_constraintEnd_toEndOf="parent">
 
-    <include
-        layout="@layout/keyboard_quick_switch_thumbnail"
-        android:id="@+id/thumbnail2"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
+        <include
+            layout="@layout/keyboard_quick_switch_thumbnail"
+            android:id="@+id/thumbnail1"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
 
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toEndOf="@id/thumbnail1"
-        app:layout_constraintEnd_toEndOf="parent"/>
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
+
+        <include
+            layout="@layout/keyboard_quick_switch_thumbnail"
+            android:id="@+id/thumbnail2"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/thumbnail1"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
 </com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_overview.xml
index 8c97c68..062a9c9 100644
--- a/quickstep/res/layout/keyboard_quick_switch_overview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_overview.xml
@@ -20,35 +20,47 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
     android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
-    android:background="@drawable/keyboard_quick_switch_overview_button_background"
     android:clipToOutline="true"
     android:importantForAccessibility="yes"
     launcher:borderColor="?androidprv:attr/materialColorOutline">
 
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
-        android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
-        android:layout_marginBottom="8dp"
-        android:src="@drawable/ic_empty_recents"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/keyboard_quick_switch_overview_button_background"
 
-        app:tint="?androidprv:attr/materialColorOnSurface"
-        app:layout_constraintVertical_chainStyle="packed"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/text"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-    <TextView
-        style="@style/KeyboardQuickSwitchOverview"
-        android:id="@+id/text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAlignment="center"
-
-        app:layout_constraintTop_toBottomOf="@id/icon"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
+            android:layout_marginBottom="8dp"
+            android:src="@drawable/ic_empty_recents"
+
+            app:tint="?androidprv:attr/materialColorOnSurface"
+            app:layout_constraintVertical_chainStyle="packed"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/text"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <TextView
+            style="@style/KeyboardQuickSwitchOverview"
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAlignment="center"
+
+            app:layout_constraintTop_toBottomOf="@id/icon"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
 </com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 5e2d52a..691df6e 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -25,6 +25,16 @@
     android:clipToOutline="true"
     launcher:borderColor="?androidprv:attr/materialColorOutline">
 
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
         <include
             layout="@layout/keyboard_quick_switch_thumbnail"
             android:id="@+id/thumbnail1"
@@ -49,4 +59,6 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
 
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
 </com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 64d3f67..58c0c40 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -18,7 +18,6 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
     android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top"
     android:layout_marginHorizontal="@dimen/keyboard_quick_switch_margin_ends"
     android:background="@drawable/keyboard_quick_switch_view_background"
@@ -44,7 +43,9 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/content"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
+            android:layout_height="wrap_content"
+            android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
+            android:clipToPadding="false"/>
 
     </HorizontalScrollView>
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 84129fd..926ede1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -23,6 +23,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@
 
     @Nullable private ImageView mThumbnailView1;
     @Nullable private ImageView mThumbnailView2;
+    @Nullable private View mContent;
 
     public KeyboardQuickSwitchTaskView(@NonNull Context context) {
         this(context, null);
@@ -84,7 +86,20 @@
                                 .getColor(
                                         R.styleable.TaskView_borderColor,
                                         DEFAULT_BORDER_COLOR),
-                /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate);
+                /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate,
+                /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() {
+                    @NonNull
+                    @Override
+                    public View getContainerView() {
+                        return KeyboardQuickSwitchTaskView.this;
+                    }
+
+                    @NonNull
+                    @Override
+                    public View getContentView() {
+                        return mContent;
+                    }
+                });
     }
 
     @Override
@@ -93,6 +108,7 @@
 
         mThumbnailView1 = findViewById(R.id.thumbnail1);
         mThumbnailView2 = findViewById(R.id.thumbnail2);
+        mContent = findViewById(R.id.content);
     }
 
     @NonNull
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
index c30661c..c43fb27 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -16,11 +16,13 @@
 package com.android.quickstep.util;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.view.View;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -34,7 +36,9 @@
  * Utility class for drawing a rounded-rect border around a view.
  * <p>
  * To use this class:
- * 1. Create an instance in the target view.
+ * 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.
  * 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
@@ -55,6 +59,7 @@
     @Px private final int mBorderWidthPx;
     @Px private final int mBorderRadiusPx;
     @NonNull private final Runnable mInvalidateViewCallback;
+    @Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider;
     private final long mAppearanceDurationMs;
     private final long mDisappearanceDurationMs;
     @NonNull private final Interpolator mInterpolator;
@@ -75,6 +80,22 @@
                 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,
                 DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_INTERPOLATOR);
@@ -86,6 +107,7 @@
             int borderRadiusPx,
             @ColorInt int borderColor,
             @NonNull Runnable invalidateViewCallback,
+            @Nullable ViewScaleTargetProvider viewScaleTargetProvider,
             long appearanceDurationMs,
             long disappearanceDurationMs,
             @NonNull Interpolator interpolator) {
@@ -93,6 +115,7 @@
         mBorderWidthPx = borderWidthPx;
         mBorderRadiusPx = borderRadiusPx;
         mInvalidateViewCallback = invalidateViewCallback;
+        mViewScaleTargetProvider = viewScaleTargetProvider;
         mAppearanceDurationMs = appearanceDurationMs;
         mDisappearanceDurationMs = disappearanceDurationMs;
         mInterpolator = interpolator;
@@ -106,8 +129,10 @@
         float interpolatedProgress = mInterpolator.getInterpolation(
                 mBorderAnimationProgress.value);
         float borderWidth = mBorderWidthPx * interpolatedProgress;
-        // Inset the border by half the width to create an inwards-growth animation
-        mAlignmentAdjustment = borderWidth / 2f;
+        // 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);
 
         mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
         mBorderPaint.setStrokeWidth(borderWidth);
@@ -121,13 +146,16 @@
      * calling super.
      */
     public void drawBorder(Canvas canvas) {
+        // Increase the radius if we are scaling the container up
+        float radiusAdjustment = mViewScaleTargetProvider == null
+                ? -mAlignmentAdjustment : mAlignmentAdjustment;
         canvas.drawRoundRect(
                 /* left= */ mBorderBounds.left + mAlignmentAdjustment,
                 /* top= */ mBorderBounds.top + mAlignmentAdjustment,
                 /* right= */ mBorderBounds.right - mAlignmentAdjustment,
                 /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
-                /* rx= */ mBorderRadiusPx - mAlignmentAdjustment,
-                /* ry= */ mBorderRadiusPx - mAlignmentAdjustment,
+                /* rx= */ mBorderRadiusPx + radiusAdjustment,
+                /* ry= */ mBorderRadiusPx + radiusAdjustment,
                 /* paint= */ mBorderPaint);
     }
 
@@ -141,22 +169,81 @@
         mRunningBorderAnimation.setDuration(
                 isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
 
+        mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                setViewScales();
+            }
+        });
         mRunningBorderAnimation.addListener(
-                AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null));
+                AnimatorListeners.forEndCallback(() -> {
+                    mRunningBorderAnimation = null;
+                    if (isAppearing) {
+                        return;
+                    }
+                    resetViewScales();
+                }));
 
         return mRunningBorderAnimation;
     }
 
     /**
      * Immediately shows/hides the border without an animation.
-     *
+     * <p>
      * To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
      */
     public void setBorderVisible(boolean visible) {
         if (mRunningBorderAnimation != null) {
             mRunningBorderAnimation.end();
         }
+        mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
+        if (visible) {
+            setViewScales();
+        }
         mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
+        if (!visible) {
+            resetViewScales();
+        }
+    }
+
+    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);
     }
 
     /**
@@ -169,4 +256,25 @@
          */
         void updateBorderBounds(Rect rect);
     }
+
+    /**
+     * Provider for scaling target views for the beginning and end of this animation.
+     */
+    public interface ViewScaleTargetProvider {
+
+        /**
+         * Returns the content view's container. This view will be scaled up to make room for the
+         * border.
+         */
+        @NonNull
+        View getContainerView();
+
+        /**
+         * 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.
+         */
+        @NonNull
+        View getContentView();
+    }
 }