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.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
20 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
21 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
22 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
23 import static android.view.ViewRootImpl.sNewInsetsMode;
24 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
25 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
26 import static android.view.WindowInsets.Type.displayCutout;
27 import static android.view.WindowInsets.Type.ime;
28 import static android.view.WindowInsets.Type.indexOf;
29 import static android.view.WindowInsets.Type.isVisibleInsetsType;
30 import static android.view.WindowInsets.Type.statusBars;
31 import static android.view.WindowInsets.Type.systemBars;
32 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
33 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
34 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
35 
36 import android.annotation.IntDef;
37 import android.annotation.Nullable;
38 import android.graphics.Insets;
39 import android.graphics.Rect;
40 import android.os.Parcel;
41 import android.os.Parcelable;
42 import android.util.ArraySet;
43 import android.util.SparseIntArray;
44 import android.view.WindowInsets.Type;
45 import android.view.WindowInsets.Type.InsetsType;
46 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.io.PrintWriter;
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.Arrays;
54 import java.util.Objects;
55 import java.util.StringJoiner;
56 
57 /**
58  * Holder for state of system windows that cause window insets for all other windows in the system.
59  * @hide
60  */
61 public class InsetsState implements Parcelable {
62 
63     /**
64      * Internal representation of inset source types. This is different from the public API in
65      * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
66      * at the same time.
67      */
68     @Retention(RetentionPolicy.SOURCE)
69     @IntDef(prefix = "ITYPE", value = {
70             ITYPE_STATUS_BAR,
71             ITYPE_NAVIGATION_BAR,
72             ITYPE_CAPTION_BAR,
73             ITYPE_TOP_GESTURES,
74             ITYPE_BOTTOM_GESTURES,
75             ITYPE_LEFT_GESTURES,
76             ITYPE_RIGHT_GESTURES,
77             ITYPE_TOP_TAPPABLE_ELEMENT,
78             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
79             ITYPE_LEFT_DISPLAY_CUTOUT,
80             ITYPE_TOP_DISPLAY_CUTOUT,
81             ITYPE_RIGHT_DISPLAY_CUTOUT,
82             ITYPE_BOTTOM_DISPLAY_CUTOUT,
83             ITYPE_IME,
84             ITYPE_CLIMATE_BAR,
85             ITYPE_EXTRA_NAVIGATION_BAR
86     })
87     public @interface InternalInsetsType {}
88 
89     /**
90      * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate
91      * that the objects/parameters aren't associated with an {@link InternalInsetsType}
92      */
93     public static final int ITYPE_INVALID = -1;
94 
95     static final int FIRST_TYPE = 0;
96 
97     public static final int ITYPE_STATUS_BAR = FIRST_TYPE;
98     public static final int ITYPE_NAVIGATION_BAR = 1;
99     public static final int ITYPE_CAPTION_BAR = 2;
100 
101     public static final int ITYPE_TOP_GESTURES = 3;
102     public static final int ITYPE_BOTTOM_GESTURES = 4;
103     public static final int ITYPE_LEFT_GESTURES = 5;
104     public static final int ITYPE_RIGHT_GESTURES = 6;
105     public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7;
106     public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8;
107 
108     public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9;
109     public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10;
110     public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11;
111     public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12;
112 
113     /** Input method window. */
114     public static final int ITYPE_IME = 13;
115 
116     /** Additional system decorations inset type. */
117     public static final int ITYPE_CLIMATE_BAR = 14;
118     public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15;
119 
120     static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
121     public static final int SIZE = LAST_TYPE + 1;
122 
123     // Derived types
124 
125     /** A shelf is the same as the navigation bar. */
126     public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR;
127 
128     @Retention(RetentionPolicy.SOURCE)
129     @IntDef(prefix = "IINSETS_SIDE", value = {
130             ISIDE_LEFT,
131             ISIDE_TOP,
132             ISIDE_RIGHT,
133             ISIDE_BOTTOM,
134             ISIDE_FLOATING,
135             ISIDE_UNKNOWN
136     })
137     public @interface InternalInsetsSide {}
138     static final int ISIDE_LEFT = 0;
139     static final int ISIDE_TOP = 1;
140     static final int ISIDE_RIGHT = 2;
141     static final int ISIDE_BOTTOM = 3;
142     static final int ISIDE_FLOATING = 4;
143     static final int ISIDE_UNKNOWN = 5;
144 
145     private InsetsSource[] mSources = new InsetsSource[SIZE];
146 
147     /**
148      * The frame of the display these sources are relative to.
149      */
150     private final Rect mDisplayFrame = new Rect();
151 
152     public InsetsState() {
153     }
154 
155     public InsetsState(InsetsState copy) {
156         set(copy);
157     }
158 
159     public InsetsState(InsetsState copy, boolean copySources) {
160         set(copy, copySources);
161     }
162 
163     /**
164      * Calculates {@link WindowInsets} based on the current source configuration.
165      *
166      * @param frame The frame to calculate the insets relative to.
167      * @param ignoringVisibilityState {@link InsetsState} used to calculate
168      *        {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass
169      *        {@code null} to use this state to calculate that information.
170      * @return The calculated insets.
171      */
172     public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
173             boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout,
174             int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
175             @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
176         Insets[] typeInsetsMap = new Insets[Type.SIZE];
177         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
178         boolean[] typeVisibilityMap = new boolean[SIZE];
179         final Rect relativeFrame = new Rect(frame);
180         final Rect relativeFrameMax = new Rect(frame);
181         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
182             InsetsSource source = mSources[type];
183             if (source == null) {
184                 int index = indexOf(toPublicType(type));
185                 if (typeInsetsMap[index] == null) {
186                     typeInsetsMap[index] = Insets.NONE;
187                 }
188                 continue;
189             }
190 
191             boolean skipNonImeInImeMode = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
192                     && source.getType() != ITYPE_IME;
193             boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
194                     && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR);
195             boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
196                     && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR
197                             || type == ITYPE_IME);
198             if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) {
199                 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
200                 continue;
201             }
202 
203             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
204                     typeSideMap, typeVisibilityMap);
205 
206             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
207             // target.
208             if (source.getType() != ITYPE_IME) {
209                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
210                         ? ignoringVisibilityState.getSource(type)
211                         : source;
212                 if (ignoringVisibilitySource == null) {
213                     continue;
214                 }
215                 processSource(ignoringVisibilitySource, relativeFrameMax,
216                         true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */,
217                         null /* typeVisibilityMap */);
218             }
219         }
220         final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
221 
222         @InsetsType int compatInsetsTypes = systemBars() | displayCutout();
223         if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) {
224             compatInsetsTypes |= ime();
225         }
226         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
227             compatInsetsTypes &= ~statusBars();
228         }
229 
230         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
231                 alwaysConsumeSystemBars, cutout, compatInsetsTypes,
232                 sNewInsetsMode == NEW_INSETS_MODE_FULL
233                         && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
234     }
235 
236     public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
237         Insets insets = Insets.NONE;
238         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
239             InsetsSource source = mSources[type];
240             if (source == null) {
241                 continue;
242             }
243             if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
244                 continue;
245             }
246 
247             // Ignore everything that's not a system bar or IME.
248             int publicType = InsetsState.toPublicType(type);
249             if (!isVisibleInsetsType(publicType, softInputMode)) {
250                 continue;
251             }
252             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
253         }
254         return insets.toRect();
255     }
256 
257     /**
258      * Calculate which insets *cannot* be controlled, because the frame does not cover the
259      * respective side of the inset.
260      *
261      * If the frame of our window doesn't cover the entire inset, the control API makes very
262      * little sense, as we don't deal with negative insets.
263      */
264     @InsetsType
265     public int calculateUncontrollableInsetsFromFrame(Rect frame) {
266         int blocked = 0;
267         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
268             InsetsSource source = mSources[type];
269             if (source == null) {
270                 continue;
271             }
272             if (!canControlSide(frame, getInsetSide(
273                     source.calculateInsets(frame, true /* ignoreVisibility */)))) {
274                 blocked |= toPublicType(type);
275             }
276         }
277         return blocked;
278     }
279 
280     private boolean canControlSide(Rect frame, int side) {
281         switch (side) {
282             case ISIDE_LEFT:
283             case ISIDE_RIGHT:
284                 return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right;
285             case ISIDE_TOP:
286             case ISIDE_BOTTOM:
287                 return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom;
288             case ISIDE_FLOATING:
289                 return true;
290             default:
291                 return false;
292         }
293     }
294 
295     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
296             Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
297             @Nullable boolean[] typeVisibilityMap) {
298         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
299 
300         int type = toPublicType(source.getType());
301         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
302                 insets, type);
303 
304         if (type == MANDATORY_SYSTEM_GESTURES) {
305             // Mandatory system gestures are also system gestures.
306             // TODO: find a way to express this more generally. One option would be to define
307             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
308             //       ability to set systemGestureInsets() independently from
309             //       mandatorySystemGestureInsets() in the Builder.
310             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
311                     insets, SYSTEM_GESTURES);
312         }
313     }
314 
315     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
316             @InternalInsetsSide @Nullable SparseIntArray typeSideMap,
317             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
318         int index = indexOf(type);
319         Insets existing = typeInsetsMap[index];
320         if (existing == null) {
321             typeInsetsMap[index] = insets;
322         } else {
323             typeInsetsMap[index] = Insets.max(existing, insets);
324         }
325 
326         if (typeVisibilityMap != null) {
327             typeVisibilityMap[index] = source.isVisible();
328         }
329 
330         if (typeSideMap != null) {
331             @InternalInsetsSide int insetSide = getInsetSide(insets);
332             if (insetSide != ISIDE_UNKNOWN) {
333                 typeSideMap.put(source.getType(), insetSide);
334             }
335         }
336     }
337 
338     /**
339      * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
340      * is set in order that this method returns a meaningful result.
341      */
342     private @InternalInsetsSide int getInsetSide(Insets insets) {
343         if (Insets.NONE.equals(insets)) {
344             return ISIDE_FLOATING;
345         }
346         if (insets.left != 0) {
347             return ISIDE_LEFT;
348         }
349         if (insets.top != 0) {
350             return ISIDE_TOP;
351         }
352         if (insets.right != 0) {
353             return ISIDE_RIGHT;
354         }
355         if (insets.bottom != 0) {
356             return ISIDE_BOTTOM;
357         }
358         return ISIDE_UNKNOWN;
359     }
360 
361     public InsetsSource getSource(@InternalInsetsType int type) {
362         InsetsSource source = mSources[type];
363         if (source != null) {
364             return source;
365         }
366         source = new InsetsSource(type);
367         mSources[type] = source;
368         return source;
369     }
370 
371     public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
372         return mSources[type];
373     }
374 
375     public boolean hasSources() {
376         for (int i = 0; i < SIZE; i++) {
377             if (mSources[i] != null) {
378                 return true;
379             }
380         }
381         return false;
382     }
383 
384     /**
385      * Returns the source visibility or the default visibility if the source doesn't exist. This is
386      * useful if when treating this object as a request.
387      *
388      * @param type The {@link InternalInsetsType} to query.
389      * @return {@code true} if the source is visible or the type is default visible and the source
390      *         doesn't exist.
391      */
392     public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
393         final InsetsSource source = mSources[type];
394         return source != null ? source.isVisible() : getDefaultVisibility(type);
395     }
396 
397     public void setDisplayFrame(Rect frame) {
398         mDisplayFrame.set(frame);
399     }
400 
401     public Rect getDisplayFrame() {
402         return mDisplayFrame;
403     }
404 
405     /**
406      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
407      * to the client.
408      *
409      * @param type The {@link InternalInsetsType} of the source to remove
410      */
411     public void removeSource(@InternalInsetsType int type) {
412         mSources[type] = null;
413     }
414 
415     /**
416      * A shortcut for setting the visibility of the source.
417      *
418      * @param type The {@link InternalInsetsType} of the source to set the visibility
419      * @param visible {@code true} for visible
420      */
421     public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
422         InsetsSource source = mSources[type];
423         if (source != null) {
424             source.setVisible(visible);
425         }
426     }
427 
428     public void set(InsetsState other) {
429         set(other, false /* copySources */);
430     }
431 
432     public void set(InsetsState other, boolean copySources) {
433         mDisplayFrame.set(other.mDisplayFrame);
434         if (copySources) {
435             for (int i = 0; i < SIZE; i++) {
436                 InsetsSource source = other.mSources[i];
437                 mSources[i] = source != null ? new InsetsSource(source) : null;
438             }
439         } else {
440             for (int i = 0; i < SIZE; i++) {
441                 mSources[i] = other.mSources[i];
442             }
443         }
444     }
445 
446     public void addSource(InsetsSource source) {
447         mSources[source.getType()] = source;
448     }
449 
450     public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
451         final ArraySet<Integer> result = new ArraySet<>();
452         if ((types & Type.STATUS_BARS) != 0) {
453             result.add(ITYPE_STATUS_BAR);
454         }
455         if ((types & Type.NAVIGATION_BARS) != 0) {
456             result.add(ITYPE_NAVIGATION_BAR);
457         }
458         if ((types & Type.CAPTION_BAR) != 0) {
459             result.add(ITYPE_CAPTION_BAR);
460         }
461         if ((types & Type.DISPLAY_CUTOUT) != 0) {
462             result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
463             result.add(ITYPE_TOP_DISPLAY_CUTOUT);
464             result.add(ITYPE_RIGHT_DISPLAY_CUTOUT);
465             result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT);
466         }
467         if ((types & Type.IME) != 0) {
468             result.add(ITYPE_IME);
469         }
470         return result;
471     }
472 
473     /**
474      * Converting a internal type to the public type.
475      * @param type internal insets type, {@code InternalInsetsType}.
476      * @return public insets type, {@code Type.InsetsType}.
477      */
478     public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
479         switch (type) {
480             case ITYPE_STATUS_BAR:
481             case ITYPE_CLIMATE_BAR:
482                 return Type.STATUS_BARS;
483             case ITYPE_NAVIGATION_BAR:
484             case ITYPE_EXTRA_NAVIGATION_BAR:
485                 return Type.NAVIGATION_BARS;
486             case ITYPE_CAPTION_BAR:
487                 return Type.CAPTION_BAR;
488             case ITYPE_IME:
489                 return Type.IME;
490             case ITYPE_TOP_GESTURES:
491             case ITYPE_BOTTOM_GESTURES:
492                 return Type.MANDATORY_SYSTEM_GESTURES;
493             case ITYPE_LEFT_GESTURES:
494             case ITYPE_RIGHT_GESTURES:
495                 return Type.SYSTEM_GESTURES;
496             case ITYPE_TOP_TAPPABLE_ELEMENT:
497             case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
498                 return Type.TAPPABLE_ELEMENT;
499             case ITYPE_LEFT_DISPLAY_CUTOUT:
500             case ITYPE_TOP_DISPLAY_CUTOUT:
501             case ITYPE_RIGHT_DISPLAY_CUTOUT:
502             case ITYPE_BOTTOM_DISPLAY_CUTOUT:
503                 return Type.DISPLAY_CUTOUT;
504             default:
505                 throw new IllegalArgumentException("Unknown type: " + type);
506         }
507     }
508 
509     public static boolean getDefaultVisibility(@InternalInsetsType int type) {
510         return type != ITYPE_IME;
511     }
512 
513     public static boolean containsType(@InternalInsetsType int[] types,
514             @InternalInsetsType int type) {
515         if (types == null) {
516             return false;
517         }
518         for (int t : types) {
519             if (t == type) {
520                 return true;
521             }
522         }
523         return false;
524     }
525 
526     public void dump(String prefix, PrintWriter pw) {
527         pw.println(prefix + "InsetsState");
528         for (int i = 0; i < SIZE; i++) {
529             InsetsSource source = mSources[i];
530             if (source == null) continue;
531             source.dump(prefix + "  ", pw);
532         }
533     }
534 
535     public static String typeToString(@InternalInsetsType int type) {
536         switch (type) {
537             case ITYPE_STATUS_BAR:
538                 return "ITYPE_STATUS_BAR";
539             case ITYPE_NAVIGATION_BAR:
540                 return "ITYPE_NAVIGATION_BAR";
541             case ITYPE_CAPTION_BAR:
542                 return "ITYPE_CAPTION_BAR";
543             case ITYPE_TOP_GESTURES:
544                 return "ITYPE_TOP_GESTURES";
545             case ITYPE_BOTTOM_GESTURES:
546                 return "ITYPE_BOTTOM_GESTURES";
547             case ITYPE_LEFT_GESTURES:
548                 return "ITYPE_LEFT_GESTURES";
549             case ITYPE_RIGHT_GESTURES:
550                 return "ITYPE_RIGHT_GESTURES";
551             case ITYPE_TOP_TAPPABLE_ELEMENT:
552                 return "ITYPE_TOP_TAPPABLE_ELEMENT";
553             case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
554                 return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
555             case ITYPE_LEFT_DISPLAY_CUTOUT:
556                 return "ITYPE_LEFT_DISPLAY_CUTOUT";
557             case ITYPE_TOP_DISPLAY_CUTOUT:
558                 return "ITYPE_TOP_DISPLAY_CUTOUT";
559             case ITYPE_RIGHT_DISPLAY_CUTOUT:
560                 return "ITYPE_RIGHT_DISPLAY_CUTOUT";
561             case ITYPE_BOTTOM_DISPLAY_CUTOUT:
562                 return "ITYPE_BOTTOM_DISPLAY_CUTOUT";
563             case ITYPE_IME:
564                 return "ITYPE_IME";
565             case ITYPE_CLIMATE_BAR:
566                 return "ITYPE_CLIMATE_BAR";
567             case ITYPE_EXTRA_NAVIGATION_BAR:
568                 return "ITYPE_EXTRA_NAVIGATION_BAR";
569             default:
570                 return "ITYPE_UNKNOWN_" + type;
571         }
572     }
573 
574     @Override
575     public boolean equals(Object o) {
576         return equals(o, false, false);
577     }
578 
579     /**
580      * An equals method can exclude the caption insets. This is useful because we assemble the
581      * caption insets information on the client side, and when we communicate with server, it's
582      * excluded.
583      * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
584      *                                           ignore the caption insets source value.
585      * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
586      *                                  not visible.
587      * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
588      */
589     @VisibleForTesting
590     public boolean equals(Object o, boolean excludingCaptionInsets,
591             boolean excludeInvisibleImeFrames) {
592         if (this == o) { return true; }
593         if (o == null || getClass() != o.getClass()) { return false; }
594 
595         InsetsState state = (InsetsState) o;
596 
597         if (!mDisplayFrame.equals(state.mDisplayFrame)) {
598             return false;
599         }
600         for (int i = 0; i < SIZE; i++) {
601             if (excludingCaptionInsets) {
602                 if (i == ITYPE_CAPTION_BAR) continue;
603             }
604             InsetsSource source = mSources[i];
605             InsetsSource otherSource = state.mSources[i];
606             if (source == null && otherSource == null) {
607                 continue;
608             }
609             if (source != null && otherSource == null || source == null && otherSource != null) {
610                 return false;
611             }
612             if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
613                 return false;
614             }
615         }
616         return true;
617     }
618 
619     @Override
620     public int hashCode() {
621         return Objects.hash(mDisplayFrame, Arrays.hashCode(mSources));
622     }
623 
624     public InsetsState(Parcel in) {
625         readFromParcel(in);
626     }
627 
628     @Override
629     public int describeContents() {
630         return 0;
631     }
632 
633     @Override
634     public void writeToParcel(Parcel dest, int flags) {
635         dest.writeParcelable(mDisplayFrame, flags);
636         dest.writeParcelableArray(mSources, 0);
637     }
638 
639     public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
640 
641         public InsetsState createFromParcel(Parcel in) {
642             return new InsetsState(in);
643         }
644 
645         public InsetsState[] newArray(int size) {
646             return new InsetsState[size];
647         }
648     };
649 
650     public void readFromParcel(Parcel in) {
651         mDisplayFrame.set(in.readParcelable(null /* loader */));
652         mSources = in.readParcelableArray(null, InsetsSource.class);
653     }
654 
655     @Override
656     public String toString() {
657         StringJoiner joiner = new StringJoiner(", ");
658         for (int i = 0; i < SIZE; i++) {
659             InsetsSource source = mSources[i];
660             if (source != null) {
661                 joiner.add(source.toString());
662             }
663         }
664         return "InsetsState: {"
665                 + "mDisplayFrame=" + mDisplayFrame
666                 + ", mSources= { " + joiner
667                 + " }";
668     }
669 }
670 
671