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