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.InsetsStateProto.DISPLAY_CUTOUT; 20 import static android.view.InsetsStateProto.DISPLAY_FRAME; 21 import static android.view.InsetsStateProto.SOURCES; 22 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 23 import static android.view.WindowInsets.Type.displayCutout; 24 import static android.view.WindowInsets.Type.ime; 25 import static android.view.WindowInsets.Type.indexOf; 26 import static android.view.WindowInsets.Type.isVisibleInsetsType; 27 import static android.view.WindowInsets.Type.statusBars; 28 import static android.view.WindowInsets.Type.systemBars; 29 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; 30 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 31 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 32 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 33 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; 34 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 35 36 import android.annotation.IntDef; 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.app.WindowConfiguration; 40 import android.graphics.Insets; 41 import android.graphics.Rect; 42 import android.os.Parcel; 43 import android.os.Parcelable; 44 import android.util.ArraySet; 45 import android.util.SparseIntArray; 46 import android.util.proto.ProtoOutputStream; 47 import android.view.WindowInsets.Type; 48 import android.view.WindowInsets.Type.InsetsType; 49 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 50 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.io.PrintWriter; 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 import java.util.Arrays; 57 import java.util.Objects; 58 import java.util.StringJoiner; 59 60 /** 61 * Holder for state of system windows that cause window insets for all other windows in the system. 62 * @hide 63 */ 64 public class InsetsState implements Parcelable { 65 66 /** 67 * Internal representation of inset source types. This is different from the public API in 68 * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows 69 * at the same time. 70 */ 71 @Retention(RetentionPolicy.SOURCE) 72 @IntDef(prefix = "ITYPE", value = { 73 ITYPE_STATUS_BAR, 74 ITYPE_NAVIGATION_BAR, 75 ITYPE_CAPTION_BAR, 76 ITYPE_TOP_GESTURES, 77 ITYPE_BOTTOM_GESTURES, 78 ITYPE_LEFT_GESTURES, 79 ITYPE_RIGHT_GESTURES, 80 ITYPE_TOP_MANDATORY_GESTURES, 81 ITYPE_BOTTOM_MANDATORY_GESTURES, 82 ITYPE_LEFT_MANDATORY_GESTURES, 83 ITYPE_RIGHT_MANDATORY_GESTURES, 84 ITYPE_TOP_TAPPABLE_ELEMENT, 85 ITYPE_BOTTOM_TAPPABLE_ELEMENT, 86 ITYPE_LEFT_DISPLAY_CUTOUT, 87 ITYPE_TOP_DISPLAY_CUTOUT, 88 ITYPE_RIGHT_DISPLAY_CUTOUT, 89 ITYPE_BOTTOM_DISPLAY_CUTOUT, 90 ITYPE_IME, 91 ITYPE_CLIMATE_BAR, 92 ITYPE_EXTRA_NAVIGATION_BAR 93 }) 94 public @interface InternalInsetsType {} 95 96 /** 97 * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate 98 * that the objects/parameters aren't associated with an {@link InternalInsetsType} 99 */ 100 public static final int ITYPE_INVALID = -1; 101 102 static final int FIRST_TYPE = 0; 103 104 public static final int ITYPE_STATUS_BAR = FIRST_TYPE; 105 public static final int ITYPE_NAVIGATION_BAR = 1; 106 public static final int ITYPE_CAPTION_BAR = 2; 107 108 public static final int ITYPE_TOP_GESTURES = 3; 109 public static final int ITYPE_BOTTOM_GESTURES = 4; 110 public static final int ITYPE_LEFT_GESTURES = 5; 111 public static final int ITYPE_RIGHT_GESTURES = 6; 112 113 public static final int ITYPE_TOP_MANDATORY_GESTURES = 7; 114 public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8; 115 public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9; 116 public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10; 117 118 public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11; 119 public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12; 120 public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13; 121 public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14; 122 123 public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15; 124 public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16; 125 public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17; 126 public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18; 127 128 /** Input method window. */ 129 public static final int ITYPE_IME = 19; 130 131 /** Additional system decorations inset type. */ 132 public static final int ITYPE_CLIMATE_BAR = 20; 133 public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21; 134 135 static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR; 136 public static final int SIZE = LAST_TYPE + 1; 137 138 // Derived types 139 140 /** A shelf is the same as the navigation bar. */ 141 public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR; 142 143 @Retention(RetentionPolicy.SOURCE) 144 @IntDef(prefix = "IINSETS_SIDE", value = { 145 ISIDE_LEFT, 146 ISIDE_TOP, 147 ISIDE_RIGHT, 148 ISIDE_BOTTOM, 149 ISIDE_FLOATING, 150 ISIDE_UNKNOWN 151 }) 152 public @interface InternalInsetsSide {} 153 static final int ISIDE_LEFT = 0; 154 static final int ISIDE_TOP = 1; 155 static final int ISIDE_RIGHT = 2; 156 static final int ISIDE_BOTTOM = 3; 157 static final int ISIDE_FLOATING = 4; 158 static final int ISIDE_UNKNOWN = 5; 159 160 private final InsetsSource[] mSources = new InsetsSource[SIZE]; 161 162 /** 163 * The frame of the display these sources are relative to. 164 */ 165 private final Rect mDisplayFrame = new Rect(); 166 167 /** The area cut from the display. */ 168 private final DisplayCutout.ParcelableWrapper mDisplayCutout = 169 new DisplayCutout.ParcelableWrapper(); 170 171 /** The rounded corners on the display */ 172 private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; 173 174 /** The bounds of the Privacy Indicator */ 175 private PrivacyIndicatorBounds mPrivacyIndicatorBounds = 176 new PrivacyIndicatorBounds(); 177 InsetsState()178 public InsetsState() { 179 } 180 InsetsState(InsetsState copy)181 public InsetsState(InsetsState copy) { 182 set(copy); 183 } 184 InsetsState(InsetsState copy, boolean copySources)185 public InsetsState(InsetsState copy, boolean copySources) { 186 set(copy, copySources); 187 } 188 189 /** 190 * Calculates {@link WindowInsets} based on the current source configuration. 191 * 192 * @param frame The frame to calculate the insets relative to. 193 * @param ignoringVisibilityState {@link InsetsState} used to calculate 194 * {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass 195 * {@code null} to use this state to calculate that information. 196 * @return The calculated insets. 197 */ calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, boolean alwaysConsumeSystemBars, int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, int windowType, @WindowConfiguration.WindowingMode int windowingMode, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)198 public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, 199 boolean isScreenRound, boolean alwaysConsumeSystemBars, 200 int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, 201 int windowType, @WindowConfiguration.WindowingMode int windowingMode, 202 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 203 Insets[] typeInsetsMap = new Insets[Type.SIZE]; 204 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; 205 boolean[] typeVisibilityMap = new boolean[SIZE]; 206 final Rect relativeFrame = new Rect(frame); 207 final Rect relativeFrameMax = new Rect(frame); 208 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 209 InsetsSource source = mSources[type]; 210 if (source == null) { 211 int index = indexOf(toPublicType(type)); 212 if (typeInsetsMap[index] == null) { 213 typeInsetsMap[index] = Insets.NONE; 214 } 215 continue; 216 } 217 218 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, 219 typeSideMap, typeVisibilityMap); 220 221 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME 222 // target. 223 if (source.getType() != ITYPE_IME) { 224 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null 225 ? ignoringVisibilityState.getSource(type) 226 : source; 227 if (ignoringVisibilitySource == null) { 228 continue; 229 } 230 processSource(ignoringVisibilitySource, relativeFrameMax, 231 true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */, 232 null /* typeVisibilityMap */); 233 } 234 } 235 final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; 236 237 @InsetsType int compatInsetsTypes = systemBars() | displayCutout(); 238 if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) { 239 compatInsetsTypes |= ime(); 240 } 241 if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { 242 compatInsetsTypes &= ~statusBars(); 243 } 244 if (clearCompatInsets(windowType, legacyWindowFlags, windowingMode)) { 245 compatInsetsTypes = 0; 246 } 247 248 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, 249 alwaysConsumeSystemBars, calculateRelativeCutout(frame), 250 calculateRelativeRoundedCorners(frame), 251 calculateRelativePrivacyIndicatorBounds(frame), 252 compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); 253 } 254 calculateRelativeCutout(Rect frame)255 private DisplayCutout calculateRelativeCutout(Rect frame) { 256 final DisplayCutout raw = mDisplayCutout.get(); 257 if (mDisplayFrame.equals(frame)) { 258 return raw; 259 } 260 if (frame == null) { 261 return DisplayCutout.NO_CUTOUT; 262 } 263 final int insetLeft = frame.left - mDisplayFrame.left; 264 final int insetTop = frame.top - mDisplayFrame.top; 265 final int insetRight = mDisplayFrame.right - frame.right; 266 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 267 if (insetLeft >= raw.getSafeInsetLeft() 268 && insetTop >= raw.getSafeInsetTop() 269 && insetRight >= raw.getSafeInsetRight() 270 && insetBottom >= raw.getSafeInsetBottom()) { 271 return DisplayCutout.NO_CUTOUT; 272 } 273 return raw.inset(insetLeft, insetTop, insetRight, insetBottom); 274 } 275 calculateRelativeRoundedCorners(Rect frame)276 private RoundedCorners calculateRelativeRoundedCorners(Rect frame) { 277 if (mDisplayFrame.equals(frame)) { 278 return mRoundedCorners; 279 } 280 if (frame == null) { 281 return RoundedCorners.NO_ROUNDED_CORNERS; 282 } 283 final int insetLeft = frame.left - mDisplayFrame.left; 284 final int insetTop = frame.top - mDisplayFrame.top; 285 final int insetRight = mDisplayFrame.right - frame.right; 286 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 287 return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom); 288 } 289 calculateRelativePrivacyIndicatorBounds(Rect frame)290 private PrivacyIndicatorBounds calculateRelativePrivacyIndicatorBounds(Rect frame) { 291 if (mDisplayFrame.equals(frame)) { 292 return mPrivacyIndicatorBounds; 293 } 294 if (frame == null) { 295 return null; 296 } 297 final int insetLeft = frame.left - mDisplayFrame.left; 298 final int insetTop = frame.top - mDisplayFrame.top; 299 final int insetRight = mDisplayFrame.right - frame.right; 300 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 301 return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom); 302 } 303 calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility)304 public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { 305 Insets insets = Insets.NONE; 306 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 307 InsetsSource source = mSources[type]; 308 if (source == null) { 309 continue; 310 } 311 int publicType = InsetsState.toPublicType(type); 312 if ((publicType & types) == 0) { 313 continue; 314 } 315 insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets); 316 } 317 return insets.toRect(); 318 } 319 calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode)320 public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { 321 Insets insets = Insets.NONE; 322 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 323 InsetsSource source = mSources[type]; 324 if (source == null) { 325 continue; 326 } 327 328 // Ignore everything that's not a system bar or IME. 329 int publicType = InsetsState.toPublicType(type); 330 if (!isVisibleInsetsType(publicType, softInputMode)) { 331 continue; 332 } 333 insets = Insets.max(source.calculateVisibleInsets(frame), insets); 334 } 335 return insets.toRect(); 336 } 337 338 /** 339 * Calculate which insets *cannot* be controlled, because the frame does not cover the 340 * respective side of the inset. 341 * 342 * If the frame of our window doesn't cover the entire inset, the control API makes very 343 * little sense, as we don't deal with negative insets. 344 */ 345 @InsetsType calculateUncontrollableInsetsFromFrame(Rect frame)346 public int calculateUncontrollableInsetsFromFrame(Rect frame) { 347 int blocked = 0; 348 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 349 InsetsSource source = mSources[type]; 350 if (source == null) { 351 continue; 352 } 353 if (!canControlSide(frame, getInsetSide( 354 source.calculateInsets(frame, true /* ignoreVisibility */)))) { 355 blocked |= toPublicType(type); 356 } 357 } 358 return blocked; 359 } 360 canControlSide(Rect frame, int side)361 private boolean canControlSide(Rect frame, int side) { 362 switch (side) { 363 case ISIDE_LEFT: 364 case ISIDE_RIGHT: 365 return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right; 366 case ISIDE_TOP: 367 case ISIDE_BOTTOM: 368 return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom; 369 case ISIDE_FLOATING: 370 return true; 371 default: 372 return false; 373 } 374 } 375 processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap)376 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, 377 Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, 378 @Nullable boolean[] typeVisibilityMap) { 379 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); 380 381 int type = toPublicType(source.getType()); 382 processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, 383 insets, type); 384 385 if (type == Type.MANDATORY_SYSTEM_GESTURES) { 386 // Mandatory system gestures are also system gestures. 387 // TODO: find a way to express this more generally. One option would be to define 388 // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the 389 // ability to set systemGestureInsets() independently from 390 // mandatorySystemGestureInsets() in the Builder. 391 processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, 392 insets, Type.SYSTEM_GESTURES); 393 } 394 } 395 processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, @InternalInsetsSide @Nullable SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type)396 private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, 397 @InternalInsetsSide @Nullable SparseIntArray typeSideMap, 398 @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { 399 int index = indexOf(type); 400 Insets existing = typeInsetsMap[index]; 401 if (existing == null) { 402 typeInsetsMap[index] = insets; 403 } else { 404 typeInsetsMap[index] = Insets.max(existing, insets); 405 } 406 407 if (typeVisibilityMap != null) { 408 typeVisibilityMap[index] = source.isVisible(); 409 } 410 411 if (typeSideMap != null) { 412 @InternalInsetsSide int insetSide = getInsetSide(insets); 413 if (insetSide != ISIDE_UNKNOWN) { 414 typeSideMap.put(source.getType(), insetSide); 415 } 416 } 417 } 418 419 /** 420 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b 421 * is set in order that this method returns a meaningful result. 422 */ getInsetSide(Insets insets)423 static @InternalInsetsSide int getInsetSide(Insets insets) { 424 if (Insets.NONE.equals(insets)) { 425 return ISIDE_FLOATING; 426 } 427 if (insets.left != 0) { 428 return ISIDE_LEFT; 429 } 430 if (insets.top != 0) { 431 return ISIDE_TOP; 432 } 433 if (insets.right != 0) { 434 return ISIDE_RIGHT; 435 } 436 if (insets.bottom != 0) { 437 return ISIDE_BOTTOM; 438 } 439 return ISIDE_UNKNOWN; 440 } 441 getSource(@nternalInsetsType int type)442 public InsetsSource getSource(@InternalInsetsType int type) { 443 InsetsSource source = mSources[type]; 444 if (source != null) { 445 return source; 446 } 447 source = new InsetsSource(type); 448 mSources[type] = source; 449 return source; 450 } 451 peekSource(@nternalInsetsType int type)452 public @Nullable InsetsSource peekSource(@InternalInsetsType int type) { 453 return mSources[type]; 454 } 455 456 /** 457 * Returns the source visibility or the default visibility if the source doesn't exist. This is 458 * useful if when treating this object as a request. 459 * 460 * @param type The {@link InternalInsetsType} to query. 461 * @return {@code true} if the source is visible or the type is default visible and the source 462 * doesn't exist. 463 */ getSourceOrDefaultVisibility(@nternalInsetsType int type)464 public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) { 465 final InsetsSource source = mSources[type]; 466 return source != null ? source.isVisible() : getDefaultVisibility(type); 467 } 468 setDisplayFrame(Rect frame)469 public void setDisplayFrame(Rect frame) { 470 mDisplayFrame.set(frame); 471 } 472 getDisplayFrame()473 public Rect getDisplayFrame() { 474 return mDisplayFrame; 475 } 476 setDisplayCutout(DisplayCutout cutout)477 public void setDisplayCutout(DisplayCutout cutout) { 478 mDisplayCutout.set(cutout); 479 } 480 getDisplayCutout()481 public DisplayCutout getDisplayCutout() { 482 return mDisplayCutout.get(); 483 } 484 setRoundedCorners(RoundedCorners roundedCorners)485 public void setRoundedCorners(RoundedCorners roundedCorners) { 486 mRoundedCorners = roundedCorners; 487 } 488 getRoundedCorners()489 public RoundedCorners getRoundedCorners() { 490 return mRoundedCorners; 491 } 492 setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds)493 public void setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds) { 494 mPrivacyIndicatorBounds = bounds; 495 } 496 getPrivacyIndicatorBounds()497 public PrivacyIndicatorBounds getPrivacyIndicatorBounds() { 498 return mPrivacyIndicatorBounds; 499 } 500 501 /** 502 * Modifies the state of this class to exclude a certain type to make it ready for dispatching 503 * to the client. 504 * 505 * @param type The {@link InternalInsetsType} of the source to remove 506 * @return {@code true} if this InsetsState was modified; {@code false} otherwise. 507 */ removeSource(@nternalInsetsType int type)508 public boolean removeSource(@InternalInsetsType int type) { 509 if (mSources[type] == null) { 510 return false; 511 } 512 mSources[type] = null; 513 return true; 514 } 515 516 /** 517 * A shortcut for setting the visibility of the source. 518 * 519 * @param type The {@link InternalInsetsType} of the source to set the visibility 520 * @param visible {@code true} for visible 521 */ setSourceVisible(@nternalInsetsType int type, boolean visible)522 public void setSourceVisible(@InternalInsetsType int type, boolean visible) { 523 InsetsSource source = mSources[type]; 524 if (source != null) { 525 source.setVisible(visible); 526 } 527 } 528 529 /** 530 * Scales the frame and the visible frame (if there is one) of each source. 531 * 532 * @param scale the scale to be applied 533 */ scale(float scale)534 public void scale(float scale) { 535 mDisplayFrame.scale(scale); 536 mDisplayCutout.scale(scale); 537 mRoundedCorners = mRoundedCorners.scale(scale); 538 mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale); 539 for (int i = 0; i < SIZE; i++) { 540 final InsetsSource source = mSources[i]; 541 if (source != null) { 542 source.getFrame().scale(scale); 543 final Rect visibleFrame = source.getVisibleFrame(); 544 if (visibleFrame != null) { 545 visibleFrame.scale(scale); 546 } 547 } 548 } 549 } 550 set(InsetsState other)551 public void set(InsetsState other) { 552 set(other, false /* copySources */); 553 } 554 set(InsetsState other, boolean copySources)555 public void set(InsetsState other, boolean copySources) { 556 mDisplayFrame.set(other.mDisplayFrame); 557 mDisplayCutout.set(other.mDisplayCutout); 558 mRoundedCorners = other.getRoundedCorners(); 559 mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); 560 if (copySources) { 561 for (int i = 0; i < SIZE; i++) { 562 InsetsSource source = other.mSources[i]; 563 mSources[i] = source != null ? new InsetsSource(source) : null; 564 } 565 } else { 566 for (int i = 0; i < SIZE; i++) { 567 mSources[i] = other.mSources[i]; 568 } 569 } 570 } 571 572 /** 573 * Sets the values from the other InsetsState. But for sources, only specific types of source 574 * would be set. 575 * 576 * @param other the other InsetsState. 577 * @param types the only types of sources would be set. 578 */ set(InsetsState other, @InsetsType int types)579 public void set(InsetsState other, @InsetsType int types) { 580 mDisplayFrame.set(other.mDisplayFrame); 581 mDisplayCutout.set(other.mDisplayCutout); 582 mRoundedCorners = other.getRoundedCorners(); 583 mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); 584 final ArraySet<Integer> t = toInternalType(types); 585 for (int i = t.size() - 1; i >= 0; i--) { 586 final int type = t.valueAt(i); 587 mSources[type] = other.mSources[type]; 588 } 589 } 590 addSource(InsetsSource source)591 public void addSource(InsetsSource source) { 592 mSources[source.getType()] = source; 593 } 594 clearCompatInsets(int windowType, int windowFlags, int windowingMode)595 public static boolean clearCompatInsets(int windowType, int windowFlags, int windowingMode) { 596 return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0 597 && windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR 598 && !WindowConfiguration.inMultiWindowMode(windowingMode); 599 } 600 toInternalType(@nsetsType int types)601 public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) { 602 final ArraySet<Integer> result = new ArraySet<>(); 603 if ((types & Type.STATUS_BARS) != 0) { 604 result.add(ITYPE_STATUS_BAR); 605 result.add(ITYPE_CLIMATE_BAR); 606 } 607 if ((types & Type.NAVIGATION_BARS) != 0) { 608 result.add(ITYPE_NAVIGATION_BAR); 609 result.add(ITYPE_EXTRA_NAVIGATION_BAR); 610 } 611 if ((types & Type.CAPTION_BAR) != 0) { 612 result.add(ITYPE_CAPTION_BAR); 613 } 614 if ((types & Type.SYSTEM_GESTURES) != 0) { 615 result.add(ITYPE_LEFT_GESTURES); 616 result.add(ITYPE_TOP_GESTURES); 617 result.add(ITYPE_RIGHT_GESTURES); 618 result.add(ITYPE_BOTTOM_GESTURES); 619 } 620 if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) { 621 result.add(ITYPE_LEFT_MANDATORY_GESTURES); 622 result.add(ITYPE_TOP_MANDATORY_GESTURES); 623 result.add(ITYPE_RIGHT_MANDATORY_GESTURES); 624 result.add(ITYPE_BOTTOM_MANDATORY_GESTURES); 625 } 626 if ((types & Type.DISPLAY_CUTOUT) != 0) { 627 result.add(ITYPE_LEFT_DISPLAY_CUTOUT); 628 result.add(ITYPE_TOP_DISPLAY_CUTOUT); 629 result.add(ITYPE_RIGHT_DISPLAY_CUTOUT); 630 result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT); 631 } 632 if ((types & Type.IME) != 0) { 633 result.add(ITYPE_IME); 634 } 635 return result; 636 } 637 638 /** 639 * Converting a internal type to the public type. 640 * @param type internal insets type, {@code InternalInsetsType}. 641 * @return public insets type, {@code Type.InsetsType}. 642 */ toPublicType(@nternalInsetsType int type)643 public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) { 644 switch (type) { 645 case ITYPE_STATUS_BAR: 646 case ITYPE_CLIMATE_BAR: 647 return Type.STATUS_BARS; 648 case ITYPE_NAVIGATION_BAR: 649 case ITYPE_EXTRA_NAVIGATION_BAR: 650 return Type.NAVIGATION_BARS; 651 case ITYPE_CAPTION_BAR: 652 return Type.CAPTION_BAR; 653 case ITYPE_IME: 654 return Type.IME; 655 case ITYPE_TOP_MANDATORY_GESTURES: 656 case ITYPE_BOTTOM_MANDATORY_GESTURES: 657 case ITYPE_LEFT_MANDATORY_GESTURES: 658 case ITYPE_RIGHT_MANDATORY_GESTURES: 659 return Type.MANDATORY_SYSTEM_GESTURES; 660 case ITYPE_TOP_GESTURES: 661 case ITYPE_BOTTOM_GESTURES: 662 case ITYPE_LEFT_GESTURES: 663 case ITYPE_RIGHT_GESTURES: 664 return Type.SYSTEM_GESTURES; 665 case ITYPE_LEFT_TAPPABLE_ELEMENT: 666 case ITYPE_TOP_TAPPABLE_ELEMENT: 667 case ITYPE_RIGHT_TAPPABLE_ELEMENT: 668 case ITYPE_BOTTOM_TAPPABLE_ELEMENT: 669 return Type.TAPPABLE_ELEMENT; 670 case ITYPE_LEFT_DISPLAY_CUTOUT: 671 case ITYPE_TOP_DISPLAY_CUTOUT: 672 case ITYPE_RIGHT_DISPLAY_CUTOUT: 673 case ITYPE_BOTTOM_DISPLAY_CUTOUT: 674 return Type.DISPLAY_CUTOUT; 675 default: 676 throw new IllegalArgumentException("Unknown type: " + type); 677 } 678 } 679 getDefaultVisibility(@nternalInsetsType int type)680 public static boolean getDefaultVisibility(@InternalInsetsType int type) { 681 return type != ITYPE_IME; 682 } 683 containsType(@nternalInsetsType int[] types, @InternalInsetsType int type)684 public static boolean containsType(@InternalInsetsType int[] types, 685 @InternalInsetsType int type) { 686 if (types == null) { 687 return false; 688 } 689 for (int t : types) { 690 if (t == type) { 691 return true; 692 } 693 } 694 return false; 695 } 696 dump(String prefix, PrintWriter pw)697 public void dump(String prefix, PrintWriter pw) { 698 final String newPrefix = prefix + " "; 699 pw.println(prefix + "InsetsState"); 700 pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame); 701 pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get()); 702 pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners); 703 pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds); 704 for (int i = 0; i < SIZE; i++) { 705 InsetsSource source = mSources[i]; 706 if (source == null) continue; 707 source.dump(newPrefix + " ", pw); 708 } 709 } 710 dumpDebug(ProtoOutputStream proto, long fieldId)711 void dumpDebug(ProtoOutputStream proto, long fieldId) { 712 final long token = proto.start(fieldId); 713 InsetsSource source = mSources[ITYPE_IME]; 714 if (source != null) { 715 source.dumpDebug(proto, SOURCES); 716 } 717 mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME); 718 mDisplayCutout.get().dumpDebug(proto, DISPLAY_CUTOUT); 719 proto.end(token); 720 } 721 typeToString(@nternalInsetsType int type)722 public static String typeToString(@InternalInsetsType int type) { 723 switch (type) { 724 case ITYPE_STATUS_BAR: 725 return "ITYPE_STATUS_BAR"; 726 case ITYPE_NAVIGATION_BAR: 727 return "ITYPE_NAVIGATION_BAR"; 728 case ITYPE_CAPTION_BAR: 729 return "ITYPE_CAPTION_BAR"; 730 case ITYPE_TOP_GESTURES: 731 return "ITYPE_TOP_GESTURES"; 732 case ITYPE_BOTTOM_GESTURES: 733 return "ITYPE_BOTTOM_GESTURES"; 734 case ITYPE_LEFT_GESTURES: 735 return "ITYPE_LEFT_GESTURES"; 736 case ITYPE_RIGHT_GESTURES: 737 return "ITYPE_RIGHT_GESTURES"; 738 case ITYPE_TOP_MANDATORY_GESTURES: 739 return "ITYPE_TOP_MANDATORY_GESTURES"; 740 case ITYPE_BOTTOM_MANDATORY_GESTURES: 741 return "ITYPE_BOTTOM_MANDATORY_GESTURES"; 742 case ITYPE_LEFT_MANDATORY_GESTURES: 743 return "ITYPE_LEFT_MANDATORY_GESTURES"; 744 case ITYPE_RIGHT_MANDATORY_GESTURES: 745 return "ITYPE_RIGHT_MANDATORY_GESTURES"; 746 case ITYPE_LEFT_TAPPABLE_ELEMENT: 747 return "ITYPE_LEFT_TAPPABLE_ELEMENT"; 748 case ITYPE_TOP_TAPPABLE_ELEMENT: 749 return "ITYPE_TOP_TAPPABLE_ELEMENT"; 750 case ITYPE_RIGHT_TAPPABLE_ELEMENT: 751 return "ITYPE_RIGHT_TAPPABLE_ELEMENT"; 752 case ITYPE_BOTTOM_TAPPABLE_ELEMENT: 753 return "ITYPE_BOTTOM_TAPPABLE_ELEMENT"; 754 case ITYPE_LEFT_DISPLAY_CUTOUT: 755 return "ITYPE_LEFT_DISPLAY_CUTOUT"; 756 case ITYPE_TOP_DISPLAY_CUTOUT: 757 return "ITYPE_TOP_DISPLAY_CUTOUT"; 758 case ITYPE_RIGHT_DISPLAY_CUTOUT: 759 return "ITYPE_RIGHT_DISPLAY_CUTOUT"; 760 case ITYPE_BOTTOM_DISPLAY_CUTOUT: 761 return "ITYPE_BOTTOM_DISPLAY_CUTOUT"; 762 case ITYPE_IME: 763 return "ITYPE_IME"; 764 case ITYPE_CLIMATE_BAR: 765 return "ITYPE_CLIMATE_BAR"; 766 case ITYPE_EXTRA_NAVIGATION_BAR: 767 return "ITYPE_EXTRA_NAVIGATION_BAR"; 768 default: 769 return "ITYPE_UNKNOWN_" + type; 770 } 771 } 772 773 @Override equals(@ullable Object o)774 public boolean equals(@Nullable Object o) { 775 return equals(o, false, false); 776 } 777 778 /** 779 * An equals method can exclude the caption insets. This is useful because we assemble the 780 * caption insets information on the client side, and when we communicate with server, it's 781 * excluded. 782 * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but 783 * ignore the caption insets source value. 784 * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is 785 * not visible. 786 * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. 787 */ 788 @VisibleForTesting equals(@ullable Object o, boolean excludingCaptionInsets, boolean excludeInvisibleImeFrames)789 public boolean equals(@Nullable Object o, boolean excludingCaptionInsets, 790 boolean excludeInvisibleImeFrames) { 791 if (this == o) { return true; } 792 if (o == null || getClass() != o.getClass()) { return false; } 793 794 InsetsState state = (InsetsState) o; 795 796 if (!mDisplayFrame.equals(state.mDisplayFrame) 797 || !mDisplayCutout.equals(state.mDisplayCutout) 798 || !mRoundedCorners.equals(state.mRoundedCorners) 799 || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) { 800 return false; 801 } 802 for (int i = 0; i < SIZE; i++) { 803 if (excludingCaptionInsets) { 804 if (i == ITYPE_CAPTION_BAR) continue; 805 } 806 InsetsSource source = mSources[i]; 807 InsetsSource otherSource = state.mSources[i]; 808 if (source == null && otherSource == null) { 809 continue; 810 } 811 if (source == null || otherSource == null) { 812 return false; 813 } 814 if (!otherSource.equals(source, excludeInvisibleImeFrames)) { 815 return false; 816 } 817 } 818 return true; 819 } 820 821 @Override hashCode()822 public int hashCode() { 823 return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources), 824 mRoundedCorners, mPrivacyIndicatorBounds); 825 } 826 InsetsState(Parcel in)827 public InsetsState(Parcel in) { 828 readFromParcel(in); 829 } 830 831 @Override describeContents()832 public int describeContents() { 833 return 0; 834 } 835 836 @Override writeToParcel(Parcel dest, int flags)837 public void writeToParcel(Parcel dest, int flags) { 838 mDisplayFrame.writeToParcel(dest, flags); 839 mDisplayCutout.writeToParcel(dest, flags); 840 dest.writeTypedArray(mSources, 0 /* parcelableFlags */); 841 dest.writeTypedObject(mRoundedCorners, flags); 842 dest.writeTypedObject(mPrivacyIndicatorBounds, flags); 843 } 844 845 public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() { 846 847 public InsetsState createFromParcel(Parcel in) { 848 return new InsetsState(in); 849 } 850 851 public InsetsState[] newArray(int size) { 852 return new InsetsState[size]; 853 } 854 }; 855 readFromParcel(Parcel in)856 public void readFromParcel(Parcel in) { 857 mDisplayFrame.readFromParcel(in); 858 mDisplayCutout.readFromParcel(in); 859 in.readTypedArray(mSources, InsetsSource.CREATOR); 860 mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); 861 mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); 862 } 863 864 @Override toString()865 public String toString() { 866 StringJoiner joiner = new StringJoiner(", "); 867 for (int i = 0; i < SIZE; i++) { 868 InsetsSource source = mSources[i]; 869 if (source != null) { 870 joiner.add(source.toString()); 871 } 872 } 873 return "InsetsState: {" 874 + "mDisplayFrame=" + mDisplayFrame 875 + ", mDisplayCutout=" + mDisplayCutout 876 + ", mRoundedCorners=" + mRoundedCorners 877 + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds 878 + ", mSources= { " + joiner 879 + " }"; 880 } 881 toSourceVisibilityString()882 public @NonNull String toSourceVisibilityString() { 883 StringJoiner joiner = new StringJoiner(", "); 884 for (int i = 0; i < SIZE; i++) { 885 InsetsSource source = mSources[i]; 886 if (source != null) { 887 joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible")); 888 } 889 } 890 return joiner.toString(); 891 } 892 } 893 894