blob: c9273d8cc2a9e62df26be970a25944839be5ac18 [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;
Tarandeep Singha6f35612019-01-11 19:50:46 -080042import java.util.Objects;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020043
44/**
45 * Holder for state of system windows that cause window insets for all other windows in the system.
46 * @hide
47 */
48public 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_IME
62 })
63 public @interface InternalInsetType {}
64
65 static final int FIRST_TYPE = 0;
66
67 /** Top bar. Can be status bar or caption in freeform windowing mode. */
68 public static final int TYPE_TOP_BAR = FIRST_TYPE;
69
70 /**
71 * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar
72 * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have
73 * multiple, like Android Auto.
74 */
75 public static final int TYPE_SIDE_BAR_1 = 1;
76 public static final int TYPE_SIDE_BAR_2 = 2;
77 public static final int TYPE_SIDE_BAR_3 = 3;
78
79 /** Input method window. */
80 public static final int TYPE_IME = 4;
81 static final int LAST_TYPE = TYPE_IME;
82
83 // Derived types
84
85 /** First side bar is navigation bar. */
86 public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1;
87
88 /** A shelf is the same as the navigation bar. */
89 public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR;
90
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010091 @Retention(RetentionPolicy.SOURCE)
92 @IntDef(prefix = "INSET_SIDE", value = {
93 INSET_SIDE_LEFT,
94 INSET_SIDE_TOP,
95 INSET_SIDE_RIGHT,
96 INSET_SIDE_BOTTOM,
97 INSET_SIDE_UNKNWON
98 })
99 public @interface InsetSide {}
100 static final int INSET_SIDE_LEFT = 0;
101 static final int INSET_SIDE_TOP = 1;
102 static final int INSET_SIDE_RIGHT = 2;
103 static final int INSET_SIDE_BOTTOM = 3;
104 static final int INSET_SIDE_UNKNWON = 4;
105
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200106 private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
107
Tarandeep Singha6f35612019-01-11 19:50:46 -0800108 /**
109 * The frame of the display these sources are relative to.
110 */
111 private final Rect mDisplayFrame = new Rect();
112
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200113 public InsetsState() {
114 }
115
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100116 public InsetsState(InsetsState copy) {
117 set(copy);
118 }
119
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800120 public InsetsState(InsetsState copy, boolean copySources) {
121 set(copy, copySources);
122 }
123
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200124 /**
125 * Calculates {@link WindowInsets} based on the current source configuration.
126 *
127 * @param frame The frame to calculate the insets relative to.
128 * @return The calculated insets.
129 */
130 public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100131 boolean alwaysConsumeNavBar, DisplayCutout cutout,
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100132 @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100133 @Nullable @InsetSide SparseIntArray typeSideMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100134 Insets[] typeInsetsMap = new Insets[Type.SIZE];
135 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
Jorim Jaggi90990792019-01-21 23:00:20 +0100136 boolean[] typeVisibilityMap = new boolean[SIZE];
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200137 final Rect relativeFrame = new Rect(frame);
138 final Rect relativeFrameMax = new Rect(frame);
Jorim Jaggi90990792019-01-21 23:00:20 +0100139 if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100140 && legacyContentInsets != null && legacyStableInsets != null) {
141 WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
142 WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
143 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200144 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
145 InsetsSource source = mSources.get(type);
146 if (source == null) {
147 continue;
148 }
Jorim Jaggi90990792019-01-21 23:00:20 +0100149 if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
150 && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) {
151 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
152 continue;
153 }
154
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100155 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
Jorim Jaggi90990792019-01-21 23:00:20 +0100156 typeSideMap, typeVisibilityMap);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200157
158 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
159 // target.
160 if (source.getType() != TYPE_IME) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100161 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
Jorim Jaggi90990792019-01-21 23:00:20 +0100162 typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200163 }
164 }
Jorim Jaggi90990792019-01-21 23:00:20 +0100165 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200166 alwaysConsumeNavBar, cutout);
167 }
168
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100169 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Jorim Jaggi90990792019-01-21 23:00:20 +0100170 Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
171 @Nullable boolean[] typeVisibilityMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100172 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
173
174 int index = indexOf(toPublicType(source.getType()));
175 Insets existing = typeInsetsMap[index];
176 if (existing == null) {
177 typeInsetsMap[index] = insets;
178 } else {
179 typeInsetsMap[index] = Insets.max(existing, insets);
180 }
181
Jorim Jaggi90990792019-01-21 23:00:20 +0100182 if (typeVisibilityMap != null) {
183 typeVisibilityMap[index] = source.isVisible();
184 }
185
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100186 if (typeSideMap != null && !Insets.NONE.equals(insets)) {
187 @InsetSide int insetSide = getInsetSide(insets);
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100188 if (insetSide != INSET_SIDE_UNKNWON) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100189 typeSideMap.put(source.getType(), getInsetSide(insets));
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100190 }
191 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200192 }
193
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100194 /**
195 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
196 * is set in order that this method returns a meaningful result.
197 */
198 private @InsetSide int getInsetSide(Insets insets) {
199 if (insets.left != 0) {
200 return INSET_SIDE_LEFT;
201 }
202 if (insets.top != 0) {
203 return INSET_SIDE_TOP;
204 }
205 if (insets.right != 0) {
206 return INSET_SIDE_RIGHT;
207 }
208 if (insets.bottom != 0) {
209 return INSET_SIDE_BOTTOM;
210 }
211 return INSET_SIDE_UNKNWON;
212 }
213
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200214 public InsetsSource getSource(@InternalInsetType int type) {
215 return mSources.computeIfAbsent(type, InsetsSource::new);
216 }
217
Tarandeep Singha6f35612019-01-11 19:50:46 -0800218 public void setDisplayFrame(Rect frame) {
219 mDisplayFrame.set(frame);
220 }
221
222 public Rect getDisplayFrame() {
223 return mDisplayFrame;
224 }
225
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200226 /**
227 * Modifies the state of this class to exclude a certain type to make it ready for dispatching
228 * to the client.
229 *
230 * @param type The {@link InternalInsetType} of the source to remove
231 */
232 public void removeSource(int type) {
233 mSources.remove(type);
234 }
235
236 public void set(InsetsState other) {
237 set(other, false /* copySources */);
238 }
239
240 public void set(InsetsState other, boolean copySources) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800241 mDisplayFrame.set(other.mDisplayFrame);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200242 mSources.clear();
243 if (copySources) {
244 for (int i = 0; i < other.mSources.size(); i++) {
245 InsetsSource source = other.mSources.valueAt(i);
246 mSources.put(source.getType(), new InsetsSource(source));
247 }
248 } else {
249 mSources.putAll(other.mSources);
250 }
251 }
252
Jorim Jaggie35c0592018-11-06 16:21:08 +0100253 public void addSource(InsetsSource source) {
254 mSources.put(source.getType(), source);
255 }
256
257 public int getSourcesCount() {
258 return mSources.size();
259 }
260
261 public InsetsSource sourceAt(int index) {
262 return mSources.valueAt(index);
263 }
264
Jorim Jaggib6030952018-10-23 18:31:52 +0200265 public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) {
266 final ArraySet<Integer> result = new ArraySet<>();
267 if ((insetTypes & Type.TOP_BAR) != 0) {
268 result.add(TYPE_TOP_BAR);
269 }
270 if ((insetTypes & Type.SIDE_BARS) != 0) {
271 result.add(TYPE_SIDE_BAR_1);
272 result.add(TYPE_SIDE_BAR_2);
273 result.add(TYPE_SIDE_BAR_3);
274 }
275 if ((insetTypes & Type.IME) != 0) {
276 result.add(TYPE_IME);
277 }
278 return result;
279 }
280
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100281 static @InsetType int toPublicType(@InternalInsetType int type) {
282 switch (type) {
283 case TYPE_TOP_BAR:
284 return Type.TOP_BAR;
285 case TYPE_SIDE_BAR_1:
286 case TYPE_SIDE_BAR_2:
287 case TYPE_SIDE_BAR_3:
288 return Type.SIDE_BARS;
289 case TYPE_IME:
290 return Type.IME;
291 default:
292 throw new IllegalArgumentException("Unknown type: " + type);
293 }
294 }
295
Jorim Jaggid89efeb2019-01-22 17:48:34 +0100296 public static boolean getDefaultVisibility(@InsetType int type) {
Jorim Jaggie35c0592018-11-06 16:21:08 +0100297 switch (type) {
298 case TYPE_TOP_BAR:
299 case TYPE_SIDE_BAR_1:
300 case TYPE_SIDE_BAR_2:
301 case TYPE_SIDE_BAR_3:
302 return true;
303 case TYPE_IME:
304 return false;
305 default:
306 return true;
307 }
308 }
309
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200310 public void dump(String prefix, PrintWriter pw) {
311 pw.println(prefix + "InsetsState");
312 for (int i = mSources.size() - 1; i >= 0; i--) {
313 mSources.valueAt(i).dump(prefix + " ", pw);
314 }
315 }
316
Jorim Jaggicfd6f3b2018-11-07 15:30:18 +0100317 public static String typeToString(int type) {
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200318 switch (type) {
319 case TYPE_TOP_BAR:
320 return "TYPE_TOP_BAR";
321 case TYPE_SIDE_BAR_1:
322 return "TYPE_SIDE_BAR_1";
323 case TYPE_SIDE_BAR_2:
324 return "TYPE_SIDE_BAR_2";
325 case TYPE_SIDE_BAR_3:
326 return "TYPE_SIDE_BAR_3";
327 case TYPE_IME:
328 return "TYPE_IME";
329 default:
330 return "TYPE_UNKNOWN";
331 }
332 }
333
334 @Override
335 public boolean equals(Object o) {
336 if (this == o) { return true; }
337 if (o == null || getClass() != o.getClass()) { return false; }
338
339 InsetsState state = (InsetsState) o;
340
Tarandeep Singha6f35612019-01-11 19:50:46 -0800341 if (!mDisplayFrame.equals(state.mDisplayFrame)) {
342 return false;
343 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200344 if (mSources.size() != state.mSources.size()) {
345 return false;
346 }
347 for (int i = mSources.size() - 1; i >= 0; i--) {
348 InsetsSource source = mSources.valueAt(i);
349 InsetsSource otherSource = state.mSources.get(source.getType());
350 if (otherSource == null) {
351 return false;
352 }
353 if (!otherSource.equals(source)) {
354 return false;
355 }
356 }
357 return true;
358 }
359
360 @Override
361 public int hashCode() {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800362 return Objects.hash(mDisplayFrame, mSources);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200363 }
364
365 public InsetsState(Parcel in) {
366 readFromParcel(in);
367 }
368
369 @Override
370 public int describeContents() {
371 return 0;
372 }
373
374 @Override
375 public void writeToParcel(Parcel dest, int flags) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800376 dest.writeParcelable(mDisplayFrame, flags);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200377 dest.writeInt(mSources.size());
378 for (int i = 0; i < mSources.size(); i++) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800379 dest.writeParcelable(mSources.valueAt(i), flags);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200380 }
381 }
382
383 public static final Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
384
385 public InsetsState createFromParcel(Parcel in) {
386 return new InsetsState(in);
387 }
388
389 public InsetsState[] newArray(int size) {
390 return new InsetsState[size];
391 }
392 };
393
394 public void readFromParcel(Parcel in) {
395 mSources.clear();
Tarandeep Singha6f35612019-01-11 19:50:46 -0800396 mDisplayFrame.set(in.readParcelable(null /* loader */));
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200397 final int size = in.readInt();
398 for (int i = 0; i < size; i++) {
399 final InsetsSource source = in.readParcelable(null /* loader */);
400 mSources.put(source.getType(), source);
401 }
402 }
403}
404