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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
20 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
21 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
22 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
23 import static android.view.InsetsStateProto.DISPLAY_FRAME;
24 import static android.view.InsetsStateProto.SOURCES;
25 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
26 import static android.view.WindowInsets.Type.captionBar;
27 import static android.view.WindowInsets.Type.displayCutout;
28 import static android.view.WindowInsets.Type.ime;
29 import static android.view.WindowInsets.Type.indexOf;
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.FLAG_LAYOUT_NO_LIMITS;
34 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
35 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
36 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
37 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
38 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
39 
40 import android.annotation.IntDef;
41 import android.annotation.NonNull;
42 import android.annotation.Nullable;
43 import android.app.WindowConfiguration.ActivityType;
44 import android.graphics.Insets;
45 import android.graphics.Rect;
46 import android.os.Parcel;
47 import android.os.Parcelable;
48 import android.util.SparseArray;
49 import android.util.SparseIntArray;
50 import android.util.proto.ProtoOutputStream;
51 import android.view.WindowInsets.Type;
52 import android.view.WindowInsets.Type.InsetsType;
53 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 
57 import java.io.PrintWriter;
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.util.Objects;
61 import java.util.StringJoiner;
62 
63 /**
64  * Holder for state of system windows that cause window insets for all other windows in the system.
65  * @hide
66  */
67 public class InsetsState implements Parcelable {
68 
69     @Retention(RetentionPolicy.SOURCE)
70     @IntDef(prefix = "ISIDE", value = {
71             ISIDE_LEFT,
72             ISIDE_TOP,
73             ISIDE_RIGHT,
74             ISIDE_BOTTOM,
75             ISIDE_FLOATING,
76             ISIDE_UNKNOWN
77     })
78     public @interface InternalInsetsSide {}
79     static final int ISIDE_LEFT = 0;
80     static final int ISIDE_TOP = 1;
81     static final int ISIDE_RIGHT = 2;
82     static final int ISIDE_BOTTOM = 3;
83     static final int ISIDE_FLOATING = 4;
84     static final int ISIDE_UNKNOWN = 5;
85 
86     private final SparseArray<InsetsSource> mSources;
87 
88     /**
89      * The frame of the display these sources are relative to.
90      */
91     private final Rect mDisplayFrame = new Rect();
92 
93     /** The area cut from the display. */
94     private final DisplayCutout.ParcelableWrapper mDisplayCutout =
95             new DisplayCutout.ParcelableWrapper();
96 
97     /**
98      * The frame that rounded corners are relative to.
99      *
100      * There are 2 cases that will draw fake rounded corners:
101      *   1. In split-screen mode
102      *   2. Devices with a task bar
103      * We need to report these fake rounded corners to apps by re-calculating based on this frame.
104      */
105     private final Rect mRoundedCornerFrame = new Rect();
106 
107     /** The rounded corners on the display */
108     private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
109 
110     /** The bounds of the Privacy Indicator */
111     private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
112             new PrivacyIndicatorBounds();
113 
114     /** The display shape */
115     private DisplayShape mDisplayShape = DisplayShape.NONE;
116 
InsetsState()117     public InsetsState() {
118         mSources = new SparseArray<>();
119     }
120 
InsetsState(InsetsState copy)121     public InsetsState(InsetsState copy) {
122         this(copy, false /* copySources */);
123     }
124 
InsetsState(InsetsState copy, boolean copySources)125     public InsetsState(InsetsState copy, boolean copySources) {
126         mSources = new SparseArray<>(copy.mSources.size());
127         set(copy, copySources);
128     }
129 
130     /**
131      * Calculates {@link WindowInsets} based on the current source configuration.
132      *
133      * @param frame The frame to calculate the insets relative to.
134      * @param ignoringVisibilityState {@link InsetsState} used to calculate
135      *        {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass
136      *        {@code null} to use this state to calculate that information.
137      * @return The calculated insets.
138      */
calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, int windowType, @ActivityType int activityType, @Nullable @InternalInsetsSide SparseIntArray idSideMap)139     public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
140             boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags,
141             int legacySystemUiFlags, int windowType, @ActivityType int activityType,
142             @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
143         Insets[] typeInsetsMap = new Insets[Type.SIZE];
144         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
145         boolean[] typeVisibilityMap = new boolean[Type.SIZE];
146         final Rect relativeFrame = new Rect(frame);
147         final Rect relativeFrameMax = new Rect(frame);
148         @InsetsType int forceConsumingTypes = 0;
149         @InsetsType int suppressScrimTypes = 0;
150         for (int i = mSources.size() - 1; i >= 0; i--) {
151             final InsetsSource source = mSources.valueAt(i);
152             final @InsetsType int type = source.getType();
153 
154             if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
155                 forceConsumingTypes |= type;
156             }
157 
158             if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
159                 suppressScrimTypes |= type;
160             }
161 
162             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
163                     idSideMap, typeVisibilityMap);
164 
165             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
166             // target.
167             if (type != WindowInsets.Type.ime()) {
168                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
169                         ? ignoringVisibilityState.peekSource(source.getId())
170                         : source;
171                 if (ignoringVisibilitySource == null) {
172                     continue;
173                 }
174                 processSource(ignoringVisibilitySource, relativeFrameMax,
175                         true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */,
176                         null /* typeVisibilityMap */);
177             }
178         }
179         final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
180 
181         @InsetsType int compatInsetsTypes = systemBars() | displayCutout();
182         if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) {
183             compatInsetsTypes |= ime();
184         }
185         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
186             compatInsetsTypes &= ~statusBars();
187         }
188         if (clearsCompatInsets(windowType, legacyWindowFlags, activityType, forceConsumingTypes)) {
189             compatInsetsTypes = 0;
190         }
191 
192         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
193                 forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
194                 calculateRelativeRoundedCorners(frame),
195                 calculateRelativePrivacyIndicatorBounds(frame),
196                 calculateRelativeDisplayShape(frame),
197                 compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
198     }
199 
calculateRelativeCutout(Rect frame)200     private DisplayCutout calculateRelativeCutout(Rect frame) {
201         final DisplayCutout raw = mDisplayCutout.get();
202         if (mDisplayFrame.equals(frame)) {
203             return raw;
204         }
205         if (frame == null) {
206             return DisplayCutout.NO_CUTOUT;
207         }
208         final int insetLeft = frame.left - mDisplayFrame.left;
209         final int insetTop = frame.top - mDisplayFrame.top;
210         final int insetRight = mDisplayFrame.right - frame.right;
211         final int insetBottom = mDisplayFrame.bottom - frame.bottom;
212         if (insetLeft >= raw.getSafeInsetLeft()
213                 && insetTop >= raw.getSafeInsetTop()
214                 && insetRight >= raw.getSafeInsetRight()
215                 && insetBottom >= raw.getSafeInsetBottom()) {
216             return DisplayCutout.NO_CUTOUT;
217         }
218         return raw.inset(insetLeft, insetTop, insetRight, insetBottom);
219     }
220 
calculateRelativeRoundedCorners(Rect frame)221     private RoundedCorners calculateRelativeRoundedCorners(Rect frame) {
222         if (frame == null) {
223             return RoundedCorners.NO_ROUNDED_CORNERS;
224         }
225         // If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this
226         // frame.
227         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
228         for (int i = mSources.size() - 1; i >= 0; i--) {
229             final InsetsSource source = mSources.valueAt(i);
230             if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
231                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
232                 roundedCornerFrame.inset(insets);
233             }
234         }
235         if (!roundedCornerFrame.isEmpty() && !roundedCornerFrame.equals(mDisplayFrame)) {
236             return mRoundedCorners.insetWithFrame(frame, roundedCornerFrame);
237         }
238         if (mDisplayFrame.equals(frame)) {
239             return mRoundedCorners;
240         }
241         final int insetLeft = frame.left - mDisplayFrame.left;
242         final int insetTop = frame.top - mDisplayFrame.top;
243         final int insetRight = mDisplayFrame.right - frame.right;
244         final int insetBottom = mDisplayFrame.bottom - frame.bottom;
245         return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom);
246     }
247 
calculateRelativePrivacyIndicatorBounds(Rect frame)248     private PrivacyIndicatorBounds calculateRelativePrivacyIndicatorBounds(Rect frame) {
249         if (mDisplayFrame.equals(frame)) {
250             return mPrivacyIndicatorBounds;
251         }
252         if (frame == null) {
253             return null;
254         }
255         final int insetLeft = frame.left - mDisplayFrame.left;
256         final int insetTop = frame.top - mDisplayFrame.top;
257         final int insetRight = mDisplayFrame.right - frame.right;
258         final int insetBottom = mDisplayFrame.bottom - frame.bottom;
259         return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
260     }
261 
calculateRelativeDisplayShape(Rect frame)262     private DisplayShape calculateRelativeDisplayShape(Rect frame) {
263         if (mDisplayFrame.equals(frame)) {
264             return mDisplayShape;
265         }
266         if (frame == null) {
267             return DisplayShape.NONE;
268         }
269         return mDisplayShape.setOffset(-frame.left, -frame.top);
270     }
271 
calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility)272     public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
273         Insets insets = Insets.NONE;
274         for (int i = mSources.size() - 1; i >= 0; i--) {
275             final InsetsSource source = mSources.valueAt(i);
276             if ((source.getType() & types) == 0) {
277                 continue;
278             }
279             insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
280         }
281         return insets;
282     }
283 
calculateInsets(Rect frame, @InsetsType int types, @InsetsType int requestedVisibleTypes)284     public Insets calculateInsets(Rect frame, @InsetsType int types,
285             @InsetsType int requestedVisibleTypes) {
286         Insets insets = Insets.NONE;
287         for (int i = mSources.size() - 1; i >= 0; i--) {
288             final InsetsSource source = mSources.valueAt(i);
289             if ((source.getType() & types & requestedVisibleTypes) == 0) {
290                 continue;
291             }
292             insets = Insets.max(source.calculateInsets(frame, true), insets);
293         }
294         return insets;
295     }
296 
calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType, @SoftInputModeFlags int softInputMode, int windowFlags)297     public Insets calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType,
298             @SoftInputModeFlags int softInputMode, int windowFlags) {
299         final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
300         final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
301                 ? systemBars() | ime()
302                 : systemBars();
303         @InsetsType int forceConsumingTypes = 0;
304         Insets insets = Insets.NONE;
305         for (int i = mSources.size() - 1; i >= 0; i--) {
306             final InsetsSource source = mSources.valueAt(i);
307             if ((source.getType() & visibleInsetsTypes) == 0) {
308                 continue;
309             }
310             if (source.hasFlags(FLAG_FORCE_CONSUMING)) {
311                 forceConsumingTypes |= source.getType();
312             }
313             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
314         }
315         return clearsCompatInsets(windowType, windowFlags, activityType, forceConsumingTypes)
316                 ? Insets.NONE
317                 : insets;
318     }
319 
320     /**
321      * Calculate which insets *cannot* be controlled, because the frame does not cover the
322      * respective side of the inset.
323      *
324      * If the frame of our window doesn't cover the entire inset, the control API makes very
325      * little sense, as we don't deal with negative insets.
326      */
327     @InsetsType
calculateUncontrollableInsetsFromFrame(Rect frame)328     public int calculateUncontrollableInsetsFromFrame(Rect frame) {
329         int blocked = 0;
330         for (int i = mSources.size() - 1; i >= 0; i--) {
331             final InsetsSource source = mSources.valueAt(i);
332             if (!canControlSource(frame, source)) {
333                 blocked |= source.getType();
334             }
335         }
336         return blocked;
337     }
338 
canControlSource(Rect frame, InsetsSource source)339     private static boolean canControlSource(Rect frame, InsetsSource source) {
340         final Insets insets = source.calculateInsets(frame, true /* ignoreVisibility */);
341         final Rect sourceFrame = source.getFrame();
342         final int sourceWidth = sourceFrame.width();
343         final int sourceHeight = sourceFrame.height();
344         return insets.left == sourceWidth || insets.right == sourceWidth
345                 || insets.top == sourceHeight || insets.bottom == sourceHeight;
346     }
347 
processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap)348     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
349             Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap,
350             @Nullable boolean[] typeVisibilityMap) {
351         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
352 
353         final int type = source.getType();
354         processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
355                 insets, type);
356 
357         if (type == Type.MANDATORY_SYSTEM_GESTURES) {
358             // Mandatory system gestures are also system gestures.
359             // TODO: find a way to express this more generally. One option would be to define
360             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
361             //       ability to set systemGestureInsets() independently from
362             //       mandatorySystemGestureInsets() in the Builder.
363             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
364                     insets, Type.SYSTEM_GESTURES);
365         }
366         if (type == Type.CAPTION_BAR) {
367             // Caption should also be gesture and tappable elements. This should not be needed when
368             // the caption is added from the shell, as the shell can add other types at the same
369             // time.
370             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
371                     insets, Type.SYSTEM_GESTURES);
372             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
373                     insets, Type.MANDATORY_SYSTEM_GESTURES);
374             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
375                     insets, Type.TAPPABLE_ELEMENT);
376         }
377     }
378 
processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, @InternalInsetsSide @Nullable SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type)379     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
380             @InternalInsetsSide @Nullable SparseIntArray idSideMap,
381             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
382         int index = indexOf(type);
383 
384         // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
385         // as non-equal while they provide the same insets of each type from WindowInsets#getInsets
386         // if one WindowInsets has Insets.NONE for a type and the other has null for the same type.
387         if (!Insets.NONE.equals(insets)) {
388             Insets existing = typeInsetsMap[index];
389             if (existing == null) {
390                 typeInsetsMap[index] = insets;
391             } else {
392                 typeInsetsMap[index] = Insets.max(existing, insets);
393             }
394         }
395 
396         if (typeVisibilityMap != null) {
397             typeVisibilityMap[index] = source.isVisible();
398         }
399 
400         if (idSideMap != null) {
401             @InternalInsetsSide int insetSide = getInsetSide(insets);
402             if (insetSide != ISIDE_UNKNOWN) {
403                 idSideMap.put(source.getId(), insetSide);
404             }
405         }
406     }
407 
408     /**
409      * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
410      * is set in order that this method returns a meaningful result.
411      */
getInsetSide(Insets insets)412     static @InternalInsetsSide int getInsetSide(Insets insets) {
413         if (Insets.NONE.equals(insets)) {
414             return ISIDE_FLOATING;
415         }
416         if (insets.left != 0) {
417             return ISIDE_LEFT;
418         }
419         if (insets.top != 0) {
420             return ISIDE_TOP;
421         }
422         if (insets.right != 0) {
423             return ISIDE_RIGHT;
424         }
425         if (insets.bottom != 0) {
426             return ISIDE_BOTTOM;
427         }
428         return ISIDE_UNKNOWN;
429     }
430 
431     /**
432      * Gets the source mapped from the ID, or creates one if no such mapping has been made.
433      */
getOrCreateSource(int id, int type)434     public InsetsSource getOrCreateSource(int id, int type) {
435         InsetsSource source = mSources.get(id);
436         if (source != null) {
437             return source;
438         }
439         source = new InsetsSource(id, type);
440         mSources.put(id, source);
441         return source;
442     }
443 
444     /**
445      * Gets the source mapped from the ID, or <code>null</code> if no such mapping has been made.
446      */
peekSource(int id)447     public @Nullable InsetsSource peekSource(int id) {
448         return mSources.get(id);
449     }
450 
451     /**
452      * Given an index in the range <code>0...sourceSize()-1</code>, returns the source ID from the
453      * <code>index</code>th ID-source mapping that this state stores.
454      */
sourceIdAt(int index)455     public int sourceIdAt(int index) {
456         return mSources.keyAt(index);
457     }
458 
459     /**
460      * Given an index in the range <code>0...sourceSize()-1</code>, returns the source from the
461      * <code>index</code>th ID-source mapping that this state stores.
462      */
sourceAt(int index)463     public InsetsSource sourceAt(int index) {
464         return mSources.valueAt(index);
465     }
466 
467     /**
468      * Returns the amount of the sources.
469      */
sourceSize()470     public int sourceSize() {
471         return mSources.size();
472     }
473 
474     /**
475      * Returns if the source is visible or the type is default visible and the source doesn't exist.
476      *
477      * @param id The ID of the source.
478      * @param type The {@link InsetsType} to see if it is default visible.
479      * @return {@code true} if the source is visible or the type is default visible and the source
480      *         doesn't exist.
481      */
isSourceOrDefaultVisible(int id, @InsetsType int type)482     public boolean isSourceOrDefaultVisible(int id, @InsetsType int type) {
483         final InsetsSource source = mSources.get(id);
484         return source != null ? source.isVisible() : (type & Type.defaultVisible()) != 0;
485     }
486 
setDisplayFrame(Rect frame)487     public void setDisplayFrame(Rect frame) {
488         mDisplayFrame.set(frame);
489     }
490 
getDisplayFrame()491     public Rect getDisplayFrame() {
492         return mDisplayFrame;
493     }
494 
setDisplayCutout(DisplayCutout cutout)495     public void setDisplayCutout(DisplayCutout cutout) {
496         mDisplayCutout.set(cutout);
497     }
498 
getDisplayCutout()499     public DisplayCutout getDisplayCutout() {
500         return mDisplayCutout.get();
501     }
502 
getDisplayCutoutSafe(Rect outBounds)503     public void getDisplayCutoutSafe(Rect outBounds) {
504         outBounds.set(
505                 WindowLayout.MIN_X, WindowLayout.MIN_Y, WindowLayout.MAX_X, WindowLayout.MAX_Y);
506         final DisplayCutout cutout = mDisplayCutout.get();
507         final Rect displayFrame = mDisplayFrame;
508         if (!cutout.isEmpty()) {
509             if (cutout.getSafeInsetLeft() > 0) {
510                 outBounds.left = displayFrame.left + cutout.getSafeInsetLeft();
511             }
512             if (cutout.getSafeInsetTop() > 0) {
513                 outBounds.top = displayFrame.top + cutout.getSafeInsetTop();
514             }
515             if (cutout.getSafeInsetRight() > 0) {
516                 outBounds.right = displayFrame.right - cutout.getSafeInsetRight();
517             }
518             if (cutout.getSafeInsetBottom() > 0) {
519                 outBounds.bottom = displayFrame.bottom - cutout.getSafeInsetBottom();
520             }
521         }
522     }
523 
setRoundedCorners(RoundedCorners roundedCorners)524     public void setRoundedCorners(RoundedCorners roundedCorners) {
525         mRoundedCorners = roundedCorners;
526     }
527 
getRoundedCorners()528     public RoundedCorners getRoundedCorners() {
529         return mRoundedCorners;
530     }
531 
532     /**
533      * Set the frame that will be used to calculate the rounded corners.
534      *
535      * @see #mRoundedCornerFrame
536      */
setRoundedCornerFrame(Rect frame)537     public void setRoundedCornerFrame(Rect frame) {
538         mRoundedCornerFrame.set(frame);
539     }
540 
setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds)541     public void setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds) {
542         mPrivacyIndicatorBounds = bounds;
543     }
544 
getPrivacyIndicatorBounds()545     public PrivacyIndicatorBounds getPrivacyIndicatorBounds() {
546         return mPrivacyIndicatorBounds;
547     }
548 
setDisplayShape(DisplayShape displayShape)549     public void setDisplayShape(DisplayShape displayShape) {
550         mDisplayShape = displayShape;
551     }
552 
getDisplayShape()553     public DisplayShape getDisplayShape() {
554         return mDisplayShape;
555     }
556 
557     /**
558      * Removes the source which has the ID from this state, if there was any.
559      *
560      * @param id The ID of the source to remove.
561      */
removeSource(int id)562     public void removeSource(int id) {
563         mSources.delete(id);
564     }
565 
566     /**
567      * Removes the source at the specified index.
568      *
569      * @param index The index of the source to remove.
570      */
removeSourceAt(int index)571     public void removeSourceAt(int index) {
572         mSources.removeAt(index);
573     }
574 
575     /**
576      * A shortcut for setting the visibility of the source.
577      *
578      * @param id The ID of the source to set the visibility
579      * @param visible {@code true} for visible
580      */
setSourceVisible(int id, boolean visible)581     public void setSourceVisible(int id, boolean visible) {
582         final InsetsSource source = mSources.get(id);
583         if (source != null) {
584             source.setVisible(visible);
585         }
586     }
587 
588     /**
589      * Scales the frame and the visible frame (if there is one) of each source.
590      *
591      * @param scale the scale to be applied
592      */
scale(float scale)593     public void scale(float scale) {
594         mDisplayFrame.scale(scale);
595         mDisplayCutout.scale(scale);
596         mRoundedCorners = mRoundedCorners.scale(scale);
597         mRoundedCornerFrame.scale(scale);
598         mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
599         mDisplayShape = mDisplayShape.setScale(scale);
600         for (int i = mSources.size() - 1; i >= 0; i--) {
601             final InsetsSource source = mSources.valueAt(i);
602             source.getFrame().scale(scale);
603             final Rect visibleFrame = source.getVisibleFrame();
604             if (visibleFrame != null) {
605                 visibleFrame.scale(scale);
606             }
607         }
608     }
609 
set(InsetsState other)610     public void set(InsetsState other) {
611         set(other, false /* copySources */);
612     }
613 
set(InsetsState other, boolean copySources)614     public void set(InsetsState other, boolean copySources) {
615         mDisplayFrame.set(other.mDisplayFrame);
616         mDisplayCutout.set(other.mDisplayCutout);
617         mRoundedCorners = other.getRoundedCorners();
618         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
619         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
620         mDisplayShape = other.getDisplayShape();
621         mSources.clear();
622         for (int i = 0, size = other.mSources.size(); i < size; i++) {
623             final InsetsSource otherSource = other.mSources.valueAt(i);
624             mSources.append(otherSource.getId(), copySources
625                     ? new InsetsSource(otherSource)
626                     : otherSource);
627         }
628     }
629 
630     /**
631      * Sets the values from the other InsetsState. But for sources, only specific types of source
632      * would be set.
633      *
634      * @param other the other InsetsState.
635      * @param types the only types of sources would be set.
636      */
set(InsetsState other, @InsetsType int types)637     public void set(InsetsState other, @InsetsType int types) {
638         mDisplayFrame.set(other.mDisplayFrame);
639         mDisplayCutout.set(other.mDisplayCutout);
640         mRoundedCorners = other.getRoundedCorners();
641         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
642         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
643         mDisplayShape = other.getDisplayShape();
644         if (types == 0) {
645             return;
646         }
647         for (int i = mSources.size() - 1; i >= 0; i--) {
648             final InsetsSource source = mSources.valueAt(i);
649             if ((source.getType() & types) != 0) {
650                 mSources.removeAt(i);
651             }
652         }
653         for (int i = other.mSources.size() - 1; i >= 0; i--) {
654             final InsetsSource otherSource = other.mSources.valueAt(i);
655             if ((otherSource.getType() & types) != 0) {
656                 mSources.put(otherSource.getId(), otherSource);
657             }
658         }
659     }
660 
addSource(InsetsSource source)661     public void addSource(InsetsSource source) {
662         mSources.put(source.getId(), source);
663     }
664 
clearsCompatInsets(int windowType, int windowFlags, @ActivityType int activityType, @InsetsType int forceConsumingTypes)665     public static boolean clearsCompatInsets(int windowType, int windowFlags,
666             @ActivityType int activityType, @InsetsType int forceConsumingTypes) {
667         return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0
668                 // For compatibility reasons, this excludes the wallpaper, the system error windows,
669                 // and the app windows while any system bar is forcibly consumed.
670                 && windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR
671                 // This ensures the app content won't be obscured by compat insets even if the app
672                 // has FLAG_LAYOUT_NO_LIMITS.
673                 && (forceConsumingTypes == 0 || activityType != ACTIVITY_TYPE_STANDARD);
674     }
675 
dump(String prefix, PrintWriter pw)676     public void dump(String prefix, PrintWriter pw) {
677         final String newPrefix = prefix + "  ";
678         pw.println(prefix + "InsetsState");
679         pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame);
680         pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get());
681         pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
682         pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
683         pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
684         pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
685         for (int i = 0, size = mSources.size(); i < size; i++) {
686             mSources.valueAt(i).dump(newPrefix + "  ", pw);
687         }
688     }
689 
dumpDebug(ProtoOutputStream proto, long fieldId)690     void dumpDebug(ProtoOutputStream proto, long fieldId) {
691         final long token = proto.start(fieldId);
692         final InsetsSource source = mSources.get(InsetsSource.ID_IME);
693         if (source != null) {
694             source.dumpDebug(proto, SOURCES);
695         }
696         mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME);
697         mDisplayCutout.get().dumpDebug(proto, DISPLAY_CUTOUT);
698         proto.end(token);
699     }
700 
701     @Override
equals(@ullable Object o)702     public boolean equals(@Nullable Object o) {
703         return equals(o, false, false);
704     }
705 
706     /**
707      * An equals method can exclude the caption insets. This is useful because we assemble the
708      * caption insets information on the client side, and when we communicate with server, it's
709      * excluded.
710      * @param excludesCaptionBar If {@link Type#captionBar()}} should be ignored.
711      * @param excludesInvisibleIme If {@link WindowInsets.Type#ime()} should be ignored when IME is
712      *                             not visible.
713      * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
714      */
715     @VisibleForTesting
equals(@ullable Object o, boolean excludesCaptionBar, boolean excludesInvisibleIme)716     public boolean equals(@Nullable Object o, boolean excludesCaptionBar,
717             boolean excludesInvisibleIme) {
718         if (this == o) { return true; }
719         if (o == null || getClass() != o.getClass()) { return false; }
720 
721         InsetsState state = (InsetsState) o;
722 
723         if (!mDisplayFrame.equals(state.mDisplayFrame)
724                 || !mDisplayCutout.equals(state.mDisplayCutout)
725                 || !mRoundedCorners.equals(state.mRoundedCorners)
726                 || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame)
727                 || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)
728                 || !mDisplayShape.equals(state.mDisplayShape)) {
729             return false;
730         }
731 
732         final SparseArray<InsetsSource> thisSources = mSources;
733         final SparseArray<InsetsSource> thatSources = state.mSources;
734         if (!excludesCaptionBar && !excludesInvisibleIme) {
735             return thisSources.contentEquals(thatSources);
736         } else {
737             final int thisSize = thisSources.size();
738             final int thatSize = thatSources.size();
739             int thisIndex = 0;
740             int thatIndex = 0;
741             while (thisIndex < thisSize || thatIndex < thatSize) {
742                 InsetsSource thisSource = thisIndex < thisSize
743                         ? thisSources.valueAt(thisIndex)
744                         : null;
745 
746                 // Seek to the next non-excluding source of ours.
747                 while (thisSource != null
748                         && (excludesCaptionBar && thisSource.getType() == captionBar()
749                                 || excludesInvisibleIme && thisSource.getType() == ime()
750                                         && !thisSource.isVisible())) {
751                     thisIndex++;
752                     thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null;
753                 }
754 
755                 InsetsSource thatSource = thatIndex < thatSize
756                         ? thatSources.valueAt(thatIndex)
757                         : null;
758 
759                 // Seek to the next non-excluding source of theirs.
760                 while (thatSource != null
761                         && (excludesCaptionBar && thatSource.getType() == captionBar()
762                                 || excludesInvisibleIme && thatSource.getType() == ime()
763                                         && !thatSource.isVisible())) {
764                     thatIndex++;
765                     thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null;
766                 }
767 
768                 if (!Objects.equals(thisSource, thatSource)) {
769                     return false;
770                 }
771 
772                 thisIndex++;
773                 thatIndex++;
774             }
775             return true;
776         }
777     }
778 
779     @Override
780     public int hashCode() {
781         return Objects.hash(mDisplayFrame, mDisplayCutout, mSources.contentHashCode(),
782                 mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
783     }
784 
785     public InsetsState(Parcel in) {
786         mSources = readFromParcel(in);
787     }
788 
789     @Override
790     public int describeContents() {
791         return 0;
792     }
793 
794     @Override
795     public void writeToParcel(Parcel dest, int flags) {
796         mDisplayFrame.writeToParcel(dest, flags);
797         mDisplayCutout.writeToParcel(dest, flags);
798         dest.writeTypedObject(mRoundedCorners, flags);
799         mRoundedCornerFrame.writeToParcel(dest, flags);
800         dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
801         dest.writeTypedObject(mDisplayShape, flags);
802         final int size = mSources.size();
803         dest.writeInt(size);
804         for (int i = 0; i < size; i++) {
805             dest.writeTypedObject(mSources.valueAt(i), flags);
806         }
807     }
808 
809     public static final @NonNull Creator<InsetsState> CREATOR = new Creator<>() {
810 
811         public InsetsState createFromParcel(Parcel in) {
812             return new InsetsState(in);
813         }
814 
815         public InsetsState[] newArray(int size) {
816             return new InsetsState[size];
817         }
818     };
819 
820     public SparseArray<InsetsSource> readFromParcel(Parcel in) {
821         mDisplayFrame.readFromParcel(in);
822         mDisplayCutout.readFromParcel(in);
823         mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
824         mRoundedCornerFrame.readFromParcel(in);
825         mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
826         mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
827         final int size = in.readInt();
828         final SparseArray<InsetsSource> sources;
829         if (mSources == null) {
830             // We are constructing this InsetsState.
831             sources = new SparseArray<>(size);
832         } else {
833             sources = mSources;
834             sources.clear();
835         }
836         for (int i = 0; i < size; i++) {
837             final InsetsSource source = in.readTypedObject(InsetsSource.CREATOR);
838             sources.append(source.getId(), source);
839         }
840         return sources;
841     }
842 
843     @Override
844     public String toString() {
845         final StringJoiner joiner = new StringJoiner(", ");
846         for (int i = 0, size = mSources.size(); i < size; i++) {
847             joiner.add(mSources.valueAt(i).toString());
848         }
849         return "InsetsState: {"
850                 + "mDisplayFrame=" + mDisplayFrame
851                 + ", mDisplayCutout=" + mDisplayCutout
852                 + ", mRoundedCorners=" + mRoundedCorners
853                 + "  mRoundedCornerFrame=" + mRoundedCornerFrame
854                 + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
855                 + ", mDisplayShape=" + mDisplayShape
856                 + ", mSources= { " + joiner
857                 + " }";
858     }
859 
860     /**
861      * Traverses sources in two {@link InsetsState}s and calls back when events defined in
862      * {@link OnTraverseCallbacks} happen. This is optimized for {@link SparseArray} that we avoid
863      * triggering the binary search while getting the key or the value.
864      *
865      * This can be used to copy attributes of sources from one InsetsState to the other one, or to
866      * remove sources existing in one InsetsState but not in the other one.
867      *
868      * @param state1 The first {@link InsetsState} to be traversed.
869      * @param state2 The second {@link InsetsState} to be traversed.
870      * @param cb The {@link OnTraverseCallbacks} to call back to the caller.
871      */
872     public static void traverse(InsetsState state1, InsetsState state2, OnTraverseCallbacks cb) {
873         cb.onStart(state1, state2);
874         final int size1 = state1.sourceSize();
875         final int size2 = state2.sourceSize();
876         int index1 = 0;
877         int index2 = 0;
878         while (index1 < size1 && index2 < size2) {
879             int id1 = state1.sourceIdAt(index1);
880             int id2 = state2.sourceIdAt(index2);
881             while (id1 != id2) {
882                 if (id1 < id2) {
883                     cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
884                     index1++;
885                     if (index1 < size1) {
886                         id1 = state1.sourceIdAt(index1);
887                     } else {
888                         break;
889                     }
890                 } else {
891                     cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
892                     index2++;
893                     if (index2 < size2) {
894                         id2 = state2.sourceIdAt(index2);
895                     } else {
896                         break;
897                     }
898                 }
899             }
900             if (index1 >= size1 || index2 >= size2) {
901                 break;
902             }
903             final InsetsSource source1 = state1.sourceAt(index1);
904             final InsetsSource source2 = state2.sourceAt(index2);
905             cb.onIdMatch(source1, source2);
906             index1++;
907             index2++;
908         }
909         while (index2 < size2) {
910             cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
911             index2++;
912         }
913         while (index1 < size1) {
914             cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
915             index1++;
916         }
917         cb.onFinish(state1, state2);
918     }
919 
920     /**
921      * Used with {@link #traverse(InsetsState, InsetsState, OnTraverseCallbacks)} to call back when
922      * certain events happen.
923      */
924     public interface OnTraverseCallbacks {
925 
926         /**
927          * Called at the beginning of the traverse.
928          *
929          * @param state1 same as the state1 supplied to {@link #traverse}
930          * @param state2 same as the state2 supplied to {@link #traverse}
931          */
932         default void onStart(InsetsState state1, InsetsState state2) { }
933 
934         /**
935          * Called when finding two IDs from two InsetsStates are the same.
936          *
937          * @param source1 the source in state1.
938          * @param source2 the source in state2.
939          */
940         default void onIdMatch(InsetsSource source1, InsetsSource source2) { }
941 
942         /**
943          * Called when finding an ID in state2 but not in state1.
944          *
945          * @param index2 the index of the ID in state2.
946          * @param source2 the source which has the ID in state2.
947          */
948         default void onIdNotFoundInState1(int index2, InsetsSource source2) { }
949 
950         /**
951          * Called when finding an ID in state1 but not in state2.
952          *
953          * @param index1 the index of the ID in state1.
954          * @param source1 the source which has the ID in state1.
955          */
956         default void onIdNotFoundInState2(int index1, InsetsSource source1) { }
957 
958         /**
959          * Called at the end of the traverse.
960          *
961          * @param state1 same as the state1 supplied to {@link #traverse}
962          * @param state2 same as the state2 supplied to {@link #traverse}
963          */
964         default void onFinish(InsetsState state1, InsetsState state2) { }
965     }
966 }
967 
968