From b2551fc7168f85b54764e6e338bb1143228a6794 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Fri, 23 Sep 2022 12:04:19 +0100 Subject: [PATCH] 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: I66c2b2ff68deb56394b52c07b98e36d23822025f --- .../utilities/PreviewPositionHelper.java | 209 ++++++++++++++++++ .../shared/recents/utilities/Utilities.java | 10 + 2 files changed, 219 insertions(+) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java new file mode 100644 index 0000000000000..72f8b7b09dca4 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -0,0 +1,209 @@ +package com.android.systemui.shared.recents.utilities; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.Surface; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +/** + * Utility class to position the thumbnail in the TaskView + */ +public class PreviewPositionHelper { + + public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f; + + // 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; + } + + public void setOrientationChanged(boolean orientationChanged) { + mIsOrientationChanged = orientationChanged; + } + + public boolean isOrientationChanged() { + return mIsOrientationChanged; + } + + /** + * Updates the matrix based on the provided parameters + */ + public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, + int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet, + 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) screenWidthPx; + float scaledTaskbarSize = taskbarSize * canvasScreenRatio; + thumbnailClipHint.bottom = 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 && !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); + } + + public RectF getClippedInsets() { + return mClippedInsets; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 56326e36ff5e0..77a13bd91b907 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -62,6 +62,16 @@ public class Utilities { return false; // Default } + /** + * 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; + } + /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */ public static float computeContrastBetweenColors(int bg, int fg) { float bgR = Color.red(bg) / 255f;