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