• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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