• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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