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.content.res.Resources.ID_NULL; 20 import static android.util.DisplayMetrics.DENSITY_DEFAULT; 21 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; 22 import static android.view.DisplayCutoutProto.BOUND_BOTTOM; 23 import static android.view.DisplayCutoutProto.BOUND_LEFT; 24 import static android.view.DisplayCutoutProto.BOUND_RIGHT; 25 import static android.view.DisplayCutoutProto.BOUND_TOP; 26 import static android.view.DisplayCutoutProto.INSETS; 27 import static android.view.DisplayCutoutProto.SIDE_OVERRIDES; 28 import static android.view.DisplayCutoutProto.WATERFALL_INSETS; 29 import static android.view.Surface.ROTATION_0; 30 import static android.view.Surface.ROTATION_270; 31 32 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 33 34 import android.annotation.IntDef; 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.content.res.Resources; 38 import android.content.res.TypedArray; 39 import android.graphics.Insets; 40 import android.graphics.Matrix; 41 import android.graphics.Path; 42 import android.graphics.Rect; 43 import android.os.Parcel; 44 import android.os.Parcelable; 45 import android.text.TextUtils; 46 import android.util.DisplayUtils; 47 import android.util.Pair; 48 import android.util.RotationUtils; 49 import android.util.proto.ProtoOutputStream; 50 import android.view.Surface.Rotation; 51 52 import com.android.internal.R; 53 import com.android.internal.annotations.GuardedBy; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.window.flags.Flags; 56 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Collections; 62 import java.util.List; 63 64 /** 65 * Represents the area of the display that is not functional for displaying content. 66 * 67 * <p>{@code DisplayCutout} is immutable. 68 */ 69 public final class DisplayCutout { 70 71 private static final String TAG = "DisplayCutout"; 72 73 /** 74 * Category for overlays that allow emulating a display cutout on devices that don't have 75 * one. 76 * 77 * @see android.content.om.IOverlayManager 78 * @hide 79 */ 80 public static final String EMULATION_OVERLAY_CATEGORY = 81 "com.android.internal.display_cutout_emulation"; 82 83 private static final Rect ZERO_RECT = new Rect(); 84 private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo( 85 0 /* displayWidth */, 0 /* physicalDisplayHeight */, 86 0 /* physicalDisplayHeight */, 0 /* displayHeight */, 0f /* density */, 87 "" /* cutoutSpec */, 0 /* ROTATION_0 */, 0f /* scale */, 88 0f /* physicalPixelDisplaySizeRatio*/); 89 90 /** 91 * An instance where {@link #isEmpty()} returns {@code true}. 92 * 93 * @hide 94 */ 95 public static final DisplayCutout NO_CUTOUT = new DisplayCutout( 96 ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO, 97 false /* copyArguments */); 98 99 100 private static final Pair<Path, DisplayCutout> NULL_PAIR = new Pair<>(null, null); 101 private static final Object CACHE_LOCK = new Object(); 102 103 @GuardedBy("CACHE_LOCK") 104 private static String sCachedSpec; 105 @GuardedBy("CACHE_LOCK") 106 private static int sCachedDisplayWidth; 107 @GuardedBy("CACHE_LOCK") 108 private static int sCachedDisplayHeight; 109 @GuardedBy("CACHE_LOCK") 110 private static float sCachedDensity; 111 @GuardedBy("CACHE_LOCK") 112 private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR; 113 @GuardedBy("CACHE_LOCK") 114 private static Insets sCachedWaterfallInsets; 115 @GuardedBy("CACHE_LOCK") 116 private static float sCachedPhysicalPixelDisplaySizeRatio; 117 118 @GuardedBy("CACHE_LOCK") 119 private static int[] sCachedSideOverrides; 120 121 @GuardedBy("CACHE_LOCK") 122 private static CutoutPathParserInfo sCachedCutoutPathParserInfo; 123 @GuardedBy("CACHE_LOCK") 124 private static Path sCachedCutoutPath; 125 126 private final Rect mSafeInsets; 127 @NonNull 128 private final Insets mWaterfallInsets; 129 130 /** 131 * The bound is at the left of the screen. 132 * @hide 133 */ 134 public static final int BOUNDS_POSITION_LEFT = 0; 135 136 /** 137 * The bound is at the top of the screen. 138 * @hide 139 */ 140 public static final int BOUNDS_POSITION_TOP = 1; 141 142 /** 143 * The bound is at the right of the screen. 144 * @hide 145 */ 146 public static final int BOUNDS_POSITION_RIGHT = 2; 147 148 /** 149 * The bound is at the bottom of the screen. 150 * @hide 151 */ 152 public static final int BOUNDS_POSITION_BOTTOM = 3; 153 154 /** 155 * The number of possible positions at which bounds can be located. 156 * @hide 157 */ 158 public static final int BOUNDS_POSITION_LENGTH = 4; 159 160 private static final int INVALID_SIDE_OVERRIDE = -1; 161 private static final String SIDE_STRING_TOP = "top"; 162 private static final String SIDE_STRING_BOTTOM = "bottom"; 163 private static final String SIDE_STRING_RIGHT = "right"; 164 private static final String SIDE_STRING_LEFT = "left"; 165 166 // The side index is always under the natural rotation of the device. 167 private int[] mSideOverrides; 168 169 static final int[] INVALID_OVERRIDES = new int[]{INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE, 170 INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE}; 171 172 /** @hide */ 173 @IntDef(prefix = { "BOUNDS_POSITION_" }, value = { 174 BOUNDS_POSITION_LEFT, 175 BOUNDS_POSITION_TOP, 176 BOUNDS_POSITION_RIGHT, 177 BOUNDS_POSITION_BOTTOM 178 }) 179 @Retention(RetentionPolicy.SOURCE) 180 public @interface BoundsPosition {} 181 182 private static class Bounds { 183 private final Rect[] mRects; 184 Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments)185 private Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments) { 186 mRects = new Rect[BOUNDS_POSITION_LENGTH]; 187 mRects[BOUNDS_POSITION_LEFT] = getCopyOrRef(left, copyArguments); 188 mRects[BOUNDS_POSITION_TOP] = getCopyOrRef(top, copyArguments); 189 mRects[BOUNDS_POSITION_RIGHT] = getCopyOrRef(right, copyArguments); 190 mRects[BOUNDS_POSITION_BOTTOM] = getCopyOrRef(bottom, copyArguments); 191 192 } 193 Bounds(Rect[] rects, boolean copyArguments)194 private Bounds(Rect[] rects, boolean copyArguments) { 195 if (rects.length != BOUNDS_POSITION_LENGTH) { 196 throw new IllegalArgumentException( 197 "rects must have exactly 4 elements: rects=" + Arrays.toString(rects)); 198 } 199 if (copyArguments) { 200 mRects = new Rect[BOUNDS_POSITION_LENGTH]; 201 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 202 mRects[i] = new Rect(rects[i]); 203 } 204 } else { 205 for (Rect rect : rects) { 206 if (rect == null) { 207 throw new IllegalArgumentException( 208 "rects must have non-null elements: rects=" 209 + Arrays.toString(rects)); 210 } 211 } 212 mRects = rects; 213 } 214 } 215 isEmpty()216 private boolean isEmpty() { 217 for (Rect rect : mRects) { 218 if (!rect.isEmpty()) { 219 return false; 220 } 221 } 222 return true; 223 } 224 getRect(@oundsPosition int pos)225 private Rect getRect(@BoundsPosition int pos) { 226 return new Rect(mRects[pos]); 227 } 228 getRects()229 private Rect[] getRects() { 230 Rect[] rects = new Rect[BOUNDS_POSITION_LENGTH]; 231 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 232 rects[i] = new Rect(mRects[i]); 233 } 234 return rects; 235 } 236 scale(float scale)237 private void scale(float scale) { 238 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 239 mRects[i].scale(scale); 240 } 241 } 242 243 @Override hashCode()244 public int hashCode() { 245 int result = 0; 246 for (Rect rect : mRects) { 247 result = result * 48271 + rect.hashCode(); 248 } 249 return result; 250 } 251 252 @Override equals(@ullable Object o)253 public boolean equals(@Nullable Object o) { 254 if (o == this) { 255 return true; 256 } 257 if (o instanceof Bounds) { 258 Bounds b = (Bounds) o; 259 return Arrays.deepEquals(mRects, b.mRects); 260 } 261 return false; 262 } 263 264 @Override toString()265 public String toString() { 266 return "Bounds=" + Arrays.toString(mRects); 267 } 268 269 } 270 271 private final Bounds mBounds; 272 273 /** 274 * Stores all the needed info to create the cutout paths. 275 * 276 * @hide 277 */ 278 public static class CutoutPathParserInfo { 279 private final int mDisplayWidth; 280 private final int mDisplayHeight; 281 private final int mPhysicalDisplayWidth; 282 private final int mPhysicalDisplayHeight; 283 private final float mDensity; 284 private final String mCutoutSpec; 285 private final @Rotation int mRotation; 286 private final float mScale; 287 private final float mPhysicalPixelDisplaySizeRatio; 288 CutoutPathParserInfo(int displayWidth, int displayHeight, int physicalDisplayWidth, int physicalDisplayHeight, float density, @Nullable String cutoutSpec, @Rotation int rotation, float scale, float physicalPixelDisplaySizeRatio)289 public CutoutPathParserInfo(int displayWidth, int displayHeight, int physicalDisplayWidth, 290 int physicalDisplayHeight, float density, @Nullable String cutoutSpec, 291 @Rotation int rotation, float scale, float physicalPixelDisplaySizeRatio) { 292 mDisplayWidth = displayWidth; 293 mDisplayHeight = displayHeight; 294 mPhysicalDisplayWidth = physicalDisplayWidth; 295 mPhysicalDisplayHeight = physicalDisplayHeight; 296 mDensity = density; 297 mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec; 298 mRotation = rotation; 299 mScale = scale; 300 mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; 301 } 302 CutoutPathParserInfo(@onNull CutoutPathParserInfo cutoutPathParserInfo)303 public CutoutPathParserInfo(@NonNull CutoutPathParserInfo cutoutPathParserInfo) { 304 mDisplayWidth = cutoutPathParserInfo.mDisplayWidth; 305 mDisplayHeight = cutoutPathParserInfo.mDisplayHeight; 306 mPhysicalDisplayWidth = cutoutPathParserInfo.mPhysicalDisplayWidth; 307 mPhysicalDisplayHeight = cutoutPathParserInfo.mPhysicalDisplayHeight; 308 mDensity = cutoutPathParserInfo.mDensity; 309 mCutoutSpec = cutoutPathParserInfo.mCutoutSpec; 310 mRotation = cutoutPathParserInfo.mRotation; 311 mScale = cutoutPathParserInfo.mScale; 312 mPhysicalPixelDisplaySizeRatio = cutoutPathParserInfo.mPhysicalPixelDisplaySizeRatio; 313 } 314 getDisplayWidth()315 public int getDisplayWidth() { 316 return mDisplayWidth; 317 } 318 getDisplayHeight()319 public int getDisplayHeight() { 320 return mDisplayHeight; 321 } 322 getPhysicalDisplayWidth()323 public int getPhysicalDisplayWidth() { 324 return mPhysicalDisplayWidth; 325 } 326 getPhysicalDisplayHeight()327 public int getPhysicalDisplayHeight() { 328 return mPhysicalDisplayHeight; 329 } 330 getDensity()331 public float getDensity() { 332 return mDensity; 333 } 334 getCutoutSpec()335 public @NonNull String getCutoutSpec() { 336 return mCutoutSpec; 337 } 338 getRotation()339 public int getRotation() { 340 return mRotation; 341 } 342 getScale()343 public float getScale() { 344 return mScale; 345 } 346 getPhysicalPixelDisplaySizeRatio()347 public float getPhysicalPixelDisplaySizeRatio() { 348 return mPhysicalPixelDisplaySizeRatio; 349 } 350 hasCutout()351 private boolean hasCutout() { 352 return !mCutoutSpec.isEmpty(); 353 } 354 355 @Override hashCode()356 public int hashCode() { 357 int result = 0; 358 result = result * 48271 + Integer.hashCode(mDisplayWidth); 359 result = result * 48271 + Integer.hashCode(mDisplayHeight); 360 result = result * 48271 + Float.hashCode(mDensity); 361 result = result * 48271 + mCutoutSpec.hashCode(); 362 result = result * 48271 + Integer.hashCode(mRotation); 363 result = result * 48271 + Float.hashCode(mScale); 364 result = result * 48271 + Float.hashCode(mPhysicalPixelDisplaySizeRatio); 365 result = result * 48271 + Integer.hashCode(mPhysicalDisplayWidth); 366 result = result * 48271 + Integer.hashCode(mPhysicalDisplayHeight); 367 return result; 368 } 369 370 @Override equals(@ullable Object o)371 public boolean equals(@Nullable Object o) { 372 if (o == this) { 373 return true; 374 } 375 if (o instanceof CutoutPathParserInfo) { 376 CutoutPathParserInfo c = (CutoutPathParserInfo) o; 377 return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight 378 && mPhysicalDisplayWidth == c.mPhysicalDisplayWidth 379 && mPhysicalDisplayHeight == c.mPhysicalDisplayHeight 380 && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec) 381 && mRotation == c.mRotation && mScale == c.mScale 382 && mPhysicalPixelDisplaySizeRatio == c.mPhysicalPixelDisplaySizeRatio; 383 } 384 return false; 385 } 386 387 @Override toString()388 public String toString() { 389 return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth 390 + " displayHeight=" + mDisplayHeight 391 + " physicalDisplayWidth=" + mPhysicalDisplayWidth 392 + " physicalDisplayHeight=" + mPhysicalDisplayHeight 393 + " density={" + mDensity + "}" 394 + " cutoutSpec={" + mCutoutSpec + "}" 395 + " rotation={" + mRotation + "}" 396 + " scale={" + mScale + "}" 397 + " physicalPixelDisplaySizeRatio={" + mPhysicalPixelDisplaySizeRatio + "}" 398 + "}"; 399 } 400 } 401 402 private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo; 403 404 /** 405 * Creates a DisplayCutout instance. 406 * 407 * <p>Note that this is only useful for tests. For production code, developers should always 408 * use a {@link DisplayCutout} obtained from the system.</p> 409 * 410 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 411 * {@link #getSafeInsetTop()} etc. 412 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 413 * it's treated as an empty rectangle (0,0)-(0,0). 414 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 415 * it's treated as an empty rectangle (0,0)-(0,0). 416 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 417 * passed, it's treated as an empty rectangle (0,0)-(0,0). 418 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 419 * passed, it's treated as an empty rectangle (0,0)-(0,0). 420 */ 421 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom)422 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 423 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) { 424 this(getCopyOrRef(safeInsets.toRect(), true), Insets.NONE, 425 new Bounds(boundLeft, boundTop, boundRight, boundBottom, true), null, null); 426 } 427 428 /** 429 * Creates a DisplayCutout instance. 430 * 431 * <p>Note that this is only useful for tests. For production code, developers should always 432 * use a {@link DisplayCutout} obtained from the system.</p> 433 * 434 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 435 * {@link #getSafeInsetTop()} etc. 436 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 437 * it's treated as an empty rectangle (0,0)-(0,0). 438 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 439 * it's treated as an empty rectangle (0,0)-(0,0). 440 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 441 * passed, it's treated as an empty rectangle (0,0)-(0,0). 442 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 443 * passed, it's treated as an empty rectangle (0,0)-(0,0). 444 * @param waterfallInsets the insets for the curved areas in waterfall display. 445 * @param info the cutout path parser info. 446 * @hide 447 */ 448 @VisibleForTesting DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info)449 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 450 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, 451 @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info) { 452 this(getCopyOrRef(safeInsets.toRect(), true), waterfallInsets, 453 new Bounds(boundLeft, boundTop, boundRight, boundBottom, true), info, null); 454 } 455 456 /** 457 * Creates a DisplayCutout instance. 458 * 459 * <p>Note that this is only useful for tests. For production code, developers should always 460 * use a {@link DisplayCutout} obtained from the system.</p> 461 * 462 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 463 * {@link #getSafeInsetTop()} etc. 464 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 465 * it's treated as an empty rectangle (0,0)-(0,0). 466 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 467 * it's treated as an empty rectangle (0,0)-(0,0). 468 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 469 * passed, it's treated as an empty rectangle (0,0)-(0,0). 470 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 471 * passed, it's treated as an empty rectangle (0,0)-(0,0). 472 * @param waterfallInsets the insets for the curved areas in waterfall display. 473 * @param info the cutout path parser info. 474 * @hide 475 */ DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info, @Nullable int[] sideOverrides)476 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 477 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, 478 @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info, 479 @Nullable int[] sideOverrides) { 480 this(safeInsets.toRect(), waterfallInsets, 481 new Bounds(boundLeft, boundTop, boundRight, boundBottom, true), 482 info, sideOverrides); 483 } 484 485 /** 486 * Creates a DisplayCutout instance. 487 * 488 * <p>Note that this is only useful for tests. For production code, developers should always 489 * use a {@link DisplayCutout} obtained from the system.</p> 490 * 491 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 492 * {@link #getSafeInsetTop()} etc. 493 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 494 * it's treated as an empty rectangle (0,0)-(0,0). 495 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 496 * it's treated as an empty rectangle (0,0)-(0,0). 497 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 498 * passed, it's treated as an empty rectangle (0,0)-(0,0). 499 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 500 * passed, it's treated as an empty rectangle (0,0)-(0,0). 501 * @param waterfallInsets the insets for the curved areas in waterfall display. 502 */ DisplayCutout(@onNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, @NonNull Insets waterfallInsets)503 public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, 504 @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, 505 @NonNull Insets waterfallInsets) { 506 this(getCopyOrRef(safeInsets.toRect(), true), waterfallInsets, 507 new Bounds(boundLeft, boundTop, boundRight, boundBottom, true), null, null); 508 } 509 510 /** 511 * Creates a DisplayCutout instance. 512 * 513 * <p>Note that this is only useful for tests. For production code, developers should always 514 * use a {@link DisplayCutout} obtained from the system.</p> 515 * 516 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 517 * {@link #getSafeInsetTop()} etc. 518 * @param boundingRects the bounding rects of the display cutouts as returned by 519 * {@link #getBoundingRects()} ()}. 520 * @deprecated Use {@link DisplayCutout#DisplayCutout(Insets, Rect, Rect, Rect, Rect)} instead. 521 */ 522 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) 523 @Deprecated DisplayCutout(@ullable Rect safeInsets, @Nullable List<Rect> boundingRects)524 public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) { 525 this(getCopyOrRef(safeInsets, true), Insets.NONE, 526 new Bounds(extractBoundsFromList(safeInsets, boundingRects), true), null, null); 527 } 528 529 /** 530 * Creates a DisplayCutout instance. 531 * 532 * @param safeInsets the insets from each edge which avoid the display cutout as returned by 533 * {@link #getSafeInsetTop()} etc. 534 * @param waterfallInsets the insets for the curved areas in waterfall display. 535 * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, 536 * it's treated as an empty rectangle (0,0)-(0,0). 537 * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, 538 * it's treated as an empty rectangle (0,0)-(0,0). 539 * @param boundRight the right bounding rect of the display cutout in pixels. If null is 540 * passed, it's treated as an empty rectangle (0,0)-(0,0). 541 * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is 542 * passed, it's treated as an empty rectangle (0,0)-(0,0). 543 * @param info the cutout path parser info. 544 * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments 545 * are not copied and MUST remain unchanged forever. 546 */ DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop, Rect boundRight, Rect boundBottom, CutoutPathParserInfo info, boolean copyArguments)547 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop, 548 Rect boundRight, Rect boundBottom, CutoutPathParserInfo info, 549 boolean copyArguments) { 550 this(getCopyOrRef(safeInsets, copyArguments), waterfallInsets, 551 new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments), info, 552 null); 553 } 554 DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds, CutoutPathParserInfo info, boolean copyArguments)555 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds, 556 CutoutPathParserInfo info, boolean copyArguments) { 557 this(getCopyOrRef(safeInsets, copyArguments), waterfallInsets, 558 new Bounds(bounds, copyArguments), info, null); 559 } 560 DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, CutoutPathParserInfo info)561 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, 562 CutoutPathParserInfo info) { 563 this(safeInsets, waterfallInsets, bounds, info, null); 564 } 565 DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, CutoutPathParserInfo info, int[] sideOverrides)566 private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, 567 CutoutPathParserInfo info, int[] sideOverrides) { 568 mSafeInsets = safeInsets; 569 mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; 570 mBounds = bounds; 571 mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; 572 mSideOverrides = sideOverrides; 573 } 574 getCopyOrRef(Rect r, boolean copyArguments)575 private static Rect getCopyOrRef(Rect r, boolean copyArguments) { 576 if (r == null) { 577 return ZERO_RECT; 578 } else if (copyArguments) { 579 return new Rect(r); 580 } else { 581 return r; 582 } 583 } 584 585 /** 586 * Returns the insets representing the curved areas of a waterfall display. 587 * 588 * A waterfall display has curved areas along the edges of the screen. Apps should be careful 589 * when showing UI and handling touch input in those insets because the curve may impair 590 * legibility and can frequently lead to unintended touch inputs. 591 * 592 * @return the insets for the curved areas of a waterfall display in pixels or {@code 593 * Insets.NONE} if there are no curved areas or they don't overlap with the window. 594 */ getWaterfallInsets()595 public @NonNull Insets getWaterfallInsets() { 596 return mWaterfallInsets; 597 } 598 599 600 /** 601 * Find the position of the bounding rect, and create an array of Rect whose index represents 602 * the position (= BoundsPosition). 603 * 604 * @hide 605 */ extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects)606 public static Rect[] extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects) { 607 Rect[] sortedBounds = new Rect[BOUNDS_POSITION_LENGTH]; 608 for (int i = 0; i < sortedBounds.length; ++i) { 609 sortedBounds[i] = ZERO_RECT; 610 } 611 if (safeInsets != null && boundingRects != null) { 612 // There is at most one non-functional area per short edge of the device, but none 613 // on the long edges, so either a) safeInsets.top and safeInsets.bottom is 0, or 614 // b) safeInsets.left and safeInset.right is 0. 615 final boolean topBottomInset = safeInsets.top > 0 || safeInsets.bottom > 0; 616 for (Rect bound : boundingRects) { 617 if (topBottomInset) { 618 if (bound.top == 0) { 619 sortedBounds[BOUNDS_POSITION_TOP] = bound; 620 } else { 621 sortedBounds[BOUNDS_POSITION_BOTTOM] = bound; 622 } 623 } else { 624 if (bound.left == 0) { 625 sortedBounds[BOUNDS_POSITION_LEFT] = bound; 626 } else { 627 sortedBounds[BOUNDS_POSITION_RIGHT] = bound; 628 } 629 } 630 } 631 } 632 return sortedBounds; 633 } 634 635 /** 636 * Returns true if there is no cutout, i.e. the bounds are empty. 637 * 638 * @hide 639 */ isBoundsEmpty()640 public boolean isBoundsEmpty() { 641 return mBounds.isEmpty(); 642 } 643 644 /** 645 * Returns true if the safe insets are empty (and therefore the current view does not 646 * overlap with the cutout or cutout area). 647 * 648 * @hide 649 */ isEmpty()650 public boolean isEmpty() { 651 return mSafeInsets.equals(ZERO_RECT); 652 } 653 654 /** 655 * Returns the inset from the top which avoids the display cutout in pixels. 656 * 657 * @see WindowInsets.Type#displayCutout() 658 */ getSafeInsetTop()659 public int getSafeInsetTop() { 660 return mSafeInsets.top; 661 } 662 663 /** 664 * Returns the inset from the bottom which avoids the display cutout in pixels. 665 * 666 * @see WindowInsets.Type#displayCutout() 667 */ getSafeInsetBottom()668 public int getSafeInsetBottom() { 669 return mSafeInsets.bottom; 670 } 671 672 /** 673 * Returns the inset from the left which avoids the display cutout in pixels. 674 * 675 * @see WindowInsets.Type#displayCutout() 676 */ getSafeInsetLeft()677 public int getSafeInsetLeft() { 678 return mSafeInsets.left; 679 } 680 681 /** 682 * Returns the inset from the right which avoids the display cutout in pixels. 683 * 684 * @see WindowInsets.Type#displayCutout() 685 */ getSafeInsetRight()686 public int getSafeInsetRight() { 687 return mSafeInsets.right; 688 } 689 690 /** 691 * Returns the safe insets in a rect in pixel units. 692 * 693 * @return a rect which is set to the safe insets. 694 * @hide 695 */ getSafeInsets()696 public Rect getSafeInsets() { 697 return new Rect(mSafeInsets); 698 } 699 700 /** 701 * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional 702 * area on the display. 703 * 704 * There will be at most one non-functional area per edge of the device. 705 * 706 * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the 707 * curved areas of the display but not the non-functional areas.</p> 708 * 709 * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is 710 * returned. 711 */ 712 @NonNull getBoundingRects()713 public List<Rect> getBoundingRects() { 714 List<Rect> result = new ArrayList<>(); 715 for (Rect bound : getBoundingRectsAll()) { 716 if (!bound.isEmpty()) { 717 result.add(new Rect(bound)); 718 } 719 } 720 return result; 721 } 722 723 /** 724 * Returns an array of {@code Rect}s, each of which is the bounding rectangle for a non- 725 * functional area on the display. Ordinal value of BoundPosition is used as an index of 726 * the array. 727 * 728 * There will be at most one non-functional area per edge of the device. 729 * 730 * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the 731 * curved areas of the display but not the non-functional areas.</p> 732 * 733 * @return an array of bounding {@code Rect}s, one for each display cutout area. This might 734 * contain ZERO_RECT, which means there is no cutout area at the position. 735 * 736 * @hide 737 */ getBoundingRectsAll()738 public Rect[] getBoundingRectsAll() { 739 return mBounds.getRects(); 740 } 741 742 /** 743 * Returns a bounding rectangle for a non-functional area on the display which is located on 744 * the left of the screen. 745 * 746 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 747 * is returned. 748 */ getBoundingRectLeft()749 public @NonNull Rect getBoundingRectLeft() { 750 return mBounds.getRect(BOUNDS_POSITION_LEFT); 751 } 752 753 /** 754 * Returns a bounding rectangle for a non-functional area on the display which is located on 755 * the top of the screen. 756 * 757 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 758 * is returned. 759 */ getBoundingRectTop()760 public @NonNull Rect getBoundingRectTop() { 761 return mBounds.getRect(BOUNDS_POSITION_TOP); 762 } 763 764 /** 765 * Returns a bounding rectangle for a non-functional area on the display which is located on 766 * the right of the screen. 767 * 768 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 769 * is returned. 770 */ getBoundingRectRight()771 public @NonNull Rect getBoundingRectRight() { 772 return mBounds.getRect(BOUNDS_POSITION_RIGHT); 773 } 774 775 /** 776 * Returns a bounding rectangle for a non-functional area on the display which is located on 777 * the bottom of the screen. 778 * 779 * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle 780 * is returned. 781 */ getBoundingRectBottom()782 public @NonNull Rect getBoundingRectBottom() { 783 return mBounds.getRect(BOUNDS_POSITION_BOTTOM); 784 } 785 786 /** 787 * Returns a {@link Path} that contains the cutout paths of all sides on the display. 788 * 789 * To get a cutout path for one specific side, apps can intersect the {@link Path} with the 790 * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()}, 791 * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}. 792 * 793 * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns 794 * null if there is no cutout on the display. 795 */ getCutoutPath()796 public @Nullable Path getCutoutPath() { 797 if (!mCutoutPathParserInfo.hasCutout()) { 798 return null; 799 } 800 synchronized (CACHE_LOCK) { 801 if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) { 802 return sCachedCutoutPath; 803 } 804 } 805 final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser( 806 mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getPhysicalDisplayWidth(), 807 mCutoutPathParserInfo.getPhysicalDisplayHeight(), 808 mCutoutPathParserInfo.getPhysicalPixelDisplaySizeRatio()) 809 .parse(mCutoutPathParserInfo.getCutoutSpec()); 810 811 final Path cutoutPath = cutoutSpec.getPath(); 812 if (cutoutPath == null || cutoutPath.isEmpty()) { 813 return null; 814 } 815 final Matrix matrix = new Matrix(); 816 if (mCutoutPathParserInfo.getRotation() != ROTATION_0) { 817 RotationUtils.transformPhysicalToLogicalCoordinates( 818 mCutoutPathParserInfo.getRotation(), 819 mCutoutPathParserInfo.getDisplayWidth(), 820 mCutoutPathParserInfo.getDisplayHeight(), 821 matrix 822 ); 823 } 824 matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale()); 825 cutoutPath.transform(matrix); 826 827 synchronized (CACHE_LOCK) { 828 sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo); 829 sCachedCutoutPath = cutoutPath; 830 } 831 return cutoutPath; 832 } 833 834 /** 835 * @return the {@link CutoutPathParserInfo}; 836 * 837 * @hide 838 */ getCutoutPathParserInfo()839 public CutoutPathParserInfo getCutoutPathParserInfo() { 840 return mCutoutPathParserInfo; 841 } 842 843 @Override hashCode()844 public int hashCode() { 845 int result = 0; 846 result = 48271 * result + mSafeInsets.hashCode(); 847 result = 48271 * result + mBounds.hashCode(); 848 result = 48271 * result + mWaterfallInsets.hashCode(); 849 result = 48271 * result + mCutoutPathParserInfo.hashCode(); 850 result = 48271 * result + Arrays.hashCode(mSideOverrides); 851 return result; 852 } 853 854 @Override equals(@ullable Object o)855 public boolean equals(@Nullable Object o) { 856 if (o == this) { 857 return true; 858 } 859 if (o instanceof DisplayCutout) { 860 DisplayCutout c = (DisplayCutout) o; 861 return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds) 862 && mWaterfallInsets.equals(c.mWaterfallInsets) 863 && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo) 864 && Arrays.equals(mSideOverrides, c.mSideOverrides); 865 } 866 return false; 867 } 868 869 @Override toString()870 public String toString() { 871 return "DisplayCutout{insets=" + mSafeInsets 872 + " waterfall=" + mWaterfallInsets 873 + " boundingRect={" + mBounds + "}" 874 + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}" 875 + " sideOverrides=" + sideOverridesToString(mSideOverrides) 876 + "}"; 877 } 878 sideOverridesToString(int[] sideOverrides)879 private static String sideOverridesToString(int[] sideOverrides) { 880 if (sideOverrides == null) { 881 return "null"; 882 } 883 final StringBuilder sb = new StringBuilder(); 884 sb.append("{"); 885 final int length = sideOverrides.length; 886 if (length != BOUNDS_POSITION_LENGTH) { 887 sb.append("length=").append(sideOverrides.length).append(". "); 888 } 889 boolean hasContent = false; 890 for (int i = ROTATION_0; i < length; i++) { 891 final int override = sideOverrides[i]; 892 if (override != INVALID_SIDE_OVERRIDE) { 893 if (hasContent) { 894 sb.append(", "); 895 } 896 sb.append(Surface.rotationToString(i)).append(": "); 897 switch(override) { 898 case BOUNDS_POSITION_LEFT: 899 sb.append(SIDE_STRING_LEFT); 900 break; 901 case BOUNDS_POSITION_TOP: 902 sb.append(SIDE_STRING_TOP); 903 break; 904 case BOUNDS_POSITION_RIGHT: 905 sb.append(SIDE_STRING_RIGHT); 906 break; 907 case BOUNDS_POSITION_BOTTOM: 908 sb.append(SIDE_STRING_BOTTOM); 909 break; 910 } 911 hasContent = true; 912 } 913 } 914 return sb.append("}").toString(); 915 } 916 917 /** 918 * @hide 919 */ dumpDebug(ProtoOutputStream proto, long fieldId)920 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 921 final long token = proto.start(fieldId); 922 mSafeInsets.dumpDebug(proto, INSETS); 923 mBounds.getRect(BOUNDS_POSITION_LEFT).dumpDebug(proto, BOUND_LEFT); 924 mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP); 925 mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT); 926 mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM); 927 mWaterfallInsets.toRect().dumpDebug(proto, WATERFALL_INSETS); 928 if (mSideOverrides != null) { 929 for (int sideOverride : mSideOverrides) { 930 proto.write(SIDE_OVERRIDES, sideOverride); 931 } 932 } 933 proto.end(token); 934 } 935 936 /** 937 * Insets the reference frame of the cutout in the given directions. 938 * 939 * @return a copy of this instance which has been inset 940 * @hide 941 */ inset(int insetLeft, int insetTop, int insetRight, int insetBottom)942 public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { 943 if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0 944 || (isBoundsEmpty() && mWaterfallInsets.equals(Insets.NONE))) { 945 return this; 946 } 947 948 Rect safeInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom, 949 new Rect(mSafeInsets)); 950 951 // If we are not cutting off part of the cutout by insetting it on bottom/right, and we also 952 // don't move it around, we can avoid the allocation and copy of the instance. 953 if (insetLeft == 0 && insetTop == 0 && mSafeInsets.equals(safeInsets)) { 954 return this; 955 } 956 957 Rect waterfallInsets = insetInsets(insetLeft, insetTop, insetRight, insetBottom, 958 mWaterfallInsets.toRect()); 959 960 Rect[] bounds = mBounds.getRects(); 961 for (int i = 0; i < bounds.length; ++i) { 962 if (!bounds[i].equals(ZERO_RECT)) { 963 bounds[i].offset(-insetLeft, -insetTop); 964 } 965 } 966 967 return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, 968 mCutoutPathParserInfo, false /* copyArguments */); 969 } 970 insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom, Rect insets)971 private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom, 972 Rect insets) { 973 // Note: it's not really well defined what happens when the inset is negative, because we 974 // don't know if the safe inset needs to expand in general. 975 if (insetTop > 0 || insets.top > 0) { 976 insets.top = atLeastZero(insets.top - insetTop); 977 } 978 if (insetBottom > 0 || insets.bottom > 0) { 979 insets.bottom = atLeastZero(insets.bottom - insetBottom); 980 } 981 if (insetLeft > 0 || insets.left > 0) { 982 insets.left = atLeastZero(insets.left - insetLeft); 983 } 984 if (insetRight > 0 || insets.right > 0) { 985 insets.right = atLeastZero(insets.right - insetRight); 986 } 987 return insets; 988 } 989 990 /** 991 * Returns a copy of this instance with the safe insets replaced with the parameter. 992 * 993 * @param safeInsets the new safe insets in pixels 994 * @return a copy of this instance with the safe insets replaced with the argument. 995 * 996 * @hide 997 */ replaceSafeInsets(Rect safeInsets)998 public DisplayCutout replaceSafeInsets(Rect safeInsets) { 999 return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds, 1000 mCutoutPathParserInfo, mSideOverrides); 1001 } 1002 atLeastZero(int value)1003 private static int atLeastZero(int value) { 1004 return value < 0 ? 0 : value; 1005 } 1006 1007 1008 /** 1009 * Creates an instance from a bounding rect. 1010 * 1011 * @hide 1012 */ 1013 @VisibleForTesting fromBoundingRect( int left, int top, int right, int bottom, @BoundsPosition int pos)1014 public static DisplayCutout fromBoundingRect( 1015 int left, int top, int right, int bottom, @BoundsPosition int pos) { 1016 Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; 1017 for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { 1018 bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect(); 1019 } 1020 return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */); 1021 } 1022 1023 /** 1024 * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo. 1025 * 1026 * @hide 1027 */ constructDisplayCutout(Rect[] bounds, Insets waterfallInsets, CutoutPathParserInfo info)1028 public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets, 1029 CutoutPathParserInfo info) { 1030 return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info, 1031 false /* copyArguments */); 1032 } 1033 1034 /** 1035 * Creates an instance from a bounding {@link Path}. 1036 * 1037 * @hide 1038 */ fromBounds(Rect[] bounds)1039 public static DisplayCutout fromBounds(Rect[] bounds) { 1040 return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */, 1041 false /* copyArguments */); 1042 } 1043 1044 /** 1045 * Gets the display cutout by the given display unique id. 1046 * 1047 * Loads the default config {@link R.string#config_mainBuiltInDisplayCutout) if 1048 * {@link R.array#config_displayUniqueIdArray} is not set. 1049 */ getDisplayCutoutPath(Resources res, String displayUniqueId)1050 private static String getDisplayCutoutPath(Resources res, String displayUniqueId) { 1051 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1052 final String[] array = res.getStringArray(R.array.config_displayCutoutPathArray); 1053 if (index >= 0 && index < array.length) { 1054 return array[index]; 1055 } 1056 return res.getString(R.string.config_mainBuiltInDisplayCutout); 1057 } 1058 1059 /** 1060 * Gets the display cutout approximation rect by the given display unique id. 1061 * 1062 * Loads the default config {@link R.string#config_mainBuiltInDisplayCutoutRectApproximation} if 1063 * {@link R.array#config_displayUniqueIdArray} is not set. 1064 */ getDisplayCutoutApproximationRect(Resources res, String displayUniqueId)1065 private static String getDisplayCutoutApproximationRect(Resources res, String displayUniqueId) { 1066 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1067 final String[] array = res.getStringArray( 1068 R.array.config_displayCutoutApproximationRectArray); 1069 if (index >= 0 && index < array.length) { 1070 return array[index]; 1071 } 1072 return res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation); 1073 } 1074 1075 /** 1076 * Gets whether to mask a built-in display cutout of a display which is determined by the 1077 * given display unique id. 1078 * 1079 * Loads the default config {@link R.bool#config_maskMainBuiltInDisplayCutout} if 1080 * {@link R.array#config_displayUniqueIdArray} is not set. 1081 * 1082 * @hide 1083 */ getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId)1084 public static boolean getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId) { 1085 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1086 final TypedArray array = res.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray); 1087 boolean maskCutout; 1088 if (index >= 0 && index < array.length()) { 1089 maskCutout = array.getBoolean(index, false); 1090 } else { 1091 maskCutout = res.getBoolean(R.bool.config_maskMainBuiltInDisplayCutout); 1092 } 1093 array.recycle(); 1094 return maskCutout; 1095 } 1096 1097 /** 1098 * Gets whether to fill a built-in display cutout of a display which is determined by the 1099 * given display unique id. 1100 * 1101 * Loads the default config{@link R.bool#config_fillMainBuiltInDisplayCutout} if 1102 * {@link R.array#config_displayUniqueIdArray} is not set. 1103 * 1104 * @hide 1105 */ getFillBuiltInDisplayCutout(Resources res, String displayUniqueId)1106 public static boolean getFillBuiltInDisplayCutout(Resources res, String displayUniqueId) { 1107 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1108 final TypedArray array = res.obtainTypedArray(R.array.config_fillBuiltInDisplayCutoutArray); 1109 boolean fillCutout; 1110 if (index >= 0 && index < array.length()) { 1111 fillCutout = array.getBoolean(index, false); 1112 } else { 1113 fillCutout = res.getBoolean(R.bool.config_fillMainBuiltInDisplayCutout); 1114 } 1115 array.recycle(); 1116 return fillCutout; 1117 } 1118 1119 /** 1120 * Gets the waterfall cutout by the given display unique id. 1121 * 1122 * Loads the default waterfall dimens if {@link R.array#config_displayUniqueIdArray} is not set. 1123 * {@link R.dimen#waterfall_display_left_edge_size}, 1124 * {@link R.dimen#waterfall_display_top_edge_size}, 1125 * {@link R.dimen#waterfall_display_right_edge_size}, 1126 * {@link R.dimen#waterfall_display_bottom_edge_size} 1127 */ getWaterfallInsets(Resources res, String displayUniqueId)1128 private static Insets getWaterfallInsets(Resources res, String displayUniqueId) { 1129 Insets insets; 1130 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1131 final TypedArray array = res.obtainTypedArray(R.array.config_waterfallCutoutArray); 1132 final int resourceId = index >= 0 && index < array.length() 1133 ? array.getResourceId(index, ID_NULL) 1134 : ID_NULL; 1135 if (resourceId != ID_NULL) { 1136 final TypedArray waterfall = res.obtainTypedArray(resourceId); 1137 insets = Insets.of( 1138 waterfall.getDimensionPixelSize(0 /* waterfall left edge size */, 0), 1139 waterfall.getDimensionPixelSize(1 /* waterfall top edge size */, 0), 1140 waterfall.getDimensionPixelSize(2 /* waterfall right edge size */, 0), 1141 waterfall.getDimensionPixelSize(3 /* waterfall bottom edge size */, 0)); 1142 waterfall.recycle(); 1143 } else { 1144 insets = loadWaterfallInset(res); 1145 } 1146 array.recycle(); 1147 return insets; 1148 } 1149 1150 private static int[] getDisplayCutoutSideOverrides(Resources res, String displayUniqueId) 1151 throws IllegalArgumentException { 1152 if (!Flags.movableCutoutConfiguration()) { 1153 return null; 1154 } 1155 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 1156 final TypedArray array = res.obtainTypedArray( 1157 R.array.config_displayCutoutSideOverrideArray); 1158 final int resourceId = index >= 0 && index < array.length() 1159 ? array.getResourceId(index, ID_NULL) 1160 : ID_NULL; 1161 final int[] rawOverrides = resourceId != ID_NULL 1162 ? array.getResources().getIntArray(resourceId) 1163 : res.getIntArray(R.array.config_mainBuiltInDisplayCutoutSideOverride); 1164 array.recycle(); 1165 if (rawOverrides.length == 0) { 1166 return INVALID_OVERRIDES; 1167 } else if (rawOverrides.length != 4) { 1168 throw new IllegalArgumentException( 1169 "Invalid side override definition, exact 4 overrides required: " 1170 + Arrays.toString(rawOverrides)); 1171 } 1172 for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { 1173 if (rawOverrides[rotation] < BOUNDS_POSITION_LEFT 1174 || rawOverrides[rotation] >= BOUNDS_POSITION_LENGTH) { 1175 throw new IllegalArgumentException("Invalid side override definition: " 1176 + Arrays.toString(rawOverrides)); 1177 } 1178 } 1179 return rawOverrides; 1180 } 1181 1182 /** 1183 * Creates the display cutout according to 1184 * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest 1185 * rectangle-base approximation of the cutout. 1186 * @hide 1187 */ 1188 public static DisplayCutout fromResourcesRectApproximation(Resources res, 1189 String displayUniqueId, int physicalDisplayWidth, int physicalDisplayHeight, 1190 int displayWidth, int displayHeight) { 1191 return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), 1192 getDisplayCutoutApproximationRect(res, displayUniqueId), physicalDisplayWidth, 1193 physicalDisplayHeight, displayWidth, displayHeight, 1194 DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, 1195 getWaterfallInsets(res, displayUniqueId), 1196 getDisplayCutoutSideOverrides(res, displayUniqueId)).second; 1197 } 1198 1199 /** 1200 * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec. 1201 * 1202 * @hide 1203 */ 1204 @VisibleForTesting(visibility = PRIVATE) 1205 public static DisplayCutout fromSpec(String pathSpec, int displayWidth, 1206 int displayHeight, float density, Insets waterfallInsets, int[] sideOverrides) { 1207 return pathAndDisplayCutoutFromSpec( 1208 pathSpec, null, displayWidth, displayHeight, displayWidth, displayHeight, density, 1209 waterfallInsets, sideOverrides).second; 1210 } 1211 1212 /** 1213 * Gets the cutout path and the corresponding DisplayCutout instance from the spec string. 1214 * 1215 * @param pathSpec the spec string read from config for certain display. 1216 * @param rectSpec the rect approximation spec string read from config for certain display. 1217 * @param physicalDisplayWidth the max physical display width the display supports. 1218 * @param physicalDisplayHeight the max physical display height the display supports. 1219 * @param displayWidth the display width. 1220 * @param displayHeight the display height. 1221 * @param density the display density. 1222 * @param waterfallInsets the waterfall insets of the display. 1223 * @return a Pair contains the cutout path and the corresponding DisplayCutout instance. 1224 */ 1225 private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec( 1226 String pathSpec, String rectSpec, int physicalDisplayWidth, int physicalDisplayHeight, 1227 int displayWidth, int displayHeight, float density, Insets waterfallInsets, 1228 int[] sideOverrides) { 1229 // Always use the rect approximation spec to create the cutout if it's not null because 1230 // transforming and sending a Region constructed from a path is very costly. 1231 String spec = rectSpec != null ? rectSpec : pathSpec; 1232 if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) { 1233 return NULL_PAIR; 1234 } 1235 1236 final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio( 1237 physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight); 1238 1239 synchronized (CACHE_LOCK) { 1240 if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth 1241 && sCachedDisplayHeight == displayHeight 1242 && sCachedDensity == density 1243 && waterfallInsets.equals(sCachedWaterfallInsets) 1244 && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio 1245 && Arrays.equals(sCachedSideOverrides, sideOverrides)) { 1246 return sCachedCutout; 1247 } 1248 } 1249 1250 spec = spec.trim(); 1251 1252 CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, 1253 physicalDisplayWidth, physicalDisplayHeight, physicalPixelDisplaySizeRatio) 1254 .parse(spec); 1255 Rect safeInset = cutoutSpec.getSafeInset(); 1256 final Rect boundLeft = cutoutSpec.getLeftBound(); 1257 final Rect boundTop = cutoutSpec.getTopBound(); 1258 final Rect boundRight = cutoutSpec.getRightBound(); 1259 final Rect boundBottom = cutoutSpec.getBottomBound(); 1260 1261 if (!waterfallInsets.equals(Insets.NONE)) { 1262 safeInset.set( 1263 Math.max(waterfallInsets.left, safeInset.left), 1264 Math.max(waterfallInsets.top, safeInset.top), 1265 Math.max(waterfallInsets.right, safeInset.right), 1266 Math.max(waterfallInsets.bottom, safeInset.bottom)); 1267 } 1268 1269 final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo( 1270 displayWidth, displayHeight, physicalDisplayWidth, physicalDisplayHeight, density, 1271 pathSpec.trim(), ROTATION_0, 1f /* scale */, physicalPixelDisplaySizeRatio); 1272 final int sideOverride = getSideOverride(sideOverrides, ROTATION_0); 1273 final Rect[] bounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, false) 1274 .getRects(); 1275 final int rotateDistance = getRotationToOverride(sideOverride, bounds, 1276 ROTATION_0 /* defaultRotation */); 1277 if (rotateDistance != ROTATION_0) { 1278 Collections.rotate(Arrays.asList(bounds), rotateDistance); 1279 } 1280 final Rect safeInsets = DisplayCutout.computeSafeInsets(displayWidth, displayHeight, 1281 waterfallInsets, bounds); 1282 final DisplayCutout cutout = new DisplayCutout(safeInsets, waterfallInsets, 1283 new Bounds(bounds[BOUNDS_POSITION_LEFT], bounds[BOUNDS_POSITION_TOP], 1284 bounds[BOUNDS_POSITION_RIGHT], bounds[BOUNDS_POSITION_BOTTOM], false), 1285 cutoutPathParserInfo, sideOverrides); 1286 1287 final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); 1288 synchronized (CACHE_LOCK) { 1289 sCachedSpec = spec; 1290 sCachedDisplayWidth = displayWidth; 1291 sCachedDisplayHeight = displayHeight; 1292 sCachedDensity = density; 1293 sCachedCutout = result; 1294 sCachedWaterfallInsets = waterfallInsets; 1295 sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; 1296 sCachedSideOverrides = sideOverrides; 1297 } 1298 return result; 1299 } 1300 1301 private static Insets loadWaterfallInset(Resources res) { 1302 return Insets.of( 1303 res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), 1304 res.getDimensionPixelSize(R.dimen.waterfall_display_top_edge_size), 1305 res.getDimensionPixelSize(R.dimen.waterfall_display_right_edge_size), 1306 res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size)); 1307 } 1308 1309 /** 1310 * @return a copy of this cutout that has been rotated for a display in toRotation. 1311 * @hide 1312 */ 1313 public DisplayCutout getRotated(int startWidth, int startHeight, 1314 int fromRotation, int toRotation) { 1315 if (this == DisplayCutout.NO_CUTOUT) { 1316 return DisplayCutout.NO_CUTOUT; 1317 } 1318 final int rotation = RotationUtils.deltaRotation(fromRotation, toRotation); 1319 if (rotation == ROTATION_0) { 1320 return this; 1321 } 1322 final Insets waterfallInsets = RotationUtils.rotateInsets(getWaterfallInsets(), rotation); 1323 // returns a copy 1324 final Rect[] newBounds = getBoundingRectsAll(); 1325 final Rect displayBounds = new Rect(0, 0, startWidth, startHeight); 1326 for (int i = 0; i < newBounds.length; ++i) { 1327 if (newBounds[i].isEmpty()) continue; 1328 RotationUtils.rotateBounds(newBounds[i], displayBounds, rotation); 1329 } 1330 final int defaultRotation = -rotation; 1331 final int override = getSideOverride(mSideOverrides, toRotation); 1332 Collections.rotate(Arrays.asList(newBounds), 1333 getRotationToOverride(override, newBounds, defaultRotation)); 1334 final CutoutPathParserInfo info = getCutoutPathParserInfo(); 1335 final CutoutPathParserInfo newInfo = new CutoutPathParserInfo( 1336 info.getDisplayWidth(), info.getDisplayHeight(), info.getPhysicalDisplayWidth(), 1337 info.getPhysicalDisplayHeight(), info.getDensity(), info.getCutoutSpec(), 1338 toRotation, info.getScale(), info.getPhysicalPixelDisplaySizeRatio()); 1339 final boolean swapAspect = (rotation % 2) != 0; 1340 final int endWidth = swapAspect ? startHeight : startWidth; 1341 final int endHeight = swapAspect ? startWidth : startHeight; 1342 final DisplayCutout tmp = 1343 DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo); 1344 final Rect safeInsets = DisplayCutout.computeSafeInsets(endWidth, endHeight, tmp); 1345 tmp.mSideOverrides = mSideOverrides; 1346 return tmp.replaceSafeInsets(safeInsets); 1347 } 1348 1349 private static int getSideOverride(int[] sideOverrides, @Rotation int rotation) { 1350 if (sideOverrides == null || sideOverrides.length != 4) { 1351 return INVALID_SIDE_OVERRIDE; 1352 } 1353 return sideOverrides[rotation]; 1354 } 1355 1356 /** @return the rotation needed to rotate from the original side to the overridden one. */ 1357 private static @Rotation int getRotationToOverride(int sideOverride, Rect[] bounds, 1358 @Rotation int defaultRotation) { 1359 if (sideOverride == INVALID_SIDE_OVERRIDE) { 1360 return defaultRotation; 1361 } 1362 int side = -1; 1363 for (int i = 0; i <= BOUNDS_POSITION_BOTTOM; i++) { 1364 if (bounds[i].isEmpty()) { 1365 continue; 1366 } 1367 if (side != -1) { 1368 // We don't rotate at all when there are multiple non empty cutout bounds. 1369 return defaultRotation; 1370 } 1371 side = i; 1372 } 1373 if (side == -1) { 1374 return defaultRotation; 1375 } 1376 int rotation = sideOverride - side; 1377 if (rotation < 0) { 1378 rotation += 4; 1379 } 1380 return rotation; 1381 } 1382 1383 /** 1384 * Compute the insets derived from a cutout. This is usually used to populate the safe-insets 1385 * of the cutout via {@link #replaceSafeInsets}. 1386 * @hide 1387 */ 1388 public static Rect computeSafeInsets(int displayW, int displayH, DisplayCutout cutout) { 1389 return computeSafeInsets(displayW, displayH, cutout.getWaterfallInsets(), 1390 cutout.getBoundingRectsAll()); 1391 } 1392 1393 private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets, 1394 Rect[] bounds) { 1395 1396 int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide( 1397 displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT)); 1398 int topInset = Math.max(waterFallInsets.top, findCutoutInsetForSide( 1399 displayW, displayH, bounds[BOUNDS_POSITION_TOP], Gravity.TOP)); 1400 int rightInset = Math.max(waterFallInsets.right, findCutoutInsetForSide( 1401 displayW, displayH, bounds[BOUNDS_POSITION_RIGHT], Gravity.RIGHT)); 1402 int bottomInset = Math.max(waterFallInsets.bottom, findCutoutInsetForSide( 1403 displayW, displayH, bounds[BOUNDS_POSITION_BOTTOM], Gravity.BOTTOM)); 1404 1405 return new Rect(leftInset, topInset, rightInset, bottomInset); 1406 } 1407 1408 private static int findCutoutInsetForSide(int displayW, int displayH, 1409 @NonNull Rect boundingRect, int gravity) { 1410 if (boundingRect.isEmpty()) { 1411 return 0; 1412 } 1413 1414 int inset = 0; 1415 return switch (gravity) { 1416 case Gravity.TOP -> Math.max(inset, boundingRect.bottom); 1417 case Gravity.BOTTOM -> Math.max(inset, displayH - boundingRect.top); 1418 case Gravity.LEFT -> Math.max(inset, boundingRect.right); 1419 case Gravity.RIGHT -> Math.max(inset, displayW - boundingRect.left); 1420 default -> throw new IllegalArgumentException("unknown gravity: " + gravity); 1421 }; 1422 } 1423 1424 /** 1425 * Helper class for passing {@link DisplayCutout} through binder. 1426 * 1427 * Needed, because {@code readFromParcel} cannot be used with immutable classes. 1428 * 1429 * @hide 1430 */ 1431 public static final class ParcelableWrapper implements Parcelable { 1432 1433 private DisplayCutout mInner; 1434 ParcelableWrapper()1435 public ParcelableWrapper() { 1436 this(NO_CUTOUT); 1437 } 1438 ParcelableWrapper(DisplayCutout cutout)1439 public ParcelableWrapper(DisplayCutout cutout) { 1440 mInner = cutout; 1441 } 1442 1443 @Override describeContents()1444 public int describeContents() { 1445 return 0; 1446 } 1447 1448 @Override writeToParcel(Parcel out, int flags)1449 public void writeToParcel(Parcel out, int flags) { 1450 writeCutoutToParcel(mInner, out, flags); 1451 } 1452 1453 /** 1454 * Writes a DisplayCutout to a {@link Parcel}. 1455 * 1456 * @see #readCutoutFromParcel(Parcel) 1457 */ writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags)1458 public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) { 1459 if (cutout == null) { 1460 out.writeInt(-1); 1461 } else if (cutout == NO_CUTOUT) { 1462 out.writeInt(0); 1463 } else { 1464 out.writeInt(1); 1465 out.writeTypedObject(cutout.mSafeInsets, flags); 1466 out.writeTypedArray(cutout.mBounds.getRects(), flags); 1467 out.writeTypedObject(cutout.mWaterfallInsets, flags); 1468 out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth()); 1469 out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight()); 1470 out.writeInt(cutout.mCutoutPathParserInfo.getPhysicalDisplayWidth()); 1471 out.writeInt(cutout.mCutoutPathParserInfo.getPhysicalDisplayHeight()); 1472 out.writeFloat(cutout.mCutoutPathParserInfo.getDensity()); 1473 out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec()); 1474 out.writeInt(cutout.mCutoutPathParserInfo.getRotation()); 1475 out.writeFloat(cutout.mCutoutPathParserInfo.getScale()); 1476 out.writeFloat(cutout.mCutoutPathParserInfo.getPhysicalPixelDisplaySizeRatio()); 1477 out.writeIntArray(cutout.mSideOverrides); 1478 } 1479 } 1480 1481 /** 1482 * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing 1483 * instance. 1484 * 1485 * Needed for AIDL out parameters. 1486 */ readFromParcel(Parcel in)1487 public void readFromParcel(Parcel in) { 1488 mInner = readCutoutFromParcel(in); 1489 } 1490 1491 public static final @android.annotation.NonNull Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { 1492 @Override 1493 public ParcelableWrapper createFromParcel(Parcel in) { 1494 return new ParcelableWrapper(readCutoutFromParcel(in)); 1495 } 1496 1497 @Override 1498 public ParcelableWrapper[] newArray(int size) { 1499 return new ParcelableWrapper[size]; 1500 } 1501 }; 1502 1503 /** 1504 * Reads a DisplayCutout from a {@link Parcel}. 1505 * 1506 * @see #writeCutoutToParcel(DisplayCutout, Parcel, int) 1507 */ readCutoutFromParcel(Parcel in)1508 public static DisplayCutout readCutoutFromParcel(Parcel in) { 1509 int variant = in.readInt(); 1510 if (variant == -1) { 1511 return null; 1512 } 1513 if (variant == 0) { 1514 return NO_CUTOUT; 1515 } 1516 1517 Rect safeInsets = in.readTypedObject(Rect.CREATOR); 1518 Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; 1519 in.readTypedArray(bounds, Rect.CREATOR); 1520 Insets waterfallInsets = in.readTypedObject(Insets.CREATOR); 1521 int displayWidth = in.readInt(); 1522 int displayHeight = in.readInt(); 1523 int physicalDisplayWidth = in.readInt(); 1524 int physicalDisplayHeight = in.readInt(); 1525 float density = in.readFloat(); 1526 String cutoutSpec = in.readString(); 1527 int rotation = in.readInt(); 1528 float scale = in.readFloat(); 1529 float physicalPixelDisplaySizeRatio = in.readFloat(); 1530 final CutoutPathParserInfo info = new CutoutPathParserInfo( 1531 displayWidth, displayHeight, physicalDisplayWidth, physicalDisplayHeight, 1532 density, cutoutSpec, rotation, scale, physicalPixelDisplaySizeRatio); 1533 final int[] sideOverrides = in.createIntArray(); 1534 1535 return new DisplayCutout(safeInsets, waterfallInsets, 1536 new Bounds(bounds, false /* copyArguments */), info, sideOverrides); 1537 } 1538 get()1539 public DisplayCutout get() { 1540 return mInner; 1541 } 1542 set(ParcelableWrapper cutout)1543 public void set(ParcelableWrapper cutout) { 1544 mInner = cutout.get(); 1545 } 1546 set(DisplayCutout cutout)1547 public void set(DisplayCutout cutout) { 1548 mInner = cutout; 1549 } 1550 scale(float scale)1551 public void scale(float scale) { 1552 final Rect safeInsets = mInner.getSafeInsets(); 1553 safeInsets.scale(scale); 1554 final Bounds bounds = new Bounds(mInner.mBounds.mRects, true); 1555 bounds.scale(scale); 1556 final Rect waterfallInsets = mInner.mWaterfallInsets.toRect(); 1557 waterfallInsets.scale(scale); 1558 final CutoutPathParserInfo info = new CutoutPathParserInfo( 1559 mInner.mCutoutPathParserInfo.getDisplayWidth(), 1560 mInner.mCutoutPathParserInfo.getDisplayHeight(), 1561 mInner.mCutoutPathParserInfo.getPhysicalDisplayWidth(), 1562 mInner.mCutoutPathParserInfo.getPhysicalDisplayHeight(), 1563 mInner.mCutoutPathParserInfo.getDensity(), 1564 mInner.mCutoutPathParserInfo.getCutoutSpec(), 1565 mInner.mCutoutPathParserInfo.getRotation(), 1566 scale, 1567 mInner.mCutoutPathParserInfo.getPhysicalPixelDisplaySizeRatio()); 1568 final int[] sideOverrides = mInner.mSideOverrides; 1569 1570 mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info, 1571 sideOverrides); 1572 } 1573 1574 @Override hashCode()1575 public int hashCode() { 1576 return mInner.hashCode(); 1577 } 1578 1579 @Override equals(@ullable Object o)1580 public boolean equals(@Nullable Object o) { 1581 return o instanceof ParcelableWrapper 1582 && mInner.equals(((ParcelableWrapper) o).mInner); 1583 } 1584 1585 @Override toString()1586 public String toString() { 1587 return String.valueOf(mInner); 1588 } 1589 } 1590 1591 /** 1592 * A Builder class to construct a DisplayCutout instance. 1593 * 1594 * <p>Note that this is only for tests purpose. For production code, developers should always 1595 * use a {@link DisplayCutout} obtained from the system.</p> 1596 */ 1597 public static final class Builder { 1598 private Insets mSafeInsets = Insets.NONE; 1599 private Insets mWaterfallInsets = Insets.NONE; 1600 private Path mCutoutPath; 1601 private final Rect mBoundingRectLeft = new Rect(); 1602 private final Rect mBoundingRectTop = new Rect(); 1603 private final Rect mBoundingRectRight = new Rect(); 1604 private final Rect mBoundingRectBottom = new Rect(); 1605 1606 /** 1607 * Begin building a DisplayCutout. 1608 */ Builder()1609 public Builder() { 1610 } 1611 1612 /** 1613 * Construct a new {@link DisplayCutout} with the set parameters. 1614 */ 1615 @NonNull build()1616 public DisplayCutout build() { 1617 final CutoutPathParserInfo info; 1618 if (mCutoutPath != null) { 1619 // Create a fake CutoutPathParserInfo and set it to sCachedCutoutPathParserInfo so 1620 // that when getCutoutPath() is called, it will return the cached Path. 1621 info = new CutoutPathParserInfo(0, 0, 0, 0, 0, "test", ROTATION_0, 1f, 1f); 1622 synchronized (CACHE_LOCK) { 1623 DisplayCutout.sCachedCutoutPathParserInfo = info; 1624 DisplayCutout.sCachedCutoutPath = mCutoutPath; 1625 } 1626 } else { 1627 info = null; 1628 } 1629 return new DisplayCutout(mSafeInsets.toRect(), mWaterfallInsets, mBoundingRectLeft, 1630 mBoundingRectTop, mBoundingRectRight, mBoundingRectBottom, info, false); 1631 } 1632 1633 /** 1634 * Set the safe insets. If not set, the default value is {@link Insets#NONE}. 1635 */ 1636 @SuppressWarnings("MissingGetterMatchingBuilder") 1637 @NonNull setSafeInsets(@onNull Insets safeInsets)1638 public Builder setSafeInsets(@NonNull Insets safeInsets) { 1639 mSafeInsets = safeInsets; 1640 return this; 1641 } 1642 1643 /** 1644 * Set the waterfall insets of the DisplayCutout. If not set, the default value is 1645 * {@link Insets#NONE} 1646 */ 1647 @NonNull setWaterfallInsets(@onNull Insets waterfallInsets)1648 public Builder setWaterfallInsets(@NonNull Insets waterfallInsets) { 1649 mWaterfallInsets = waterfallInsets; 1650 return this; 1651 } 1652 1653 /** 1654 * Set a bounding rectangle for a non-functional area on the display which is located on 1655 * the left of the screen. If not set, the default value is an empty rectangle. 1656 */ 1657 @NonNull setBoundingRectLeft(@onNull Rect boundingRectLeft)1658 public Builder setBoundingRectLeft(@NonNull Rect boundingRectLeft) { 1659 mBoundingRectLeft.set(boundingRectLeft); 1660 return this; 1661 } 1662 1663 /** 1664 * Set a bounding rectangle for a non-functional area on the display which is located on 1665 * the top of the screen. If not set, the default value is an empty rectangle. 1666 */ 1667 @NonNull setBoundingRectTop(@onNull Rect boundingRectTop)1668 public Builder setBoundingRectTop(@NonNull Rect boundingRectTop) { 1669 mBoundingRectTop.set(boundingRectTop); 1670 return this; 1671 } 1672 1673 /** 1674 * Set a bounding rectangle for a non-functional area on the display which is located on 1675 * the right of the screen. If not set, the default value is an empty rectangle. 1676 */ 1677 @NonNull setBoundingRectRight(@onNull Rect boundingRectRight)1678 public Builder setBoundingRectRight(@NonNull Rect boundingRectRight) { 1679 mBoundingRectRight.set(boundingRectRight); 1680 return this; 1681 } 1682 1683 /** 1684 * Set a bounding rectangle for a non-functional area on the display which is located on 1685 * the bottom of the screen. If not set, the default value is an empty rectangle. 1686 */ 1687 @NonNull setBoundingRectBottom(@onNull Rect boundingRectBottom)1688 public Builder setBoundingRectBottom(@NonNull Rect boundingRectBottom) { 1689 mBoundingRectBottom.set(boundingRectBottom); 1690 return this; 1691 } 1692 1693 /** 1694 * Set the cutout {@link Path}. 1695 * 1696 * Note that not support creating/testing multiple display cutouts with setCutoutPath() in 1697 * parallel. 1698 */ 1699 @NonNull setCutoutPath(@onNull Path cutoutPath)1700 public Builder setCutoutPath(@NonNull Path cutoutPath) { 1701 mCutoutPath = cutoutPath; 1702 return this; 1703 } 1704 } 1705 } 1706