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