1 package com.android.systemui.shared.recents.utilities; 2 3 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 4 import static android.view.Surface.ROTATION_180; 5 import static android.view.Surface.ROTATION_270; 6 import static android.view.Surface.ROTATION_90; 7 8 import android.graphics.Matrix; 9 import android.graphics.Rect; 10 import android.graphics.RectF; 11 12 import com.android.systemui.shared.recents.model.ThumbnailData; 13 import com.android.wm.shell.util.SplitBounds; 14 15 /** 16 * Utility class to position the thumbnail in the TaskView 17 */ 18 public class PreviewPositionHelper { 19 20 public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f; 21 22 /** 23 * Specifies that a stage is positioned at the top half of the screen if 24 * in portrait mode or at the left half of the screen if in landscape mode. 25 * TODO(b/254378592): Remove after consolidation 26 */ 27 public static final int STAGE_POSITION_TOP_OR_LEFT = 0; 28 29 /** 30 * Specifies that a stage is positioned at the bottom half of the screen if 31 * in portrait mode or at the right half of the screen if in landscape mode. 32 * TODO(b/254378592): Remove after consolidation 33 */ 34 public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1; 35 36 private final Matrix mMatrix = new Matrix(); 37 private boolean mIsOrientationChanged; 38 private SplitBounds mSplitBounds; 39 private int mDesiredStagePosition; 40 getMatrix()41 public Matrix getMatrix() { 42 return mMatrix; 43 } 44 setOrientationChanged(boolean orientationChanged)45 public void setOrientationChanged(boolean orientationChanged) { 46 mIsOrientationChanged = orientationChanged; 47 } 48 isOrientationChanged()49 public boolean isOrientationChanged() { 50 return mIsOrientationChanged; 51 } 52 setSplitBounds(SplitBounds splitBounds, int desiredStagePosition)53 public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) { 54 mSplitBounds = splitBounds; 55 mDesiredStagePosition = desiredStagePosition; 56 } 57 58 /** 59 * Updates the matrix based on the provided parameters 60 */ updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, int canvasWidth, int canvasHeight, boolean isLargeScreen, int currentRotation, boolean isRtl)61 public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, 62 int canvasWidth, int canvasHeight, boolean isLargeScreen, int currentRotation, 63 boolean isRtl) { 64 boolean isRotated = false; 65 boolean isOrientationDifferent; 66 67 int thumbnailRotation = thumbnailData.rotation; 68 int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation); 69 RectF thumbnailClipHint = new RectF(); 70 float scale = thumbnailData.scale; 71 final float thumbnailScale; 72 73 // Landscape vs portrait change. 74 // Note: Disable rotation in grid layout. 75 boolean windowingModeSupportsRotation = 76 thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen; 77 isOrientationDifferent = isOrientationChange(deltaRotate) 78 && windowingModeSupportsRotation; 79 if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) { 80 // If we haven't measured , skip the thumbnail drawing and only draw the background 81 // color 82 thumbnailScale = 0f; 83 } else { 84 // Rotate the screenshot if not in multi-window mode 85 isRotated = deltaRotate > 0 && windowingModeSupportsRotation; 86 87 float surfaceWidth = thumbnailBounds.width() / scale; 88 float surfaceHeight = thumbnailBounds.height() / scale; 89 float availableWidth = surfaceWidth; 90 float availableHeight = surfaceHeight; 91 92 float canvasAspect = canvasWidth / (float) canvasHeight; 93 float availableAspect = isRotated 94 ? availableHeight / availableWidth 95 : availableWidth / availableHeight; 96 boolean isAspectLargelyDifferent = 97 Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect, 98 availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); 99 if (isRotated && isAspectLargelyDifferent) { 100 // Do not rotate thumbnail if it would not improve fit 101 isRotated = false; 102 isOrientationDifferent = false; 103 } 104 105 if (isAspectLargelyDifferent) { 106 // Crop letterbox insets if insets isn't already clipped 107 thumbnailClipHint.left = thumbnailData.letterboxInsets.left; 108 thumbnailClipHint.right = thumbnailData.letterboxInsets.right; 109 thumbnailClipHint.top = thumbnailData.letterboxInsets.top; 110 thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom; 111 availableWidth = surfaceWidth 112 - (thumbnailClipHint.left + thumbnailClipHint.right); 113 availableHeight = surfaceHeight 114 - (thumbnailClipHint.top + thumbnailClipHint.bottom); 115 } 116 117 final float targetW, targetH; 118 if (isOrientationDifferent) { 119 targetW = canvasHeight; 120 targetH = canvasWidth; 121 } else { 122 targetW = canvasWidth; 123 targetH = canvasHeight; 124 } 125 float targetAspect = targetW / targetH; 126 127 // Update the clipHint such that 128 // > the final clipped position has same aspect ratio as requested by canvas 129 // > first fit the width and crop the extra height 130 // > if that will leave empty space, fit the height and crop the width instead 131 float croppedWidth = availableWidth; 132 float croppedHeight = croppedWidth / targetAspect; 133 if (croppedHeight > availableHeight) { 134 croppedHeight = availableHeight; 135 if (croppedHeight < targetH) { 136 croppedHeight = Math.min(targetH, surfaceHeight); 137 } 138 croppedWidth = croppedHeight * targetAspect; 139 140 // One last check in case the task aspect radio messed up something 141 if (croppedWidth > surfaceWidth) { 142 croppedWidth = surfaceWidth; 143 croppedHeight = croppedWidth / targetAspect; 144 } 145 } 146 147 // Update the clip hints. Align to 0,0, crop the remaining. 148 if (isRtl) { 149 thumbnailClipHint.left += availableWidth - croppedWidth; 150 if (thumbnailClipHint.right < 0) { 151 thumbnailClipHint.left += thumbnailClipHint.right; 152 thumbnailClipHint.right = 0; 153 } 154 } else { 155 thumbnailClipHint.right += availableWidth - croppedWidth; 156 if (thumbnailClipHint.left < 0) { 157 thumbnailClipHint.right += thumbnailClipHint.left; 158 thumbnailClipHint.left = 0; 159 } 160 } 161 thumbnailClipHint.bottom += availableHeight - croppedHeight; 162 if (thumbnailClipHint.top < 0) { 163 thumbnailClipHint.bottom += thumbnailClipHint.top; 164 thumbnailClipHint.top = 0; 165 } else if (thumbnailClipHint.bottom < 0) { 166 thumbnailClipHint.top += thumbnailClipHint.bottom; 167 thumbnailClipHint.bottom = 0; 168 } 169 170 thumbnailScale = targetW / (croppedWidth * scale); 171 } 172 173 if (!isRotated) { 174 mMatrix.setTranslate( 175 -thumbnailClipHint.left * scale, 176 -thumbnailClipHint.top * scale); 177 } else { 178 setThumbnailRotation(deltaRotate, thumbnailBounds); 179 } 180 181 mMatrix.postScale(thumbnailScale, thumbnailScale); 182 mIsOrientationChanged = isOrientationDifferent; 183 } 184 getRotationDelta(int oldRotation, int newRotation)185 private int getRotationDelta(int oldRotation, int newRotation) { 186 int delta = newRotation - oldRotation; 187 if (delta < 0) delta += 4; 188 return delta; 189 } 190 191 /** 192 * @param deltaRotation the number of 90 degree turns from the current orientation 193 * @return {@code true} if the change in rotation results in a shift from landscape to 194 * portrait or vice versa, {@code false} otherwise 195 */ isOrientationChange(int deltaRotation)196 private boolean isOrientationChange(int deltaRotation) { 197 return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270; 198 } 199 setThumbnailRotation(int deltaRotate, Rect thumbnailPosition)200 private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) { 201 float translateX = 0; 202 float translateY = 0; 203 204 mMatrix.setRotate(90 * deltaRotate); 205 switch (deltaRotate) { /* Counter-clockwise */ 206 case ROTATION_90: 207 translateX = thumbnailPosition.height(); 208 break; 209 case ROTATION_270: 210 translateY = thumbnailPosition.width(); 211 break; 212 case ROTATION_180: 213 translateX = thumbnailPosition.width(); 214 translateY = thumbnailPosition.height(); 215 break; 216 } 217 mMatrix.postTranslate(translateX, translateY); 218 } 219 } 220