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