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