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