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
This commit is contained in:
Nick Chameyev 2022-09-23 12:04:18 +01:00
parent 5e47e78a17
commit cbfb35edc7
5 changed files with 51 additions and 230 deletions

View File

@ -46,9 +46,9 @@ import com.android.launcher3.util.TraceHelper;
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 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
// 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);

View File

@ -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.os.Build;
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.TaskOverlayFactory.TaskOverlay;
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 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
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 @@ public class TaskThumbnailView extends View {
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 @@ public class TaskThumbnailView extends View {
*/
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 @@ public class TaskThumbnailView extends View {
}
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 @@ public class TaskThumbnailView extends View {
}
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;
}
}
}

View File

@ -98,9 +98,9 @@ import com.android.quickstep.util.RecentsOrientedState;
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 @@ public class TaskView extends FrameLayout implements Reusable {
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 class TaskView extends FrameLayout implements Reusable {
*/
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 @@ public class TaskView extends FrameLayout implements Reusable {
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 {

View File

@ -20,26 +20,34 @@ import android.graphics.RectF
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 @@ class TaskThumbnailViewTest : DeviceProfileBaseTest() {
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 @@ class TaskThumbnailViewTest : DeviceProfileBaseTest() {
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)
}
}

View File

@ -818,16 +818,6 @@ public final class Utilities {
};
}
/**
* 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