1 /* 2 * Copyright 2017 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.util.DisplayMetrics.DENSITY_DEFAULT; 20 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; 21 import static android.view.DisplayCutoutProto.BOUND_BOTTOM; 22 import static android.view.DisplayCutoutProto.BOUND_LEFT; 23 import static android.view.DisplayCutoutProto.BOUND_RIGHT; 24 import static android.view.DisplayCutoutProto.BOUND_TOP; 25 import static android.view.DisplayCutoutProto.INSETS; 26 import static android.view.Surface.ROTATION_0; 27 28 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 29 30 import android.annotation.IntDef; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.content.res.Resources; 34 import android.content.res.TypedArray; 35 import android.graphics.Insets; 36 import android.graphics.Matrix; 37 import android.graphics.Path; 38 import android.graphics.Rect; 39 import android.os.Parcel; 40 import android.os.Parcelable; 41 import android.text.TextUtils; 42 import android.util.DisplayUtils; 43 import android.util.Pair; 44 import android.util.RotationUtils; 45 import android.util.proto.ProtoOutputStream; 46 import android.view.Surface.Rotation; 47 48 import com.android.internal.R; 49 import com.android.internal.annotations.GuardedBy; 50 import com.android.internal.annotations.VisibleForTesting; 51 52 import java.lang.annotation.Retention; 53 import java.lang.annotation.RetentionPolicy; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.List; 57 58 /** 59 * Represents the area of the display that is not functional for displaying content. 60 * 61 * <p>{@code DisplayCutout} is immutable. 62 */ 63 public final class DisplayCutout { 64 65 private static final String TAG = "DisplayCutout"; 66 67 /** 68 * Category for overlays that allow emulating a display cutout on devices that don't have 69 * one. 70 * 71 * @see android.content.om.IOverlayManager 72 * @hide 73 */ 74 public static final String EMULATION_OVERLAY_CATEGORY = 75 "com.android.internal.display_cutout_emulation"; 76 77 private static final Rect ZERO_RECT = new Rect(); 78 private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo( 79 0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */, 80 0 /* rotation */, 0f /* scale */); 81 82 /** 83 * An instance where {@link #isEmpty()} returns {@code true}. 84 * 85 * @hide 86 */ 87 public static final DisplayCutout NO_CUTOUT = new DisplayCutout( 88 ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO, 89 false /* copyArguments */); 90 91 92 private static final Pair<Path, DisplayCutout> NULL_PAIR = new Pair<>(null, null); 93 private static final Object CACHE_LOCK = new Object(); 94 95 @GuardedBy("CACHE_LOCK") 96 private static String sCachedSpec; 97 @GuardedBy("CACHE_LOCK") 98 private static int sCachedDisplayWidth; 99 @GuardedBy("CACHE_LOCK") 100 private static int sCachedDisplayHeight; 101 @GuardedBy("CACHE_LOCK") 102 private static float sCachedDensity; 103 @GuardedBy("CACHE_LOCK") 104 private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR; 105 @GuardedBy("CACHE_LOCK") 106 private static Insets sCachedWaterfallInsets; 107 108 @GuardedBy("CACHE_LOCK") 109 private static CutoutPathParserInfo sCachedCutoutPathParserInfo; 110 @GuardedBy("CACHE_LOCK") 111 private static Path sCachedCutoutPath; 112 113 private final Rect mSafeInsets; 114 @NonNull 115 private final Insets mWaterfallInsets; 116 117 /** 118 * The bound is at the left of the screen. 119 * @hide 120 */ 121 public static final int BOUNDS_POSITION_LEFT = 0; 122 123 /** 124 * The bound is at the top of the screen. 125 * @hide 126 */ 127 public static final int BOUNDS_POSITION_TOP = 1; 128 129 /** 130 * The bound is at the right of the screen. 131 * @hide 132 */ 133 public static final int BOUNDS_POSITION_RIGHT = 2; 134 135 /** 136 * The bound is at the bottom of the screen. 137 * @hide 138 */ 139 public static final int BOUNDS_POSITION_BOTTOM = 3; 140 141 /** 142 * The number of possible positions at which bounds can be located. 143 * @hide 144 */ 145 public static final int BOUNDS_POSITION_LENGTH = 4; 146 147 /** @hide */ 148 @IntDef(prefix = { "BOUNDS_POSITION_" }, value = { 149 BOUNDS_POSITION_LEFT, 150 BOUNDS_POSITION_TOP, 151 BOUNDS_POSITION_RIGHT, 152 BOUNDS_POSITION_BOTTOM 153 }) 154 @Retention(RetentionPolicy.SOURCE) 155 public @interface BoundsPosition {} 156 157 private static class Bounds { 158 private final Rect[] mRects; 159 Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments)160 private Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments) { 161 mRects = new Rect[BOUNDS_POSITION_LENGTH]; 162 mRects[BOUNDS_POSITION_LEFT] = getCopyOrRef(left, copyArguments); 163 mRects[BOUNDS_POSITION_TOP] = getCopyOrRef(top, copyArguments); 164 mRects[BOUNDS_POSITION_RIGHT] = getCopyOrRef(right, copyArguments); 165 mRects[BOUNDS_POSITION_BOTTOM] = getCopyOrRef(bottom, copyArguments); 166 167 } 168 Bounds(Rect[] rects, boolean copyArguments)169 private Bounds(Rect[] rects, boolean copyArguments) { 170 if (rects.length != BOUNDS_POSITION_LENGTH) { 171 throw new IllegalArgumentException( 172 "rects must have exactly 4 elements: rects=" + Arrays.toString(rects)); 173 } 174 if (copyArguments) { 175 mRects = new Rect[BOUNDS_POSITION_LENGTH]; 176 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 177 mRects[i] = new Rect(rects[i]); 178 } 179 } else { 180 for (Rect rect : rects) { 181 if (rect == null) { 182 throw new IllegalArgumentException( 183 "rects must have non-null elements: rects=" 184 + Arrays.toString(rects)); 185 } 186 } 187 mRects = rects; 188 } 189 } 190 isEmpty()191 private boolean isEmpty() { 192 for (Rect rect : mRects) { 193 if (!rect.isEmpty()) { 194 return false; 195 } 196 } 197 return true; 198 } 199 getRect(@oundsPosition int pos)200 private Rect getRect(@BoundsPosition int pos) { 201 return new Rect(mRects[pos]); 202 } 203 getRects()204 private Rect[] getRects() { 205 Rect[] rects = new Rect[BOUNDS_POSITION_LENGTH]; 206 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 207 rects[i] = new Rect(mRects[i]); 208 } 209 return rects; 210 } 211 scale(float scale)212 private void scale(float scale) { 213 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 214 mRects[i].scale(scale); 215 } 216 } 217 218 @Override hashCode()219 public int hashCode() { 220 int result = 0; 221 for (Rect rect : mRects) { 222 result = result * 48271 + rect.hashCode(); 223 } 224 return result; 225 } 226 227 @Override equals(@ullable Object o)228 public boolean equals(@Nullable Object o) { 229 if (o == this) { 230 return true; 231 } 232 if (o instanceof Bounds) { 233 Bounds b = (Bounds) o; 234 return Arrays.deepEquals(mRects, b.mRects); 235 } 236 return false; 237 } 238 239 @Override toString()240 public String toString() { 241 return "Bounds=" + Arrays.toString(mRects); 242 } 243 244 } 245 246 private final Bounds mBounds; 247 248 /** 249 * Stores all the needed info to create the cutout paths. 250 * 251 * @hide 252 */ 253 public static class CutoutPathParserInfo { 254 private final int mDisplayWidth; 255 private final int mDisplayHeight; 256 private final float mDensity; 257 private final String mCutoutSpec; 258 private final @Rotation int mRotation; 259 private final float mScale; 260 CutoutPathParserInfo(int displayWidth, int displayHeight, float density, String cutoutSpec, @Rotation int rotation, float scale)261 public CutoutPathParserInfo(int displayWidth, int displayHeight, float density, 262 String cutoutSpec, @Rotation int rotation, float scale) { 263 mDisplayWidth = displayWidth; 264 mDisplayHeight = displayHeight; 265 mDensity = density; 266 mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec; 267 mRotation = rotation; 268 mScale = scale; 269 } 270 CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo)271 public CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo) { 272 mDisplayWidth = cutoutPathParserInfo.mDisplayWidth; 273 mDisplayHeight = cutoutPathParserInfo.mDisplayHeight; 274 mDensity = cutoutPathParserInfo.mDensity; 275 mCutoutSpec = cutoutPathParserInfo.mCutoutSpec; 276 mRotation = cutoutPathParserInfo.mRotation; 277 mScale = cutoutPathParserInfo.mScale; 278 } 279 getDisplayWidth()280 public int getDisplayWidth() { 281 return mDisplayWidth; 282 } 283 getDisplayHeight()284 public int getDisplayHeight() { 285 return mDisplayHeight; 286 } 287 getDensity()288 public float getDensity() { 289 return mDensity; 290 } 291 getCutoutSpec()292 public @NonNull String getCutoutSpec() { 293 return mCutoutSpec; 294 } 295 getRotation()296 public int getRotation() { 297 return mRotation; 298 } 299 getScale()300 public float getScale() { 301 return mScale; 302 } 303 hasCutout()304 private boolean hasCutout() { 305 return !mCutoutSpec.isEmpty(); 306 } 307 308 @Override hashCode()309 public int hashCode() { 310 int result = 0; 311 result = result * 48271 + Integer.hashCode(mDisplayWidth); 312 result = result * 48271 + Integer.hashCode(mDisplayHeight); 313 result = result * 48271 + Float.hashCode(mDensity); 314 result = result * 48271 + mCutoutSpec.hashCode(); 315 result = result * 48271 + Integer.hashCode(mRotation); 316 result = result * 48271 + Float.hashCode(mScale); 317 return result; 318 } 319 320 @Override equals(@ullable Object o)321 public boolean equals(@Nullable Object o) { 322 if (o == this) { 323 return true; 324 } 325 if (o instanceof CutoutPathParserInfo) { 326 CutoutPathParserInfo c = (CutoutPathParserInfo) o; 327 return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight 328 && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec) 329 && mRotation == c.mRotation && mScale == c.mScale; 330 } 331 return false; 332 } 333 334 @Override toString()335 public String toString() { 336 return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth 337 + " displayHeight=" + mDisplayHeight 338 + " density={" + mDensity + "}" 339 + " cutoutSpec={" + mCutoutSpec + "}" 340 + " rotation={" + mRotation + "}" 341 + " scale={" + mScale + "}" 342 + "}"; 343 } 344 } 345 346 private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo; 347 348 /** 349 * Creates a DisplayCutout instance. 350 * 351 * <p>Note that this is only useful for tests. For production code, developers should always 352 * use a {@link DisplayCutout} obtained from the system.</p> 353 * 354 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 355 * {@link #getSafeInsetTop()} etc. 356 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 357 * it's treated as an empty rectangle (0,0)-(0,0). 358 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 359 * it's treated as an empty rectangle (0,0)-(0,0). 360 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 361 * passed, it's treated as an empty rectangle (0,0)-(0,0). 362 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 363 * passed, it's treated as an empty rectangle (0,0)-(0,0). 364 */ 365 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom)366 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 367 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) { 368 this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, null, 369 true); 370 } 371 372 /** 373 * Creates a DisplayCutout instance. 374 * 375 * <p>Note that this is only useful for tests. For production code, developers should always 376 * use a {@link DisplayCutout} obtained from the system.</p> 377 * 378 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 379 * {@link #getSafeInsetTop()} etc. 380 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 381 * it's treated as an empty rectangle (0,0)-(0,0). 382 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 383 * it's treated as an empty rectangle (0,0)-(0,0). 384 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 385 * passed, it's treated as an empty rectangle (0,0)-(0,0). 386 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 387 * passed, it's treated as an empty rectangle (0,0)-(0,0). 388 * @param waterfallInsets the insets for the curved areas in waterfall display. 389 */ DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, @NonNull Insets waterfallInsets)390 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 391 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, 392 @NonNull Insets waterfallInsets) { 393 this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom, 394 null, true); 395 } 396 397 /** 398 * Creates a DisplayCutout instance. 399 * 400 * <p>Note that this is only useful for tests. For production code, developers should always 401 * use a {@link DisplayCutout} obtained from the system.</p> 402 * 403 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 404 * {@link #getSafeInsetTop()} etc. 405 * @param boundingRects the bounding rects of the display cutouts as returned by 406 * {@link #getBoundingRects()} ()}. 407 * @deprecated Use {@link DisplayCutout#DisplayCutout(Insets, Rect, Rect, Rect, Rect)} instead. 408 */ 409 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) 410 @Deprecated DisplayCutout(@ullable Rect safeInsets, @Nullable List<Rect> boundingRects)411 public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) { 412 this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), null, 413 true /* copyArguments */); 414 } 415 416 /** 417 * Creates a DisplayCutout instance. 418 * 419 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 420 * {@link #getSafeInsetTop()} etc. 421 * @param waterfallInsets the insets for the curved areas in waterfall display. 422 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 423 * it's treated as an empty rectangle (0,0)-(0,0). 424 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 425 * it's treated as an empty rectangle (0,0)-(0,0). 426 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 427 * passed, it's treated as an empty rectangle (0,0)-(0,0). 428 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 429 * passed, it's treated as an empty rectangle (0,0)-(0,0). 430 * @param info the cutout path parser info. 431 * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments 432 * are not copied and MUST remain unchanged forever. 433 */ DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop, Rect boundRight, Rect boundBottom, CutoutPathParserInfo info, boolean copyArguments)434 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop, 435 Rect boundRight, Rect boundBottom, CutoutPathParserInfo info, 436 boolean copyArguments) { 437 mSafeInsets = getCopyOrRef(safeInsets, copyArguments); 438 mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; 439 mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments); 440 mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; 441 } 442 DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds, CutoutPathParserInfo info, boolean copyArguments)443 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds, 444 CutoutPathParserInfo info, boolean copyArguments) { 445 mSafeInsets = getCopyOrRef(safeInsets, copyArguments); 446 mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; 447 mBounds = new Bounds(bounds, copyArguments); 448 mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; 449 } 450 DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, CutoutPathParserInfo info)451 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, 452 CutoutPathParserInfo info) { 453 mSafeInsets = safeInsets; 454 mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; 455 mBounds = bounds; 456 mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; 457 } 458 getCopyOrRef(Rect r, boolean copyArguments)459 private static Rect getCopyOrRef(Rect r, boolean copyArguments) { 460 if (r == null) { 461 return ZERO_RECT; 462 } else if (copyArguments) { 463 return new Rect(r); 464 } else { 465 return r; 466 } 467 } 468 469 /** 470 * Returns the insets representing the curved areas of a waterfall display. 471 * 472 * A waterfall display has curved areas along the edges of the screen. Apps should be careful 473 * when showing UI and handling touch input in those insets because the curve may impair 474 * legibility and can frequently lead to unintended touch inputs. 475 * 476 * @return the insets for the curved areas of a waterfall display in pixels or {@code 477 * Insets.NONE} if there are no curved areas or they don't overlap with the window. 478 */ getWaterfallInsets()479 public @NonNull Insets getWaterfallInsets() { 480 return mWaterfallInsets; 481 } 482 483 484 /** 485 * Find the position of the bounding rect, and create an array of Rect whose index represents 486 * the position (= BoundsPosition). 487 * 488 * @hide 489 */ extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects)490 public static Rect[] extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects) { 491 Rect[] sortedBounds = new Rect[BOUNDS_POSITION_LENGTH]; 492 for (int i = 0; i < sortedBounds.length; ++i) { 493 sortedBounds[i] = ZERO_RECT; 494 } 495 if (safeInsets != null && boundingRects != null) { 496 // There is at most one non-functional area per short edge of the device, but none 497 // on the long edges, so either a) safeInsets.top and safeInsets.bottom is 0, or 498 // b) safeInsets.left and safeInset.right is 0. 499 final boolean topBottomInset = safeInsets.top > 0 || safeInsets.bottom > 0; 500 for (Rect bound : boundingRects) { 501 if (topBottomInset) { 502 if (bound.top == 0) { 503 sortedBounds[BOUNDS_POSITION_TOP] = bound; 504 } else { 505 sortedBounds[BOUNDS_POSITION_BOTTOM] = bound; 506 } 507 } else { 508 if (bound.left == 0) { 509 sortedBounds[BOUNDS_POSITION_LEFT] = bound; 510 } else { 511 sortedBounds[BOUNDS_POSITION_RIGHT] = bound; 512 } 513 } 514 } 515 } 516 return sortedBounds; 517 } 518 519 /** 520 * Returns true if there is no cutout, i.e. the bounds are empty. 521 * 522 * @hide 523 */ isBoundsEmpty()524 public boolean isBoundsEmpty() { 525 return mBounds.isEmpty(); 526 } 527 528 /** 529 * Returns true if the safe insets are empty (and therefore the current view does not 530 * overlap with the cutout or cutout area). 531 * 532 * @hide 533 */ isEmpty()534 public boolean isEmpty() { 535 return mSafeInsets.equals(ZERO_RECT); 536 } 537 538 /** 539 * Returns the inset from the top which avoids the display cutout in pixels. 540 * 541 * @see WindowInsets.Type#displayCutout() 542 */ getSafeInsetTop()543 public int getSafeInsetTop() { 544 return mSafeInsets.top; 545 } 546 547 /** 548 * Returns the inset from the bottom which avoids the display cutout in pixels. 549 * 550 * @see WindowInsets.Type#displayCutout() 551 */ getSafeInsetBottom()552 public int getSafeInsetBottom() { 553 return mSafeInsets.bottom; 554 } 555 556 /** 557 * Returns the inset from the left which avoids the display cutout in pixels. 558 * 559 * @see WindowInsets.Type#displayCutout() 560 */ getSafeInsetLeft()561 public int getSafeInsetLeft() { 562 return mSafeInsets.left; 563 } 564 565 /** 566 * Returns the inset from the right which avoids the display cutout in pixels. 567 * 568 * @see WindowInsets.Type#displayCutout() 569 */ getSafeInsetRight()570 public int getSafeInsetRight() { 571 return mSafeInsets.right; 572 } 573 574 /** 575 * Returns the safe insets in a rect in pixel units. 576 * 577 * @return a rect which is set to the safe insets. 578 * @hide 579 */ getSafeInsets()580 public Rect getSafeInsets() { 581 return new Rect(mSafeInsets); 582 } 583 584 /** 585 * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional 586 * area on the display. 587 * 588 * There will be at most one non-functional area per short edge of the device, and none on 589 * the long edges. 590 * 591 * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is 592 * returned. 593 */ 594 @NonNull getBoundingRects()595 public List<Rect> getBoundingRects() { 596 List<Rect> result = new ArrayList<>(); 597 for (Rect bound : getBoundingRectsAll()) { 598 if (!bound.isEmpty()) { 599 result.add(new Rect(bound)); 600 } 601 } 602 return result; 603 } 604 605 /** 606 * Returns an array of {@code Rect}s, each of which is the bounding rectangle for a non- 607 * functional area on the display. Ordinal value of BoundPosition is used as an index of 608 * the array. 609 * 610 * There will be at most one non-functional area per short edge of the device, and none on 611 * the long edges. 612 * 613 * @return an array of bounding {@code Rect}s, one for each display cutout area. This might 614 * contain ZERO_RECT, which means there is no cutout area at the position. 615 * 616 * @hide 617 */ getBoundingRectsAll()618 public Rect[] getBoundingRectsAll() { 619 return mBounds.getRects(); 620 } 621 622 /** 623 * Returns a bounding rectangle for a non-functional area on the display which is located on 624 * the left of the screen. 625 * 626 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 627 * is returned. 628 */ getBoundingRectLeft()629 public @NonNull Rect getBoundingRectLeft() { 630 return mBounds.getRect(BOUNDS_POSITION_LEFT); 631 } 632 633 /** 634 * Returns a bounding rectangle for a non-functional area on the display which is located on 635 * the top of the screen. 636 * 637 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 638 * is returned. 639 */ getBoundingRectTop()640 public @NonNull Rect getBoundingRectTop() { 641 return mBounds.getRect(BOUNDS_POSITION_TOP); 642 } 643 644 /** 645 * Returns a bounding rectangle for a non-functional area on the display which is located on 646 * the right of the screen. 647 * 648 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 649 * is returned. 650 */ getBoundingRectRight()651 public @NonNull Rect getBoundingRectRight() { 652 return mBounds.getRect(BOUNDS_POSITION_RIGHT); 653 } 654 655 /** 656 * Returns a bounding rectangle for a non-functional area on the display which is located on 657 * the bottom of the screen. 658 * 659 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 660 * is returned. 661 */ getBoundingRectBottom()662 public @NonNull Rect getBoundingRectBottom() { 663 return mBounds.getRect(BOUNDS_POSITION_BOTTOM); 664 } 665 666 /** 667 * Returns a {@link Path} that contains the cutout paths of all sides on the display. 668 * 669 * To get a cutout path for one specific side, apps can intersect the {@link Path} with the 670 * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()}, 671 * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}. 672 * 673 * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns 674 * null if there is no cutout on the display. 675 */ getCutoutPath()676 public @Nullable Path getCutoutPath() { 677 if (!mCutoutPathParserInfo.hasCutout()) { 678 return null; 679 } 680 synchronized (CACHE_LOCK) { 681 if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) { 682 return sCachedCutoutPath; 683 } 684 } 685 final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser( 686 mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getDisplayWidth(), 687 mCutoutPathParserInfo.getDisplayHeight()) 688 .parse(mCutoutPathParserInfo.getCutoutSpec()); 689 690 final Path cutoutPath = cutoutSpec.getPath(); 691 if (cutoutPath == null || cutoutPath.isEmpty()) { 692 return null; 693 } 694 final Matrix matrix = new Matrix(); 695 if (mCutoutPathParserInfo.getRotation() != ROTATION_0) { 696 RotationUtils.transformPhysicalToLogicalCoordinates( 697 mCutoutPathParserInfo.getRotation(), 698 mCutoutPathParserInfo.getDisplayWidth(), 699 mCutoutPathParserInfo.getDisplayHeight(), 700 matrix 701 ); 702 } 703 matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale()); 704 cutoutPath.transform(matrix); 705 706 synchronized (CACHE_LOCK) { 707 sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo); 708 sCachedCutoutPath = cutoutPath; 709 } 710 return cutoutPath; 711 } 712 713 /** 714 * @return the {@link CutoutPathParserInfo}; 715 * 716 * @hide 717 */ getCutoutPathParserInfo()718 public CutoutPathParserInfo getCutoutPathParserInfo() { 719 return mCutoutPathParserInfo; 720 } 721 722 @Override hashCode()723 public int hashCode() { 724 int result = 0; 725 result = 48271 * result + mSafeInsets.hashCode(); 726 result = 48271 * result + mBounds.hashCode(); 727 result = 48271 * result + mWaterfallInsets.hashCode(); 728 result = 48271 * result + mCutoutPathParserInfo.hashCode(); 729 return result; 730 } 731 732 @Override equals(@ullable Object o)733 public boolean equals(@Nullable Object o) { 734 if (o == this) { 735 return true; 736 } 737 if (o instanceof DisplayCutout) { 738 DisplayCutout c = (DisplayCutout) o; 739 return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds) 740 && mWaterfallInsets.equals(c.mWaterfallInsets) 741 && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo); 742 } 743 return false; 744 } 745 746 @Override toString()747 public String toString() { 748 return "DisplayCutout{insets=" + mSafeInsets 749 + " waterfall=" + mWaterfallInsets 750 + " boundingRect={" + mBounds + "}" 751 + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}" 752 + "}"; 753 } 754 755 /** 756 * @hide 757 */ dumpDebug(ProtoOutputStream proto, long fieldId)758 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 759 final long token = proto.start(fieldId); 760 mSafeInsets.dumpDebug(proto, INSETS); 761 mBounds.getRect(BOUNDS_POSITION_LEFT).dumpDebug(proto, BOUND_LEFT); 762 mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP); 763 mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT); 764 mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM); 765 mWaterfallInsets.toRect().dumpDebug(proto, INSETS); 766 proto.end(token); 767 } 768 769 /** 770 * Insets the reference frame of the cutout in the given directions. 771 * 772 * @return a copy of this instance which has been inset 773 * @hide 774 */ inset(int insetLeft, int insetTop, int insetRight, int insetBottom)775 public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { 776 if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0 777 || (isBoundsEmpty() && mWaterfallInsets.equals(Insets.NONE))) { 778 return this; 779 } 780 781 Rect safeInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom, 782 new Rect(mSafeInsets)); 783 784 // If we are not cutting off part of the cutout by insetting it on bottom/right, and we also 785 // don't move it around, we can avoid the allocation and copy of the instance. 786 if (insetLeft == 0 && insetTop == 0 && mSafeInsets.equals(safeInsets)) { 787 return this; 788 } 789 790 Rect waterfallInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom, 791 mWaterfallInsets.toRect()); 792 793 Rect[] bounds = mBounds.getRects(); 794 for (int i = 0; i < bounds.length; ++i) { 795 if (!bounds[i].equals(ZERO_RECT)) { 796 bounds[i].offset(-insetLeft, -insetTop); 797 } 798 } 799 800 return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, 801 mCutoutPathParserInfo, false /* copyArguments */); 802 } 803 insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom, Rect insets)804 private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom, 805 Rect insets) { 806 // Note: it's not really well defined what happens when the inset is negative, because we 807 // don't know if the safe inset needs to expand in general. 808 if (insetTop > 0 || insets.top > 0) { 809 insets.top = atLeastZero(insets.top - insetTop); 810 } 811 if (insetBottom > 0 || insets.bottom > 0) { 812 insets.bottom = atLeastZero(insets.bottom - insetBottom); 813 } 814 if (insetLeft > 0 || insets.left > 0) { 815 insets.left = atLeastZero(insets.left - insetLeft); 816 } 817 if (insetRight > 0 || insets.right > 0) { 818 insets.right = atLeastZero(insets.right - insetRight); 819 } 820 return insets; 821 } 822 823 /** 824 * Returns a copy of this instance with the safe insets replaced with the parameter. 825 * 826 * @param safeInsets the new safe insets in pixels 827 * @return a copy of this instance with the safe insets replaced with the argument. 828 * 829 * @hide 830 */ replaceSafeInsets(Rect safeInsets)831 public DisplayCutout replaceSafeInsets(Rect safeInsets) { 832 return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds, 833 mCutoutPathParserInfo); 834 } 835 atLeastZero(int value)836 private static int atLeastZero(int value) { 837 return value < 0 ? 0 : value; 838 } 839 840 841 /** 842 * Creates an instance from a bounding rect. 843 * 844 * @hide 845 */ 846 @VisibleForTesting fromBoundingRect( int left, int top, int right, int bottom, @BoundsPosition int pos)847 public static DisplayCutout fromBoundingRect( 848 int left, int top, int right, int bottom, @BoundsPosition int pos) { 849 Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; 850 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 851 bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect(); 852 } 853 return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */); 854 } 855 856 /** 857 * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo. 858 * 859 * @hide 860 */ constructDisplayCutout(Rect[] bounds, Insets waterfallInsets, CutoutPathParserInfo info)861 public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets, 862 CutoutPathParserInfo info) { 863 return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info, 864 false /* copyArguments */); 865 } 866 867 /** 868 * Creates an instance from a bounding {@link Path}. 869 * 870 * @hide 871 */ fromBounds(Rect[] bounds)872 public static DisplayCutout fromBounds(Rect[] bounds) { 873 return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */, 874 false /* copyArguments */); 875 } 876 877 /** 878 * Gets the display cutout by the given display unique id. 879 * 880 * Loads the default config {@link R.string#config_mainBuiltInDisplayCutout) if 881 * {@link R.array#config_displayUniqueIdArray} is not set. 882 */ getDisplayCutoutPath(Resources res, String displayUniqueId)883 private static String getDisplayCutoutPath(Resources res, String displayUniqueId) { 884 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 885 final String[] array = res.getStringArray(R.array.config_displayCutoutPathArray); 886 if (index >= 0 && index < array.length) { 887 return array[index]; 888 } 889 return res.getString(R.string.config_mainBuiltInDisplayCutout); 890 } 891 892 /** 893 * Gets the display cutout approximation rect by the given display unique id. 894 * 895 * Loads the default config {@link R.string#config_mainBuiltInDisplayCutoutRectApproximation} if 896 * {@link R.array#config_displayUniqueIdArray} is not set. 897 */ getDisplayCutoutApproximationRect(Resources res, String displayUniqueId)898 private static String getDisplayCutoutApproximationRect(Resources res, String displayUniqueId) { 899 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 900 final String[] array = res.getStringArray( 901 R.array.config_displayCutoutApproximationRectArray); 902 if (index >= 0 && index < array.length) { 903 return array[index]; 904 } 905 return res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation); 906 } 907 908 /** 909 * Gets whether to mask a built-in display cutout of a display which is determined by the 910 * given display unique id. 911 * 912 * Loads the default config {@link R.bool#config_maskMainBuiltInDisplayCutout} if 913 * {@link R.array#config_displayUniqueIdArray} is not set. 914 * 915 * @hide 916 */ getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId)917 public static boolean getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId) { 918 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 919 final TypedArray array = res.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray); 920 boolean maskCutout; 921 if (index >= 0 && index < array.length()) { 922 maskCutout = array.getBoolean(index, false); 923 } else { 924 maskCutout = res.getBoolean(R.bool.config_maskMainBuiltInDisplayCutout); 925 } 926 array.recycle(); 927 return maskCutout; 928 } 929 930 /** 931 * Gets whether to fill a built-in display cutout of a display which is determined by the 932 * given display unique id. 933 * 934 * Loads the default config{@link R.bool#config_fillMainBuiltInDisplayCutout} if 935 * {@link R.array#config_displayUniqueIdArray} is not set. 936 * 937 * @hide 938 */ getFillBuiltInDisplayCutout(Resources res, String displayUniqueId)939 public static boolean getFillBuiltInDisplayCutout(Resources res, String displayUniqueId) { 940 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 941 final TypedArray array = res.obtainTypedArray(R.array.config_fillBuiltInDisplayCutoutArray); 942 boolean fillCutout; 943 if (index >= 0 && index < array.length()) { 944 fillCutout = array.getBoolean(index, false); 945 } else { 946 fillCutout = res.getBoolean(R.bool.config_fillMainBuiltInDisplayCutout); 947 } 948 array.recycle(); 949 return fillCutout; 950 } 951 952 /** 953 * Gets the waterfall cutout by the given display unique id. 954 * 955 * Loads the default waterfall dimens if {@link R.array#config_displayUniqueIdArray} is not set. 956 * {@link R.dimen#waterfall_display_left_edge_size}, 957 * {@link R.dimen#waterfall_display_top_edge_size}, 958 * {@link R.dimen#waterfall_display_right_edge_size}, 959 * {@link R.dimen#waterfall_display_bottom_edge_size} 960 */ getWaterfallInsets(Resources res, String displayUniqueId)961 private static Insets getWaterfallInsets(Resources res, String displayUniqueId) { 962 Insets insets; 963 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 964 final TypedArray array = res.obtainTypedArray(R.array.config_waterfallCutoutArray); 965 if (index >= 0 && index < array.length() && array.getResourceId(index, 0) > 0) { 966 final int resourceId = array.getResourceId(index, 0); 967 final TypedArray waterfall = res.obtainTypedArray(resourceId); 968 insets = Insets.of( 969 waterfall.getDimensionPixelSize(0 /* waterfall left edge size */, 0), 970 waterfall.getDimensionPixelSize(1 /* waterfall top edge size */, 0), 971 waterfall.getDimensionPixelSize(2 /* waterfall right edge size */, 0), 972 waterfall.getDimensionPixelSize(3 /* waterfall bottom edge size */, 0)); 973 waterfall.recycle(); 974 } else { 975 insets = loadWaterfallInset(res); 976 } 977 array.recycle(); 978 return insets; 979 } 980 981 /** 982 * Creates the display cutout according to 983 * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest 984 * rectangle-base approximation of the cutout. 985 * 986 * @hide 987 */ fromResourcesRectApproximation(Resources res, String displayUniqueId, int displayWidth, int displayHeight)988 public static DisplayCutout fromResourcesRectApproximation(Resources res, 989 String displayUniqueId, int displayWidth, int displayHeight) { 990 return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), 991 getDisplayCutoutApproximationRect(res, displayUniqueId), 992 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, 993 getWaterfallInsets(res, displayUniqueId)).second; 994 } 995 996 /** 997 * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout. 998 * 999 * @hide 1000 */ pathFromResources(Resources res, String displayUniqueId, int displayWidth, int displayHeight)1001 public static Path pathFromResources(Resources res, String displayUniqueId, int displayWidth, 1002 int displayHeight) { 1003 return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), null, 1004 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, 1005 getWaterfallInsets(res, displayUniqueId)).first; 1006 } 1007 1008 /** 1009 * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec. 1010 * 1011 * @hide 1012 */ 1013 @VisibleForTesting(visibility = PRIVATE) fromSpec(String pathSpec, int displayWidth, int displayHeight, float density, Insets waterfallInsets)1014 public static DisplayCutout fromSpec(String pathSpec, int displayWidth, 1015 int displayHeight, float density, Insets waterfallInsets) { 1016 return pathAndDisplayCutoutFromSpec( 1017 pathSpec, null, displayWidth, displayHeight, density, waterfallInsets) 1018 .second; 1019 } 1020 1021 /** 1022 * Gets the cutout path and the corresponding DisplayCutout instance from the spec string. 1023 * 1024 * @param pathSpec the spec string read from config_mainBuiltInDisplayCutout. 1025 * @param rectSpec the spec string read from config_mainBuiltInDisplayCutoutRectApproximation. 1026 * @param displayWidth the display width. 1027 * @param displayHeight the display height. 1028 * @param density the display density. 1029 * @param waterfallInsets the waterfall insets of the display. 1030 * @return a Pair contains the cutout path and the corresponding DisplayCutout instance. 1031 */ pathAndDisplayCutoutFromSpec( String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density, Insets waterfallInsets)1032 private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec( 1033 String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density, 1034 Insets waterfallInsets) { 1035 // Always use the rect approximation spec to create the cutout if it's not null because 1036 // transforming and sending a Region constructed from a path is very costly. 1037 String spec = rectSpec != null ? rectSpec : pathSpec; 1038 if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) { 1039 return NULL_PAIR; 1040 } 1041 1042 synchronized (CACHE_LOCK) { 1043 if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth 1044 && sCachedDisplayHeight == displayHeight 1045 && sCachedDensity == density 1046 && waterfallInsets.equals(sCachedWaterfallInsets)) { 1047 return sCachedCutout; 1048 } 1049 } 1050 1051 spec = spec.trim(); 1052 1053 CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, 1054 displayWidth, displayHeight).parse(spec); 1055 Rect safeInset = cutoutSpec.getSafeInset(); 1056 final Rect boundLeft = cutoutSpec.getLeftBound(); 1057 final Rect boundTop = cutoutSpec.getTopBound(); 1058 final Rect boundRight = cutoutSpec.getRightBound(); 1059 final Rect boundBottom = cutoutSpec.getBottomBound(); 1060 1061 1062 if (!waterfallInsets.equals(Insets.NONE)) { 1063 safeInset.set( 1064 Math.max(waterfallInsets.left, safeInset.left), 1065 Math.max(waterfallInsets.top, safeInset.top), 1066 Math.max(waterfallInsets.right, safeInset.right), 1067 Math.max(waterfallInsets.bottom, safeInset.bottom)); 1068 } 1069 1070 final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo(displayWidth, 1071 displayHeight, density, pathSpec.trim(), ROTATION_0, 1f /* scale */); 1072 1073 final DisplayCutout cutout = new DisplayCutout( 1074 safeInset, waterfallInsets, boundLeft, boundTop, boundRight, boundBottom, 1075 cutoutPathParserInfo , false /* copyArguments */); 1076 final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); 1077 synchronized (CACHE_LOCK) { 1078 sCachedSpec = spec; 1079 sCachedDisplayWidth = displayWidth; 1080 sCachedDisplayHeight = displayHeight; 1081 sCachedDensity = density; 1082 sCachedCutout = result; 1083 sCachedWaterfallInsets = waterfallInsets; 1084 } 1085 return result; 1086 } 1087 loadWaterfallInset(Resources res)1088 private static Insets loadWaterfallInset(Resources res) { 1089 return Insets.of( 1090 res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), 1091 res.getDimensionPixelSize(R.dimen.waterfall_display_top_edge_size), 1092 res.getDimensionPixelSize(R.dimen.waterfall_display_right_edge_size), 1093 res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size)); 1094 } 1095 1096 /** 1097 * Helper class for passing {@link DisplayCutout} through binder. 1098 * 1099 * Needed, because {@code readFromParcel} cannot be used with immutable classes. 1100 * 1101 * @hide 1102 */ 1103 public static final class ParcelableWrapper implements Parcelable { 1104 1105 private DisplayCutout mInner; 1106 ParcelableWrapper()1107 public ParcelableWrapper() { 1108 this(NO_CUTOUT); 1109 } 1110 ParcelableWrapper(DisplayCutout cutout)1111 public ParcelableWrapper(DisplayCutout cutout) { 1112 mInner = cutout; 1113 } 1114 1115 @Override describeContents()1116 public int describeContents() { 1117 return 0; 1118 } 1119 1120 @Override writeToParcel(Parcel out, int flags)1121 public void writeToParcel(Parcel out, int flags) { 1122 writeCutoutToParcel(mInner, out, flags); 1123 } 1124 1125 /** 1126 * Writes a DisplayCutout to a {@link Parcel}. 1127 * 1128 * @see #readCutoutFromParcel(Parcel) 1129 */ writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags)1130 public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) { 1131 if (cutout == null) { 1132 out.writeInt(-1); 1133 } else if (cutout == NO_CUTOUT) { 1134 out.writeInt(0); 1135 } else { 1136 out.writeInt(1); 1137 out.writeTypedObject(cutout.mSafeInsets, flags); 1138 out.writeTypedArray(cutout.mBounds.getRects(), flags); 1139 out.writeTypedObject(cutout.mWaterfallInsets, flags); 1140 out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth()); 1141 out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight()); 1142 out.writeFloat(cutout.mCutoutPathParserInfo.getDensity()); 1143 out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec()); 1144 out.writeInt(cutout.mCutoutPathParserInfo.getRotation()); 1145 out.writeFloat(cutout.mCutoutPathParserInfo.getScale()); 1146 } 1147 } 1148 1149 /** 1150 * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing 1151 * instance. 1152 * 1153 * Needed for AIDL out parameters. 1154 */ readFromParcel(Parcel in)1155 public void readFromParcel(Parcel in) { 1156 mInner = readCutoutFromParcel(in); 1157 } 1158 1159 public static final @android.annotation.NonNull Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { 1160 @Override 1161 public ParcelableWrapper createFromParcel(Parcel in) { 1162 return new ParcelableWrapper(readCutoutFromParcel(in)); 1163 } 1164 1165 @Override 1166 public ParcelableWrapper[] newArray(int size) { 1167 return new ParcelableWrapper[size]; 1168 } 1169 }; 1170 1171 /** 1172 * Reads a DisplayCutout from a {@link Parcel}. 1173 * 1174 * @see #writeCutoutToParcel(DisplayCutout, Parcel, int) 1175 */ readCutoutFromParcel(Parcel in)1176 public static DisplayCutout readCutoutFromParcel(Parcel in) { 1177 int variant = in.readInt(); 1178 if (variant == -1) { 1179 return null; 1180 } 1181 if (variant == 0) { 1182 return NO_CUTOUT; 1183 } 1184 1185 Rect safeInsets = in.readTypedObject(Rect.CREATOR); 1186 Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; 1187 in.readTypedArray(bounds, Rect.CREATOR); 1188 Insets waterfallInsets = in.readTypedObject(Insets.CREATOR); 1189 int displayWidth = in.readInt(); 1190 int displayHeight = in.readInt(); 1191 float density = in.readFloat(); 1192 String cutoutSpec = in.readString(); 1193 int rotation = in.readInt(); 1194 float scale = in.readFloat(); 1195 final CutoutPathParserInfo info = new CutoutPathParserInfo( 1196 displayWidth, displayHeight, density, cutoutSpec, rotation, scale); 1197 1198 return new DisplayCutout( 1199 safeInsets, waterfallInsets, bounds, info, false /* copyArguments */); 1200 } 1201 get()1202 public DisplayCutout get() { 1203 return mInner; 1204 } 1205 set(ParcelableWrapper cutout)1206 public void set(ParcelableWrapper cutout) { 1207 mInner = cutout.get(); 1208 } 1209 set(DisplayCutout cutout)1210 public void set(DisplayCutout cutout) { 1211 mInner = cutout; 1212 } 1213 scale(float scale)1214 public void scale(float scale) { 1215 final Rect safeInsets = mInner.getSafeInsets(); 1216 safeInsets.scale(scale); 1217 final Bounds bounds = new Bounds(mInner.mBounds.mRects, true); 1218 bounds.scale(scale); 1219 final Rect waterfallInsets = mInner.mWaterfallInsets.toRect(); 1220 waterfallInsets.scale(scale); 1221 final CutoutPathParserInfo info = new CutoutPathParserInfo( 1222 mInner.mCutoutPathParserInfo.getDisplayWidth(), 1223 mInner.mCutoutPathParserInfo.getDisplayHeight(), 1224 mInner.mCutoutPathParserInfo.getDensity(), 1225 mInner.mCutoutPathParserInfo.getCutoutSpec(), 1226 mInner.mCutoutPathParserInfo.getRotation(), 1227 scale); 1228 1229 mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info); 1230 } 1231 1232 @Override hashCode()1233 public int hashCode() { 1234 return mInner.hashCode(); 1235 } 1236 1237 @Override equals(@ullable Object o)1238 public boolean equals(@Nullable Object o) { 1239 return o instanceof ParcelableWrapper 1240 && mInner.equals(((ParcelableWrapper) o).mInner); 1241 } 1242 1243 @Override toString()1244 public String toString() { 1245 return String.valueOf(mInner); 1246 } 1247 } 1248 } 1249