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, int screenWidthPx, int screenHeightPx, int taskbarSize, boolean isLargeScreen, int currentRotation, boolean isRtl)61 public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, 62 int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx, 63 int taskbarSize, boolean isLargeScreen, 64 int currentRotation, boolean isRtl) { 65 boolean isRotated = false; 66 boolean isOrientationDifferent; 67 68 int thumbnailRotation = thumbnailData.rotation; 69 int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation); 70 RectF thumbnailClipHint = new RectF(); 71 float scale = thumbnailData.scale; 72 final float thumbnailScale; 73 74 // Landscape vs portrait change. 75 // Note: Disable rotation in grid layout. 76 boolean windowingModeSupportsRotation = 77 thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen; 78 isOrientationDifferent = isOrientationChange(deltaRotate) 79 && windowingModeSupportsRotation; 80 if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) { 81 // If we haven't measured , skip the thumbnail drawing and only draw the background 82 // color 83 thumbnailScale = 0f; 84 } else { 85 // Rotate the screenshot if not in multi-window mode 86 isRotated = deltaRotate > 0 && windowingModeSupportsRotation; 87 88 float surfaceWidth = thumbnailBounds.width() / scale; 89 float surfaceHeight = thumbnailBounds.height() / scale; 90 float availableWidth = surfaceWidth; 91 float availableHeight = surfaceHeight; 92 93 float canvasAspect = canvasWidth / (float) canvasHeight; 94 float availableAspect = isRotated 95 ? availableHeight / availableWidth 96 : availableWidth / availableHeight; 97 boolean isAspectLargelyDifferent = 98 Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect, 99 availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); 100 if (isRotated && isAspectLargelyDifferent) { 101 // Do not rotate thumbnail if it would not improve fit 102 isRotated = false; 103 isOrientationDifferent = false; 104 } 105 106 if (isAspectLargelyDifferent) { 107 // Crop letterbox insets if insets isn't already clipped 108 thumbnailClipHint.left = thumbnailData.letterboxInsets.left; 109 thumbnailClipHint.right = thumbnailData.letterboxInsets.right; 110 thumbnailClipHint.top = thumbnailData.letterboxInsets.top; 111 thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom; 112 availableWidth = surfaceWidth 113 - (thumbnailClipHint.left + thumbnailClipHint.right); 114 availableHeight = surfaceHeight 115 - (thumbnailClipHint.top + thumbnailClipHint.bottom); 116 } 117 118 final float targetW, targetH; 119 if (isOrientationDifferent) { 120 targetW = canvasHeight; 121 targetH = canvasWidth; 122 } else { 123 targetW = canvasWidth; 124 targetH = canvasHeight; 125 } 126 float targetAspect = targetW / targetH; 127 128 // Update the clipHint such that 129 // > the final clipped position has same aspect ratio as requested by canvas 130 // > first fit the width and crop the extra height 131 // > if that will leave empty space, fit the height and crop the width instead 132 float croppedWidth = availableWidth; 133 float croppedHeight = croppedWidth / targetAspect; 134 if (croppedHeight > availableHeight) { 135 croppedHeight = availableHeight; 136 if (croppedHeight < targetH) { 137 croppedHeight = Math.min(targetH, surfaceHeight); 138 } 139 croppedWidth = croppedHeight * targetAspect; 140 141 // One last check in case the task aspect radio messed up something 142 if (croppedWidth > surfaceWidth) { 143 croppedWidth = surfaceWidth; 144 croppedHeight = croppedWidth / targetAspect; 145 } 146 } 147 148 // Update the clip hints. Align to 0,0, crop the remaining. 149 if (isRtl) { 150 thumbnailClipHint.left += availableWidth - croppedWidth; 151 if (thumbnailClipHint.right < 0) { 152 thumbnailClipHint.left += thumbnailClipHint.right; 153 thumbnailClipHint.right = 0; 154 } 155 } else { 156 thumbnailClipHint.right += availableWidth - croppedWidth; 157 if (thumbnailClipHint.left < 0) { 158 thumbnailClipHint.right += thumbnailClipHint.left; 159 thumbnailClipHint.left = 0; 160 } 161 } 162 thumbnailClipHint.bottom += availableHeight - croppedHeight; 163 if (thumbnailClipHint.top < 0) { 164 thumbnailClipHint.bottom += thumbnailClipHint.top; 165 thumbnailClipHint.top = 0; 166 } else if (thumbnailClipHint.bottom < 0) { 167 thumbnailClipHint.top += thumbnailClipHint.bottom; 168 thumbnailClipHint.bottom = 0; 169 } 170 171 thumbnailScale = targetW / (croppedWidth * scale); 172 } 173 174 if (!isRotated) { 175 mMatrix.setTranslate( 176 -thumbnailClipHint.left * scale, 177 -thumbnailClipHint.top * scale); 178 } else { 179 setThumbnailRotation(deltaRotate, thumbnailBounds); 180 } 181 182 mMatrix.postScale(thumbnailScale, thumbnailScale); 183 mIsOrientationChanged = isOrientationDifferent; 184 } 185 getRotationDelta(int oldRotation, int newRotation)186 private int getRotationDelta(int oldRotation, int newRotation) { 187 int delta = newRotation - oldRotation; 188 if (delta < 0) delta += 4; 189 return delta; 190 } 191 192 /** 193 * @param deltaRotation the number of 90 degree turns from the current orientation 194 * @return {@code true} if the change in rotation results in a shift from landscape to 195 * portrait or vice versa, {@code false} otherwise 196 */ isOrientationChange(int deltaRotation)197 private boolean isOrientationChange(int deltaRotation) { 198 return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270; 199 } 200 setThumbnailRotation(int deltaRotate, Rect thumbnailPosition)201 private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) { 202 float translateX = 0; 203 float translateY = 0; 204 205 mMatrix.setRotate(90 * deltaRotate); 206 switch (deltaRotate) { /* Counter-clockwise */ 207 case ROTATION_90: 208 translateX = thumbnailPosition.height(); 209 break; 210 case ROTATION_270: 211 translateY = thumbnailPosition.width(); 212 break; 213 case ROTATION_180: 214 translateX = thumbnailPosition.width(); 215 translateY = thumbnailPosition.height(); 216 break; 217 } 218 mMatrix.postTranslate(translateX, translateY); 219 } 220 } 221