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