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 android.util;
18 
19 import static android.view.Surface.ROTATION_0;
20 import static android.view.Surface.ROTATION_180;
21 import static android.view.Surface.ROTATION_270;
22 import static android.view.Surface.ROTATION_90;
23 
24 import android.annotation.Dimension;
25 import android.graphics.Insets;
26 import android.graphics.Matrix;
27 import android.graphics.Point;
28 import android.graphics.PointF;
29 import android.graphics.Rect;
30 import android.view.Surface;
31 import android.view.Surface.Rotation;
32 import android.view.SurfaceControl;
33 
34 /**
35  * A class containing utility methods related to rotation.
36  *
37  * @hide
38  */
39 @android.ravenwood.annotation.RavenwoodKeepWholeClass
40 public class RotationUtils {
41 
42     /**
43      * Rotates an Insets according to the given rotation.
44      */
rotateInsets(Insets insets, @Rotation int rotation)45     public static Insets rotateInsets(Insets insets, @Rotation int rotation) {
46         if (insets == null || insets == Insets.NONE) {
47             return insets;
48         }
49         Insets rotated;
50         switch (rotation) {
51             case ROTATION_0:
52                 rotated = insets;
53                 break;
54             case ROTATION_90:
55                 rotated = Insets.of(
56                         insets.top,
57                         insets.right,
58                         insets.bottom,
59                         insets.left);
60                 break;
61             case ROTATION_180:
62                 rotated = Insets.of(
63                         insets.right,
64                         insets.bottom,
65                         insets.left,
66                         insets.top);
67                 break;
68             case ROTATION_270:
69                 rotated = Insets.of(
70                         insets.bottom,
71                         insets.left,
72                         insets.top,
73                         insets.right);
74                 break;
75             default:
76                 throw new IllegalArgumentException("unknown rotation: " + rotation);
77         }
78         return rotated;
79     }
80 
81     /**
82      * Rotates bounds as if parentBounds and bounds are a group. The group is rotated from
83      * oldRotation to newRotation. This assumes that parentBounds is at 0,0 and remains at 0,0 after
84      * rotation. The bounds will be at the same physical position in parentBounds.
85      *
86      * Only 'inOutBounds' is mutated.
87      */
rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int oldRotation, @Rotation int newRotation)88     public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int oldRotation,
89             @Rotation int newRotation) {
90         rotateBounds(inOutBounds, parentBounds, deltaRotation(oldRotation, newRotation));
91     }
92 
93     /**
94      * Rotates inOutBounds together with the parent for a given rotation delta. This assumes that
95      * the parent starts at 0,0 and remains at 0,0 after the rotation. The inOutBounds will remain
96      * at the same physical position within the parent.
97      *
98      * Only 'inOutBounds' is mutated.
99      */
rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, @Rotation int rotation)100     public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
101             @Rotation int rotation) {
102         final int origLeft = inOutBounds.left;
103         final int origTop = inOutBounds.top;
104         switch (rotation) {
105             case ROTATION_0:
106                 return;
107             case ROTATION_90:
108                 inOutBounds.left = inOutBounds.top;
109                 inOutBounds.top = parentWidth - inOutBounds.right;
110                 inOutBounds.right = inOutBounds.bottom;
111                 inOutBounds.bottom = parentWidth - origLeft;
112                 return;
113             case ROTATION_180:
114                 inOutBounds.left = parentWidth - inOutBounds.right;
115                 inOutBounds.right = parentWidth - origLeft;
116                 inOutBounds.top = parentHeight - inOutBounds.bottom;
117                 inOutBounds.bottom = parentHeight - origTop;
118                 return;
119             case ROTATION_270:
120                 inOutBounds.left = parentHeight - inOutBounds.bottom;
121                 inOutBounds.bottom = inOutBounds.right;
122                 inOutBounds.right = parentHeight - inOutBounds.top;
123                 inOutBounds.top = origLeft;
124         }
125     }
126 
127     /**
128      * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
129      * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
130      * remains at 0,0 after rotation. The bounds will be at the same physical position in
131      * parentBounds.
132      *
133      * Only 'inOutBounds' is mutated.
134      */
rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int rotation)135     public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int rotation) {
136         rotateBounds(inOutBounds, parentBounds.right, parentBounds.bottom, rotation);
137     }
138 
139     /** @return the rotation needed to rotate from oldRotation to newRotation. */
140     @Rotation
deltaRotation(@otation int oldRotation, @Rotation int newRotation)141     public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
142         int delta = newRotation - oldRotation;
143         if (delta < 0) delta += 4;
144         return delta;
145     }
146 
147     /**
148      * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
149      * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
150      * corner appropriately.
151      */
rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc, @Rotation int rotation)152     public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
153             @Rotation int rotation) {
154         // Note: the matrix values look inverted, but they aren't because our coordinate-space
155         // is actually left-handed.
156         // Note: setMatrix expects values in column-major order.
157         switch (rotation) {
158             case ROTATION_0:
159                 t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f);
160                 break;
161             case ROTATION_90:
162                 t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f);
163                 break;
164             case ROTATION_180:
165                 t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f);
166                 break;
167             case ROTATION_270:
168                 t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f);
169                 break;
170         }
171     }
172 
173     /**
174      * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the
175      * origin as if the point is stuck to the rectangle. The rectangle is transformed such that
176      * it's top/left remains at the origin after the rotation.
177      */
rotatePoint(Point inOutPoint, @Rotation int rotation, int parentW, int parentH)178     public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
179             int parentW, int parentH) {
180         int origX = inOutPoint.x;
181         switch (rotation) {
182             case ROTATION_0:
183                 return;
184             case ROTATION_90:
185                 inOutPoint.x = inOutPoint.y;
186                 inOutPoint.y = parentW - origX;
187                 return;
188             case ROTATION_180:
189                 inOutPoint.x = parentW - inOutPoint.x;
190                 inOutPoint.y = parentH - inOutPoint.y;
191                 return;
192             case ROTATION_270:
193                 inOutPoint.x = parentH - inOutPoint.y;
194                 inOutPoint.y = origX;
195         }
196     }
197 
198     /**
199      * Same as {@link #rotatePoint}, but for float coordinates.
200      */
rotatePointF(PointF inOutPoint, @Rotation int rotation, float parentW, float parentH)201     public static void rotatePointF(PointF inOutPoint, @Rotation int rotation,
202             float parentW, float parentH) {
203         float origX = inOutPoint.x;
204         switch (rotation) {
205             case ROTATION_0:
206                 return;
207             case ROTATION_90:
208                 inOutPoint.x = inOutPoint.y;
209                 inOutPoint.y = parentW - origX;
210                 return;
211             case ROTATION_180:
212                 inOutPoint.x = parentW - inOutPoint.x;
213                 inOutPoint.y = parentH - inOutPoint.y;
214                 return;
215             case ROTATION_270:
216                 inOutPoint.x = parentH - inOutPoint.y;
217                 inOutPoint.y = origX;
218         }
219     }
220 
221     /**
222      * Sets a matrix such that given a rotation, it transforms physical display
223      * coordinates to that rotation's logical coordinates.
224      *
225      * @param rotation the rotation to which the matrix should transform
226      * @param out the matrix to be set
227      */
transformPhysicalToLogicalCoordinates(@otation int rotation, @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out)228     public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation,
229             @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
230         switch (rotation) {
231             case ROTATION_0:
232                 out.reset();
233                 break;
234             case ROTATION_90:
235                 out.setRotate(270);
236                 out.postTranslate(0, physicalWidth);
237                 break;
238             case ROTATION_180:
239                 out.setRotate(180);
240                 out.postTranslate(physicalWidth, physicalHeight);
241                 break;
242             case ROTATION_270:
243                 out.setRotate(90);
244                 out.postTranslate(physicalHeight, 0);
245                 break;
246             default:
247                 throw new IllegalArgumentException("Unknown rotation: " + rotation);
248         }
249     }
250 
251     /**
252      * Reverses the rotation direction around the Z axis. Note that this method assumes all
253      * rotations are relative to {@link Surface.ROTATION_0}.
254      *
255      * @param rotation the original rotation.
256      * @return the new rotation that should be applied.
257      */
258     @Surface.Rotation
reverseRotationDirectionAroundZAxis(@urface.Rotation int rotation)259     public static int reverseRotationDirectionAroundZAxis(@Surface.Rotation int rotation) {
260         // Flipping 270 and 90 has the same effect as changing the direction which rotation is
261         // applied.
262         if (rotation == Surface.ROTATION_90) {
263             rotation = Surface.ROTATION_270;
264         } else if (rotation == Surface.ROTATION_270) {
265             rotation = Surface.ROTATION_90;
266         }
267         return rotation;
268     }
269 }
270