1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
20 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
21 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
22 import static android.view.WindowInsets.Type.SIZE;
23 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
24 import static android.view.WindowInsets.Type.indexOf;
25 
26 import android.annotation.IntDef;
27 import android.annotation.Nullable;
28 import android.graphics.Insets;
29 import android.graphics.Rect;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.util.ArrayMap;
33 import android.util.ArraySet;
34 import android.util.SparseIntArray;
35 import android.view.WindowInsets.Type;
36 import android.view.WindowInsets.Type.InsetType;
37 import android.view.WindowManager.LayoutParams;
38 
39 import java.io.PrintWriter;
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.util.Objects;
43 
44 /**
45  * Holder for state of system windows that cause window insets for all other windows in the system.
46  * @hide
47  */
48 public class InsetsState implements Parcelable {
49 
50     /**
51      * Internal representation of inset source types. This is different from the public API in
52      * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
53      * at the same time.
54      */
55     @Retention(RetentionPolicy.SOURCE)
56     @IntDef(prefix = "TYPE", value = {
57             TYPE_TOP_BAR,
58             TYPE_SIDE_BAR_1,
59             TYPE_SIDE_BAR_2,
60             TYPE_SIDE_BAR_3,
61             TYPE_TOP_GESTURES,
62             TYPE_BOTTOM_GESTURES,
63             TYPE_LEFT_GESTURES,
64             TYPE_RIGHT_GESTURES,
65             TYPE_TOP_TAPPABLE_ELEMENT,
66             TYPE_BOTTOM_TAPPABLE_ELEMENT,
67             TYPE_IME
68     })
69     public @interface InternalInsetType {}
70 
71     static final int FIRST_TYPE = 0;
72 
73     /** Top bar. Can be status bar or caption in freeform windowing mode. */
74     public static final int TYPE_TOP_BAR = FIRST_TYPE;
75 
76     /**
77      * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar
78      * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have
79      * multiple, like Android Auto.
80      */
81     public static final int TYPE_SIDE_BAR_1 = 1;
82     public static final int TYPE_SIDE_BAR_2 = 2;
83     public static final int TYPE_SIDE_BAR_3 = 3;
84 
85     public static final int TYPE_TOP_GESTURES = 4;
86     public static final int TYPE_BOTTOM_GESTURES = 5;
87     public static final int TYPE_LEFT_GESTURES = 6;
88     public static final int TYPE_RIGHT_GESTURES = 7;
89     public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8;
90     public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9;
91 
92     /** Input method window. */
93     public static final int TYPE_IME = 10;
94 
95     static final int LAST_TYPE = TYPE_IME;
96 
97     // Derived types
98 
99     /** First side bar is navigation bar. */
100     public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1;
101 
102     /** A shelf is the same as the navigation bar. */
103     public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR;
104 
105     @Retention(RetentionPolicy.SOURCE)
106     @IntDef(prefix = "INSET_SIDE", value = {
107             INSET_SIDE_LEFT,
108             INSET_SIDE_TOP,
109             INSET_SIDE_RIGHT,
110             INSET_SIDE_BOTTOM,
111             INSET_SIDE_UNKNWON
112     })
113     public @interface InsetSide {}
114     static final int INSET_SIDE_LEFT = 0;
115     static final int INSET_SIDE_TOP = 1;
116     static final int INSET_SIDE_RIGHT = 2;
117     static final int INSET_SIDE_BOTTOM = 3;
118     static final int INSET_SIDE_UNKNWON = 4;
119 
120     private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
121 
122     /**
123      * The frame of the display these sources are relative to.
124      */
125     private final Rect mDisplayFrame = new Rect();
126 
InsetsState()127     public InsetsState() {
128     }
129 
InsetsState(InsetsState copy)130     public InsetsState(InsetsState copy) {
131         set(copy);
132     }
133 
InsetsState(InsetsState copy, boolean copySources)134     public InsetsState(InsetsState copy, boolean copySources) {
135         set(copy, copySources);
136     }
137 
138     /**
139      * Calculates {@link WindowInsets} based on the current source configuration.
140      *
141      * @param frame The frame to calculate the insets relative to.
142      * @return The calculated insets.
143      */
calculateInsets(Rect frame, boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets, int legacySoftInputMode, @Nullable @InsetSide SparseIntArray typeSideMap)144     public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
145             boolean alwaysConsumeSystemBars, DisplayCutout cutout,
146             @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
147             int legacySoftInputMode, @Nullable @InsetSide SparseIntArray typeSideMap) {
148         Insets[] typeInsetsMap = new Insets[Type.SIZE];
149         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
150         boolean[] typeVisibilityMap = new boolean[SIZE];
151         final Rect relativeFrame = new Rect(frame);
152         final Rect relativeFrameMax = new Rect(frame);
153         if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
154                 && legacyContentInsets != null && legacyStableInsets != null) {
155             WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
156             WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
157         }
158         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
159             InsetsSource source = mSources.get(type);
160             if (source == null) {
161                 continue;
162             }
163 
164             boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
165                     && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
166             boolean skipIme = source.getType() == TYPE_IME
167                     && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
168             boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
169                     && (toPublicType(type) & Type.compatSystemInsets()) != 0;
170             if (skipSystemBars || skipIme || skipLegacyTypes) {
171                 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
172                 continue;
173             }
174 
175             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
176                     typeSideMap, typeVisibilityMap);
177 
178             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
179             // target.
180             if (source.getType() != TYPE_IME) {
181                 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
182                         typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
183             }
184         }
185         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
186                 alwaysConsumeSystemBars, cutout);
187     }
188 
processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap)189     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
190             Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
191             @Nullable boolean[] typeVisibilityMap) {
192         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
193 
194         int type = toPublicType(source.getType());
195         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
196                 insets, type);
197 
198         if (type == MANDATORY_SYSTEM_GESTURES) {
199             // Mandatory system gestures are also system gestures.
200             // TODO: find a way to express this more generally. One option would be to define
201             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
202             //       ability to set systemGestureInsets() independently from
203             //       mandatorySystemGestureInsets() in the Builder.
204             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
205                     insets, SYSTEM_GESTURES);
206         }
207     }
208 
processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, @InsetSide @Nullable SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type)209     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
210             @InsetSide @Nullable SparseIntArray typeSideMap,
211             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
212         int index = indexOf(type);
213         Insets existing = typeInsetsMap[index];
214         if (existing == null) {
215             typeInsetsMap[index] = insets;
216         } else {
217             typeInsetsMap[index] = Insets.max(existing, insets);
218         }
219 
220         if (typeVisibilityMap != null) {
221             typeVisibilityMap[index] = source.isVisible();
222         }
223 
224         if (typeSideMap != null && !Insets.NONE.equals(insets)) {
225             @InsetSide int insetSide = getInsetSide(insets);
226             if (insetSide != INSET_SIDE_UNKNWON) {
227                 typeSideMap.put(source.getType(), getInsetSide(insets));
228             }
229         }
230     }
231 
232     /**
233      * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
234      * is set in order that this method returns a meaningful result.
235      */
getInsetSide(Insets insets)236     private @InsetSide int getInsetSide(Insets insets) {
237         if (insets.left != 0) {
238             return INSET_SIDE_LEFT;
239         }
240         if (insets.top != 0) {
241             return INSET_SIDE_TOP;
242         }
243         if (insets.right != 0) {
244             return INSET_SIDE_RIGHT;
245         }
246         if (insets.bottom != 0) {
247             return INSET_SIDE_BOTTOM;
248         }
249         return INSET_SIDE_UNKNWON;
250     }
251 
getSource(@nternalInsetType int type)252     public InsetsSource getSource(@InternalInsetType int type) {
253         return mSources.computeIfAbsent(type, InsetsSource::new);
254     }
255 
setDisplayFrame(Rect frame)256     public void setDisplayFrame(Rect frame) {
257         mDisplayFrame.set(frame);
258     }
259 
getDisplayFrame()260     public Rect getDisplayFrame() {
261         return mDisplayFrame;
262     }
263 
264     /**
265      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
266      * to the client.
267      *
268      * @param type The {@link InternalInsetType} of the source to remove
269      */
removeSource(int type)270     public void removeSource(int type) {
271         mSources.remove(type);
272     }
273 
set(InsetsState other)274     public void set(InsetsState other) {
275         set(other, false /* copySources */);
276     }
277 
set(InsetsState other, boolean copySources)278     public void set(InsetsState other, boolean copySources) {
279         mDisplayFrame.set(other.mDisplayFrame);
280         mSources.clear();
281         if (copySources) {
282             for (int i = 0; i < other.mSources.size(); i++) {
283                 InsetsSource source = other.mSources.valueAt(i);
284                 mSources.put(source.getType(), new InsetsSource(source));
285             }
286         } else {
287             mSources.putAll(other.mSources);
288         }
289     }
290 
addSource(InsetsSource source)291     public void addSource(InsetsSource source) {
292         mSources.put(source.getType(), source);
293     }
294 
getSourcesCount()295     public int getSourcesCount() {
296         return mSources.size();
297     }
298 
sourceAt(int index)299     public InsetsSource sourceAt(int index) {
300         return mSources.valueAt(index);
301     }
302 
toInternalType(@nsetType int insetTypes)303     public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) {
304         final ArraySet<Integer> result = new ArraySet<>();
305         if ((insetTypes & Type.TOP_BAR) != 0) {
306             result.add(TYPE_TOP_BAR);
307         }
308         if ((insetTypes & Type.SIDE_BARS) != 0) {
309             result.add(TYPE_SIDE_BAR_1);
310             result.add(TYPE_SIDE_BAR_2);
311             result.add(TYPE_SIDE_BAR_3);
312         }
313         if ((insetTypes & Type.IME) != 0) {
314             result.add(TYPE_IME);
315         }
316         return result;
317     }
318 
toPublicType(@nternalInsetType int type)319     static @InsetType int toPublicType(@InternalInsetType int type) {
320         switch (type) {
321             case TYPE_TOP_BAR:
322                 return Type.TOP_BAR;
323             case TYPE_SIDE_BAR_1:
324             case TYPE_SIDE_BAR_2:
325             case TYPE_SIDE_BAR_3:
326                 return Type.SIDE_BARS;
327             case TYPE_IME:
328                 return Type.IME;
329             case TYPE_TOP_GESTURES:
330             case TYPE_BOTTOM_GESTURES:
331                 return Type.MANDATORY_SYSTEM_GESTURES;
332             case TYPE_LEFT_GESTURES:
333             case TYPE_RIGHT_GESTURES:
334                 return Type.SYSTEM_GESTURES;
335             case TYPE_TOP_TAPPABLE_ELEMENT:
336             case TYPE_BOTTOM_TAPPABLE_ELEMENT:
337                 return Type.TAPPABLE_ELEMENT;
338             default:
339                 throw new IllegalArgumentException("Unknown type: " + type);
340         }
341     }
342 
getDefaultVisibility(@nsetType int type)343     public static boolean getDefaultVisibility(@InsetType int type) {
344         switch (type) {
345             case TYPE_TOP_BAR:
346             case TYPE_SIDE_BAR_1:
347             case TYPE_SIDE_BAR_2:
348             case TYPE_SIDE_BAR_3:
349                 return true;
350             case TYPE_IME:
351                 return false;
352             default:
353                 return true;
354         }
355     }
356 
dump(String prefix, PrintWriter pw)357     public void dump(String prefix, PrintWriter pw) {
358         pw.println(prefix + "InsetsState");
359         for (int i = mSources.size() - 1; i >= 0; i--) {
360             mSources.valueAt(i).dump(prefix + "  ", pw);
361         }
362     }
363 
typeToString(int type)364     public static String typeToString(int type) {
365         switch (type) {
366             case TYPE_TOP_BAR:
367                 return "TYPE_TOP_BAR";
368             case TYPE_SIDE_BAR_1:
369                 return "TYPE_SIDE_BAR_1";
370             case TYPE_SIDE_BAR_2:
371                 return "TYPE_SIDE_BAR_2";
372             case TYPE_SIDE_BAR_3:
373                 return "TYPE_SIDE_BAR_3";
374             case TYPE_TOP_GESTURES:
375                 return "TYPE_TOP_GESTURES";
376             case TYPE_BOTTOM_GESTURES:
377                 return "TYPE_BOTTOM_GESTURES";
378             case TYPE_LEFT_GESTURES:
379                 return "TYPE_LEFT_GESTURES";
380             case TYPE_RIGHT_GESTURES:
381                 return "TYPE_RIGHT_GESTURES";
382             case TYPE_TOP_TAPPABLE_ELEMENT:
383                 return "TYPE_TOP_TAPPABLE_ELEMENT";
384             case TYPE_BOTTOM_TAPPABLE_ELEMENT:
385                 return "TYPE_BOTTOM_TAPPABLE_ELEMENT";
386             default:
387                 return "TYPE_UNKNOWN_" + type;
388         }
389     }
390 
391     @Override
equals(Object o)392     public boolean equals(Object o) {
393         if (this == o) { return true; }
394         if (o == null || getClass() != o.getClass()) { return false; }
395 
396         InsetsState state = (InsetsState) o;
397 
398         if (!mDisplayFrame.equals(state.mDisplayFrame)) {
399             return false;
400         }
401         if (mSources.size() != state.mSources.size()) {
402             return false;
403         }
404         for (int i = mSources.size() - 1; i >= 0; i--) {
405             InsetsSource source = mSources.valueAt(i);
406             InsetsSource otherSource = state.mSources.get(source.getType());
407             if (otherSource == null) {
408                 return false;
409             }
410             if (!otherSource.equals(source)) {
411                 return false;
412             }
413         }
414         return true;
415     }
416 
417     @Override
hashCode()418     public int hashCode() {
419         return Objects.hash(mDisplayFrame, mSources);
420     }
421 
InsetsState(Parcel in)422     public InsetsState(Parcel in) {
423         readFromParcel(in);
424     }
425 
426     @Override
describeContents()427     public int describeContents() {
428         return 0;
429     }
430 
431     @Override
writeToParcel(Parcel dest, int flags)432     public void writeToParcel(Parcel dest, int flags) {
433         dest.writeParcelable(mDisplayFrame, flags);
434         dest.writeInt(mSources.size());
435         for (int i = 0; i < mSources.size(); i++) {
436             dest.writeParcelable(mSources.valueAt(i), flags);
437         }
438     }
439 
440     public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
441 
442         public InsetsState createFromParcel(Parcel in) {
443             return new InsetsState(in);
444         }
445 
446         public InsetsState[] newArray(int size) {
447             return new InsetsState[size];
448         }
449     };
450 
readFromParcel(Parcel in)451     public void readFromParcel(Parcel in) {
452         mSources.clear();
453         mDisplayFrame.set(in.readParcelable(null /* loader */));
454         final int size = in.readInt();
455         for (int i = 0; i < size; i++) {
456             final InsetsSource source = in.readParcelable(null /* loader */);
457             mSources.put(source.getType(), source);
458         }
459     }
460 }
461 
462