Extract PreviewPositionHelper to shared library

Moves Launcher's PreviewPositionHelper to shared
library between SystemUI and Launcher to reuse
it in the future in the partial screensharing
recents selector.

There should be no functional changes in the
code itself.

Bug: 240924926
Test: presubmit
Change-Id: Ib38b6f9db91e63a2598bf81229e3cd3e1a49ca60
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 4cdf557..c03aa3f 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -46,9 +46,9 @@
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
@@ -317,9 +317,9 @@
             // mIsRecentsRtl is the inverse of TaskView RTL.
             boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
-                    mThumbnailPosition, mThumbnailData,
-                    mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
+                    mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
+                    mDp.widthPx, mDp.taskbarSize, mDp.isTablet,
+                    mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
             if (DEBUG) {
                 Log.d(TAG, " taskRect: " + mTaskRect);
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 43d38a7..04a0af1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,10 +16,12 @@
 
 package com.android.quickstep.views;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
+import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT;
+import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
@@ -39,7 +41,6 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
-import android.view.Surface;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -58,6 +59,7 @@
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 
 /**
  * A task in the Recents view.
@@ -65,7 +67,6 @@
 public class TaskThumbnailView extends View {
     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
-    private static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -417,7 +418,7 @@
         float thumbnailDataAspect =
                 mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
 
-        return Utilities.isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
+        return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
                 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
     }
 
@@ -441,8 +442,8 @@
      */
     private void refreshOverlay() {
         if (mOverlayEnabled) {
-            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
-                    mPreviewPositionHelper.mIsOrientationChanged);
+            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
+                    mPreviewPositionHelper.isOrientationChanged());
         } else {
             getTaskOverlay().reset();
         }
@@ -463,18 +464,19 @@
     }
 
     private void updateThumbnailMatrix() {
-        mPreviewPositionHelper.mIsOrientationChanged = false;
+        mPreviewPositionHelper.setOrientationChanged(false);
         if (mBitmapShader != null && mThumbnailData != null) {
             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
                     mThumbnailData.thumbnail.getHeight());
             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
                     .getRecentsActivityRotation();
             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            DeviceProfile dp = mActivity.getDeviceProfile();
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
-                    getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
-                    currentRotation, isRtl);
+                    getMeasuredWidth(), getMeasuredHeight(), dp.widthPx, dp.taskbarSize,
+                    dp.isTablet, currentRotation, isRtl);
 
-            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
+            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
             mPaint.setShader(mBitmapShader);
         }
         getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
@@ -514,199 +516,4 @@
         }
         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
     }
-
-    /**
-     * Utility class to position the thumbnail in the TaskView
-     */
-    public static class PreviewPositionHelper {
-
-        private static final RectF EMPTY_RECT_F = new RectF();
-
-        // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
-        private final RectF mClippedInsets = new RectF();
-        private final Matrix mMatrix = new Matrix();
-        private boolean mIsOrientationChanged;
-
-        public Matrix getMatrix() {
-            return mMatrix;
-        }
-
-        /**
-         * Updates the matrix based on the provided parameters
-         */
-        public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
-                boolean isRtl) {
-            boolean isRotated = false;
-            boolean isOrientationDifferent;
-
-            int thumbnailRotation = thumbnailData.rotation;
-            int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
-            RectF thumbnailClipHint = new RectF();
-            float canvasScreenRatio = canvasWidth / (float) dp.widthPx;
-            float scaledTaskbarSize = dp.taskbarSize * canvasScreenRatio;
-            thumbnailClipHint.bottom = dp.isTablet ? scaledTaskbarSize : 0;
-
-            float scale = thumbnailData.scale;
-            final float thumbnailScale;
-
-            // Landscape vs portrait change.
-            // Note: Disable rotation in grid layout.
-            boolean windowingModeSupportsRotation =
-                    thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !dp.isTablet;
-            isOrientationDifferent = isOrientationChange(deltaRotate)
-                    && windowingModeSupportsRotation;
-            if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
-                // If we haven't measured , skip the thumbnail drawing and only draw the background
-                // color
-                thumbnailScale = 0f;
-            } else {
-                // Rotate the screenshot if not in multi-window mode
-                isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
-
-                float surfaceWidth = thumbnailBounds.width() / scale;
-                float surfaceHeight = thumbnailBounds.height() / scale;
-                float availableWidth = surfaceWidth
-                        - (thumbnailClipHint.left + thumbnailClipHint.right);
-                float availableHeight = surfaceHeight
-                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-
-                float canvasAspect = canvasWidth / (float) canvasHeight;
-                float availableAspect = isRotated
-                        ? availableHeight / availableWidth
-                        : availableWidth / availableHeight;
-                boolean isAspectLargelyDifferent =
-                        Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
-                                availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
-                if (isRotated && isAspectLargelyDifferent) {
-                    // Do not rotate thumbnail if it would not improve fit
-                    isRotated = false;
-                    isOrientationDifferent = false;
-                }
-
-                if (isAspectLargelyDifferent) {
-                    // Crop letterbox insets if insets isn't already clipped
-                    thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
-                    thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
-                    thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
-                    thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
-                    availableWidth = surfaceWidth
-                            - (thumbnailClipHint.left + thumbnailClipHint.right);
-                    availableHeight = surfaceHeight
-                            - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-                }
-
-                final float targetW, targetH;
-                if (isOrientationDifferent) {
-                    targetW = canvasHeight;
-                    targetH = canvasWidth;
-                } else {
-                    targetW = canvasWidth;
-                    targetH = canvasHeight;
-                }
-                float targetAspect = targetW / targetH;
-
-                // Update the clipHint such that
-                //   > the final clipped position has same aspect ratio as requested by canvas
-                //   > first fit the width and crop the extra height
-                //   > if that will leave empty space, fit the height and crop the width instead
-                float croppedWidth = availableWidth;
-                float croppedHeight = croppedWidth / targetAspect;
-                if (croppedHeight > availableHeight) {
-                    croppedHeight = availableHeight;
-                    if (croppedHeight < targetH) {
-                        croppedHeight = Math.min(targetH, surfaceHeight);
-                    }
-                    croppedWidth = croppedHeight * targetAspect;
-
-                    // One last check in case the task aspect radio messed up something
-                    if (croppedWidth > surfaceWidth) {
-                        croppedWidth = surfaceWidth;
-                        croppedHeight = croppedWidth / targetAspect;
-                    }
-                }
-
-                // Update the clip hints. Align to 0,0, crop the remaining.
-                if (isRtl) {
-                    thumbnailClipHint.left += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.right < 0) {
-                        thumbnailClipHint.left += thumbnailClipHint.right;
-                        thumbnailClipHint.right = 0;
-                    }
-                } else {
-                    thumbnailClipHint.right += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.left < 0) {
-                        thumbnailClipHint.right += thumbnailClipHint.left;
-                        thumbnailClipHint.left = 0;
-                    }
-                }
-                thumbnailClipHint.bottom += availableHeight - croppedHeight;
-                if (thumbnailClipHint.top < 0) {
-                    thumbnailClipHint.bottom += thumbnailClipHint.top;
-                    thumbnailClipHint.top = 0;
-                } else if (thumbnailClipHint.bottom < 0) {
-                    thumbnailClipHint.top += thumbnailClipHint.bottom;
-                    thumbnailClipHint.bottom = 0;
-                }
-
-                thumbnailScale = targetW / (croppedWidth * scale);
-            }
-
-            if (!isRotated) {
-                mMatrix.setTranslate(
-                        -thumbnailClipHint.left * scale,
-                        -thumbnailClipHint.top * scale);
-            } else {
-                setThumbnailRotation(deltaRotate, thumbnailBounds);
-            }
-
-            mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
-
-            mMatrix.postScale(thumbnailScale, thumbnailScale);
-            mIsOrientationChanged = isOrientationDifferent;
-        }
-
-        private int getRotationDelta(int oldRotation, int newRotation) {
-            int delta = newRotation - oldRotation;
-            if (delta < 0) delta += 4;
-            return delta;
-        }
-
-        /**
-         * @param deltaRotation the number of 90 degree turns from the current orientation
-         * @return {@code true} if the change in rotation results in a shift from landscape to
-         * portrait or vice versa, {@code false} otherwise
-         */
-        private boolean isOrientationChange(int deltaRotation) {
-            return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
-        }
-
-        private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
-            float translateX = 0;
-            float translateY = 0;
-
-            mMatrix.setRotate(90 * deltaRotate);
-            switch (deltaRotate) { /* Counter-clockwise */
-                case Surface.ROTATION_90:
-                    translateX = thumbnailPosition.height();
-                    break;
-                case Surface.ROTATION_270:
-                    translateY = thumbnailPosition.width();
-                    break;
-                case Surface.ROTATION_180:
-                    translateX = thumbnailPosition.width();
-                    translateY = thumbnailPosition.height();
-                    break;
-            }
-            mMatrix.postTranslate(translateX, translateY);
-        }
-
-        /**
-         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
-         */
-        public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
-            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
-                    ? mClippedInsets : EMPTY_RECT_F;
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 07d8989..0125775 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -98,9 +98,9 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -121,6 +121,8 @@
     private static final String TAG = TaskView.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final RectF EMPTY_RECT_F = new RectF();
+
     public static final int FLAG_UPDATE_ICON = 1;
     public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
 
@@ -1572,7 +1574,7 @@
          */
         public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
                 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
-            RectF insets = pph.getInsetsToDrawInFullscreen(dp);
+            RectF insets = getInsetsToDrawInFullscreen(pph, dp);
 
             float currentInsetsLeft = insets.left * fullscreenProgress;
             float currentInsetsTop = insets.top * fullscreenProgress;
@@ -1591,6 +1593,14 @@
                 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
             }
         }
+
+        /**
+         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
+         */
+        private static RectF getInsetsToDrawInFullscreen(PreviewPositionHelper pph, DeviceProfile dp) {
+            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
+                    ? pph.getClippedInsets() : EMPTY_RECT_F;
+        }
     }
 
     public class TaskIdAttributeContainer {
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
similarity index 64%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
rename to quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index cf3c8c9..4785350 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -20,26 +20,34 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.DeviceProfileBaseTest
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper
+import com.android.quickstep.views.TaskView.FullscreenDrawParams
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 
 /**
- * Test for TaskThumbnailView class.
+ * Test for FullscreenDrawParams class.
  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewTest : DeviceProfileBaseTest() {
+class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
 
     private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
 
     private val mPreviewPositionHelper = PreviewPositionHelper()
+    private lateinit var params: FullscreenDrawParams
+
+    @Before
+    fun setup() {
+        params = FullscreenDrawParams(context)
+    }
 
     @Test
-    fun getInsetsToDrawInFullscreen_clipTaskbarSizeFromBottomForTablets() {
+    fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets() {
         initializeVarsForTablet()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -49,15 +57,18 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize / 2f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 
     @Test
-    fun getInsetsToDrawInFullscreen_doNotClipTaskbarSizeFromBottomForPhones() {
+    fun setFullProgress_currentDrawnInsets_doNotClipTaskbarSizeFromBottomForPhones() {
         initializeVarsForPhone()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -67,10 +78,13 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 616b08a..f70511a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -819,16 +819,6 @@
     }
 
     /**
-     * Compares the ratio of two quantities and returns whether that ratio is greater than the
-     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
-     * of a percentage.
-     */
-    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
-            float bound) {
-        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
-    }
-
-    /**
      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
      * the final bounds.