1 /*
2  * Copyright (C) 2021 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.view;
18 
19 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
20 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
21 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
22 import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
23 import static android.view.Surface.ROTATION_0;
24 import static android.view.Surface.ROTATION_270;
25 import static android.view.Surface.ROTATION_90;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.graphics.Rect;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.util.DisplayUtils;
35 import android.util.Pair;
36 import android.view.RoundedCorner.Position;
37 
38 import com.android.internal.R;
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import java.util.Arrays;
43 
44 /**
45  * A class to create & manage all the {@link RoundedCorner} on the display.
46  *
47  * @hide
48  */
49 public class RoundedCorners implements Parcelable {
50 
51     public static final RoundedCorners NO_ROUNDED_CORNERS = new RoundedCorners(
52             new RoundedCorner(POSITION_TOP_LEFT), new RoundedCorner(POSITION_TOP_RIGHT),
53             new RoundedCorner(POSITION_BOTTOM_RIGHT), new RoundedCorner(POSITION_BOTTOM_LEFT));
54 
55     /**
56      * The number of possible positions at which rounded corners can be located.
57      */
58     public static final int ROUNDED_CORNER_POSITION_LENGTH = 4;
59 
60     private static final Object CACHE_LOCK = new Object();
61 
62     @GuardedBy("CACHE_LOCK")
63     private static int sCachedDisplayWidth;
64     @GuardedBy("CACHE_LOCK")
65     private static int sCachedDisplayHeight;
66     @GuardedBy("CACHE_LOCK")
67     private static Pair<Integer, Integer> sCachedRadii;
68     @GuardedBy("CACHE_LOCK")
69     private static RoundedCorners sCachedRoundedCorners;
70     @GuardedBy("CACHE_LOCK")
71     private static float sCachedPhysicalPixelDisplaySizeRatio;
72 
73     @VisibleForTesting
74     public final RoundedCorner[] mRoundedCorners;
75 
RoundedCorners(RoundedCorner[] roundedCorners)76     public RoundedCorners(RoundedCorner[] roundedCorners) {
77         mRoundedCorners = roundedCorners;
78     }
79 
RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight, RoundedCorner bottomLeft)80     public RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight,
81             RoundedCorner bottomLeft) {
82         mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
83         mRoundedCorners[POSITION_TOP_LEFT] = topLeft;
84         mRoundedCorners[POSITION_TOP_RIGHT] = topRight;
85         mRoundedCorners[POSITION_BOTTOM_RIGHT] = bottomRight;
86         mRoundedCorners[POSITION_BOTTOM_LEFT] = bottomLeft;
87     }
88 
RoundedCorners(RoundedCorners roundedCorners)89     public RoundedCorners(RoundedCorners roundedCorners) {
90         mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
91         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
92             mRoundedCorners[i] = new RoundedCorner(roundedCorners.mRoundedCorners[i]);
93         }
94     }
95 
96     /**
97      * Creates the rounded corners according to @android:dimen/rounded_corner_radius,
98      * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom
99      */
fromResources( Resources res, String displayUniqueId, int physicalDisplayWidth, int physicalDisplayHeight, int displayWidth, int displayHeight)100     public static RoundedCorners fromResources(
101             Resources res, String displayUniqueId, int physicalDisplayWidth,
102             int physicalDisplayHeight, int displayWidth, int displayHeight) {
103         return fromRadii(loadRoundedCornerRadii(res, displayUniqueId), physicalDisplayWidth,
104                 physicalDisplayHeight, displayWidth, displayHeight);
105     }
106 
107     /**
108      * Creates the rounded corners from radius
109      */
110     @VisibleForTesting
fromRadii(Pair<Integer, Integer> radii, int displayWidth, int displayHeight)111     public static RoundedCorners fromRadii(Pair<Integer, Integer> radii, int displayWidth,
112             int displayHeight) {
113         return fromRadii(radii, displayWidth, displayHeight, displayWidth, displayHeight);
114     }
115 
fromRadii(Pair<Integer, Integer> radii, int physicalDisplayWidth, int physicalDisplayHeight, int displayWidth, int displayHeight)116     private static RoundedCorners fromRadii(Pair<Integer, Integer> radii, int physicalDisplayWidth,
117             int physicalDisplayHeight, int displayWidth, int displayHeight) {
118         if (radii == null) {
119             return null;
120         }
121 
122         final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio(
123                 physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight);
124 
125         synchronized (CACHE_LOCK) {
126             if (radii.equals(sCachedRadii) && sCachedDisplayWidth == displayWidth
127                     && sCachedDisplayHeight == displayHeight
128                     && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) {
129                 return sCachedRoundedCorners;
130             }
131         }
132 
133         final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
134         int topRadius = radii.first > 0 ? radii.first : 0;
135         int bottomRadius = radii.second > 0 ? radii.second : 0;
136         if (physicalPixelDisplaySizeRatio != 1f) {
137             topRadius = (int) (topRadius * physicalPixelDisplaySizeRatio + 0.5);
138             bottomRadius = (int) (bottomRadius * physicalPixelDisplaySizeRatio + 0.5);
139         }
140         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
141             roundedCorners[i] = createRoundedCorner(
142                     i,
143                     i <= POSITION_TOP_RIGHT ? topRadius : bottomRadius,
144                     displayWidth,
145                     displayHeight);
146         }
147 
148         final RoundedCorners result = new RoundedCorners(roundedCorners);
149         synchronized (CACHE_LOCK) {
150             sCachedDisplayWidth = displayWidth;
151             sCachedDisplayHeight = displayHeight;
152             sCachedRadii = radii;
153             sCachedRoundedCorners = result;
154             sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
155         }
156         return result;
157     }
158 
159     /**
160      * Loads the rounded corner radii from resources.
161      *
162      * @param res
163      * @param displayUniqueId the display unique id.
164      * @return a Pair of radius. The first is the top rounded corner radius and second is the
165      * bottom corner radius.
166      */
167     @Nullable
loadRoundedCornerRadii( Resources res, String displayUniqueId)168     private static Pair<Integer, Integer> loadRoundedCornerRadii(
169             Resources res, String displayUniqueId) {
170         final int radiusDefault = getRoundedCornerRadius(res, displayUniqueId);
171         final int radiusTop = getRoundedCornerTopRadius(res, displayUniqueId);
172         final int radiusBottom = getRoundedCornerBottomRadius(res, displayUniqueId);
173         if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) {
174             return null;
175         }
176         final Pair<Integer, Integer> radii = new Pair<>(
177                         radiusTop > 0 ? radiusTop : radiusDefault,
178                         radiusBottom > 0 ? radiusBottom : radiusDefault);
179         return radii;
180     }
181 
182     /**
183      * Gets the default rounded corner radius of a display which is determined by the
184      * given display unique id.
185      *
186      * Loads the default dimen{@link R.dimen#rounded_corner_radius} if
187      * {@link R.array#config_displayUniqueIdArray} is not set.
188      *
189      * @hide
190      */
getRoundedCornerRadius(Resources res, String displayUniqueId)191     public static int getRoundedCornerRadius(Resources res, String displayUniqueId) {
192         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
193         final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerRadiusArray);
194         int radius;
195         if (index >= 0 && index < array.length()) {
196             radius = array.getDimensionPixelSize(index, 0);
197         } else {
198             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
199         }
200         array.recycle();
201         // For devices with round displays (e.g. watches) that don't otherwise provide the rounded
202         // corner radius via resource overlays, we can infer the corner radius directly from the
203         // display size.
204         if (radius == 0 && res.getConfiguration().isScreenRound()) {
205             radius = res.getDisplayMetrics().widthPixels / 2;
206         }
207         return radius;
208     }
209 
210     /**
211      * Gets the top rounded corner radius of a display which is determined by the
212      * given display unique id.
213      *
214      * Loads the default dimen{@link R.dimen#rounded_corner_radius_top} if
215      * {@link R.array#config_displayUniqueIdArray} is not set.
216      *
217      * @hide
218      */
getRoundedCornerTopRadius(Resources res, String displayUniqueId)219     public static int getRoundedCornerTopRadius(Resources res, String displayUniqueId) {
220         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
221         final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray);
222         int radius;
223         if (index >= 0 && index < array.length()) {
224             radius = array.getDimensionPixelSize(index, 0);
225         } else {
226             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
227         }
228         array.recycle();
229         return radius;
230     }
231 
232     /**
233      * Gets the bottom rounded corner radius of a display which is determined by the
234      * given display unique id.
235      *
236      * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom} if
237      * {@link R.array#config_displayUniqueIdArray} is not set.
238      *
239      * @hide
240      */
getRoundedCornerBottomRadius(Resources res, String displayUniqueId)241     public static int getRoundedCornerBottomRadius(Resources res, String displayUniqueId) {
242         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
243         final TypedArray array = res.obtainTypedArray(
244                 R.array.config_roundedCornerBottomRadiusArray);
245         int radius;
246         if (index >= 0 && index < array.length()) {
247             radius = array.getDimensionPixelSize(index, 0);
248         } else {
249             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
250         }
251         array.recycle();
252         return radius;
253     }
254 
255     /**
256      * Gets the rounded corner radius adjustment of a display which is determined by the
257      * given display unique id.
258      *
259      * Loads the default dimen{@link R.dimen#rounded_corner_radius_adjustment} if
260      * {@link R.array#config_displayUniqueIdArray} is not set.
261      *
262      * @hide
263      */
getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId)264     public static int getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId) {
265         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
266         final TypedArray array = res.obtainTypedArray(
267                 R.array.config_roundedCornerRadiusAdjustmentArray);
268         int radius;
269         if (index >= 0 && index < array.length()) {
270             radius = array.getDimensionPixelSize(index, 0);
271         } else {
272             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_adjustment);
273         }
274         array.recycle();
275         return radius;
276     }
277 
278     /**
279      * Gets the rounded corner top radius adjustment of a display which is determined by the
280      * given display unique id.
281      *
282      * Loads the default dimen{@link R.dimen#rounded_corner_radius_top_adjustment} if
283      * {@link R.array#config_displayUniqueIdArray} is not set.
284      *
285      * @hide
286      */
getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId)287     public static int getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId) {
288         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
289         final TypedArray array = res.obtainTypedArray(
290                 R.array.config_roundedCornerTopRadiusAdjustmentArray);
291         int radius;
292         if (index >= 0 && index < array.length()) {
293             radius = array.getDimensionPixelSize(index, 0);
294         } else {
295             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top_adjustment);
296         }
297         array.recycle();
298         return radius;
299     }
300 
301     /**
302      * Gets the rounded corner bottom radius adjustment of a display which is determined by the
303      * given display unique id.
304      *
305      * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom_adjustment} if
306      * {@link R.array#config_displayUniqueIdArray} is not set.
307      *
308      * @hide
309      */
getRoundedCornerRadiusBottomAdjustment( Resources res, String displayUniqueId)310     public static int getRoundedCornerRadiusBottomAdjustment(
311             Resources res, String displayUniqueId) {
312         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
313         final TypedArray array = res.obtainTypedArray(
314                 R.array.config_roundedCornerBottomRadiusAdjustmentArray);
315         int radius;
316         if (index >= 0 && index < array.length()) {
317             radius = array.getDimensionPixelSize(index, 0);
318         } else {
319             radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom_adjustment);
320         }
321         array.recycle();
322         return radius;
323     }
324 
325     /**
326      * Gets whether a built-in display is round.
327      *
328      * Loads the default config{@link R.bool#config_mainBuiltInDisplayIsRound} if
329      * {@link R.array#config_displayUniqueIdArray} is not set.
330      *
331      * @hide
332      */
getBuiltInDisplayIsRound(Resources res, String displayUniqueId)333     public static boolean getBuiltInDisplayIsRound(Resources res, String displayUniqueId) {
334         final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
335         final TypedArray array = res.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray);
336         boolean isRound;
337         if (index >= 0 && index < array.length()) {
338             isRound = array.getBoolean(index, false);
339         } else {
340             isRound = res.getBoolean(R.bool.config_mainBuiltInDisplayIsRound);
341         }
342         array.recycle();
343         return isRound;
344     }
345 
346     /**
347      * Insets the reference frame of the rounded corners.
348      *
349      * @param frame the frame of a window or any rectangle bounds
350      * @param roundedCornerFrame the frame that used to calculate relative {@link RoundedCorner}
351      * @return a copy of this instance which has been inset
352      */
insetWithFrame(Rect frame, Rect roundedCornerFrame)353     public RoundedCorners insetWithFrame(Rect frame, Rect roundedCornerFrame) {
354         int insetLeft = frame.left - roundedCornerFrame.left;
355         int insetTop = frame.top - roundedCornerFrame.top;
356         int insetRight = roundedCornerFrame.right - frame.right;
357         int insetBottom = roundedCornerFrame.bottom - frame.bottom;
358         final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
359         int centerX, centerY;
360         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
361             if (mRoundedCorners[i].isEmpty()) {
362                 roundedCorners[i] = new RoundedCorner(i);
363                 continue;
364             }
365             final int radius = mRoundedCorners[i].getRadius();
366             switch (i) {
367                 case POSITION_TOP_LEFT:
368                     centerX = radius;
369                     centerY = radius;
370                     break;
371                 case POSITION_TOP_RIGHT:
372                     centerX = roundedCornerFrame.width() - radius;
373                     centerY = radius;
374                     break;
375                 case POSITION_BOTTOM_RIGHT:
376                     centerX = roundedCornerFrame.width() - radius;
377                     centerY = roundedCornerFrame.height() - radius;
378                     break;
379                 case POSITION_BOTTOM_LEFT:
380                     centerX = radius;
381                     centerY = roundedCornerFrame.height() - radius;
382                     break;
383                 default:
384                     throw new IllegalArgumentException(
385                             "The position is not one of the RoundedCornerPosition =" + i);
386             }
387             roundedCorners[i] = insetRoundedCorner(i, radius, centerX, centerY, insetLeft, insetTop,
388                     insetRight, insetBottom);
389         }
390         return new RoundedCorners(roundedCorners);
391     }
392 
393     /**
394      * Insets the reference frame of the rounded corners.
395      *
396      * @return a copy of this instance which has been inset
397      */
inset(int insetLeft, int insetTop, int insetRight, int insetBottom)398     public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
399         final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
400         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
401             roundedCorners[i] = insetRoundedCorner(i, mRoundedCorners[i].getRadius(),
402                     mRoundedCorners[i].getCenter().x, mRoundedCorners[i].getCenter().y, insetLeft,
403                     insetTop, insetRight, insetBottom);
404         }
405         return new RoundedCorners(roundedCorners);
406     }
407 
insetRoundedCorner(@osition int position, int radius, int centerX, int centerY, int insetLeft, int insetTop, int insetRight, int insetBottom)408     private RoundedCorner insetRoundedCorner(@Position int position, int radius, int centerX,
409             int centerY, int insetLeft, int insetTop, int insetRight, int insetBottom) {
410         if (mRoundedCorners[position].isEmpty()) {
411             return new RoundedCorner(position);
412         }
413 
414         boolean hasRoundedCorner;
415         switch (position) {
416             case POSITION_TOP_LEFT:
417                 hasRoundedCorner = radius > insetTop && radius > insetLeft;
418                 break;
419             case POSITION_TOP_RIGHT:
420                 hasRoundedCorner = radius > insetTop && radius > insetRight;
421                 break;
422             case POSITION_BOTTOM_RIGHT:
423                 hasRoundedCorner = radius > insetBottom && radius > insetRight;
424                 break;
425             case POSITION_BOTTOM_LEFT:
426                 hasRoundedCorner = radius > insetBottom && radius > insetLeft;
427                 break;
428             default:
429                 throw new IllegalArgumentException(
430                         "The position is not one of the RoundedCornerPosition =" + position);
431         }
432         return new RoundedCorner(
433                 position, radius,
434                 hasRoundedCorner ? centerX - insetLeft : 0,
435                 hasRoundedCorner ? centerY - insetTop : 0);
436     }
437 
438     /**
439      * Returns the {@link RoundedCorner} of the given position if there is one.
440      *
441      * @param position the position of the rounded corner on the display.
442      * @return the rounded corner of the given position. Returns {@code null} if
443      * {@link RoundedCorner#isEmpty()} is {@code true}.
444      */
445     @Nullable
getRoundedCorner(@osition int position)446     public RoundedCorner getRoundedCorner(@Position int position) {
447         return mRoundedCorners[position].isEmpty()
448                 ? null : new RoundedCorner(mRoundedCorners[position]);
449     }
450 
451     /**
452      * Sets the rounded corner of given position.
453      *
454      * @param position the position of this rounded corner
455      * @param roundedCorner the rounded corner or null if there is none
456      */
setRoundedCorner(@osition int position, @Nullable RoundedCorner roundedCorner)457     public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) {
458         mRoundedCorners[position] = roundedCorner == null
459                 ? new RoundedCorner(position) : roundedCorner;
460     }
461 
462     /**
463      * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used
464      * as an index of the array.
465      *
466      * @return an array of {@link RoundedCorner}s, one for each rounded corner area.
467      */
getAllRoundedCorners()468     public RoundedCorner[] getAllRoundedCorners() {
469         RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
470         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
471             roundedCorners[i] = new RoundedCorner(roundedCorners[i]);
472         }
473         return roundedCorners;
474     }
475 
476     /**
477      * Returns a scaled RoundedCorners.
478      */
scale(float scale)479     public RoundedCorners scale(float scale) {
480         if (scale == 1f) {
481             return this;
482         }
483 
484         RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
485         for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
486             final RoundedCorner roundedCorner = mRoundedCorners[i];
487             roundedCorners[i] = new RoundedCorner(
488                     i,
489                     (int) (roundedCorner.getRadius() * scale),
490                     (int) (roundedCorner.getCenter().x * scale),
491                     (int) (roundedCorner.getCenter().y * scale));
492         }
493         return new RoundedCorners(roundedCorners);
494     }
495 
496     /**
497      * Returns a rotated RoundedCorners.
498      */
rotate(@urface.Rotation int rotation, int initialDisplayWidth, int initialDisplayHeight)499     public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth,
500             int initialDisplayHeight) {
501         if (rotation == ROTATION_0) {
502             return this;
503         }
504         final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270;
505         RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
506         int newPosistion;
507         for (int i = 0; i < mRoundedCorners.length; i++) {
508             newPosistion = getRotatedIndex(i, rotation);
509             newCorners[newPosistion] = createRoundedCorner(
510                     newPosistion,
511                     mRoundedCorners[i].getRadius(),
512                     isSizeFlipped ? initialDisplayHeight : initialDisplayWidth,
513                     isSizeFlipped ? initialDisplayWidth : initialDisplayHeight);
514         }
515         return new RoundedCorners(newCorners);
516     }
517 
createRoundedCorner(@osition int position, int radius, int displayWidth, int displayHeight)518     private static RoundedCorner createRoundedCorner(@Position int position,
519             int radius, int displayWidth, int displayHeight) {
520         switch (position) {
521             case POSITION_TOP_LEFT:
522                 return new RoundedCorner(
523                         POSITION_TOP_LEFT,
524                         radius,
525                         radius > 0 ? radius : 0,
526                         radius > 0 ? radius : 0);
527             case POSITION_TOP_RIGHT:
528                 return new RoundedCorner(
529                         POSITION_TOP_RIGHT,
530                         radius,
531                         radius > 0 ? displayWidth - radius : 0,
532                         radius > 0 ? radius : 0);
533             case POSITION_BOTTOM_RIGHT:
534                 return new RoundedCorner(
535                         POSITION_BOTTOM_RIGHT,
536                         radius,
537                         radius > 0 ? displayWidth - radius : 0,
538                         radius > 0 ? displayHeight - radius : 0);
539             case POSITION_BOTTOM_LEFT:
540                 return new RoundedCorner(
541                         POSITION_BOTTOM_LEFT,
542                         radius,
543                         radius > 0 ? radius : 0,
544                         radius > 0 ? displayHeight - radius  : 0);
545             default:
546                 throw new IllegalArgumentException(
547                         "The position is not one of the RoundedCornerPosition =" + position);
548         }
549     }
550 
getRotatedIndex(int position, int rotation)551     private static int getRotatedIndex(int position, int rotation) {
552         return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4;
553     }
554 
555     @Override
hashCode()556     public int hashCode() {
557         int result = 0;
558         for (RoundedCorner roundedCorner : mRoundedCorners) {
559             result = result * 31 + roundedCorner.hashCode();
560         }
561         return result;
562     }
563 
564     @Override
equals(Object o)565     public boolean equals(Object o) {
566         if (o == this) {
567             return true;
568         }
569         if (o instanceof RoundedCorners) {
570             RoundedCorners r = (RoundedCorners) o;
571             return Arrays.deepEquals(mRoundedCorners, r.mRoundedCorners);
572         }
573         return false;
574     }
575 
576     @Override
toString()577     public String toString() {
578         return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}";
579     }
580 
581     @Override
describeContents()582     public int describeContents() {
583         return 0;
584     }
585 
586     @Override
writeToParcel(Parcel dest, int flags)587     public void writeToParcel(Parcel dest, int flags) {
588         if (equals(NO_ROUNDED_CORNERS)) {
589             dest.writeInt(0);
590         } else {
591             dest.writeInt(1);
592             dest.writeTypedArray(mRoundedCorners, flags);
593         }
594     }
595 
596     public static final @NonNull Creator<RoundedCorners> CREATOR = new Creator<RoundedCorners>() {
597         @Override
598         public RoundedCorners createFromParcel(Parcel in) {
599             int variant = in.readInt();
600             if (variant == 0) {
601                 return NO_ROUNDED_CORNERS;
602             }
603             RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
604             in.readTypedArray(roundedCorners, RoundedCorner.CREATOR);
605             return new RoundedCorners(roundedCorners);
606         }
607 
608         @Override
609         public RoundedCorners[] newArray(int size) {
610             return new RoundedCorners[size];
611         }
612     };
613 }
614