Fix fullscreen and landscape task insets

Previously, we were incorrectly using the thumbnail insets, which
were insufficient for fullscreen tasks, and ignoring rotated tasks
entirely. Now we check what part of the thumbnail is actually
clipped (e.g. based on the scale we apply to get it to fit in the
same width as all the other tasks), and fill that out when setting
the fullscreen progress on each task.

Test:
- Quick switch (from home and from apps) to:
  - "Normal" apps such as Calculator in portrait and landscape
  - Immersive portrait apps such as Elder Scrolls: Blades
  - Immersive landscape apps such as Stardew Valley
- Swipe up from an app with the above cases in the adjacent task,
  ensure the thumbnail gets clipped from fullscreen to its final
  position in recents
- Do the above tests from landscape launcher as well

Bug: 130020567
Change-Id: I6f6b7f0ee134d464b3704990db9e1d3a01e6de0b
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index fa07e27..c26a1d0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,13 +17,10 @@
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 
-import android.graphics.Rect;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
 
 /**
@@ -45,18 +42,9 @@
         if (recentsView.getTaskViewCount() == 0) {
             return super.getOverviewScaleAndTranslation(launcher);
         }
-        // Compute scale and translation y such that the most recent task view fills the screen.
-        TaskThumbnailView dummyThumbnail = recentsView.getTaskViewAt(0).getThumbnail();
+        TaskView dummyTask = recentsView.getTaskViewAt(0);
         ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher);
-        clipAnimationHelper.fromTaskThumbnailView(dummyThumbnail, recentsView);
-        Rect targetRect = new Rect();
-        recentsView.getTaskSize(targetRect);
-        clipAnimationHelper.updateTargetRect(targetRect);
-        float toScale = clipAnimationHelper.getSourceRect().width()
-                / clipAnimationHelper.getTargetRect().width();
-        float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
-                - clipAnimationHelper.getTargetRect().centerY();
-        return new ScaleAndTranslation(toScale, 0, toTranslationY);
+        return clipAnimationHelper.getOverviewFullscreenScaleAndTranslation(dummyTask);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 3b664b7..a1a790c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -124,7 +124,7 @@
     @Override
     protected void updateProgress(float progress) {
         super.updateProgress(progress);
-        updateFullscreenProgress(progress);
+        updateFullscreenProgress(Utilities.boundToRange(progress, 0, 1));
     }
 
     private void updateFullscreenProgress(float progress) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index e932452..c33d25c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -301,34 +301,19 @@
             return;
         }
 
-        // Setup the clip animation helper source/target rects in the final transformed state
-        // of the recents view (a scale/translationY may be applied prior to this animation
-        // starting to line up the side pages during swipe up)
-        float prevRvScale = recentsView.getScaleX();
-        float prevRvTransY = recentsView.getTranslationY();
-        float targetRvScale = endState.getOverviewScaleAndTranslation(launcher).scale;
-        SCALE_PROPERTY.set(recentsView, targetRvScale);
-        recentsView.setTranslationY(0);
         ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
-        float tmpCurveScale = v.getCurveScale();
-        v.setCurveScale(1f);
-        clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
-        v.setCurveScale(tmpCurveScale);
-        SCALE_PROPERTY.set(recentsView, prevRvScale);
-        recentsView.setTranslationY(prevRvTransY);
+        LauncherState.ScaleAndTranslation fromScaleAndTranslation
+                = clipHelper.getOverviewFullscreenScaleAndTranslation(v);
+        LauncherState.ScaleAndTranslation endScaleAndTranslation
+                = endState.getOverviewScaleAndTranslation(launcher);
 
-        if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
-            float fromScale = clipHelper.getSourceRect().width()
-                    / clipHelper.getTargetRect().width();
-            float fromTranslationY = clipHelper.getSourceRect().centerY()
-                    - clipHelper.getTargetRect().centerY();
-            Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1);
-            Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
-                    fromTranslationY, 0);
-            scale.setInterpolator(LINEAR);
-            translateY.setInterpolator(LINEAR);
-            anim.playTogether(scale, translateY);
-        }
+        Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
+                fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
+        Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
+                fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY);
+        scale.setInterpolator(LINEAR);
+        translateY.setInterpolator(LINEAR);
+        anim.playTogether(scale, translateY);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 3109921..a650113 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -35,12 +35,14 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -280,6 +282,21 @@
         }
     }
 
+    /**
+     * Compute scale and translation y such that the specified task view fills the screen.
+     */
+    public LauncherState.ScaleAndTranslation getOverviewFullscreenScaleAndTranslation(TaskView v) {
+        TaskThumbnailView thumbnailView = v.getThumbnail();
+        RecentsView recentsView = v.getRecentsView();
+        fromTaskThumbnailView(thumbnailView, recentsView);
+        Rect taskSize = new Rect();
+        recentsView.getTaskSize(taskSize);
+        updateTargetRect(taskSize);
+        float scale = mSourceRect.width() / mTargetRect.width();
+        float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
+        return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
+    }
+
     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
         ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
         if (sysUiProxy != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 7e15d52..a9184ec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -33,6 +33,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -59,7 +60,7 @@
 
     private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
     private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
-    private final static Rect EMPTY_RECT = new Rect();
+    private final static RectF EMPTY_RECT_F = new RectF();
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -87,10 +88,9 @@
     private final Matrix mMatrix = new Matrix();
 
     private float mClipBottom = -1;
-    private Rect mScaledInsets = new Rect();
-    private Rect mCurrentDrawnInsets = new Rect();
-    private float mCurrentDrawnCornerRadius;
-    private boolean mIsRotated;
+    // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
+    private RectF mClippedInsets = new RectF();
+    private TaskView.FullscreenDrawParams mFullscreenParams;
 
     private Task mTask;
     private ThumbnailData mThumbnailData;
@@ -118,7 +118,7 @@
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
-        setCurrentDrawnInsetsAndRadius(EMPTY_RECT, mCornerRadius);
+        mFullscreenParams = new TaskView.FullscreenDrawParams(mCornerRadius);
     }
 
     public void bind(Task task) {
@@ -201,23 +201,27 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
+        RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
+        canvas.save();
+        canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
+        canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
         // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
         drawOnCanvas(canvas,
-                -mCurrentDrawnInsets.left,
-                -mCurrentDrawnInsets.top,
-                getMeasuredWidth() + mCurrentDrawnInsets.right,
-                getMeasuredHeight() + mCurrentDrawnInsets.bottom,
-                mCurrentDrawnCornerRadius);
+                -currentDrawnInsets.left,
+                -currentDrawnInsets.top,
+                getMeasuredWidth() + currentDrawnInsets.right,
+                getMeasuredHeight() + currentDrawnInsets.bottom,
+                mFullscreenParams.mCurrentDrawnCornerRadius);
+        canvas.restore();
     }
 
-    public Rect getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
-        // Don't show insets in the wrong orientation or in multi window mode.
-        return mIsRotated || isMultiWindowMode ? EMPTY_RECT : mScaledInsets;
+    public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
+        // Don't show insets in multi window mode.
+        return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
     }
 
-    public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
-        mCurrentDrawnInsets.set(insets);
-        mCurrentDrawnCornerRadius = radius;
+    public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
+        mFullscreenParams = fullscreenParams;
         invalidate();
     }
 
@@ -275,7 +279,7 @@
     }
 
     private void updateThumbnailMatrix() {
-        mIsRotated = false;
+        boolean isRotated = false;
         mClipBottom = -1;
         if (mBitmapShader != null && mThumbnailData != null) {
             float scale = mThumbnailData.scale;
@@ -296,30 +300,28 @@
                 final Configuration configuration =
                         getContext().getResources().getConfiguration();
                 // Rotate the screenshot if not in multi-window mode
-                mIsRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
+                isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
                         configuration.orientation != mThumbnailData.orientation &&
                         !mActivity.isInMultiWindowMode() &&
                         mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
                 // Scale the screenshot to always fit the width of the card.
-                thumbnailScale = mIsRotated
+                thumbnailScale = isRotated
                         ? getMeasuredWidth() / thumbnailHeight
                         : getMeasuredWidth() / thumbnailWidth;
             }
 
-            mScaledInsets.set(thumbnailInsets);
-            Utilities.scaleRect(mScaledInsets, thumbnailScale);
-
-            if (mIsRotated) {
+            if (isRotated) {
                 int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
                 mMatrix.setRotate(90 * rotationDir);
                 int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
                 int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
-                mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale);
+                mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
                 if (rotationDir == -1) {
                     // Crop the right/bottom side of the screenshot rather than left/top
                     float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight();
-                    mMatrix.postTranslate(0, -excessHeight);
+                    mClippedInsets.offset(0, excessHeight);
                 }
+                mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
                 // Move the screenshot to the thumbnail window (rotation moved it out).
                 if (rotationDir == 1) {
                     mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0);
@@ -327,13 +329,28 @@
                     mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth());
                 }
             } else {
-                mMatrix.setTranslate(-mThumbnailData.insets.left * scale,
-                        -mThumbnailData.insets.top * scale);
+                mClippedInsets.offsetTo(thumbnailInsets.left * scale, thumbnailInsets.top * scale);
+                mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
             }
+
+            final float widthWithInsets;
+            final float heightWithInsets;
+            if (isRotated) {
+                widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
+                heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
+            } else {
+                widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
+                heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
+            }
+            mClippedInsets.left *= thumbnailScale;
+            mClippedInsets.top *= thumbnailScale;
+            mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth();
+            mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight();
+
             mMatrix.postScale(thumbnailScale, thumbnailScale);
             mBitmapShader.setLocalMatrix(mMatrix);
 
-            float bitmapHeight = Math.max((mIsRotated ? thumbnailWidth : thumbnailHeight)
+            float bitmapHeight = Math.max((isRotated ? thumbnailWidth : thumbnailHeight)
                     * thumbnailScale, 0);
             if (Math.round(bitmapHeight) < getMeasuredHeight()) {
                 mClipBottom = bitmapHeight;
@@ -341,7 +358,7 @@
             mPaint.setShader(mBitmapShader);
         }
 
-        if (mIsRotated) {
+        if (isRotated) {
             // The overlay doesn't really work when the screenshot is rotated, so don't add it.
             mOverlay.reset();
         } else {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 6cd46d9..2b86f5e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.graphics.Outline;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
@@ -166,7 +167,7 @@
     private float mCurveScale;
     private float mZoomScale;
     private float mFullscreenProgress;
-    private final Rect mCurrentDrawnInsets = new Rect();
+    private final FullscreenDrawParams mCurrentFullscreenParams;
     private final float mCornerRadius;
     private final float mWindowCornerRadius;
     private final BaseDraggingActivity mActivity;
@@ -214,7 +215,8 @@
         });
         mCornerRadius = TaskCornerRadius.get(context);
         mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
-        mOutlineProvider = new TaskOutlineProvider(getResources(), mCornerRadius);
+        mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
+        mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
         setOutlineProvider(mOutlineProvider);
     }
 
@@ -540,26 +542,26 @@
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
 
         private final int mMarginTop;
-        private final Rect mInsets = new Rect();
-        private float mRadius;
+        private FullscreenDrawParams mFullscreenParams;
 
-        TaskOutlineProvider(Resources res, float radius) {
+        TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
             mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-            mRadius = radius;
+            mFullscreenParams = fullscreenParams;
         }
 
-        public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
-            mInsets.set(insets);
-            mRadius = radius;
+        public void setFullscreenParams(FullscreenDrawParams params) {
+            mFullscreenParams = params;
         }
 
         @Override
         public void getOutline(View view, Outline outline) {
-            outline.setRoundRect(-mInsets.left,
-                    mMarginTop - mInsets.top,
-                    view.getWidth() + mInsets.right,
-                    view.getHeight() + mInsets.bottom,
-                    mRadius);
+            RectF insets = mFullscreenParams.mCurrentDrawnInsets;
+            float scale = mFullscreenParams.mScale;
+            outline.setRoundRect(0,
+                    (int) (mMarginTop * scale),
+                    (int) ((insets.left + view.getWidth() + insets.right) * scale),
+                    (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
+                    mFullscreenParams.mCurrentDrawnCornerRadius);
         }
     }
 
@@ -658,17 +660,25 @@
 
         TaskThumbnailView thumbnail = getThumbnail();
         boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode;
-        Rect insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
-        mCurrentDrawnInsets.set((int) (insets.left * mFullscreenProgress),
-                (int) (insets.top * mFullscreenProgress),
-                (int) (insets.right * mFullscreenProgress),
-                (int) (insets.bottom * mFullscreenProgress));
+        RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
+        float currentInsetsLeft = insets.left * mFullscreenProgress;
+        float currentInsetsRight = insets.right * mFullscreenProgress;
+        mCurrentFullscreenParams.setInsets(currentInsetsLeft,
+                insets.top * mFullscreenProgress,
+                currentInsetsRight,
+                insets.bottom * mFullscreenProgress);
         float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
-        float cornerRadius = Utilities.mapRange(mFullscreenProgress, mCornerRadius,
-                fullscreenCornerRadius) / getRecentsView().getScaleX();
+        mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress,
+                mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX());
+        // We scaled the thumbnail to fit the content (excluding insets) within task view width.
+        // Now that we are drawing left/right insets again, we need to scale down to fit them.
+        if (getWidth() > 0) {
+            mCurrentFullscreenParams.setScale(getWidth()
+                    / (getWidth() + currentInsetsLeft + currentInsetsRight));
+        }
 
-        thumbnail.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
-        mOutlineProvider.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
+        thumbnail.setFullscreenParams(mCurrentFullscreenParams);
+        mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
         invalidateOutline();
     }
 
@@ -686,4 +696,30 @@
         }
         return mShowScreenshot;
     }
+
+    /**
+     * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
+     */
+    static class FullscreenDrawParams {
+        RectF mCurrentDrawnInsets = new RectF();
+        float mCurrentDrawnCornerRadius;
+        /** The current scale we apply to the thumbnail to adjust for new left/right insets. */
+        float mScale = 1;
+
+        public FullscreenDrawParams(float cornerRadius) {
+            setCornerRadius(cornerRadius);
+        }
+
+        public void setInsets(float left, float top, float right, float bottom) {
+            mCurrentDrawnInsets.set(left, top, right, bottom);
+        }
+
+        public void setCornerRadius(float cornerRadius) {
+            mCurrentDrawnCornerRadius = cornerRadius;
+        }
+
+        public void setScale(float scale) {
+            mScale = scale;
+        }
+    }
 }