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 Matrix mTmpTransform = new Matrix();
34     private final float[] mTmpFloat9 = new float[9];
35     private final RectF mTmpSourceRectF = new RectF();
36     private final RectF mTmpDestinationRectF = new RectF();
37     private final Rect mTmpDestinationRect = new Rect();
38 
PipSurfaceTransactionHelper(int cornerRadius)39     public PipSurfaceTransactionHelper(int cornerRadius) {
40         mCornerRadius = cornerRadius;
41     }
42 
scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)43     public PictureInPictureSurfaceTransaction scale(
44             SurfaceControl.Transaction tx, SurfaceControl leash,
45             Rect sourceBounds, Rect destinationBounds) {
46         float positionX = destinationBounds.left;
47         float positionY = destinationBounds.top;
48         mTmpSourceRectF.set(sourceBounds);
49         mTmpDestinationRectF.set(destinationBounds);
50         mTmpDestinationRectF.offsetTo(0, 0);
51         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
52         final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
53         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
54                 .setPosition(leash, positionX, positionY)
55                 .setCornerRadius(leash, cornerRadius);
56         return newPipSurfaceTransaction(positionX, positionY,
57                 mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
58     }
59 
scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degree, float positionX, float positionY)60     public PictureInPictureSurfaceTransaction scale(
61             SurfaceControl.Transaction tx, SurfaceControl leash,
62             Rect sourceBounds, Rect destinationBounds,
63             float degree, float positionX, float positionY) {
64         mTmpSourceRectF.set(sourceBounds);
65         mTmpDestinationRectF.set(destinationBounds);
66         mTmpDestinationRectF.offsetTo(0, 0);
67         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
68         mTmpTransform.postRotate(degree, 0, 0);
69         final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
70         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
71                 .setPosition(leash, positionX, positionY)
72                 .setCornerRadius(leash, cornerRadius);
73         return newPipSurfaceTransaction(positionX, positionY,
74                 mTmpFloat9, degree, cornerRadius, sourceBounds);
75     }
76 
scaleAndCrop( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets)77     public PictureInPictureSurfaceTransaction scaleAndCrop(
78             SurfaceControl.Transaction tx, SurfaceControl leash,
79             Rect sourceBounds, Rect destinationBounds, Rect insets) {
80         mTmpSourceRectF.set(sourceBounds);
81         mTmpDestinationRect.set(sourceBounds);
82         mTmpDestinationRect.inset(insets);
83         // Scale by the shortest edge and offset such that the top/left of the scaled inset
84         // source rect aligns with the top/left of the destination bounds
85         final float scale = sourceBounds.width() <= sourceBounds.height()
86                 ? (float) destinationBounds.width() / sourceBounds.width()
87                 : (float) destinationBounds.height() / sourceBounds.height();
88         final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
89         final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
90         mTmpTransform.setScale(scale, scale);
91         final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds);
92         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
93                 .setWindowCrop(leash, mTmpDestinationRect)
94                 .setPosition(leash, left, top)
95                 .setCornerRadius(leash, cornerRadius);
96         return newPipSurfaceTransaction(left, top,
97                 mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
98     }
99 
scaleAndRotate( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, float degree, float positionX, float positionY)100     public PictureInPictureSurfaceTransaction scaleAndRotate(
101             SurfaceControl.Transaction tx, SurfaceControl leash,
102             Rect sourceBounds, Rect destinationBounds, Rect insets,
103             float degree, float positionX, float positionY) {
104         mTmpSourceRectF.set(sourceBounds);
105         mTmpDestinationRect.set(sourceBounds);
106         mTmpDestinationRect.inset(insets);
107         // Scale by the shortest edge and offset such that the top/left of the scaled inset
108         // source rect aligns with the top/left of the destination bounds
109         final float scale = sourceBounds.width() <= sourceBounds.height()
110                 ? (float) destinationBounds.width() / sourceBounds.width()
111                 : (float) destinationBounds.height() / sourceBounds.height();
112         mTmpTransform.setRotate(degree, 0, 0);
113         mTmpTransform.postScale(scale, scale);
114         final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds);
115         // adjust the positions, take account also the insets
116         final float adjustedPositionX, adjustedPositionY;
117         if (degree < 0) {
118             adjustedPositionX = positionX + insets.top * scale;
119             adjustedPositionY = positionY + insets.left * scale;
120         } else {
121             adjustedPositionX = positionX - insets.top * scale;
122             adjustedPositionY = positionY - insets.left * scale;
123         }
124         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
125                 .setWindowCrop(leash, mTmpDestinationRect)
126                 .setPosition(leash, adjustedPositionX, adjustedPositionY)
127                 .setCornerRadius(leash, cornerRadius);
128         return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY,
129                 mTmpFloat9, degree, cornerRadius, mTmpDestinationRect);
130     }
131 
132     /** @return the round corner radius scaled by given from and to bounds */
getScaledCornerRadius(Rect fromBounds, Rect toBounds)133     private float getScaledCornerRadius(Rect fromBounds, Rect toBounds) {
134         final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
135                 / Math.hypot(toBounds.width(), toBounds.height()));
136         return mCornerRadius * scale;
137     }
138 
newPipSurfaceTransaction( float posX, float posY, float[] float9, float rotation, float cornerRadius, Rect windowCrop)139     private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction(
140             float posX, float posY, float[] float9, float rotation, float cornerRadius,
141             Rect windowCrop) {
142         return new PictureInPictureSurfaceTransaction.Builder()
143                 .setPosition(posX, posY)
144                 .setTransform(float9, rotation)
145                 .setCornerRadius(cornerRadius)
146                 .setWindowCrop(windowCrop)
147                 .build();
148     }
149 
150     /** @return {@link SurfaceControl.Transaction} instance with vsync-id */
newSurfaceControlTransaction()151     public static SurfaceControl.Transaction newSurfaceControlTransaction() {
152         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
153         tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
154         return tx;
155     }
156 }
157