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 return radius; 202 } 203 204 /** 205 * Gets the top rounded corner radius of a display which is determined by the 206 * given display unique id. 207 * 208 * Loads the default dimen{@link R.dimen#rounded_corner_radius_top} if 209 * {@link R.array#config_displayUniqueIdArray} is not set. 210 * 211 * @hide 212 */ getRoundedCornerTopRadius(Resources res, String displayUniqueId)213 public static int getRoundedCornerTopRadius(Resources res, String displayUniqueId) { 214 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 215 final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray); 216 int radius; 217 if (index >= 0 && index < array.length()) { 218 radius = array.getDimensionPixelSize(index, 0); 219 } else { 220 radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top); 221 } 222 array.recycle(); 223 return radius; 224 } 225 226 /** 227 * Gets the bottom rounded corner radius of a display which is determined by the 228 * given display unique id. 229 * 230 * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom} if 231 * {@link R.array#config_displayUniqueIdArray} is not set. 232 * 233 * @hide 234 */ getRoundedCornerBottomRadius(Resources res, String displayUniqueId)235 public static int getRoundedCornerBottomRadius(Resources res, String displayUniqueId) { 236 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 237 final TypedArray array = res.obtainTypedArray( 238 R.array.config_roundedCornerBottomRadiusArray); 239 int radius; 240 if (index >= 0 && index < array.length()) { 241 radius = array.getDimensionPixelSize(index, 0); 242 } else { 243 radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom); 244 } 245 array.recycle(); 246 return radius; 247 } 248 249 /** 250 * Gets the rounded corner radius adjustment of a display which is determined by the 251 * given display unique id. 252 * 253 * Loads the default dimen{@link R.dimen#rounded_corner_radius_adjustment} if 254 * {@link R.array#config_displayUniqueIdArray} is not set. 255 * 256 * @hide 257 */ getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId)258 public static int getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId) { 259 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 260 final TypedArray array = res.obtainTypedArray( 261 R.array.config_roundedCornerRadiusAdjustmentArray); 262 int radius; 263 if (index >= 0 && index < array.length()) { 264 radius = array.getDimensionPixelSize(index, 0); 265 } else { 266 radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_adjustment); 267 } 268 array.recycle(); 269 return radius; 270 } 271 272 /** 273 * Gets the rounded corner top radius adjustment of a display which is determined by the 274 * given display unique id. 275 * 276 * Loads the default dimen{@link R.dimen#rounded_corner_radius_top_adjustment} if 277 * {@link R.array#config_displayUniqueIdArray} is not set. 278 * 279 * @hide 280 */ getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId)281 public static int getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId) { 282 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 283 final TypedArray array = res.obtainTypedArray( 284 R.array.config_roundedCornerTopRadiusAdjustmentArray); 285 int radius; 286 if (index >= 0 && index < array.length()) { 287 radius = array.getDimensionPixelSize(index, 0); 288 } else { 289 radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top_adjustment); 290 } 291 array.recycle(); 292 return radius; 293 } 294 295 /** 296 * Gets the rounded corner bottom radius adjustment of a display which is determined by the 297 * given display unique id. 298 * 299 * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom_adjustment} if 300 * {@link R.array#config_displayUniqueIdArray} is not set. 301 * 302 * @hide 303 */ getRoundedCornerRadiusBottomAdjustment( Resources res, String displayUniqueId)304 public static int getRoundedCornerRadiusBottomAdjustment( 305 Resources res, String displayUniqueId) { 306 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 307 final TypedArray array = res.obtainTypedArray( 308 R.array.config_roundedCornerBottomRadiusAdjustmentArray); 309 int radius; 310 if (index >= 0 && index < array.length()) { 311 radius = array.getDimensionPixelSize(index, 0); 312 } else { 313 radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom_adjustment); 314 } 315 array.recycle(); 316 return radius; 317 } 318 319 /** 320 * Gets whether a built-in display is round. 321 * 322 * Loads the default config{@link R.bool#config_mainBuiltInDisplayIsRound} if 323 * {@link R.array#config_displayUniqueIdArray} is not set. 324 * 325 * @hide 326 */ getBuiltInDisplayIsRound(Resources res, String displayUniqueId)327 public static boolean getBuiltInDisplayIsRound(Resources res, String displayUniqueId) { 328 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 329 final TypedArray array = res.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray); 330 boolean isRound; 331 if (index >= 0 && index < array.length()) { 332 isRound = array.getBoolean(index, false); 333 } else { 334 isRound = res.getBoolean(R.bool.config_mainBuiltInDisplayIsRound); 335 } 336 array.recycle(); 337 return isRound; 338 } 339 340 /** 341 * Insets the reference frame of the rounded corners. 342 * 343 * @param frame the frame of a window or any rectangle bounds 344 * @param roundedCornerFrame the frame that used to calculate relative {@link RoundedCorner} 345 * @return a copy of this instance which has been inset 346 */ insetWithFrame(Rect frame, Rect roundedCornerFrame)347 public RoundedCorners insetWithFrame(Rect frame, Rect roundedCornerFrame) { 348 int insetLeft = frame.left - roundedCornerFrame.left; 349 int insetTop = frame.top - roundedCornerFrame.top; 350 int insetRight = roundedCornerFrame.right - frame.right; 351 int insetBottom = roundedCornerFrame.bottom - frame.bottom; 352 final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 353 int centerX, centerY; 354 for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) { 355 if (mRoundedCorners[i].isEmpty()) { 356 roundedCorners[i] = new RoundedCorner(i); 357 continue; 358 } 359 final int radius = mRoundedCorners[i].getRadius(); 360 switch (i) { 361 case POSITION_TOP_LEFT: 362 centerX = radius; 363 centerY = radius; 364 break; 365 case POSITION_TOP_RIGHT: 366 centerX = roundedCornerFrame.width() - radius; 367 centerY = radius; 368 break; 369 case POSITION_BOTTOM_RIGHT: 370 centerX = roundedCornerFrame.width() - radius; 371 centerY = roundedCornerFrame.height() - radius; 372 break; 373 case POSITION_BOTTOM_LEFT: 374 centerX = radius; 375 centerY = roundedCornerFrame.height() - radius; 376 break; 377 default: 378 throw new IllegalArgumentException( 379 "The position is not one of the RoundedCornerPosition =" + i); 380 } 381 roundedCorners[i] = insetRoundedCorner(i, radius, centerX, centerY, insetLeft, insetTop, 382 insetRight, insetBottom); 383 } 384 return new RoundedCorners(roundedCorners); 385 } 386 387 /** 388 * Insets the reference frame of the rounded corners. 389 * 390 * @return a copy of this instance which has been inset 391 */ inset(int insetLeft, int insetTop, int insetRight, int insetBottom)392 public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { 393 final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 394 for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) { 395 roundedCorners[i] = insetRoundedCorner(i, mRoundedCorners[i].getRadius(), 396 mRoundedCorners[i].getCenter().x, mRoundedCorners[i].getCenter().y, insetLeft, 397 insetTop, insetRight, insetBottom); 398 } 399 return new RoundedCorners(roundedCorners); 400 } 401 insetRoundedCorner(@osition int position, int radius, int centerX, int centerY, int insetLeft, int insetTop, int insetRight, int insetBottom)402 private RoundedCorner insetRoundedCorner(@Position int position, int radius, int centerX, 403 int centerY, int insetLeft, int insetTop, int insetRight, int insetBottom) { 404 if (mRoundedCorners[position].isEmpty()) { 405 return new RoundedCorner(position); 406 } 407 408 boolean hasRoundedCorner; 409 switch (position) { 410 case POSITION_TOP_LEFT: 411 hasRoundedCorner = radius > insetTop && radius > insetLeft; 412 break; 413 case POSITION_TOP_RIGHT: 414 hasRoundedCorner = radius > insetTop && radius > insetRight; 415 break; 416 case POSITION_BOTTOM_RIGHT: 417 hasRoundedCorner = radius > insetBottom && radius > insetRight; 418 break; 419 case POSITION_BOTTOM_LEFT: 420 hasRoundedCorner = radius > insetBottom && radius > insetLeft; 421 break; 422 default: 423 throw new IllegalArgumentException( 424 "The position is not one of the RoundedCornerPosition =" + position); 425 } 426 return new RoundedCorner( 427 position, radius, 428 hasRoundedCorner ? centerX - insetLeft : 0, 429 hasRoundedCorner ? centerY - insetTop : 0); 430 } 431 432 /** 433 * Returns the {@link RoundedCorner} of the given position if there is one. 434 * 435 * @param position the position of the rounded corner on the display. 436 * @return the rounded corner of the given position. Returns {@code null} if 437 * {@link RoundedCorner#isEmpty()} is {@code true}. 438 */ 439 @Nullable getRoundedCorner(@osition int position)440 public RoundedCorner getRoundedCorner(@Position int position) { 441 return mRoundedCorners[position].isEmpty() 442 ? null : new RoundedCorner(mRoundedCorners[position]); 443 } 444 445 /** 446 * Sets the rounded corner of given position. 447 * 448 * @param position the position of this rounded corner 449 * @param roundedCorner the rounded corner or null if there is none 450 */ setRoundedCorner(@osition int position, @Nullable RoundedCorner roundedCorner)451 public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) { 452 mRoundedCorners[position] = roundedCorner == null 453 ? new RoundedCorner(position) : roundedCorner; 454 } 455 456 /** 457 * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used 458 * as an index of the array. 459 * 460 * @return an array of {@link RoundedCorner}s, one for each rounded corner area. 461 */ getAllRoundedCorners()462 public RoundedCorner[] getAllRoundedCorners() { 463 RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 464 for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) { 465 roundedCorners[i] = new RoundedCorner(roundedCorners[i]); 466 } 467 return roundedCorners; 468 } 469 470 /** 471 * Returns a scaled RoundedCorners. 472 */ scale(float scale)473 public RoundedCorners scale(float scale) { 474 if (scale == 1f) { 475 return this; 476 } 477 478 RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 479 for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) { 480 final RoundedCorner roundedCorner = mRoundedCorners[i]; 481 roundedCorners[i] = new RoundedCorner( 482 i, 483 (int) (roundedCorner.getRadius() * scale), 484 (int) (roundedCorner.getCenter().x * scale), 485 (int) (roundedCorner.getCenter().y * scale)); 486 } 487 return new RoundedCorners(roundedCorners); 488 } 489 490 /** 491 * Returns a rotated RoundedCorners. 492 */ rotate(@urface.Rotation int rotation, int initialDisplayWidth, int initialDisplayHeight)493 public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth, 494 int initialDisplayHeight) { 495 if (rotation == ROTATION_0) { 496 return this; 497 } 498 final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270; 499 RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 500 int newPosistion; 501 for (int i = 0; i < mRoundedCorners.length; i++) { 502 newPosistion = getRotatedIndex(i, rotation); 503 newCorners[newPosistion] = createRoundedCorner( 504 newPosistion, 505 mRoundedCorners[i].getRadius(), 506 isSizeFlipped ? initialDisplayHeight : initialDisplayWidth, 507 isSizeFlipped ? initialDisplayWidth : initialDisplayHeight); 508 } 509 return new RoundedCorners(newCorners); 510 } 511 createRoundedCorner(@osition int position, int radius, int displayWidth, int displayHeight)512 private static RoundedCorner createRoundedCorner(@Position int position, 513 int radius, int displayWidth, int displayHeight) { 514 switch (position) { 515 case POSITION_TOP_LEFT: 516 return new RoundedCorner( 517 POSITION_TOP_LEFT, 518 radius, 519 radius > 0 ? radius : 0, 520 radius > 0 ? radius : 0); 521 case POSITION_TOP_RIGHT: 522 return new RoundedCorner( 523 POSITION_TOP_RIGHT, 524 radius, 525 radius > 0 ? displayWidth - radius : 0, 526 radius > 0 ? radius : 0); 527 case POSITION_BOTTOM_RIGHT: 528 return new RoundedCorner( 529 POSITION_BOTTOM_RIGHT, 530 radius, 531 radius > 0 ? displayWidth - radius : 0, 532 radius > 0 ? displayHeight - radius : 0); 533 case POSITION_BOTTOM_LEFT: 534 return new RoundedCorner( 535 POSITION_BOTTOM_LEFT, 536 radius, 537 radius > 0 ? radius : 0, 538 radius > 0 ? displayHeight - radius : 0); 539 default: 540 throw new IllegalArgumentException( 541 "The position is not one of the RoundedCornerPosition =" + position); 542 } 543 } 544 getRotatedIndex(int position, int rotation)545 private static int getRotatedIndex(int position, int rotation) { 546 return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4; 547 } 548 549 @Override hashCode()550 public int hashCode() { 551 int result = 0; 552 for (RoundedCorner roundedCorner : mRoundedCorners) { 553 result = result * 31 + roundedCorner.hashCode(); 554 } 555 return result; 556 } 557 558 @Override equals(Object o)559 public boolean equals(Object o) { 560 if (o == this) { 561 return true; 562 } 563 if (o instanceof RoundedCorners) { 564 RoundedCorners r = (RoundedCorners) o; 565 return Arrays.deepEquals(mRoundedCorners, r.mRoundedCorners); 566 } 567 return false; 568 } 569 570 @Override toString()571 public String toString() { 572 return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}"; 573 } 574 575 @Override describeContents()576 public int describeContents() { 577 return 0; 578 } 579 580 @Override writeToParcel(Parcel dest, int flags)581 public void writeToParcel(Parcel dest, int flags) { 582 if (equals(NO_ROUNDED_CORNERS)) { 583 dest.writeInt(0); 584 } else { 585 dest.writeInt(1); 586 dest.writeTypedArray(mRoundedCorners, flags); 587 } 588 } 589 590 public static final @NonNull Creator<RoundedCorners> CREATOR = new Creator<RoundedCorners>() { 591 @Override 592 public RoundedCorners createFromParcel(Parcel in) { 593 int variant = in.readInt(); 594 if (variant == 0) { 595 return NO_ROUNDED_CORNERS; 596 } 597 RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; 598 in.readTypedArray(roundedCorners, RoundedCorner.CREATOR); 599 return new RoundedCorners(roundedCorners); 600 } 601 602 @Override 603 public RoundedCorners[] newArray(int size) { 604 return new RoundedCorners[size]; 605 } 606 }; 607 } 608