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