Improve the staged split animation

- Introduce rounded corners (since we scale x and y differently, we can't use outline since it doesn't support rx and ry. It's achieved by custom drawing).
- Make sure the thumbnail content doesn't shift during the transition (we use custom cropping for TaskThumbnailView, and we have to do it accordingly here)

TODO: update UX of the initial split view (b/219085340)

Fixes: 194414938
Test: https://recall.googleplex.com/projects/f46cfe9c-8076-4efe-bf8a-b1cc4f1f5e1b/sessions/64953aa7-62ea-427c-8ec0-5f2bd96e4762
Change-Id: Id9a5d2f0f41cb4d619c8b3bd3a83c633e3d1f2de
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java
new file mode 100644
index 0000000..cfb0056
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A child view of {@link com.android.quickstep.views.FloatingTaskView} to draw the thumbnail in a
+ * rounded corner frame. While the purpose of this class sounds similar to
+ * {@link TaskThumbnailView}, it doesn't need a lot of complex logic in {@link TaskThumbnailView}
+ * in relation to moving with {@link RecentsView}.
+ */
+public class FloatingTaskThumbnailView extends View {
+
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Matrix mMatrix = new Matrix();
+
+    private @Nullable BitmapShader mBitmapShader;
+    private @Nullable Bitmap mBitmap;
+
+    private FloatingTaskView.FullscreenDrawParams mFullscreenParams;
+
+    public FloatingTaskThumbnailView(Context context) {
+        this(context, null);
+    }
+
+    public FloatingTaskThumbnailView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingTaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mFullscreenParams == null || mBitmap == null) {
+            return;
+        }
+
+        // Scale down the bitmap to fix x, and crop in y.
+        float scale = 1.0f * getMeasuredWidth() / mBitmap.getWidth();
+        mMatrix.postScale(scale, scale);
+        mBitmapShader.setLocalMatrix(mMatrix);
+
+        canvas.drawRoundRect(0, 0, getMeasuredWidth(),  getMeasuredHeight(),
+                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleX,
+                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleY, mPaint);
+    }
+
+    public void setThumbnail(Bitmap bitmap) {
+        mBitmap = bitmap;
+        if (bitmap != null) {
+            mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mPaint.setShader(mBitmapShader);
+        }
+    }
+
+    public void setFullscreenParams(FloatingTaskView.FullscreenDrawParams fullscreenParams) {
+        mFullscreenParams = fullscreenParams;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index f2f1c3f..c59dc18 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -15,7 +15,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 
@@ -29,6 +28,8 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.TaskCornerRadius;
+import com.android.systemui.shared.system.QuickStepContract;
 
 import java.util.function.Consumer;
 
@@ -50,9 +51,9 @@
     private RectF mStartingPosition;
     private final StatefulActivity mActivity;
     private final boolean mIsRtl;
-    private final Rect mOutline = new Rect();
+    private final FullscreenDrawParams mCurrentFullscreenParams;
     private PagedOrientationHandler mOrientationHandler;
-    private ImageView mImageView;
+    private FloatingTaskThumbnailView mThumbnailView;
 
     public FloatingTaskView(Context context) {
         this(context, null);
@@ -66,16 +67,17 @@
         super(context, attrs, defStyleAttr);
         mActivity = BaseActivity.fromContext(context);
         mIsRtl = Utilities.isRtl(getResources());
+        mCurrentFullscreenParams = new FullscreenDrawParams(context);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mImageView = findViewById(R.id.thumbnail);
-        mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
-        mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
+        mThumbnailView = findViewById(R.id.thumbnail);
+        mThumbnailView.setFullscreenParams(mCurrentFullscreenParams);
         mSplitPlaceholderView = findViewById(R.id.split_placeholder);
         mSplitPlaceholderView.setAlpha(0);
+        mSplitPlaceholderView.setFullscreenParams(mCurrentFullscreenParams);
     }
 
     private void init(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail,
@@ -86,13 +88,11 @@
                 (InsettableFrameLayout.LayoutParams) getLayoutParams();
 
         mSplitPlaceholderView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
-        positionOut.round(mOutline);
         setPivotX(0);
         setPivotY(0);
 
         // Copy bounds of exiting thumbnail into ImageView
-        mImageView.setImageBitmap(thumbnail);
-        mImageView.setVisibility(VISIBLE);
+        mThumbnailView.setThumbnail(thumbnail);
 
         RecentsView recentsView = launcher.getOverviewPanel();
         mOrientationHandler = recentsView.getPagedOrientationHandler();
@@ -133,27 +133,24 @@
         setLayoutParams(lp);
     }
 
-    // TODO(194414938) set correct corner radii
-    public void update(RectF position, float progress, float windowRadius) {
+    public void update(RectF position, float progress) {
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
 
         float dX = position.left - mStartingPosition.left;
         float dY = position.top - lp.topMargin;
+        float scaleX = position.width() / lp.width;
+        float scaleY = position.height() / lp.height;
+
+        mCurrentFullscreenParams.updateParams(position, progress, scaleX, scaleY);
 
         setTranslationX(dX);
         setTranslationY(dY);
-
-        float scaleX = position.width() / lp.width;
-        float scaleY = position.height() / lp.height;
         setScaleX(scaleX);
         setScaleY(scaleY);
+        mSplitPlaceholderView.invalidate();
+
         float childScaleX = 1f / scaleX;
         float childScaleY = 1f / scaleY;
-
-        invalidate();
-        // TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
-        mImageView.setScaleX(1f / scaleX + scaleX * progress);
-        mImageView.setScaleY(1f / scaleY + scaleY * progress);
         mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIconView(), childScaleX);
         mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
     }
@@ -181,7 +178,8 @@
     }
 
     public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
-            boolean fadeWithThumbnail) {
+            boolean fadeWithThumbnail, boolean isInitialSplit) {
+        mCurrentFullscreenParams.setIsInitialSplit(isInitialSplit);
         final BaseDragLayer dragLayer = mActivity.getDragLayer();
         int[] dragLayerBounds = new int[2];
         dragLayer.getLocationOnScreen(dragLayerBounds);
@@ -191,22 +189,16 @@
         ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
         animation.add(transitionAnimator);
         long animDuration = animation.getDuration();
-        Rect crop = new Rect();
         RectF floatingTaskViewBounds = new RectF();
-        final float initialWindowRadius = supportsRoundedCornersOnWindows(getResources())
-                ? Math.max(crop.width(), crop.height()) / 2f
-                : 0f;
 
         if (fadeWithThumbnail) {
             animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
                     0, 1, ACCEL);
-            animation.addFloat(mImageView, LauncherAnimUtils.VIEW_ALPHA,
+            animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
                     1, 0, DEACCEL_3);
         }
 
         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
-            final FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
-                    initialWindowRadius, 0, animDuration, LINEAR);
             final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
             final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
             final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
@@ -221,7 +213,7 @@
                 Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value,
                         mTaskViewScaleY.value);
 
-                update(floatingTaskViewBounds, percent, mWindowRadius.value * 1);
+                update(floatingTaskViewBounds, percent);
             }
         };
         transitionAnimator.addUpdateListener(listener);
@@ -250,4 +242,36 @@
             dY = centerY - startTaskViewBounds.centerY();
         }
     }
+
+    public static class FullscreenDrawParams {
+
+        private final float mCornerRadius;
+        private final float mWindowCornerRadius;
+
+        public boolean mIsInitialSplit = true;
+        public final RectF mFloatingTaskViewBounds = new RectF();
+        public float mCurrentDrawnCornerRadius;
+        public float mScaleX = 1;
+        public float mScaleY = 1;
+
+        public FullscreenDrawParams(Context context) {
+            mCornerRadius = TaskCornerRadius.get(context);
+            mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
+
+            mCurrentDrawnCornerRadius = mCornerRadius;
+        }
+
+        public void updateParams(RectF floatingTaskViewBounds, float progress, float scaleX,
+                float scaleY) {
+            mFloatingTaskViewBounds.set(floatingTaskViewBounds);
+            mScaleX = scaleX;
+            mScaleY = scaleY;
+            mCurrentDrawnCornerRadius = mIsInitialSplit ? 0 :
+                    Utilities.mapRange(progress, mCornerRadius, mWindowCornerRadius);
+        }
+
+        public void setIsInitialSplit(boolean isInitialSplit) {
+            mIsInitialSplit = isInitialSplit;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index cb7e08a..5e331e2 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2732,7 +2732,7 @@
                     mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
             mFirstFloatingTaskView.setAlpha(1);
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
-                    mTempRect, true /*fadeWithThumbnail*/);
+                    mTempRect, true /* fadeWithThumbnail */, true /* isInitialSplit */);
         } else {
             mSplitSelectSource.view.setVisibility(INVISIBLE);
             mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
@@ -2740,7 +2740,7 @@
                     mSplitSelectSource.drawable, startingTaskRect);
             mFirstFloatingTaskView.setAlpha(1);
             mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
-                    mTempRect, true /*fadeWithThumbnail*/);
+                    mTempRect, true /* fadeWithThumbnail */, true /* isInitialSplit */);
         }
         anim.addEndListener(success -> {
             if (success) {
@@ -4030,14 +4030,14 @@
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
         mFirstFloatingTaskView.addAnimation(pendingAnimation,
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
-                false /*fadeWithThumbnail*/);
+                false /* fadeWithThumbnail */, false /* isInitialSplit */);
 
         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                 thumbnailView, thumbnailView.getThumbnail(),
                 iconView.getDrawable(), secondTaskStartingBounds);
         mSecondFloatingTaskView.setAlpha(1);
         mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
-                secondTaskEndingBounds, true /* fadeWithThumbnail */);
+                secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isInitialSplit */);
         pendingAnimation.addEndListener(aBoolean ->
                 mSplitSelectStateController.setSecondTaskId(task.key.id,
                 aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
@@ -4110,7 +4110,7 @@
         mTempRectF.set(mTempRect);
         // TODO(194414938) set correct corner radius
         mFirstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
-        mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f, /*windowRadius=*/0f);
+        mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
 
         PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
         Pair<FloatProperty, FloatProperty> taskViewsFloat =
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index cfa482f..d37dfbf 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -17,9 +17,12 @@
 package com.android.quickstep.views;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.TypedValue;
 import android.view.Gravity;
 import android.widget.FrameLayout;
 
@@ -27,6 +30,10 @@
 
 public class SplitPlaceholderView extends FrameLayout {
 
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    private FloatingTaskView.FullscreenDrawParams mFullscreenParams;
+
     public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
             new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
                 @Override
@@ -46,6 +53,17 @@
 
     public SplitPlaceholderView(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        mPaint.setColor(getThemePrimaryColor(context));
+        setWillNotDraw(false);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        // Call this before super call to draw below the children.
+        drawBackground(canvas);
+
+        super.dispatchDraw(canvas);
     }
 
     @Nullable
@@ -53,6 +71,10 @@
         return mIconView;
     }
 
+    public void setFullscreenParams(FloatingTaskView.FullscreenDrawParams fullscreenParams) {
+        mFullscreenParams = fullscreenParams;
+    }
+
     public void setIcon(Drawable drawable, int iconSize) {
         if (mIconView == null) {
             mIconView = new IconView(getContext());
@@ -64,4 +86,20 @@
         params.gravity = Gravity.CENTER;
         mIconView.setLayoutParams(params);
     }
+
+    private void drawBackground(Canvas canvas) {
+        if (mFullscreenParams == null) {
+            return;
+        }
+
+        canvas.drawRoundRect(0, 0, getMeasuredWidth(),  getMeasuredHeight(),
+                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleX,
+                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleY, mPaint);
+    }
+
+    private static int getThemePrimaryColor(Context context) {
+        final TypedValue value = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.colorPrimary, value, true);
+        return value.data;
+    }
 }
diff --git a/res/layout/floating_split_select_view.xml b/res/layout/floating_split_select_view.xml
index 8d47f4e..e4ca52e 100644
--- a/res/layout/floating_split_select_view.xml
+++ b/res/layout/floating_split_select_view.xml
@@ -4,7 +4,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <ImageView
+    <com.android.quickstep.views.FloatingTaskThumbnailView
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -14,7 +14,6 @@
         android:id="@+id/split_placeholder"
         android:layout_width="match_parent"
         android:layout_height="@dimen/split_placeholder_size"
-        android:background="?android:colorPrimary"
         android:visibility="gone" />
 
 </com.android.quickstep.views.FloatingTaskView>
\ No newline at end of file