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.content.Context; 20 import android.graphics.Matrix; 21 import android.graphics.Rect; 22 import android.graphics.RectF; 23 import android.view.Choreographer; 24 import android.view.SurfaceControl; 25 26 import com.android.wm.shell.R; 27 import com.android.wm.shell.transition.Transitions; 28 29 /** 30 * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. 31 */ 32 public class PipSurfaceTransactionHelper { 33 /** for {@link #scale(SurfaceControl.Transaction, SurfaceControl, Rect, Rect)} operation */ 34 private final Matrix mTmpTransform = new Matrix(); 35 private final float[] mTmpFloat9 = new float[9]; 36 private final RectF mTmpSourceRectF = new RectF(); 37 private final RectF mTmpDestinationRectF = new RectF(); 38 private final Rect mTmpDestinationRect = new Rect(); 39 40 private int mCornerRadius; 41 private int mShadowRadius; 42 PipSurfaceTransactionHelper(Context context)43 public PipSurfaceTransactionHelper(Context context) { 44 onDensityOrFontScaleChanged(context); 45 } 46 47 /** 48 * Called when display size or font size of settings changed 49 * 50 * @param context the current context 51 */ onDensityOrFontScaleChanged(Context context)52 public void onDensityOrFontScaleChanged(Context context) { 53 mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); 54 mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); 55 } 56 57 /** 58 * Operates the alpha on a given transaction and leash 59 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 60 */ alpha(SurfaceControl.Transaction tx, SurfaceControl leash, float alpha)61 public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash, 62 float alpha) { 63 tx.setAlpha(leash, alpha); 64 return this; 65 } 66 67 /** 68 * Operates the crop (and position) on a given transaction and leash 69 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 70 */ crop(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds)71 public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash, 72 Rect destinationBounds) { 73 tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) 74 .setPosition(leash, destinationBounds.left, destinationBounds.top); 75 return this; 76 } 77 78 /** 79 * Operates the scale (setMatrix) on a given transaction and leash 80 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 81 */ scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)82 public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, 83 Rect sourceBounds, Rect destinationBounds) { 84 return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */); 85 } 86 87 /** 88 * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. 89 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 90 */ scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees)91 public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, 92 Rect sourceBounds, Rect destinationBounds, float degrees) { 93 mTmpSourceRectF.set(sourceBounds); 94 // We want the matrix to position the surface relative to the screen coordinates so offset 95 // the source to 0,0 96 mTmpSourceRectF.offsetTo(0, 0); 97 mTmpDestinationRectF.set(destinationBounds); 98 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 99 mTmpTransform.postRotate(degrees, 100 mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY()); 101 tx.setMatrix(leash, mTmpTransform, mTmpFloat9); 102 return this; 103 } 104 105 /** 106 * Operates the scale (setMatrix) on a given transaction and leash 107 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 108 */ scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets, boolean isInPipDirection, float fraction)109 public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, 110 SurfaceControl leash, Rect sourceRectHint, 111 Rect sourceBounds, Rect destinationBounds, Rect insets, 112 boolean isInPipDirection, float fraction) { 113 mTmpDestinationRect.set(sourceBounds); 114 // Similar to {@link #scale}, we want to position the surface relative to the screen 115 // coordinates so offset the bounds to 0,0 116 mTmpDestinationRect.offsetTo(0, 0); 117 mTmpDestinationRect.inset(insets); 118 // Scale to the bounds no smaller than the destination and offset such that the top/left 119 // of the scaled inset source rect aligns with the top/left of the destination bounds 120 final float scale; 121 if (isInPipDirection 122 && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) { 123 // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only. 124 final float endScale = sourceBounds.width() <= sourceBounds.height() 125 ? (float) destinationBounds.width() / sourceRectHint.width() 126 : (float) destinationBounds.height() / sourceRectHint.height(); 127 final float startScale = sourceBounds.width() <= sourceBounds.height() 128 ? (float) destinationBounds.width() / sourceBounds.width() 129 : (float) destinationBounds.height() / sourceBounds.height(); 130 scale = (1 - fraction) * startScale + fraction * endScale; 131 } else { 132 scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), 133 (float) destinationBounds.height() / sourceBounds.height()); 134 } 135 final float left = destinationBounds.left - insets.left * scale; 136 final float top = destinationBounds.top - insets.top * scale; 137 mTmpTransform.setScale(scale, scale); 138 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 139 .setCrop(leash, mTmpDestinationRect) 140 .setPosition(leash, left, top); 141 return this; 142 } 143 144 /** 145 * Operates the rotation according to the given degrees and scale (setMatrix) according to the 146 * source bounds and rotated destination bounds. The crop will be the unscaled source bounds. 147 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 148 */ rotateAndScaleWithCrop(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, float degrees, float positionX, float positionY, boolean isExpanding, boolean clockwise)149 public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx, 150 SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, 151 float degrees, float positionX, float positionY, boolean isExpanding, 152 boolean clockwise) { 153 mTmpDestinationRect.set(sourceBounds); 154 mTmpDestinationRect.inset(insets); 155 final int srcW = mTmpDestinationRect.width(); 156 final int srcH = mTmpDestinationRect.height(); 157 final int destW = destinationBounds.width(); 158 final int destH = destinationBounds.height(); 159 // Scale by the short side so there won't be empty area if the aspect ratio of source and 160 // destination are different. 161 final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; 162 final Rect crop = mTmpDestinationRect; 163 crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH 164 : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); 165 // Inverse scale for crop to fit in screen coordinates. 166 crop.scale(1 / scale); 167 crop.offset(insets.left, insets.top); 168 if (isExpanding) { 169 // Expand bounds (shrink insets) in source orientation. 170 positionX -= insets.left * scale; 171 positionY -= insets.top * scale; 172 } else { 173 // Shrink bounds (expand insets) in destination orientation. 174 if (clockwise) { 175 positionX -= insets.top * scale; 176 positionY += insets.left * scale; 177 } else { 178 positionX += insets.top * scale; 179 positionY -= insets.left * scale; 180 } 181 } 182 mTmpTransform.setScale(scale, scale); 183 mTmpTransform.postRotate(degrees); 184 mTmpTransform.postTranslate(positionX, positionY); 185 tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); 186 return this; 187 } 188 189 /** 190 * Resets the scale (setMatrix) on a given transaction and leash if there's any 191 * 192 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 193 */ resetScale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds)194 public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, 195 SurfaceControl leash, 196 Rect destinationBounds) { 197 tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9) 198 .setPosition(leash, destinationBounds.left, destinationBounds.top); 199 return this; 200 } 201 202 /** 203 * Operates the round corner radius on a given transaction and leash 204 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 205 */ round(SurfaceControl.Transaction tx, SurfaceControl leash, boolean applyCornerRadius)206 public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, 207 boolean applyCornerRadius) { 208 tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0); 209 return this; 210 } 211 212 /** 213 * Operates the round corner radius on a given transaction and leash, scaled by bounds 214 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 215 */ round(SurfaceControl.Transaction tx, SurfaceControl leash, Rect fromBounds, Rect toBounds)216 public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash, 217 Rect fromBounds, Rect toBounds) { 218 final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height()) 219 / Math.hypot(toBounds.width(), toBounds.height())); 220 tx.setCornerRadius(leash, mCornerRadius * scale); 221 return this; 222 } 223 224 /** 225 * Operates the shadow radius on a given transaction and leash 226 * @return same {@link PipSurfaceTransactionHelper} instance for method chaining 227 */ shadow(SurfaceControl.Transaction tx, SurfaceControl leash, boolean applyShadowRadius)228 public PipSurfaceTransactionHelper shadow(SurfaceControl.Transaction tx, SurfaceControl leash, 229 boolean applyShadowRadius) { 230 tx.setShadowRadius(leash, applyShadowRadius ? mShadowRadius : 0); 231 return this; 232 } 233 234 public interface SurfaceControlTransactionFactory { getTransaction()235 SurfaceControl.Transaction getTransaction(); 236 } 237 238 /** 239 * Implementation of {@link SurfaceControlTransactionFactory} that returns 240 * {@link SurfaceControl.Transaction} with VsyncId being set. 241 */ 242 public static class VsyncSurfaceControlTransactionFactory 243 implements SurfaceControlTransactionFactory { 244 @Override getTransaction()245 public SurfaceControl.Transaction getTransaction() { 246 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 247 tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 248 return tx; 249 } 250 } 251 } 252