1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.pip; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.graphics.Matrix; 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import android.graphics.RectF; 25 import android.view.Choreographer; 26 import android.view.SurfaceControl; 27 28 import androidx.annotation.Nullable; 29 30 import com.android.wm.shell.R; 31 import com.android.wm.shell.transition.Transitions; 32 33 /** 34 * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. 35 */ 36 public class PipSurfaceTransactionHelper { 37 /** for {@link #scale(SurfaceControl.Transaction, SurfaceControl, Rect, Rect)} operation */ 38 private final Matrix mTmpTransform = new Matrix(); 39 private final float[] mTmpFloat9 = new float[9]; 40 private final RectF mTmpSourceRectF = new RectF(); 41 private final RectF mTmpDestinationRectF = new RectF(); 42 private final Rect mTmpDestinationRect = new Rect(); 43 44 private int mCornerRadius; 45 private int mShadowRadius; 46 PipSurfaceTransactionHelper(Context context)47 public PipSurfaceTransactionHelper(Context context) { 48 onDensityOrFontScaleChanged(context); 49 } 50 51 /** 52 * Called when display size or font size of settings changed 53 * 54 * @param context the current context 55 */ onDensityOrFontScaleChanged(Context context)56 public void onDensityOrFontScaleChanged(Context context) { 57 mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); 58 mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); 59 } 60 61 /** 62 * Operates the alpha on a given transaction and leash 63 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 64 */ alpha(SurfaceControl.Transaction tx, SurfaceControl leash, float alpha)65 public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash, 66 float alpha) { 67 tx.setAlpha(leash, alpha); 68 return this; 69 } 70 71 /** 72 * Operates the crop (and position) on a given transaction and leash 73 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 74 */ cropAndPosition(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect destinationBounds)75 public PipSurfaceTransactionHelper cropAndPosition(@NonNull SurfaceControl.Transaction tx, 76 @NonNull SurfaceControl leash, @NonNull Rect destinationBounds) { 77 tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) 78 .setPosition(leash, destinationBounds.left, destinationBounds.top); 79 return this; 80 } 81 82 /** 83 * Operates {@link SurfaceControl.Transaction#setCrop} on a given transaction and leash. 84 * 85 * @param tx the transaction to apply 86 * @param leash the leash to crop 87 * @param relativeDestinationBounds the bounds to crop, which is relative to the leash 88 * coordinate 89 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 90 */ crop(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect relativeDestinationBounds)91 public PipSurfaceTransactionHelper crop(@NonNull SurfaceControl.Transaction tx, 92 @NonNull SurfaceControl leash, @NonNull Rect relativeDestinationBounds) { 93 tx.setCrop(leash, relativeDestinationBounds); 94 return this; 95 } 96 97 /** 98 * Operates the scale (setMatrix) on a given transaction and leash 99 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 100 */ scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)101 public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, 102 Rect sourceBounds, Rect destinationBounds) { 103 mTmpDestinationRectF.set(destinationBounds); 104 return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */, 105 true /* shouldOffset */); 106 } 107 108 /** 109 * Operates the scale (setMatrix) on a given transaction and leash 110 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 111 */ scale(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull RectF destinationBounds)112 public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx, 113 @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, 114 @NonNull RectF destinationBounds) { 115 return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */, 116 true /* shouldOffset */); 117 } 118 119 /** 120 * Operates the scale (setMatrix) on a given transaction and leash. 121 * 122 * @param shouldOffset {@code true} to offset the leash to (0, 0) 123 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 124 */ scale(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, boolean shouldOffset)125 public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx, 126 @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, 127 @NonNull Rect destinationBounds, boolean shouldOffset) { 128 mTmpDestinationRectF.set(destinationBounds); 129 return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */, shouldOffset); 130 } 131 132 /** 133 * Operates the scale (setMatrix) on a given transaction and leash. 134 * 135 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 136 */ scale(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, float degrees)137 public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx, 138 @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, 139 @NonNull Rect destinationBounds, float degrees) { 140 return scale(tx, leash, sourceBounds, destinationBounds, degrees, true /* shouldOffset */); 141 } 142 143 /** 144 * Operates the scale (setMatrix) on a given transaction and leash. 145 * 146 * @param shouldOffset {@code true} to offset the leash to (0, 0) 147 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 148 */ scale(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, float degrees, boolean shouldOffset)149 public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx, 150 @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, 151 @NonNull Rect destinationBounds, float degrees, boolean shouldOffset) { 152 mTmpDestinationRectF.set(destinationBounds); 153 return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees, shouldOffset); 154 } 155 156 /** 157 * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. 158 * 159 * @param shouldOffset {@code true} to offset the leash to (0, 0) 160 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 161 */ scale(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull RectF destinationBounds, float degrees, boolean shouldOffset)162 public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx, 163 @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, 164 @NonNull RectF destinationBounds, float degrees, boolean shouldOffset) { 165 mTmpSourceRectF.set(sourceBounds); 166 // We want the matrix to position the surface relative to the screen coordinates so offset 167 // the source to (0, 0) if {@code shouldOffset} is true. 168 if (shouldOffset) { 169 mTmpSourceRectF.offsetTo(0, 0); 170 } 171 mTmpDestinationRectF.set(destinationBounds); 172 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 173 mTmpTransform.postRotate(degrees, 174 mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY()); 175 tx.setMatrix(leash, mTmpTransform, mTmpFloat9); 176 return this; 177 } 178 179 /** 180 * Operates the scale (setMatrix) on a given transaction and leash. 181 * 182 * @param leashOffset the offset of the leash bounds relative to the screen coordinate 183 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 184 */ scaleAndCrop(@onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceRectHint, @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, @NonNull Rect insets, boolean isInPipDirection, float fraction, @NonNull Point leashOffset)185 public PipSurfaceTransactionHelper scaleAndCrop(@NonNull SurfaceControl.Transaction tx, 186 @NonNull SurfaceControl leash, @NonNull Rect sourceRectHint, @NonNull Rect sourceBounds, 187 @NonNull Rect destinationBounds, @NonNull Rect insets, boolean isInPipDirection, 188 float fraction, @NonNull Point leashOffset) { 189 mTmpDestinationRect.set(sourceBounds); 190 // Similar to {@link #scale}, we want to position the surface relative to the screen 191 // coordinates so offset the bounds relative to the leash. 192 mTmpDestinationRect.offset(-leashOffset.x, -leashOffset.y); 193 mTmpDestinationRect.inset(insets); 194 // Scale to the bounds no smaller than the destination and offset such that the top/left 195 // of the scaled inset source rect aligns with the top/left of the destination bounds 196 final float scale; 197 if (isInPipDirection 198 && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) { 199 // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only. 200 final float endScale = sourceBounds.width() <= sourceBounds.height() 201 ? (float) destinationBounds.width() / sourceRectHint.width() 202 : (float) destinationBounds.height() / sourceRectHint.height(); 203 final float startScale = sourceBounds.width() <= sourceBounds.height() 204 ? (float) destinationBounds.width() / sourceBounds.width() 205 : (float) destinationBounds.height() / sourceBounds.height(); 206 scale = (1 - fraction) * startScale + fraction * endScale; 207 } else { 208 scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), 209 (float) destinationBounds.height() / sourceBounds.height()); 210 } 211 float left = destinationBounds.left - mTmpDestinationRect.left * scale; 212 float top = destinationBounds.top - mTmpDestinationRect.top * scale; 213 if (scale == 1) { 214 // Work around the 1 pixel off error by rounding the position down at very beginning. 215 // We noticed such error from flicker tests, not visually. 216 left = leashOffset.x; 217 top = leashOffset.y; 218 } 219 mTmpTransform.setScale(scale, scale); 220 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 221 .setCrop(leash, mTmpDestinationRect) 222 .setPosition(leash, left, top); 223 return this; 224 } 225 226 /** 227 * Operates the rotation according to the given degrees and scale (setMatrix) according to the 228 * source bounds and rotated destination bounds. The crop will be the unscaled source bounds. 229 * 230 * @param relativeEndWindowFrame specified if 231 * {@link android.app.TaskInfo#topActivityMainWindowFrame} is provided. It's only applied for 232 * the animation that {@code isExpanding} PIP to original task. 233 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 234 */ rotateAndScaleWithCrop( @onNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, @NonNull Rect insets, float degrees, float positionX, float positionY, boolean isExpanding, boolean clockwise, @Nullable Rect relativeEndWindowFrame)235 public PipSurfaceTransactionHelper rotateAndScaleWithCrop( 236 @NonNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash, 237 @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, @NonNull Rect insets, 238 float degrees, float positionX, float positionY, boolean isExpanding, 239 boolean clockwise, @Nullable Rect relativeEndWindowFrame) { 240 mTmpDestinationRect.set(sourceBounds); 241 mTmpDestinationRect.inset(insets); 242 final int srcW = mTmpDestinationRect.width(); 243 final int srcH = mTmpDestinationRect.height(); 244 final int destW = destinationBounds.width(); 245 final int destH = destinationBounds.height(); 246 // Scale by the short side so there won't be empty area if the aspect ratio of source and 247 // destination are different. 248 final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; 249 final Rect crop = mTmpDestinationRect; 250 if (isExpanding && relativeEndWindowFrame != null) { 251 // If relative end window frame is provided, it usually means the top activity chooses 252 // a customized layout which may not match parent. In this case, we should crop the 253 // task surface with the window frame. Note that we don't need to consider the insets 254 // because the main window frame excludes the insets. 255 crop.set(relativeEndWindowFrame); 256 } else { 257 crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH 258 : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); 259 // Inverse scale for crop to fit in screen coordinates. 260 crop.scale(1 / scale); 261 crop.offset(insets.left, insets.top); 262 if (isExpanding) { 263 // Expand bounds (shrink insets) in source orientation. 264 positionX -= insets.left * scale; 265 positionY -= insets.top * scale; 266 } else { 267 // Shrink bounds (expand insets) in destination orientation. 268 if (clockwise) { 269 positionX -= insets.top * scale; 270 positionY += insets.left * scale; 271 } else { 272 positionX += insets.top * scale; 273 positionY -= insets.left * scale; 274 } 275 } 276 } 277 mTmpTransform.setScale(scale, scale); 278 mTmpTransform.postRotate(degrees); 279 mTmpTransform.postTranslate(positionX, positionY); 280 tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); 281 return this; 282 } 283 284 /** 285 * Resets the scale (setMatrix) on a given transaction and leash if there's any 286 * 287 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 288 */ resetScale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds)289 public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, 290 SurfaceControl leash, 291 Rect destinationBounds) { 292 tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9) 293 .setPosition(leash, destinationBounds.left, destinationBounds.top); 294 return this; 295 } 296 297 /** 298 * Operates the round corner radius on a given transaction and leash 299 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 300 */ round(SurfaceControl.Transaction tx, SurfaceControl leash, boolean applyCornerRadius)301 public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, 302 boolean applyCornerRadius) { 303 tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0); 304 return this; 305 } 306 307 /** 308 * Operates the round corner radius on a given transaction and leash, scaled by bounds 309 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 310 */ round(SurfaceControl.Transaction tx, SurfaceControl leash, Rect fromBounds, Rect toBounds)311 public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, 312 Rect fromBounds, Rect toBounds) { 313 final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height()) 314 / Math.hypot(toBounds.width(), toBounds.height())); 315 tx.setCornerRadius(leash, mCornerRadius * scale); 316 return this; 317 } 318 319 /** 320 * Operates the shadow radius on a given transaction and leash 321 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 322 */ shadow(SurfaceControl.Transaction tx, SurfaceControl leash, boolean applyShadowRadius)323 public PipSurfaceTransactionHelper shadow(SurfaceControl.Transaction tx, SurfaceControl leash, 324 boolean applyShadowRadius) { 325 tx.setShadowRadius(leash, applyShadowRadius ? mShadowRadius : 0); 326 return this; 327 } 328 329 public interface SurfaceControlTransactionFactory { getTransaction()330 SurfaceControl.Transaction getTransaction(); 331 } 332 333 /** 334 * Implementation of {@link SurfaceControlTransactionFactory} that returns 335 * {@link SurfaceControl.Transaction} with VsyncId being set. 336 */ 337 public static class VsyncSurfaceControlTransactionFactory 338 implements SurfaceControlTransactionFactory { 339 @Override getTransaction()340 public SurfaceControl.Transaction getTransaction() { 341 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 342 tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 343 return tx; 344 } 345 } 346 } 347