blob: 4f809fe6d54a36d1324677cd55d9effc87830d1d [file] [log] [blame]
Jorim Jaggif96c90a2018-09-26 16:55:15 +02001/*
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
17package android.view;
18
Jorim Jaggi90990792019-01-21 23:00:20 +010019import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +010020import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
Jorim Jaggi90990792019-01-21 23:00:20 +010021import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
22import static android.view.WindowInsets.Type.SIZE;
Jorim Jaggibcf99ff2018-12-03 18:04:26 +010023import static android.view.WindowInsets.Type.indexOf;
24
Jorim Jaggif96c90a2018-09-26 16:55:15 +020025import android.annotation.IntDef;
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010026import android.annotation.Nullable;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020027import android.graphics.Insets;
28import android.graphics.Rect;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.ArrayMap;
Jorim Jaggib6030952018-10-23 18:31:52 +020032import android.util.ArraySet;
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010033import android.util.SparseArray;
34import android.util.SparseIntArray;
Jorim Jaggib6030952018-10-23 18:31:52 +020035import android.view.WindowInsets.Type;
36import android.view.WindowInsets.Type.InsetType;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020037
38import java.io.PrintWriter;
39import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
Jorim Jaggie35c0592018-11-06 16:21:08 +010041import java.util.ArrayList;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020042
43/**
44 * Holder for state of system windows that cause window insets for all other windows in the system.
45 * @hide
46 */
47public class InsetsState implements Parcelable {
48
49 /**
50 * Internal representation of inset source types. This is different from the public API in
51 * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
52 * at the same time.
53 */
54 @Retention(RetentionPolicy.SOURCE)
55 @IntDef(prefix = "TYPE", value = {
56 TYPE_TOP_BAR,
57 TYPE_SIDE_BAR_1,
58 TYPE_SIDE_BAR_2,
59 TYPE_SIDE_BAR_3,
60 TYPE_IME
61 })
62 public @interface InternalInsetType {}
63
64 static final int FIRST_TYPE = 0;
65
66 /** Top bar. Can be status bar or caption in freeform windowing mode. */
67 public static final int TYPE_TOP_BAR = FIRST_TYPE;
68
69 /**
70 * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar
71 * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have
72 * multiple, like Android Auto.
73 */
74 public static final int TYPE_SIDE_BAR_1 = 1;
75 public static final int TYPE_SIDE_BAR_2 = 2;
76 public static final int TYPE_SIDE_BAR_3 = 3;
77
78 /** Input method window. */
79 public static final int TYPE_IME = 4;
80 static final int LAST_TYPE = TYPE_IME;
81
82 // Derived types
83
84 /** First side bar is navigation bar. */
85 public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1;
86
87 /** A shelf is the same as the navigation bar. */
88 public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR;
89
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010090 @Retention(RetentionPolicy.SOURCE)
91 @IntDef(prefix = "INSET_SIDE", value = {
92 INSET_SIDE_LEFT,
93 INSET_SIDE_TOP,
94 INSET_SIDE_RIGHT,
95 INSET_SIDE_BOTTOM,
96 INSET_SIDE_UNKNWON
97 })
98 public @interface InsetSide {}
99 static final int INSET_SIDE_LEFT = 0;
100 static final int INSET_SIDE_TOP = 1;
101 static final int INSET_SIDE_RIGHT = 2;
102 static final int INSET_SIDE_BOTTOM = 3;
103 static final int INSET_SIDE_UNKNWON = 4;
104
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200105 private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
106
107 public InsetsState() {
108 }
109
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100110 public InsetsState(InsetsState copy) {
111 set(copy);
112 }
113
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800114 public InsetsState(InsetsState copy, boolean copySources) {
115 set(copy, copySources);
116 }
117
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200118 /**
119 * Calculates {@link WindowInsets} based on the current source configuration.
120 *
121 * @param frame The frame to calculate the insets relative to.
122 * @return The calculated insets.
123 */
124 public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100125 boolean alwaysConsumeNavBar, DisplayCutout cutout,
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100126 @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100127 @Nullable @InsetSide SparseIntArray typeSideMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100128 Insets[] typeInsetsMap = new Insets[Type.SIZE];
129 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
Jorim Jaggi90990792019-01-21 23:00:20 +0100130 boolean[] typeVisibilityMap = new boolean[SIZE];
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200131 final Rect relativeFrame = new Rect(frame);
132 final Rect relativeFrameMax = new Rect(frame);
Jorim Jaggi90990792019-01-21 23:00:20 +0100133 if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100134 && legacyContentInsets != null && legacyStableInsets != null) {
135 WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
136 WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
137 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200138 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
139 InsetsSource source = mSources.get(type);
140 if (source == null) {
141 continue;
142 }
Jorim Jaggi90990792019-01-21 23:00:20 +0100143 if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
144 && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) {
145 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
146 continue;
147 }
148
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100149 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
Jorim Jaggi90990792019-01-21 23:00:20 +0100150 typeSideMap, typeVisibilityMap);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200151
152 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
153 // target.
154 if (source.getType() != TYPE_IME) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100155 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
Jorim Jaggi90990792019-01-21 23:00:20 +0100156 typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200157 }
158 }
Jorim Jaggi90990792019-01-21 23:00:20 +0100159 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200160 alwaysConsumeNavBar, cutout);
161 }
162
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100163 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Jorim Jaggi90990792019-01-21 23:00:20 +0100164 Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
165 @Nullable boolean[] typeVisibilityMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100166 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
167
168 int index = indexOf(toPublicType(source.getType()));
169 Insets existing = typeInsetsMap[index];
170 if (existing == null) {
171 typeInsetsMap[index] = insets;
172 } else {
173 typeInsetsMap[index] = Insets.max(existing, insets);
174 }
175
Jorim Jaggi90990792019-01-21 23:00:20 +0100176 if (typeVisibilityMap != null) {
177 typeVisibilityMap[index] = source.isVisible();
178 }
179
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100180 if (typeSideMap != null && !Insets.NONE.equals(insets)) {
181 @InsetSide int insetSide = getInsetSide(insets);
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100182 if (insetSide != INSET_SIDE_UNKNWON) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100183 typeSideMap.put(source.getType(), getInsetSide(insets));
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100184 }
185 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200186 }
187
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100188 /**
189 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
190 * is set in order that this method returns a meaningful result.
191 */
192 private @InsetSide int getInsetSide(Insets insets) {
193 if (insets.left != 0) {
194 return INSET_SIDE_LEFT;
195 }
196 if (insets.top != 0) {
197 return INSET_SIDE_TOP;
198 }
199 if (insets.right != 0) {
200 return INSET_SIDE_RIGHT;
201 }
202 if (insets.bottom != 0) {
203 return INSET_SIDE_BOTTOM;
204 }
205 return INSET_SIDE_UNKNWON;
206 }
207
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200208 public InsetsSource getSource(@InternalInsetType int type) {
209 return mSources.computeIfAbsent(type, InsetsSource::new);
210 }
211
212 /**
213 * Modifies the state of this class to exclude a certain type to make it ready for dispatching
214 * to the client.
215 *
216 * @param type The {@link InternalInsetType} of the source to remove
217 */
218 public void removeSource(int type) {
219 mSources.remove(type);
220 }
221
222 public void set(InsetsState other) {
223 set(other, false /* copySources */);
224 }
225
226 public void set(InsetsState other, boolean copySources) {
227 mSources.clear();
228 if (copySources) {
229 for (int i = 0; i < other.mSources.size(); i++) {
230 InsetsSource source = other.mSources.valueAt(i);
231 mSources.put(source.getType(), new InsetsSource(source));
232 }
233 } else {
234 mSources.putAll(other.mSources);
235 }
236 }
237
Jorim Jaggie35c0592018-11-06 16:21:08 +0100238 public void addSource(InsetsSource source) {
239 mSources.put(source.getType(), source);
240 }
241
242 public int getSourcesCount() {
243 return mSources.size();
244 }
245
246 public InsetsSource sourceAt(int index) {
247 return mSources.valueAt(index);
248 }
249
Jorim Jaggib6030952018-10-23 18:31:52 +0200250 public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) {
251 final ArraySet<Integer> result = new ArraySet<>();
252 if ((insetTypes & Type.TOP_BAR) != 0) {
253 result.add(TYPE_TOP_BAR);
254 }
255 if ((insetTypes & Type.SIDE_BARS) != 0) {
256 result.add(TYPE_SIDE_BAR_1);
257 result.add(TYPE_SIDE_BAR_2);
258 result.add(TYPE_SIDE_BAR_3);
259 }
260 if ((insetTypes & Type.IME) != 0) {
261 result.add(TYPE_IME);
262 }
263 return result;
264 }
265
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100266 static @InsetType int toPublicType(@InternalInsetType int type) {
267 switch (type) {
268 case TYPE_TOP_BAR:
269 return Type.TOP_BAR;
270 case TYPE_SIDE_BAR_1:
271 case TYPE_SIDE_BAR_2:
272 case TYPE_SIDE_BAR_3:
273 return Type.SIDE_BARS;
274 case TYPE_IME:
275 return Type.IME;
276 default:
277 throw new IllegalArgumentException("Unknown type: " + type);
278 }
279 }
280
Jorim Jaggid89efeb2019-01-22 17:48:34 +0100281 public static boolean getDefaultVisibility(@InsetType int type) {
Jorim Jaggie35c0592018-11-06 16:21:08 +0100282 switch (type) {
283 case TYPE_TOP_BAR:
284 case TYPE_SIDE_BAR_1:
285 case TYPE_SIDE_BAR_2:
286 case TYPE_SIDE_BAR_3:
287 return true;
288 case TYPE_IME:
289 return false;
290 default:
291 return true;
292 }
293 }
294
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200295 public void dump(String prefix, PrintWriter pw) {
296 pw.println(prefix + "InsetsState");
297 for (int i = mSources.size() - 1; i >= 0; i--) {
298 mSources.valueAt(i).dump(prefix + " ", pw);
299 }
300 }
301
Jorim Jaggicfd6f3b2018-11-07 15:30:18 +0100302 public static String typeToString(int type) {
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200303 switch (type) {
304 case TYPE_TOP_BAR:
305 return "TYPE_TOP_BAR";
306 case TYPE_SIDE_BAR_1:
307 return "TYPE_SIDE_BAR_1";
308 case TYPE_SIDE_BAR_2:
309 return "TYPE_SIDE_BAR_2";
310 case TYPE_SIDE_BAR_3:
311 return "TYPE_SIDE_BAR_3";
312 case TYPE_IME:
313 return "TYPE_IME";
314 default:
315 return "TYPE_UNKNOWN";
316 }
317 }
318
319 @Override
320 public boolean equals(Object o) {
321 if (this == o) { return true; }
322 if (o == null || getClass() != o.getClass()) { return false; }
323
324 InsetsState state = (InsetsState) o;
325
326 if (mSources.size() != state.mSources.size()) {
327 return false;
328 }
329 for (int i = mSources.size() - 1; i >= 0; i--) {
330 InsetsSource source = mSources.valueAt(i);
331 InsetsSource otherSource = state.mSources.get(source.getType());
332 if (otherSource == null) {
333 return false;
334 }
335 if (!otherSource.equals(source)) {
336 return false;
337 }
338 }
339 return true;
340 }
341
342 @Override
343 public int hashCode() {
344 return mSources.hashCode();
345 }
346
347 public InsetsState(Parcel in) {
348 readFromParcel(in);
349 }
350
351 @Override
352 public int describeContents() {
353 return 0;
354 }
355
356 @Override
357 public void writeToParcel(Parcel dest, int flags) {
358 dest.writeInt(mSources.size());
359 for (int i = 0; i < mSources.size(); i++) {
360 dest.writeParcelable(mSources.valueAt(i), 0 /* flags */);
361 }
362 }
363
364 public static final Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
365
366 public InsetsState createFromParcel(Parcel in) {
367 return new InsetsState(in);
368 }
369
370 public InsetsState[] newArray(int size) {
371 return new InsetsState[size];
372 }
373 };
374
375 public void readFromParcel(Parcel in) {
376 mSources.clear();
377 final int size = in.readInt();
378 for (int i = 0; i < size; i++) {
379 final InsetsSource source = in.readParcelable(null /* loader */);
380 mSources.put(source.getType(), source);
381 }
382 }
383}
384