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