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.systemui.shared.pip;
18 
19 import android.graphics.Matrix;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.view.Choreographer;
23 import android.view.SurfaceControl;
24 import android.window.PictureInPictureSurfaceTransaction;
25 
26 /**
27  * TODO(b/171721389): unify this class with
28  * {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}, for instance, there should be one
29  * source of truth on enabling/disabling and the actual value of corner radius.
30  */
31 public class PipSurfaceTransactionHelper {
32     private final int mCornerRadius;
33     private final int mShadowRadius;
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 
PipSurfaceTransactionHelper(int cornerRadius, int shadowRadius)40     public PipSurfaceTransactionHelper(int cornerRadius, int shadowRadius) {
41         mCornerRadius = cornerRadius;
42         mShadowRadius = shadowRadius;
43     }
44 
scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)45     public PictureInPictureSurfaceTransaction scale(
46             SurfaceControl.Transaction tx, SurfaceControl leash,
47             Rect sourceBounds, Rect destinationBounds) {
48         float positionX = destinationBounds.left;
49         float positionY = destinationBounds.top;
50         mTmpSourceRectF.set(sourceBounds);
51         mTmpDestinationRectF.set(destinationBounds);
52         mTmpDestinationRectF.offsetTo(0, 0);
53         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
54         final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
55         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
56                 .setPosition(leash, positionX, positionY)
57                 .setCornerRadius(leash, cornerRadius)
58                 .setShadowRadius(leash, mShadowRadius);
59         return newPipSurfaceTransaction(positionX, positionY,
60                 mTmpFloat9, 0 /* rotation */, cornerRadius, mShadowRadius, sourceBounds);
61     }
62 
scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degree, float positionX, float positionY)63     public PictureInPictureSurfaceTransaction scale(
64             SurfaceControl.Transaction tx, SurfaceControl leash,
65             Rect sourceBounds, Rect destinationBounds,
66             float degree, float positionX, float positionY) {
67         mTmpSourceRectF.set(sourceBounds);
68         mTmpDestinationRectF.set(destinationBounds);
69         mTmpDestinationRectF.offsetTo(0, 0);
70         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
71         mTmpTransform.postRotate(degree, 0, 0);
72         final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
73         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
74                 .setPosition(leash, positionX, positionY)
75                 .setCornerRadius(leash, cornerRadius)
76                 .setShadowRadius(leash, mShadowRadius);
77         return newPipSurfaceTransaction(positionX, positionY,
78                 mTmpFloat9, degree, cornerRadius, mShadowRadius, sourceBounds);
79     }
80 
scaleAndCrop( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets, float progress)81     public PictureInPictureSurfaceTransaction scaleAndCrop(
82             SurfaceControl.Transaction tx, SurfaceControl leash,
83             Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets,
84             float progress) {
85         mTmpSourceRectF.set(sourceBounds);
86         mTmpDestinationRect.set(sourceBounds);
87         mTmpDestinationRect.inset(insets);
88         // Scale to the bounds no smaller than the destination and offset such that the top/left
89         // of the scaled inset source rect aligns with the top/left of the destination bounds
90         final float scale;
91         if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) {
92             scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
93                     (float) destinationBounds.height() / sourceBounds.height());
94         } else {
95             // scale by sourceRectHint if it's not edge-to-edge
96             final float endScale = sourceRectHint.width() <= sourceRectHint.height()
97                     ? (float) destinationBounds.width() / sourceRectHint.width()
98                     : (float) destinationBounds.height() / sourceRectHint.height();
99             final float startScale = sourceRectHint.width() <= sourceRectHint.height()
100                     ? (float) destinationBounds.width() / sourceBounds.width()
101                     : (float) destinationBounds.height() / sourceBounds.height();
102             scale = (1 - progress) * startScale + progress * endScale;
103         }
104         final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
105         final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
106         mTmpTransform.setScale(scale, scale);
107         final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds);
108         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
109                 .setCrop(leash, mTmpDestinationRect)
110                 .setPosition(leash, left, top)
111                 .setCornerRadius(leash, cornerRadius)
112                 .setShadowRadius(leash, mShadowRadius);
113         return newPipSurfaceTransaction(left, top,
114                 mTmpFloat9, 0 /* rotation */, cornerRadius, mShadowRadius, mTmpDestinationRect);
115     }
116 
scaleAndRotate( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, float degree, float positionX, float positionY)117     public PictureInPictureSurfaceTransaction scaleAndRotate(
118             SurfaceControl.Transaction tx, SurfaceControl leash,
119             Rect sourceBounds, Rect destinationBounds, Rect insets,
120             float degree, float positionX, float positionY) {
121         mTmpSourceRectF.set(sourceBounds);
122         mTmpDestinationRect.set(sourceBounds);
123         mTmpDestinationRect.inset(insets);
124         // Scale by the shortest edge and offset such that the top/left of the scaled inset
125         // source rect aligns with the top/left of the destination bounds
126         final float scale = sourceBounds.width() <= sourceBounds.height()
127                 ? (float) destinationBounds.width() / sourceBounds.width()
128                 : (float) destinationBounds.height() / sourceBounds.height();
129         mTmpTransform.setRotate(degree, 0, 0);
130         mTmpTransform.postScale(scale, scale);
131         final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds);
132         // adjust the positions, take account also the insets
133         final float adjustedPositionX, adjustedPositionY;
134         if (degree < 0) {
135             adjustedPositionX = positionX + insets.top * scale;
136             adjustedPositionY = positionY + insets.left * scale;
137         } else {
138             adjustedPositionX = positionX - insets.top * scale;
139             adjustedPositionY = positionY - insets.left * scale;
140         }
141         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
142                 .setCrop(leash, mTmpDestinationRect)
143                 .setPosition(leash, adjustedPositionX, adjustedPositionY)
144                 .setCornerRadius(leash, cornerRadius)
145                 .setShadowRadius(leash, mShadowRadius);
146         return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY,
147                 mTmpFloat9, degree, cornerRadius, mShadowRadius, mTmpDestinationRect);
148     }
149 
150     /** @return the round corner radius scaled by given from and to bounds */
getScaledCornerRadius(Rect fromBounds, Rect toBounds)151     private float getScaledCornerRadius(Rect fromBounds, Rect toBounds) {
152         final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
153                 / Math.hypot(toBounds.width(), toBounds.height()));
154         return mCornerRadius * scale;
155     }
156 
newPipSurfaceTransaction( float posX, float posY, float[] float9, float rotation, float cornerRadius, float shadowRadius, Rect windowCrop)157     private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction(
158             float posX, float posY, float[] float9, float rotation,
159             float cornerRadius, float shadowRadius, Rect windowCrop) {
160         return new PictureInPictureSurfaceTransaction.Builder()
161                 .setPosition(posX, posY)
162                 .setTransform(float9, rotation)
163                 .setCornerRadius(cornerRadius)
164                 .setShadowRadius(shadowRadius)
165                 .setWindowCrop(windowCrop)
166                 .build();
167     }
168 
169     /** @return {@link SurfaceControl.Transaction} instance with vsync-id */
newSurfaceControlTransaction()170     public static SurfaceControl.Transaction newSurfaceControlTransaction() {
171         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
172         tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
173         return tx;
174     }
175 }
176