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.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 20 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; 21 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; 22 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; 23 import static android.view.ViewRootImpl.sNewInsetsMode; 24 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; 25 import static android.view.WindowInsets.Type.SYSTEM_GESTURES; 26 import static android.view.WindowInsets.Type.displayCutout; 27 import static android.view.WindowInsets.Type.ime; 28 import static android.view.WindowInsets.Type.indexOf; 29 import static android.view.WindowInsets.Type.isVisibleInsetsType; 30 import static android.view.WindowInsets.Type.statusBars; 31 import static android.view.WindowInsets.Type.systemBars; 32 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; 33 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 34 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 35 36 import android.annotation.IntDef; 37 import android.annotation.Nullable; 38 import android.graphics.Insets; 39 import android.graphics.Rect; 40 import android.os.Parcel; 41 import android.os.Parcelable; 42 import android.util.ArraySet; 43 import android.util.SparseIntArray; 44 import android.view.WindowInsets.Type; 45 import android.view.WindowInsets.Type.InsetsType; 46 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.io.PrintWriter; 51 import java.lang.annotation.Retention; 52 import java.lang.annotation.RetentionPolicy; 53 import java.util.Arrays; 54 import java.util.Objects; 55 import java.util.StringJoiner; 56 57 /** 58 * Holder for state of system windows that cause window insets for all other windows in the system. 59 * @hide 60 */ 61 public class InsetsState implements Parcelable { 62 63 /** 64 * Internal representation of inset source types. This is different from the public API in 65 * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows 66 * at the same time. 67 */ 68 @Retention(RetentionPolicy.SOURCE) 69 @IntDef(prefix = "ITYPE", value = { 70 ITYPE_STATUS_BAR, 71 ITYPE_NAVIGATION_BAR, 72 ITYPE_CAPTION_BAR, 73 ITYPE_TOP_GESTURES, 74 ITYPE_BOTTOM_GESTURES, 75 ITYPE_LEFT_GESTURES, 76 ITYPE_RIGHT_GESTURES, 77 ITYPE_TOP_TAPPABLE_ELEMENT, 78 ITYPE_BOTTOM_TAPPABLE_ELEMENT, 79 ITYPE_LEFT_DISPLAY_CUTOUT, 80 ITYPE_TOP_DISPLAY_CUTOUT, 81 ITYPE_RIGHT_DISPLAY_CUTOUT, 82 ITYPE_BOTTOM_DISPLAY_CUTOUT, 83 ITYPE_IME, 84 ITYPE_CLIMATE_BAR, 85 ITYPE_EXTRA_NAVIGATION_BAR 86 }) 87 public @interface InternalInsetsType {} 88 89 /** 90 * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate 91 * that the objects/parameters aren't associated with an {@link InternalInsetsType} 92 */ 93 public static final int ITYPE_INVALID = -1; 94 95 static final int FIRST_TYPE = 0; 96 97 public static final int ITYPE_STATUS_BAR = FIRST_TYPE; 98 public static final int ITYPE_NAVIGATION_BAR = 1; 99 public static final int ITYPE_CAPTION_BAR = 2; 100 101 public static final int ITYPE_TOP_GESTURES = 3; 102 public static final int ITYPE_BOTTOM_GESTURES = 4; 103 public static final int ITYPE_LEFT_GESTURES = 5; 104 public static final int ITYPE_RIGHT_GESTURES = 6; 105 public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7; 106 public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8; 107 108 public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9; 109 public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10; 110 public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11; 111 public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12; 112 113 /** Input method window. */ 114 public static final int ITYPE_IME = 13; 115 116 /** Additional system decorations inset type. */ 117 public static final int ITYPE_CLIMATE_BAR = 14; 118 public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15; 119 120 static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR; 121 public static final int SIZE = LAST_TYPE + 1; 122 123 // Derived types 124 125 /** A shelf is the same as the navigation bar. */ 126 public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR; 127 128 @Retention(RetentionPolicy.SOURCE) 129 @IntDef(prefix = "IINSETS_SIDE", value = { 130 ISIDE_LEFT, 131 ISIDE_TOP, 132 ISIDE_RIGHT, 133 ISIDE_BOTTOM, 134 ISIDE_FLOATING, 135 ISIDE_UNKNOWN 136 }) 137 public @interface InternalInsetsSide {} 138 static final int ISIDE_LEFT = 0; 139 static final int ISIDE_TOP = 1; 140 static final int ISIDE_RIGHT = 2; 141 static final int ISIDE_BOTTOM = 3; 142 static final int ISIDE_FLOATING = 4; 143 static final int ISIDE_UNKNOWN = 5; 144 145 private InsetsSource[] mSources = new InsetsSource[SIZE]; 146 147 /** 148 * The frame of the display these sources are relative to. 149 */ 150 private final Rect mDisplayFrame = new Rect(); 151 152 public InsetsState() { 153 } 154 155 public InsetsState(InsetsState copy) { 156 set(copy); 157 } 158 159 public InsetsState(InsetsState copy, boolean copySources) { 160 set(copy, copySources); 161 } 162 163 /** 164 * Calculates {@link WindowInsets} based on the current source configuration. 165 * 166 * @param frame The frame to calculate the insets relative to. 167 * @param ignoringVisibilityState {@link InsetsState} used to calculate 168 * {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass 169 * {@code null} to use this state to calculate that information. 170 * @return The calculated insets. 171 */ 172 public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, 173 boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, 174 int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, 175 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 176 Insets[] typeInsetsMap = new Insets[Type.SIZE]; 177 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; 178 boolean[] typeVisibilityMap = new boolean[SIZE]; 179 final Rect relativeFrame = new Rect(frame); 180 final Rect relativeFrameMax = new Rect(frame); 181 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 182 InsetsSource source = mSources[type]; 183 if (source == null) { 184 int index = indexOf(toPublicType(type)); 185 if (typeInsetsMap[index] == null) { 186 typeInsetsMap[index] = Insets.NONE; 187 } 188 continue; 189 } 190 191 boolean skipNonImeInImeMode = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME 192 && source.getType() != ITYPE_IME; 193 boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL 194 && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR); 195 boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE 196 && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR 197 || type == ITYPE_IME); 198 if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) { 199 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); 200 continue; 201 } 202 203 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, 204 typeSideMap, typeVisibilityMap); 205 206 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME 207 // target. 208 if (source.getType() != ITYPE_IME) { 209 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null 210 ? ignoringVisibilityState.getSource(type) 211 : source; 212 if (ignoringVisibilitySource == null) { 213 continue; 214 } 215 processSource(ignoringVisibilitySource, relativeFrameMax, 216 true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */, 217 null /* typeVisibilityMap */); 218 } 219 } 220 final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; 221 222 @InsetsType int compatInsetsTypes = systemBars() | displayCutout(); 223 if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) { 224 compatInsetsTypes |= ime(); 225 } 226 if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { 227 compatInsetsTypes &= ~statusBars(); 228 } 229 230 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, 231 alwaysConsumeSystemBars, cutout, compatInsetsTypes, 232 sNewInsetsMode == NEW_INSETS_MODE_FULL 233 && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); 234 } 235 236 public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { 237 Insets insets = Insets.NONE; 238 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 239 InsetsSource source = mSources[type]; 240 if (source == null) { 241 continue; 242 } 243 if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) { 244 continue; 245 } 246 247 // Ignore everything that's not a system bar or IME. 248 int publicType = InsetsState.toPublicType(type); 249 if (!isVisibleInsetsType(publicType, softInputMode)) { 250 continue; 251 } 252 insets = Insets.max(source.calculateVisibleInsets(frame), insets); 253 } 254 return insets.toRect(); 255 } 256 257 /** 258 * Calculate which insets *cannot* be controlled, because the frame does not cover the 259 * respective side of the inset. 260 * 261 * If the frame of our window doesn't cover the entire inset, the control API makes very 262 * little sense, as we don't deal with negative insets. 263 */ 264 @InsetsType 265 public int calculateUncontrollableInsetsFromFrame(Rect frame) { 266 int blocked = 0; 267 for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { 268 InsetsSource source = mSources[type]; 269 if (source == null) { 270 continue; 271 } 272 if (!canControlSide(frame, getInsetSide( 273 source.calculateInsets(frame, true /* ignoreVisibility */)))) { 274 blocked |= toPublicType(type); 275 } 276 } 277 return blocked; 278 } 279 280 private boolean canControlSide(Rect frame, int side) { 281 switch (side) { 282 case ISIDE_LEFT: 283 case ISIDE_RIGHT: 284 return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right; 285 case ISIDE_TOP: 286 case ISIDE_BOTTOM: 287 return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom; 288 case ISIDE_FLOATING: 289 return true; 290 default: 291 return false; 292 } 293 } 294 295 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, 296 Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, 297 @Nullable boolean[] typeVisibilityMap) { 298 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); 299 300 int type = toPublicType(source.getType()); 301 processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, 302 insets, type); 303 304 if (type == MANDATORY_SYSTEM_GESTURES) { 305 // Mandatory system gestures are also system gestures. 306 // TODO: find a way to express this more generally. One option would be to define 307 // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the 308 // ability to set systemGestureInsets() independently from 309 // mandatorySystemGestureInsets() in the Builder. 310 processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, 311 insets, SYSTEM_GESTURES); 312 } 313 } 314 315 private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, 316 @InternalInsetsSide @Nullable SparseIntArray typeSideMap, 317 @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { 318 int index = indexOf(type); 319 Insets existing = typeInsetsMap[index]; 320 if (existing == null) { 321 typeInsetsMap[index] = insets; 322 } else { 323 typeInsetsMap[index] = Insets.max(existing, insets); 324 } 325 326 if (typeVisibilityMap != null) { 327 typeVisibilityMap[index] = source.isVisible(); 328 } 329 330 if (typeSideMap != null) { 331 @InternalInsetsSide int insetSide = getInsetSide(insets); 332 if (insetSide != ISIDE_UNKNOWN) { 333 typeSideMap.put(source.getType(), insetSide); 334 } 335 } 336 } 337 338 /** 339 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b 340 * is set in order that this method returns a meaningful result. 341 */ 342 private @InternalInsetsSide int getInsetSide(Insets insets) { 343 if (Insets.NONE.equals(insets)) { 344 return ISIDE_FLOATING; 345 } 346 if (insets.left != 0) { 347 return ISIDE_LEFT; 348 } 349 if (insets.top != 0) { 350 return ISIDE_TOP; 351 } 352 if (insets.right != 0) { 353 return ISIDE_RIGHT; 354 } 355 if (insets.bottom != 0) { 356 return ISIDE_BOTTOM; 357 } 358 return ISIDE_UNKNOWN; 359 } 360 361 public InsetsSource getSource(@InternalInsetsType int type) { 362 InsetsSource source = mSources[type]; 363 if (source != null) { 364 return source; 365 } 366 source = new InsetsSource(type); 367 mSources[type] = source; 368 return source; 369 } 370 371 public @Nullable InsetsSource peekSource(@InternalInsetsType int type) { 372 return mSources[type]; 373 } 374 375 public boolean hasSources() { 376 for (int i = 0; i < SIZE; i++) { 377 if (mSources[i] != null) { 378 return true; 379 } 380 } 381 return false; 382 } 383 384 /** 385 * Returns the source visibility or the default visibility if the source doesn't exist. This is 386 * useful if when treating this object as a request. 387 * 388 * @param type The {@link InternalInsetsType} to query. 389 * @return {@code true} if the source is visible or the type is default visible and the source 390 * doesn't exist. 391 */ 392 public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) { 393 final InsetsSource source = mSources[type]; 394 return source != null ? source.isVisible() : getDefaultVisibility(type); 395 } 396 397 public void setDisplayFrame(Rect frame) { 398 mDisplayFrame.set(frame); 399 } 400 401 public Rect getDisplayFrame() { 402 return mDisplayFrame; 403 } 404 405 /** 406 * Modifies the state of this class to exclude a certain type to make it ready for dispatching 407 * to the client. 408 * 409 * @param type The {@link InternalInsetsType} of the source to remove 410 */ 411 public void removeSource(@InternalInsetsType int type) { 412 mSources[type] = null; 413 } 414 415 /** 416 * A shortcut for setting the visibility of the source. 417 * 418 * @param type The {@link InternalInsetsType} of the source to set the visibility 419 * @param visible {@code true} for visible 420 */ 421 public void setSourceVisible(@InternalInsetsType int type, boolean visible) { 422 InsetsSource source = mSources[type]; 423 if (source != null) { 424 source.setVisible(visible); 425 } 426 } 427 428 public void set(InsetsState other) { 429 set(other, false /* copySources */); 430 } 431 432 public void set(InsetsState other, boolean copySources) { 433 mDisplayFrame.set(other.mDisplayFrame); 434 if (copySources) { 435 for (int i = 0; i < SIZE; i++) { 436 InsetsSource source = other.mSources[i]; 437 mSources[i] = source != null ? new InsetsSource(source) : null; 438 } 439 } else { 440 for (int i = 0; i < SIZE; i++) { 441 mSources[i] = other.mSources[i]; 442 } 443 } 444 } 445 446 public void addSource(InsetsSource source) { 447 mSources[source.getType()] = source; 448 } 449 450 public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) { 451 final ArraySet<Integer> result = new ArraySet<>(); 452 if ((types & Type.STATUS_BARS) != 0) { 453 result.add(ITYPE_STATUS_BAR); 454 } 455 if ((types & Type.NAVIGATION_BARS) != 0) { 456 result.add(ITYPE_NAVIGATION_BAR); 457 } 458 if ((types & Type.CAPTION_BAR) != 0) { 459 result.add(ITYPE_CAPTION_BAR); 460 } 461 if ((types & Type.DISPLAY_CUTOUT) != 0) { 462 result.add(ITYPE_LEFT_DISPLAY_CUTOUT); 463 result.add(ITYPE_TOP_DISPLAY_CUTOUT); 464 result.add(ITYPE_RIGHT_DISPLAY_CUTOUT); 465 result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT); 466 } 467 if ((types & Type.IME) != 0) { 468 result.add(ITYPE_IME); 469 } 470 return result; 471 } 472 473 /** 474 * Converting a internal type to the public type. 475 * @param type internal insets type, {@code InternalInsetsType}. 476 * @return public insets type, {@code Type.InsetsType}. 477 */ 478 public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) { 479 switch (type) { 480 case ITYPE_STATUS_BAR: 481 case ITYPE_CLIMATE_BAR: 482 return Type.STATUS_BARS; 483 case ITYPE_NAVIGATION_BAR: 484 case ITYPE_EXTRA_NAVIGATION_BAR: 485 return Type.NAVIGATION_BARS; 486 case ITYPE_CAPTION_BAR: 487 return Type.CAPTION_BAR; 488 case ITYPE_IME: 489 return Type.IME; 490 case ITYPE_TOP_GESTURES: 491 case ITYPE_BOTTOM_GESTURES: 492 return Type.MANDATORY_SYSTEM_GESTURES; 493 case ITYPE_LEFT_GESTURES: 494 case ITYPE_RIGHT_GESTURES: 495 return Type.SYSTEM_GESTURES; 496 case ITYPE_TOP_TAPPABLE_ELEMENT: 497 case ITYPE_BOTTOM_TAPPABLE_ELEMENT: 498 return Type.TAPPABLE_ELEMENT; 499 case ITYPE_LEFT_DISPLAY_CUTOUT: 500 case ITYPE_TOP_DISPLAY_CUTOUT: 501 case ITYPE_RIGHT_DISPLAY_CUTOUT: 502 case ITYPE_BOTTOM_DISPLAY_CUTOUT: 503 return Type.DISPLAY_CUTOUT; 504 default: 505 throw new IllegalArgumentException("Unknown type: " + type); 506 } 507 } 508 509 public static boolean getDefaultVisibility(@InternalInsetsType int type) { 510 return type != ITYPE_IME; 511 } 512 513 public static boolean containsType(@InternalInsetsType int[] types, 514 @InternalInsetsType int type) { 515 if (types == null) { 516 return false; 517 } 518 for (int t : types) { 519 if (t == type) { 520 return true; 521 } 522 } 523 return false; 524 } 525 526 public void dump(String prefix, PrintWriter pw) { 527 pw.println(prefix + "InsetsState"); 528 for (int i = 0; i < SIZE; i++) { 529 InsetsSource source = mSources[i]; 530 if (source == null) continue; 531 source.dump(prefix + " ", pw); 532 } 533 } 534 535 public static String typeToString(@InternalInsetsType int type) { 536 switch (type) { 537 case ITYPE_STATUS_BAR: 538 return "ITYPE_STATUS_BAR"; 539 case ITYPE_NAVIGATION_BAR: 540 return "ITYPE_NAVIGATION_BAR"; 541 case ITYPE_CAPTION_BAR: 542 return "ITYPE_CAPTION_BAR"; 543 case ITYPE_TOP_GESTURES: 544 return "ITYPE_TOP_GESTURES"; 545 case ITYPE_BOTTOM_GESTURES: 546 return "ITYPE_BOTTOM_GESTURES"; 547 case ITYPE_LEFT_GESTURES: 548 return "ITYPE_LEFT_GESTURES"; 549 case ITYPE_RIGHT_GESTURES: 550 return "ITYPE_RIGHT_GESTURES"; 551 case ITYPE_TOP_TAPPABLE_ELEMENT: 552 return "ITYPE_TOP_TAPPABLE_ELEMENT"; 553 case ITYPE_BOTTOM_TAPPABLE_ELEMENT: 554 return "ITYPE_BOTTOM_TAPPABLE_ELEMENT"; 555 case ITYPE_LEFT_DISPLAY_CUTOUT: 556 return "ITYPE_LEFT_DISPLAY_CUTOUT"; 557 case ITYPE_TOP_DISPLAY_CUTOUT: 558 return "ITYPE_TOP_DISPLAY_CUTOUT"; 559 case ITYPE_RIGHT_DISPLAY_CUTOUT: 560 return "ITYPE_RIGHT_DISPLAY_CUTOUT"; 561 case ITYPE_BOTTOM_DISPLAY_CUTOUT: 562 return "ITYPE_BOTTOM_DISPLAY_CUTOUT"; 563 case ITYPE_IME: 564 return "ITYPE_IME"; 565 case ITYPE_CLIMATE_BAR: 566 return "ITYPE_CLIMATE_BAR"; 567 case ITYPE_EXTRA_NAVIGATION_BAR: 568 return "ITYPE_EXTRA_NAVIGATION_BAR"; 569 default: 570 return "ITYPE_UNKNOWN_" + type; 571 } 572 } 573 574 @Override 575 public boolean equals(Object o) { 576 return equals(o, false, false); 577 } 578 579 /** 580 * An equals method can exclude the caption insets. This is useful because we assemble the 581 * caption insets information on the client side, and when we communicate with server, it's 582 * excluded. 583 * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but 584 * ignore the caption insets source value. 585 * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is 586 * not visible. 587 * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. 588 */ 589 @VisibleForTesting 590 public boolean equals(Object o, boolean excludingCaptionInsets, 591 boolean excludeInvisibleImeFrames) { 592 if (this == o) { return true; } 593 if (o == null || getClass() != o.getClass()) { return false; } 594 595 InsetsState state = (InsetsState) o; 596 597 if (!mDisplayFrame.equals(state.mDisplayFrame)) { 598 return false; 599 } 600 for (int i = 0; i < SIZE; i++) { 601 if (excludingCaptionInsets) { 602 if (i == ITYPE_CAPTION_BAR) continue; 603 } 604 InsetsSource source = mSources[i]; 605 InsetsSource otherSource = state.mSources[i]; 606 if (source == null && otherSource == null) { 607 continue; 608 } 609 if (source != null && otherSource == null || source == null && otherSource != null) { 610 return false; 611 } 612 if (!otherSource.equals(source, excludeInvisibleImeFrames)) { 613 return false; 614 } 615 } 616 return true; 617 } 618 619 @Override 620 public int hashCode() { 621 return Objects.hash(mDisplayFrame, Arrays.hashCode(mSources)); 622 } 623 624 public InsetsState(Parcel in) { 625 readFromParcel(in); 626 } 627 628 @Override 629 public int describeContents() { 630 return 0; 631 } 632 633 @Override 634 public void writeToParcel(Parcel dest, int flags) { 635 dest.writeParcelable(mDisplayFrame, flags); 636 dest.writeParcelableArray(mSources, 0); 637 } 638 639 public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() { 640 641 public InsetsState createFromParcel(Parcel in) { 642 return new InsetsState(in); 643 } 644 645 public InsetsState[] newArray(int size) { 646 return new InsetsState[size]; 647 } 648 }; 649 650 public void readFromParcel(Parcel in) { 651 mDisplayFrame.set(in.readParcelable(null /* loader */)); 652 mSources = in.readParcelableArray(null, InsetsSource.class); 653 } 654 655 @Override 656 public String toString() { 657 StringJoiner joiner = new StringJoiner(", "); 658 for (int i = 0; i < SIZE; i++) { 659 InsetsSource source = mSources[i]; 660 if (source != null) { 661 joiner.add(source.toString()); 662 } 663 } 664 return "InsetsState: {" 665 + "mDisplayFrame=" + mDisplayFrame 666 + ", mSources= { " + joiner 667 + " }"; 668 } 669 } 670 671