• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.view.InsetsSourceProto.FRAME;
20 import static android.view.InsetsSourceProto.TYPE;
21 import static android.view.InsetsSourceProto.TYPE_NUMBER;
22 import static android.view.InsetsSourceProto.VISIBLE;
23 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
24 import static android.view.WindowInsets.Type.captionBar;
25 import static android.view.WindowInsets.Type.ime;
26 
27 import android.annotation.IntDef;
28 import android.annotation.IntRange;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.graphics.Insets;
32 import android.graphics.Rect;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.util.proto.ProtoOutputStream;
36 import android.view.WindowInsets.Type.InsetsType;
37 
38 import java.io.PrintWriter;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Objects;
44 import java.util.StringJoiner;
45 
46 /**
47  * Represents the state of a single entity generating insets for clients.
48  * @hide
49  */
50 public class InsetsSource implements Parcelable {
51 
52     @Retention(RetentionPolicy.SOURCE)
53     @IntDef(prefix = "SIDE_", value = {
54             SIDE_NONE,
55             SIDE_LEFT,
56             SIDE_TOP,
57             SIDE_RIGHT,
58             SIDE_BOTTOM,
59             SIDE_UNKNOWN
60     })
61     public @interface InternalInsetsSide {}
62 
63     static final int SIDE_NONE = 0;
64     static final int SIDE_LEFT = 1;
65     static final int SIDE_TOP = 2;
66     static final int SIDE_RIGHT = 3;
67     static final int SIDE_BOTTOM = 4;
68     static final int SIDE_UNKNOWN = 5;
69 
70     /** The insets source ID of IME */
71     public static final int ID_IME = createId(null, 0, ime());
72 
73     /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
74     public static final int ID_IME_CAPTION_BAR =
75             InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
76 
77     /**
78      * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
79      * draw a semi-transparent scrim behind the system bar area even when the bar contrast is
80      * enforced.
81      *
82      * @see android.R.styleable#Window_enforceStatusBarContrast
83      * @see android.R.styleable#Window_enforceNavigationBarContrast
84      */
85     public static final int FLAG_SUPPRESS_SCRIM = 1;
86 
87     /**
88      * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
89      * insets frame size when calculating the rounded corner insets to other windows.
90      *
91      * For example, task bar will draw fake rounded corners above itself, so we need to move the
92      * rounded corner up by the task bar insets size to make other windows see a rounded corner
93      * above the task bar.
94      */
95     public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
96 
97     /**
98      * Controls whether the insets provided by this source should be forcibly consumed.
99      */
100     public static final int FLAG_FORCE_CONSUMING = 1 << 2;
101 
102     /**
103      * Controls whether the insets source will play an animation when resizing.
104      */
105     public static final int FLAG_ANIMATE_RESIZING = 1 << 3;
106 
107     /**
108      * Controls whether the {@link WindowInsets.Type#captionBar()} insets provided by this source
109      * should always be forcibly consumed. Unlike with {@link #FLAG_FORCE_CONSUMING}, when this
110      * flag is used the caption bar will be consumed even when the bar is requested to be visible.
111      *
112      * Note: this flag does not take effect when the window applies
113      * {@link WindowInsetsController.Appearance#APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND}.
114      */
115     public static final int FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR = 1 << 4;
116 
117     @Retention(RetentionPolicy.SOURCE)
118     @IntDef(flag = true, prefix = "FLAG_", value = {
119             FLAG_SUPPRESS_SCRIM,
120             FLAG_INSETS_ROUNDED_CORNER,
121             FLAG_FORCE_CONSUMING,
122             FLAG_ANIMATE_RESIZING,
123             FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR
124     })
125     public @interface Flags {}
126 
127     /**
128      * Used when there are no bounding rects to describe an inset, which is only possible when the
129      * insets itself is {@link Insets#NONE}.
130      */
131     private static final Rect[] NO_BOUNDING_RECTS = new Rect[0];
132 
133     private @Flags int mFlags;
134 
135     /**
136      * An unique integer to identify this source across processes.
137      */
138     private final int mId;
139 
140     private final @InsetsType int mType;
141 
142     /** Frame of the source in screen coordinate space */
143     private final Rect mFrame;
144     private @Nullable Rect mVisibleFrame;
145     private @Nullable Rect[] mBoundingRects;
146 
147     private boolean mVisible;
148 
149     /**
150      * Used to decide which side of the relative frame should receive insets when the frame fully
151      * covers the relative frame.
152      */
153     private @InternalInsetsSide int mSideHint = SIDE_NONE;
154 
155     private final Rect mTmpFrame = new Rect();
156     private final Rect mTmpBoundingRect = new Rect();
157 
InsetsSource(int id, @InsetsType int type)158     public InsetsSource(int id, @InsetsType int type) {
159         mId = id;
160         mType = type;
161         mFrame = new Rect();
162         mVisible = (WindowInsets.Type.defaultVisible() & type) != 0;
163     }
164 
InsetsSource(InsetsSource other)165     public InsetsSource(InsetsSource other) {
166         mId = other.mId;
167         mType = other.mType;
168         mFrame = new Rect(other.mFrame);
169         mVisible = other.mVisible;
170         mVisibleFrame = other.mVisibleFrame != null
171                 ? new Rect(other.mVisibleFrame)
172                 : null;
173         mFlags = other.mFlags;
174         mSideHint = other.mSideHint;
175         mBoundingRects = other.mBoundingRects != null
176                 ? other.mBoundingRects.clone()
177                 : null;
178     }
179 
set(InsetsSource other)180     public void set(InsetsSource other) {
181         mFrame.set(other.mFrame);
182         mVisible = other.mVisible;
183         mVisibleFrame = other.mVisibleFrame != null
184                 ? new Rect(other.mVisibleFrame)
185                 : null;
186         mFlags = other.mFlags;
187         mSideHint = other.mSideHint;
188         mBoundingRects = other.mBoundingRects != null
189                 ? other.mBoundingRects.clone()
190                 : null;
191     }
192 
setFrame(int left, int top, int right, int bottom)193     public InsetsSource setFrame(int left, int top, int right, int bottom) {
194         mFrame.set(left, top, right, bottom);
195         return this;
196     }
197 
setFrame(Rect frame)198     public InsetsSource setFrame(Rect frame) {
199         mFrame.set(frame);
200         return this;
201     }
202 
setVisibleFrame(@ullable Rect visibleFrame)203     public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) {
204         mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
205         return this;
206     }
207 
setVisible(boolean visible)208     public InsetsSource setVisible(boolean visible) {
209         mVisible = visible;
210         return this;
211     }
212 
setFlags(@lags int flags)213     public InsetsSource setFlags(@Flags int flags) {
214         mFlags = flags;
215         return this;
216     }
217 
setFlags(@lags int flags, @Flags int mask)218     public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
219         mFlags = (mFlags & ~mask) | (flags & mask);
220         return this;
221     }
222 
223     /**
224      * Updates the side hint which is used to decide which side of the relative frame should receive
225      * insets when the frame fully covers the relative frame.
226      *
227      * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
228      */
updateSideHint(Rect bounds)229     public InsetsSource updateSideHint(Rect bounds) {
230         mSideHint = getInsetSide(
231                 calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
232         return this;
233     }
234 
235     /**
236      * Set the bounding rectangles of this source. They are expected to be relative to the source
237      * frame.
238      */
setBoundingRects(@ullable Rect[] rects)239     public InsetsSource setBoundingRects(@Nullable Rect[] rects) {
240         mBoundingRects = rects != null ? rects.clone() : null;
241         return this;
242     }
243 
getId()244     public int getId() {
245         return mId;
246     }
247 
getType()248     public @InsetsType int getType() {
249         return mType;
250     }
251 
getFrame()252     public Rect getFrame() {
253         return mFrame;
254     }
255 
getVisibleFrame()256     public @Nullable Rect getVisibleFrame() {
257         return mVisibleFrame;
258     }
259 
isVisible()260     public boolean isVisible() {
261         return mVisible;
262     }
263 
getFlags()264     public @Flags int getFlags() {
265         return mFlags;
266     }
267 
hasFlags(int flags)268     public boolean hasFlags(int flags) {
269         return (mFlags & flags) == flags;
270     }
271 
272     /**
273      * Returns the bounding rectangles of this source.
274      */
getBoundingRects()275     public @Nullable Rect[] getBoundingRects() {
276         return mBoundingRects;
277     }
278 
279     /**
280      * Calculates the insets this source will cause to a client window.
281      *
282      * @param relativeFrame The frame to calculate the insets relative to.
283      * @param ignoreVisibility If true, always reports back insets even if source isn't visible.
284      * @return The resulting insets. The contract is that only one side will be occupied by a
285      *         source.
286      */
calculateInsets(Rect relativeFrame, boolean ignoreVisibility)287     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
288         return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
289     }
290 
291     /**
292      * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
293      */
calculateVisibleInsets(Rect relativeFrame)294     public Insets calculateVisibleInsets(Rect relativeFrame) {
295         return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
296                 false /* ignoreVisibility */);
297     }
298 
calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility)299     private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
300         if (!ignoreVisibility && !mVisible) {
301             return Insets.NONE;
302         }
303         // During drag-move and drag-resizing, the caption insets position may not get updated
304         // before the app frame get updated. To layout the app content correctly during drag events,
305         // we always return the insets with the corresponding height covering the top.
306         // However, with the "fake" IME navigation bar treated as a caption bar, we return the
307         // insets with the corresponding height the bottom.
308         if (getType() == WindowInsets.Type.captionBar()) {
309             return getId() == ID_IME_CAPTION_BAR
310                     ? Insets.of(0, 0, 0, frame.height())
311                     : Insets.of(0, frame.height(), 0, 0);
312         }
313         // Checks for whether there is shared edge with insets for 0-width/height window.
314         final boolean hasIntersection = relativeFrame.isEmpty()
315                 ? getIntersection(frame, relativeFrame, mTmpFrame)
316                 : mTmpFrame.setIntersect(frame, relativeFrame);
317         if (!hasIntersection) {
318             return Insets.NONE;
319         }
320 
321         // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
322         // However, we should let the policy decide from the server.
323         if (getType() == WindowInsets.Type.ime()) {
324             return Insets.of(0, 0, 0, mTmpFrame.height());
325         }
326 
327         if (mTmpFrame.equals(relativeFrame)) {
328             // Covering all sides
329             switch (mSideHint) {
330                 default:
331                 case SIDE_LEFT:
332                     return Insets.of(mTmpFrame.width(), 0, 0, 0);
333                 case SIDE_TOP:
334                     return Insets.of(0, mTmpFrame.height(), 0, 0);
335                 case SIDE_RIGHT:
336                     return Insets.of(0, 0, mTmpFrame.width(), 0);
337                 case SIDE_BOTTOM:
338                     return Insets.of(0, 0, 0, mTmpFrame.height());
339             }
340         } else if (mTmpFrame.width() == relativeFrame.width()) {
341             // Intersecting at top/bottom
342             if (mTmpFrame.top == relativeFrame.top) {
343                 return Insets.of(0, mTmpFrame.height(), 0, 0);
344             } else if (mTmpFrame.bottom == relativeFrame.bottom) {
345                 return Insets.of(0, 0, 0, mTmpFrame.height());
346             }
347             // TODO: remove when insets are shell-customizable.
348             // This is a hack that says "if this is a top-inset (eg statusbar), always apply it
349             // to the top". It is used when adjusting primary split for IME.
350             if (mTmpFrame.top == 0) {
351                 return Insets.of(0, mTmpFrame.height(), 0, 0);
352             }
353         } else if (mTmpFrame.height() == relativeFrame.height()) {
354             // Intersecting at left/right
355             if (mTmpFrame.left == relativeFrame.left) {
356                 return Insets.of(mTmpFrame.width(), 0, 0, 0);
357             } else if (mTmpFrame.right == relativeFrame.right) {
358                 return Insets.of(0, 0, mTmpFrame.width(), 0);
359             }
360         } else {
361             // The source doesn't cover the width or the height of relativeFrame, but just parts of
362             // them. Here uses mSideHint to decide which side should be inset.
363             switch (mSideHint) {
364                 case SIDE_LEFT:
365                     if (mTmpFrame.left == relativeFrame.left) {
366                         return Insets.of(mTmpFrame.width(), 0, 0, 0);
367                     }
368                     break;
369                 case SIDE_TOP:
370                     if (mTmpFrame.top == relativeFrame.top) {
371                         return Insets.of(0, mTmpFrame.height(), 0, 0);
372                     }
373                     break;
374                 case SIDE_RIGHT:
375                     if (mTmpFrame.right == relativeFrame.right) {
376                         return Insets.of(0, 0, mTmpFrame.width(), 0);
377                     }
378                     break;
379                 case SIDE_BOTTOM:
380                     if (mTmpFrame.bottom == relativeFrame.bottom) {
381                         return Insets.of(0, 0, 0, mTmpFrame.height());
382                     }
383                     break;
384             }
385         }
386         return Insets.NONE;
387     }
388 
389     /**
390      * Calculates the bounding rects the source will cause to a client window.
391      */
calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility)392     public @NonNull Rect[] calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility) {
393         if (!ignoreVisibility && !mVisible) {
394             return NO_BOUNDING_RECTS;
395         }
396 
397         final Rect frame = getFrame();
398         if (mBoundingRects == null) {
399             // No bounding rects set, make a single bounding rect that covers the intersection of
400             // the |frame| and the |relativeFrame|. Also make it relative to the window origin.
401             return mTmpBoundingRect.setIntersect(frame, relativeFrame)
402                     ? new Rect[]{
403                             new Rect(
404                                     mTmpBoundingRect.left - relativeFrame.left,
405                                     mTmpBoundingRect.top - relativeFrame.top,
406                                     mTmpBoundingRect.right - relativeFrame.left,
407                                     mTmpBoundingRect.bottom - relativeFrame.top
408                             )
409                     }
410                     : NO_BOUNDING_RECTS;
411         }
412 
413         // Special treatment for captionBar inset type. During drag-resizing, the |frame| and
414         // |boundingRects| may not get updated as quickly as |relativeFrame|, so just assume the
415         // |frame| will always be either at the top or bottom of |relativeFrame|. This means some
416         // calculations to make |boundingRects| relative to |relativeFrame| can be skipped or
417         // simplified.
418         // TODO(b/254128050): remove special treatment.
419         if (getType() == WindowInsets.Type.captionBar()) {
420             final ArrayList<Rect> validBoundingRects = new ArrayList<>();
421             for (final Rect boundingRect : mBoundingRects) {
422                 // Assume that the caption |frame| and |relativeFrame| perfectly align at the top
423                 // or bottom, meaning that the provided |boundingRect|, which is relative to the
424                 // |frame| either is already relative to |relativeFrame| (for top captionBar()), or
425                 // just needs to be made relative to |relativeFrame| for bottom bars.
426                 final int frameHeight = frame.height();
427                 mTmpBoundingRect.set(boundingRect);
428                 if (getId() == ID_IME_CAPTION_BAR) {
429                     mTmpBoundingRect.offset(0, relativeFrame.height() - frameHeight);
430                 }
431                 validBoundingRects.add(new Rect(mTmpBoundingRect));
432             }
433             return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
434         }
435 
436         // Regular treatment for non-captionBar inset types.
437         final ArrayList<Rect> validBoundingRects = new ArrayList<>();
438         for (final Rect boundingRect : mBoundingRects) {
439             // |boundingRect| was provided relative to |frame|. Make it absolute to be in the same
440             // coordinate system as |frame|.
441             final Rect absBoundingRect = new Rect(
442                     boundingRect.left + frame.left,
443                     boundingRect.top + frame.top,
444                     boundingRect.right + frame.left,
445                     boundingRect.bottom + frame.top
446             );
447             // Now find the intersection of that |absBoundingRect| with |relativeFrame|. In other
448             // words, whichever part of the bounding rect is inside the window frame.
449             if (!mTmpBoundingRect.setIntersect(absBoundingRect, relativeFrame)) {
450                 // It's possible for this to be empty if the frame and bounding rects were larger
451                 // than the |relativeFrame|, such as when a system window is wider than the app
452                 // window width. Just ignore that rect since it will have no effect on the
453                 // window insets.
454                 continue;
455             }
456             // At this point, |mTmpBoundingRect| is a valid bounding rect located fully inside the
457             // window, convert it to be relative to the window so that apps don't need to know the
458             // location of the window to understand bounding rects.
459             validBoundingRects.add(new Rect(
460                     mTmpBoundingRect.left - relativeFrame.left,
461                     mTmpBoundingRect.top - relativeFrame.top,
462                     mTmpBoundingRect.right - relativeFrame.left,
463                     mTmpBoundingRect.bottom - relativeFrame.top));
464         }
465         if (validBoundingRects.isEmpty()) {
466             return NO_BOUNDING_RECTS;
467         }
468         return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
469     }
470 
471     /**
472      * Outputs the intersection of two rectangles. The shared edges will also be counted in the
473      * intersection.
474      *
475      * @param a The first rectangle being intersected with.
476      * @param b The second rectangle being intersected with.
477      * @param out The rectangle which represents the intersection.
478      * @return {@code true} if there is any intersection.
479      */
getIntersection(@onNull Rect a, @NonNull Rect b, @NonNull Rect out)480     private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) {
481         if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) {
482             out.left = Math.max(a.left, b.left);
483             out.top = Math.max(a.top, b.top);
484             out.right = Math.min(a.right, b.right);
485             out.bottom = Math.min(a.bottom, b.bottom);
486             return true;
487         }
488         out.setEmpty();
489         return false;
490     }
491 
492     /**
493      * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
494      * is set in order that this method returns a meaningful result.
495      */
getInsetSide(Insets insets)496     static @InternalInsetsSide int getInsetSide(Insets insets) {
497         if (Insets.NONE.equals(insets)) {
498             return SIDE_NONE;
499         }
500         if (insets.left != 0) {
501             return SIDE_LEFT;
502         }
503         if (insets.top != 0) {
504             return SIDE_TOP;
505         }
506         if (insets.right != 0) {
507             return SIDE_RIGHT;
508         }
509         if (insets.bottom != 0) {
510             return SIDE_BOTTOM;
511         }
512         return SIDE_UNKNOWN;
513     }
514 
sideToString(@nternalInsetsSide int side)515     static String sideToString(@InternalInsetsSide int side) {
516         switch (side) {
517             case SIDE_NONE:
518                 return "NONE";
519             case SIDE_LEFT:
520                 return "LEFT";
521             case SIDE_TOP:
522                 return "TOP";
523             case SIDE_RIGHT:
524                 return "RIGHT";
525             case SIDE_BOTTOM:
526                 return "BOTTOM";
527             default:
528                 return "UNKNOWN:" + side;
529         }
530     }
531 
532     /**
533      * Creates an identifier of an {@link InsetsSource}.
534      *
535      * @param owner An object owned by the owner. Only the owner can modify its own sources.
536      * @param index An owner may have multiple sources with the same type. For example, the system
537      *              server might have multiple display cutout sources. This is used to identify
538      *              which one is which. The value must be in a range of [0, 2047].
539      * @param type The {@link InsetsType type} of the source.
540      * @return a unique integer as the identifier.
541      */
createId(Object owner, @IntRange(from = 0, to = 2047) int index, @InsetsType int type)542     public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
543             @InsetsType int type) {
544         if (index < 0 || index >= 2048) {
545             throw new IllegalArgumentException();
546         }
547         // owner takes top 16 bits;
548         // index takes 11 bits since the 6th bit;
549         // type takes bottom 5 bits.
550         return ((System.identityHashCode(owner) % (1 << 16)) << 16)
551                 + (index << 5)
552                 + WindowInsets.Type.indexOf(type);
553     }
554 
555     /**
556      * Gets the index from the ID.
557      *
558      * @see #createId(Object, int, int)
559      */
getIndex(int id)560     public static int getIndex(int id) {
561         //   start: ????????????????***********?????
562         // & 65535: 0000000000000000***********?????
563         //    >> 5: 000000000000000000000***********
564         return (id & 65535) >> 5;
565     }
566 
567     /**
568      * Gets the {@link InsetsType} from the ID.
569      *
570      * @see #createId(Object, int, int)
571      * @see WindowInsets.Type#indexOf(int)
572      */
getType(int id)573     public static int getType(int id) {
574         // start: ???????????????????????????*****
575         //  & 31: 000000000000000000000000000*****
576         //  1 <<: See WindowInsets.Type#indexOf
577         return 1 << (id & 31);
578     }
579 
flagsToString(@lags int flags)580     public static String flagsToString(@Flags int flags) {
581         final StringJoiner joiner = new StringJoiner("|");
582         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
583             joiner.add("SUPPRESS_SCRIM");
584         }
585         if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
586             joiner.add("INSETS_ROUNDED_CORNER");
587         }
588         if ((flags & FLAG_FORCE_CONSUMING) != 0) {
589             joiner.add("FORCE_CONSUMING");
590         }
591         if ((flags & FLAG_ANIMATE_RESIZING) != 0) {
592             joiner.add("ANIMATE_RESIZING");
593         }
594         if ((flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
595             joiner.add("FORCE_CONSUMING_OPAQUE_CAPTION_BAR");
596         }
597         return joiner.toString();
598     }
599 
600     /**
601      * Export the state of {@link InsetsSource} into a protocol buffer output stream.
602      *
603      * @param proto   Stream to write the state to
604      * @param fieldId FieldId of InsetsSource as defined in the parent message
605      */
dumpDebug(ProtoOutputStream proto, long fieldId)606     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
607         final long token = proto.start(fieldId);
608         if (!android.os.Flags.androidOsBuildVanillaIceCream()) {
609             // Deprecated since V.
610             proto.write(TYPE, WindowInsets.Type.toString(mType));
611         }
612         mFrame.dumpDebug(proto, FRAME);
613         if (mVisibleFrame != null) {
614             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
615         }
616         proto.write(VISIBLE, mVisible);
617         proto.write(TYPE_NUMBER, mType);
618         proto.end(token);
619     }
620 
dump(String prefix, PrintWriter pw)621     public void dump(String prefix, PrintWriter pw) {
622         pw.print(prefix);
623         pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId));
624         pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
625         pw.print(" frame="); pw.print(mFrame.toShortString());
626         if (mVisibleFrame != null) {
627             pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString());
628         }
629         pw.print(" visible="); pw.print(mVisible);
630         pw.print(" flags="); pw.print(flagsToString(mFlags));
631         pw.print(" sideHint="); pw.print(sideToString(mSideHint));
632         pw.print(" boundingRects="); pw.print(Arrays.toString(mBoundingRects));
633         pw.println();
634     }
635 
636     @Override
equals(@ullable Object o)637     public boolean equals(@Nullable Object o) {
638         return equals(o, false);
639     }
640 
641     /**
642      * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
643      *                                  when IME is not visible.
644      */
equals(@ullable Object o, boolean excludeInvisibleImeFrames)645     public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) {
646         if (this == o) return true;
647         if (o == null || getClass() != o.getClass()) return false;
648 
649         InsetsSource that = (InsetsSource) o;
650 
651         if (mId != that.mId) return false;
652         if (mType != that.mType) return false;
653         if (mVisible != that.mVisible) return false;
654         if (mFlags != that.mFlags) return false;
655         if (mSideHint != that.mSideHint) return false;
656         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
657         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
658         if (!mFrame.equals(that.mFrame)) return false;
659         return Arrays.equals(mBoundingRects, that.mBoundingRects);
660     }
661 
662     @Override
hashCode()663     public int hashCode() {
664         return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint,
665                 Arrays.hashCode(mBoundingRects));
666     }
667 
InsetsSource(Parcel in)668     public InsetsSource(Parcel in) {
669         mId = in.readInt();
670         mType = in.readInt();
671         mFrame = Rect.CREATOR.createFromParcel(in);
672         if (in.readInt() != 0) {
673             mVisibleFrame = Rect.CREATOR.createFromParcel(in);
674         } else {
675             mVisibleFrame = null;
676         }
677         mVisible = in.readBoolean();
678         mFlags = in.readInt();
679         mSideHint = in.readInt();
680         mBoundingRects = in.createTypedArray(Rect.CREATOR);
681     }
682 
683     @Override
describeContents()684     public int describeContents() {
685         return 0;
686     }
687 
688     @Override
writeToParcel(Parcel dest, int flags)689     public void writeToParcel(Parcel dest, int flags) {
690         dest.writeInt(mId);
691         dest.writeInt(mType);
692         mFrame.writeToParcel(dest, 0);
693         if (mVisibleFrame != null) {
694             dest.writeInt(1);
695             mVisibleFrame.writeToParcel(dest, 0);
696         } else {
697             dest.writeInt(0);
698         }
699         dest.writeBoolean(mVisible);
700         dest.writeInt(mFlags);
701         dest.writeInt(mSideHint);
702         dest.writeTypedArray(mBoundingRects, flags);
703     }
704 
705     @Override
toString()706     public String toString() {
707         return "InsetsSource: {" + Integer.toHexString(mId)
708                 + " mType=" + WindowInsets.Type.toString(mType)
709                 + " mFrame=" + mFrame.toShortString()
710                 + " mVisible=" + mVisible
711                 + " mFlags=" + flagsToString(mFlags)
712                 + " mSideHint=" + sideToString(mSideHint)
713                 + " mBoundingRects=" + Arrays.toString(mBoundingRects)
714                 + "}";
715     }
716 
717     public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() {
718 
719         public InsetsSource createFromParcel(Parcel in) {
720             return new InsetsSource(in);
721         }
722 
723         public InsetsSource[] newArray(int size) {
724             return new InsetsSource[size];
725         }
726     };
727 }
728