blob: 69f86aa5b37e2963dd1e3a8e0a3ab7ea3d3002bb [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;
Jorim Jaggi648e5882019-01-24 13:24:02 +010022import static android.view.WindowInsets.Type.IME;
Jorim Jaggi90990792019-01-21 23:00:20 +010023import static android.view.WindowInsets.Type.SIZE;
Jorim Jaggibcf99ff2018-12-03 18:04:26 +010024import static android.view.WindowInsets.Type.indexOf;
25
Jorim Jaggif96c90a2018-09-26 16:55:15 +020026import android.annotation.IntDef;
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010027import android.annotation.Nullable;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020028import android.graphics.Insets;
29import android.graphics.Rect;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.util.ArrayMap;
Jorim Jaggib6030952018-10-23 18:31:52 +020033import android.util.ArraySet;
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010034import android.util.SparseArray;
35import android.util.SparseIntArray;
Jorim Jaggib6030952018-10-23 18:31:52 +020036import android.view.WindowInsets.Type;
37import android.view.WindowInsets.Type.InsetType;
Jorim Jaggi648e5882019-01-24 13:24:02 +010038import android.view.WindowManager.LayoutParams;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020039
40import java.io.PrintWriter;
41import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
Jorim Jaggie35c0592018-11-06 16:21:08 +010043import java.util.ArrayList;
Tarandeep Singha6f35612019-01-11 19:50:46 -080044import java.util.Objects;
Jorim Jaggif96c90a2018-09-26 16:55:15 +020045
46/**
47 * Holder for state of system windows that cause window insets for all other windows in the system.
48 * @hide
49 */
50public class InsetsState implements Parcelable {
51
52 /**
53 * Internal representation of inset source types. This is different from the public API in
54 * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
55 * at the same time.
56 */
57 @Retention(RetentionPolicy.SOURCE)
58 @IntDef(prefix = "TYPE", value = {
59 TYPE_TOP_BAR,
60 TYPE_SIDE_BAR_1,
61 TYPE_SIDE_BAR_2,
62 TYPE_SIDE_BAR_3,
63 TYPE_IME
64 })
65 public @interface InternalInsetType {}
66
67 static final int FIRST_TYPE = 0;
68
69 /** Top bar. Can be status bar or caption in freeform windowing mode. */
70 public static final int TYPE_TOP_BAR = FIRST_TYPE;
71
72 /**
73 * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar
74 * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have
75 * multiple, like Android Auto.
76 */
77 public static final int TYPE_SIDE_BAR_1 = 1;
78 public static final int TYPE_SIDE_BAR_2 = 2;
79 public static final int TYPE_SIDE_BAR_3 = 3;
80
81 /** Input method window. */
82 public static final int TYPE_IME = 4;
83 static final int LAST_TYPE = TYPE_IME;
84
85 // Derived types
86
87 /** First side bar is navigation bar. */
88 public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1;
89
90 /** A shelf is the same as the navigation bar. */
91 public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR;
92
Jorim Jaggi5bb571d2018-11-06 14:42:04 +010093 @Retention(RetentionPolicy.SOURCE)
94 @IntDef(prefix = "INSET_SIDE", value = {
95 INSET_SIDE_LEFT,
96 INSET_SIDE_TOP,
97 INSET_SIDE_RIGHT,
98 INSET_SIDE_BOTTOM,
99 INSET_SIDE_UNKNWON
100 })
101 public @interface InsetSide {}
102 static final int INSET_SIDE_LEFT = 0;
103 static final int INSET_SIDE_TOP = 1;
104 static final int INSET_SIDE_RIGHT = 2;
105 static final int INSET_SIDE_BOTTOM = 3;
106 static final int INSET_SIDE_UNKNWON = 4;
107
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200108 private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
109
Tarandeep Singha6f35612019-01-11 19:50:46 -0800110 /**
111 * The frame of the display these sources are relative to.
112 */
113 private final Rect mDisplayFrame = new Rect();
114
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200115 public InsetsState() {
116 }
117
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100118 public InsetsState(InsetsState copy) {
119 set(copy);
120 }
121
Jorim Jaggi02a741f2018-12-12 17:40:19 -0800122 public InsetsState(InsetsState copy, boolean copySources) {
123 set(copy, copySources);
124 }
125
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200126 /**
127 * Calculates {@link WindowInsets} based on the current source configuration.
128 *
129 * @param frame The frame to calculate the insets relative to.
130 * @return The calculated insets.
131 */
132 public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100133 boolean alwaysConsumeNavBar, DisplayCutout cutout,
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100134 @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
Jorim Jaggi648e5882019-01-24 13:24:02 +0100135 int legacySoftInputMode, @Nullable @InsetSide SparseIntArray typeSideMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100136 Insets[] typeInsetsMap = new Insets[Type.SIZE];
137 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
Jorim Jaggi90990792019-01-21 23:00:20 +0100138 boolean[] typeVisibilityMap = new boolean[SIZE];
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200139 final Rect relativeFrame = new Rect(frame);
140 final Rect relativeFrameMax = new Rect(frame);
Jorim Jaggi90990792019-01-21 23:00:20 +0100141 if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
Jorim Jaggi73f3e8a2019-01-14 13:06:23 +0100142 && legacyContentInsets != null && legacyStableInsets != null) {
143 WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
144 WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
145 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200146 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
147 InsetsSource source = mSources.get(type);
148 if (source == null) {
149 continue;
150 }
Jorim Jaggi648e5882019-01-24 13:24:02 +0100151
152 boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
153 && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
154 boolean skipIme = source.getType() == TYPE_IME
155 && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
156 if (skipSystemBars || skipIme) {
Jorim Jaggi90990792019-01-21 23:00:20 +0100157 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
158 continue;
159 }
160
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100161 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
Jorim Jaggi90990792019-01-21 23:00:20 +0100162 typeSideMap, typeVisibilityMap);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200163
164 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
165 // target.
166 if (source.getType() != TYPE_IME) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100167 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
Jorim Jaggi90990792019-01-21 23:00:20 +0100168 typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200169 }
170 }
Jorim Jaggi90990792019-01-21 23:00:20 +0100171 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200172 alwaysConsumeNavBar, cutout);
173 }
174
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100175 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Jorim Jaggi90990792019-01-21 23:00:20 +0100176 Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
177 @Nullable boolean[] typeVisibilityMap) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100178 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
179
180 int index = indexOf(toPublicType(source.getType()));
181 Insets existing = typeInsetsMap[index];
182 if (existing == null) {
183 typeInsetsMap[index] = insets;
184 } else {
185 typeInsetsMap[index] = Insets.max(existing, insets);
186 }
187
Jorim Jaggi90990792019-01-21 23:00:20 +0100188 if (typeVisibilityMap != null) {
189 typeVisibilityMap[index] = source.isVisible();
190 }
191
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100192 if (typeSideMap != null && !Insets.NONE.equals(insets)) {
193 @InsetSide int insetSide = getInsetSide(insets);
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100194 if (insetSide != INSET_SIDE_UNKNWON) {
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100195 typeSideMap.put(source.getType(), getInsetSide(insets));
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100196 }
197 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200198 }
199
Jorim Jaggi5bb571d2018-11-06 14:42:04 +0100200 /**
201 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
202 * is set in order that this method returns a meaningful result.
203 */
204 private @InsetSide int getInsetSide(Insets insets) {
205 if (insets.left != 0) {
206 return INSET_SIDE_LEFT;
207 }
208 if (insets.top != 0) {
209 return INSET_SIDE_TOP;
210 }
211 if (insets.right != 0) {
212 return INSET_SIDE_RIGHT;
213 }
214 if (insets.bottom != 0) {
215 return INSET_SIDE_BOTTOM;
216 }
217 return INSET_SIDE_UNKNWON;
218 }
219
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200220 public InsetsSource getSource(@InternalInsetType int type) {
221 return mSources.computeIfAbsent(type, InsetsSource::new);
222 }
223
Tarandeep Singha6f35612019-01-11 19:50:46 -0800224 public void setDisplayFrame(Rect frame) {
225 mDisplayFrame.set(frame);
226 }
227
228 public Rect getDisplayFrame() {
229 return mDisplayFrame;
230 }
231
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200232 /**
233 * Modifies the state of this class to exclude a certain type to make it ready for dispatching
234 * to the client.
235 *
236 * @param type The {@link InternalInsetType} of the source to remove
237 */
238 public void removeSource(int type) {
239 mSources.remove(type);
240 }
241
242 public void set(InsetsState other) {
243 set(other, false /* copySources */);
244 }
245
246 public void set(InsetsState other, boolean copySources) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800247 mDisplayFrame.set(other.mDisplayFrame);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200248 mSources.clear();
249 if (copySources) {
250 for (int i = 0; i < other.mSources.size(); i++) {
251 InsetsSource source = other.mSources.valueAt(i);
252 mSources.put(source.getType(), new InsetsSource(source));
253 }
254 } else {
255 mSources.putAll(other.mSources);
256 }
257 }
258
Jorim Jaggie35c0592018-11-06 16:21:08 +0100259 public void addSource(InsetsSource source) {
260 mSources.put(source.getType(), source);
261 }
262
263 public int getSourcesCount() {
264 return mSources.size();
265 }
266
267 public InsetsSource sourceAt(int index) {
268 return mSources.valueAt(index);
269 }
270
Jorim Jaggib6030952018-10-23 18:31:52 +0200271 public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) {
272 final ArraySet<Integer> result = new ArraySet<>();
273 if ((insetTypes & Type.TOP_BAR) != 0) {
274 result.add(TYPE_TOP_BAR);
275 }
276 if ((insetTypes & Type.SIDE_BARS) != 0) {
277 result.add(TYPE_SIDE_BAR_1);
278 result.add(TYPE_SIDE_BAR_2);
279 result.add(TYPE_SIDE_BAR_3);
280 }
281 if ((insetTypes & Type.IME) != 0) {
282 result.add(TYPE_IME);
283 }
284 return result;
285 }
286
Jorim Jaggibcf99ff2018-12-03 18:04:26 +0100287 static @InsetType int toPublicType(@InternalInsetType int type) {
288 switch (type) {
289 case TYPE_TOP_BAR:
290 return Type.TOP_BAR;
291 case TYPE_SIDE_BAR_1:
292 case TYPE_SIDE_BAR_2:
293 case TYPE_SIDE_BAR_3:
294 return Type.SIDE_BARS;
295 case TYPE_IME:
296 return Type.IME;
297 default:
298 throw new IllegalArgumentException("Unknown type: " + type);
299 }
300 }
301
Jorim Jaggid89efeb2019-01-22 17:48:34 +0100302 public static boolean getDefaultVisibility(@InsetType int type) {
Jorim Jaggie35c0592018-11-06 16:21:08 +0100303 switch (type) {
304 case TYPE_TOP_BAR:
305 case TYPE_SIDE_BAR_1:
306 case TYPE_SIDE_BAR_2:
307 case TYPE_SIDE_BAR_3:
308 return true;
309 case TYPE_IME:
310 return false;
311 default:
312 return true;
313 }
314 }
315
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200316 public void dump(String prefix, PrintWriter pw) {
317 pw.println(prefix + "InsetsState");
318 for (int i = mSources.size() - 1; i >= 0; i--) {
319 mSources.valueAt(i).dump(prefix + " ", pw);
320 }
321 }
322
Jorim Jaggicfd6f3b2018-11-07 15:30:18 +0100323 public static String typeToString(int type) {
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200324 switch (type) {
325 case TYPE_TOP_BAR:
326 return "TYPE_TOP_BAR";
327 case TYPE_SIDE_BAR_1:
328 return "TYPE_SIDE_BAR_1";
329 case TYPE_SIDE_BAR_2:
330 return "TYPE_SIDE_BAR_2";
331 case TYPE_SIDE_BAR_3:
332 return "TYPE_SIDE_BAR_3";
333 case TYPE_IME:
334 return "TYPE_IME";
335 default:
336 return "TYPE_UNKNOWN";
337 }
338 }
339
340 @Override
341 public boolean equals(Object o) {
342 if (this == o) { return true; }
343 if (o == null || getClass() != o.getClass()) { return false; }
344
345 InsetsState state = (InsetsState) o;
346
Tarandeep Singha6f35612019-01-11 19:50:46 -0800347 if (!mDisplayFrame.equals(state.mDisplayFrame)) {
348 return false;
349 }
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200350 if (mSources.size() != state.mSources.size()) {
351 return false;
352 }
353 for (int i = mSources.size() - 1; i >= 0; i--) {
354 InsetsSource source = mSources.valueAt(i);
355 InsetsSource otherSource = state.mSources.get(source.getType());
356 if (otherSource == null) {
357 return false;
358 }
359 if (!otherSource.equals(source)) {
360 return false;
361 }
362 }
363 return true;
364 }
365
366 @Override
367 public int hashCode() {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800368 return Objects.hash(mDisplayFrame, mSources);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200369 }
370
371 public InsetsState(Parcel in) {
372 readFromParcel(in);
373 }
374
375 @Override
376 public int describeContents() {
377 return 0;
378 }
379
380 @Override
381 public void writeToParcel(Parcel dest, int flags) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800382 dest.writeParcelable(mDisplayFrame, flags);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200383 dest.writeInt(mSources.size());
384 for (int i = 0; i < mSources.size(); i++) {
Tarandeep Singha6f35612019-01-11 19:50:46 -0800385 dest.writeParcelable(mSources.valueAt(i), flags);
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200386 }
387 }
388
389 public static final Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
390
391 public InsetsState createFromParcel(Parcel in) {
392 return new InsetsState(in);
393 }
394
395 public InsetsState[] newArray(int size) {
396 return new InsetsState[size];
397 }
398 };
399
400 public void readFromParcel(Parcel in) {
401 mSources.clear();
Tarandeep Singha6f35612019-01-11 19:50:46 -0800402 mDisplayFrame.set(in.readParcelable(null /* loader */));
Jorim Jaggif96c90a2018-09-26 16:55:15 +0200403 final int size = in.readInt();
404 for (int i = 0; i < size; i++) {
405 final InsetsSource source = in.readParcelable(null /* loader */);
406 mSources.put(source.getType(), source);
407 }
408 }
409}
410