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.InsetsSourceProto.FRAME; 20 import static android.view.InsetsSourceProto.TYPE; 21 import static android.view.InsetsSourceProto.TYPE_NUMBER; 22 import static android.view.InsetsSourceProto.VISIBLE; 23 import static android.view.InsetsSourceProto.VISIBLE_FRAME; 24 import static android.view.WindowInsets.Type.captionBar; 25 import static android.view.WindowInsets.Type.ime; 26 27 import android.annotation.IntDef; 28 import android.annotation.IntRange; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.graphics.Insets; 32 import android.graphics.Rect; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.util.proto.ProtoOutputStream; 36 import android.view.WindowInsets.Type.InsetsType; 37 38 import java.io.PrintWriter; 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Objects; 44 import java.util.StringJoiner; 45 46 /** 47 * Represents the state of a single entity generating insets for clients. 48 * @hide 49 */ 50 public class InsetsSource implements Parcelable { 51 52 @Retention(RetentionPolicy.SOURCE) 53 @IntDef(prefix = "SIDE_", value = { 54 SIDE_NONE, 55 SIDE_LEFT, 56 SIDE_TOP, 57 SIDE_RIGHT, 58 SIDE_BOTTOM, 59 SIDE_UNKNOWN 60 }) 61 public @interface InternalInsetsSide {} 62 63 static final int SIDE_NONE = 0; 64 static final int SIDE_LEFT = 1; 65 static final int SIDE_TOP = 2; 66 static final int SIDE_RIGHT = 3; 67 static final int SIDE_BOTTOM = 4; 68 static final int SIDE_UNKNOWN = 5; 69 70 /** The insets source ID of IME */ 71 public static final int ID_IME = createId(null, 0, ime()); 72 73 /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */ 74 public static final int ID_IME_CAPTION_BAR = 75 InsetsSource.createId(null /* owner */, 1 /* index */, captionBar()); 76 77 /** 78 * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't 79 * draw a semi-transparent scrim behind the system bar area even when the bar contrast is 80 * enforced. 81 * 82 * @see android.R.styleable#Window_enforceStatusBarContrast 83 * @see android.R.styleable#Window_enforceNavigationBarContrast 84 */ 85 public static final int FLAG_SUPPRESS_SCRIM = 1; 86 87 /** 88 * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the 89 * insets frame size when calculating the rounded corner insets to other windows. 90 * 91 * For example, task bar will draw fake rounded corners above itself, so we need to move the 92 * rounded corner up by the task bar insets size to make other windows see a rounded corner 93 * above the task bar. 94 */ 95 public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; 96 97 /** 98 * Controls whether the insets provided by this source should be forcibly consumed. 99 */ 100 public static final int FLAG_FORCE_CONSUMING = 1 << 2; 101 102 /** 103 * Controls whether the insets source will play an animation when resizing. 104 */ 105 public static final int FLAG_ANIMATE_RESIZING = 1 << 3; 106 107 @Retention(RetentionPolicy.SOURCE) 108 @IntDef(flag = true, prefix = "FLAG_", value = { 109 FLAG_SUPPRESS_SCRIM, 110 FLAG_INSETS_ROUNDED_CORNER, 111 FLAG_FORCE_CONSUMING, 112 FLAG_ANIMATE_RESIZING, 113 }) 114 public @interface Flags {} 115 116 /** 117 * Used when there are no bounding rects to describe an inset, which is only possible when the 118 * insets itself is {@link Insets#NONE}. 119 */ 120 private static final Rect[] NO_BOUNDING_RECTS = new Rect[0]; 121 122 private @Flags int mFlags; 123 124 /** 125 * An unique integer to identify this source across processes. 126 */ 127 private final int mId; 128 129 private final @InsetsType int mType; 130 131 /** Frame of the source in screen coordinate space */ 132 private final Rect mFrame; 133 private @Nullable Rect mVisibleFrame; 134 private @Nullable Rect[] mBoundingRects; 135 136 private boolean mVisible; 137 138 /** 139 * Used to decide which side of the relative frame should receive insets when the frame fully 140 * covers the relative frame. 141 */ 142 private @InternalInsetsSide int mSideHint = SIDE_NONE; 143 144 private final Rect mTmpFrame = new Rect(); 145 private final Rect mTmpBoundingRect = new Rect(); 146 InsetsSource(int id, @InsetsType int type)147 public InsetsSource(int id, @InsetsType int type) { 148 mId = id; 149 mType = type; 150 mFrame = new Rect(); 151 mVisible = (WindowInsets.Type.defaultVisible() & type) != 0; 152 } 153 InsetsSource(InsetsSource other)154 public InsetsSource(InsetsSource other) { 155 mId = other.mId; 156 mType = other.mType; 157 mFrame = new Rect(other.mFrame); 158 mVisible = other.mVisible; 159 mVisibleFrame = other.mVisibleFrame != null 160 ? new Rect(other.mVisibleFrame) 161 : null; 162 mFlags = other.mFlags; 163 mSideHint = other.mSideHint; 164 mBoundingRects = other.mBoundingRects != null 165 ? other.mBoundingRects.clone() 166 : null; 167 } 168 set(InsetsSource other)169 public void set(InsetsSource other) { 170 mFrame.set(other.mFrame); 171 mVisible = other.mVisible; 172 mVisibleFrame = other.mVisibleFrame != null 173 ? new Rect(other.mVisibleFrame) 174 : null; 175 mFlags = other.mFlags; 176 mSideHint = other.mSideHint; 177 mBoundingRects = other.mBoundingRects != null 178 ? other.mBoundingRects.clone() 179 : null; 180 } 181 setFrame(int left, int top, int right, int bottom)182 public InsetsSource setFrame(int left, int top, int right, int bottom) { 183 mFrame.set(left, top, right, bottom); 184 return this; 185 } 186 setFrame(Rect frame)187 public InsetsSource setFrame(Rect frame) { 188 mFrame.set(frame); 189 return this; 190 } 191 setVisibleFrame(@ullable Rect visibleFrame)192 public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) { 193 mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null; 194 return this; 195 } 196 setVisible(boolean visible)197 public InsetsSource setVisible(boolean visible) { 198 mVisible = visible; 199 return this; 200 } 201 setFlags(@lags int flags)202 public InsetsSource setFlags(@Flags int flags) { 203 mFlags = flags; 204 return this; 205 } 206 setFlags(@lags int flags, @Flags int mask)207 public InsetsSource setFlags(@Flags int flags, @Flags int mask) { 208 mFlags = (mFlags & ~mask) | (flags & mask); 209 return this; 210 } 211 212 /** 213 * Updates the side hint which is used to decide which side of the relative frame should receive 214 * insets when the frame fully covers the relative frame. 215 * 216 * @param bounds A rectangle which contains the frame. It will be used to calculate the hint. 217 */ updateSideHint(Rect bounds)218 public InsetsSource updateSideHint(Rect bounds) { 219 mSideHint = getInsetSide( 220 calculateInsets(bounds, mFrame, true /* ignoreVisibility */)); 221 return this; 222 } 223 224 /** 225 * Set the bounding rectangles of this source. They are expected to be relative to the source 226 * frame. 227 */ setBoundingRects(@ullable Rect[] rects)228 public InsetsSource setBoundingRects(@Nullable Rect[] rects) { 229 mBoundingRects = rects != null ? rects.clone() : null; 230 return this; 231 } 232 getId()233 public int getId() { 234 return mId; 235 } 236 getType()237 public @InsetsType int getType() { 238 return mType; 239 } 240 getFrame()241 public Rect getFrame() { 242 return mFrame; 243 } 244 getVisibleFrame()245 public @Nullable Rect getVisibleFrame() { 246 return mVisibleFrame; 247 } 248 isVisible()249 public boolean isVisible() { 250 return mVisible; 251 } 252 getFlags()253 public @Flags int getFlags() { 254 return mFlags; 255 } 256 hasFlags(int flags)257 public boolean hasFlags(int flags) { 258 return (mFlags & flags) == flags; 259 } 260 261 /** 262 * Returns the bounding rectangles of this source. 263 */ getBoundingRects()264 public @Nullable Rect[] getBoundingRects() { 265 return mBoundingRects; 266 } 267 268 /** 269 * Calculates the insets this source will cause to a client window. 270 * 271 * @param relativeFrame The frame to calculate the insets relative to. 272 * @param ignoreVisibility If true, always reports back insets even if source isn't visible. 273 * @return The resulting insets. The contract is that only one side will be occupied by a 274 * source. 275 */ calculateInsets(Rect relativeFrame, boolean ignoreVisibility)276 public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { 277 return calculateInsets(relativeFrame, mFrame, ignoreVisibility); 278 } 279 280 /** 281 * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. 282 */ calculateVisibleInsets(Rect relativeFrame)283 public Insets calculateVisibleInsets(Rect relativeFrame) { 284 return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, 285 false /* ignoreVisibility */); 286 } 287 calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility)288 private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { 289 if (!ignoreVisibility && !mVisible) { 290 return Insets.NONE; 291 } 292 // During drag-move and drag-resizing, the caption insets position may not get updated 293 // before the app frame get updated. To layout the app content correctly during drag events, 294 // we always return the insets with the corresponding height covering the top. 295 // However, with the "fake" IME navigation bar treated as a caption bar, we return the 296 // insets with the corresponding height the bottom. 297 if (getType() == WindowInsets.Type.captionBar()) { 298 return getId() == ID_IME_CAPTION_BAR 299 ? Insets.of(0, 0, 0, frame.height()) 300 : Insets.of(0, frame.height(), 0, 0); 301 } 302 // Checks for whether there is shared edge with insets for 0-width/height window. 303 final boolean hasIntersection = relativeFrame.isEmpty() 304 ? getIntersection(frame, relativeFrame, mTmpFrame) 305 : mTmpFrame.setIntersect(frame, relativeFrame); 306 if (!hasIntersection) { 307 return Insets.NONE; 308 } 309 310 // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. 311 // However, we should let the policy decide from the server. 312 if (getType() == WindowInsets.Type.ime()) { 313 return Insets.of(0, 0, 0, mTmpFrame.height()); 314 } 315 316 if (mTmpFrame.equals(relativeFrame)) { 317 // Covering all sides 318 switch (mSideHint) { 319 default: 320 case SIDE_LEFT: 321 return Insets.of(mTmpFrame.width(), 0, 0, 0); 322 case SIDE_TOP: 323 return Insets.of(0, mTmpFrame.height(), 0, 0); 324 case SIDE_RIGHT: 325 return Insets.of(0, 0, mTmpFrame.width(), 0); 326 case SIDE_BOTTOM: 327 return Insets.of(0, 0, 0, mTmpFrame.height()); 328 } 329 } else if (mTmpFrame.width() == relativeFrame.width()) { 330 // Intersecting at top/bottom 331 if (mTmpFrame.top == relativeFrame.top) { 332 return Insets.of(0, mTmpFrame.height(), 0, 0); 333 } else if (mTmpFrame.bottom == relativeFrame.bottom) { 334 return Insets.of(0, 0, 0, mTmpFrame.height()); 335 } 336 // TODO: remove when insets are shell-customizable. 337 // This is a hack that says "if this is a top-inset (eg statusbar), always apply it 338 // to the top". It is used when adjusting primary split for IME. 339 if (mTmpFrame.top == 0) { 340 return Insets.of(0, mTmpFrame.height(), 0, 0); 341 } 342 } else if (mTmpFrame.height() == relativeFrame.height()) { 343 // Intersecting at left/right 344 if (mTmpFrame.left == relativeFrame.left) { 345 return Insets.of(mTmpFrame.width(), 0, 0, 0); 346 } else if (mTmpFrame.right == relativeFrame.right) { 347 return Insets.of(0, 0, mTmpFrame.width(), 0); 348 } 349 } 350 return Insets.NONE; 351 } 352 353 /** 354 * Calculates the bounding rects the source will cause to a client window. 355 */ calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility)356 public @NonNull Rect[] calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility) { 357 if (!ignoreVisibility && !mVisible) { 358 return NO_BOUNDING_RECTS; 359 } 360 361 final Rect frame = getFrame(); 362 if (mBoundingRects == null) { 363 // No bounding rects set, make a single bounding rect that covers the intersection of 364 // the |frame| and the |relativeFrame|. Also make it relative to the window origin. 365 return mTmpBoundingRect.setIntersect(frame, relativeFrame) 366 ? new Rect[]{ 367 new Rect( 368 mTmpBoundingRect.left - relativeFrame.left, 369 mTmpBoundingRect.top - relativeFrame.top, 370 mTmpBoundingRect.right - relativeFrame.left, 371 mTmpBoundingRect.bottom - relativeFrame.top 372 ) 373 } 374 : NO_BOUNDING_RECTS; 375 } 376 377 // Special treatment for captionBar inset type. During drag-resizing, the |frame| and 378 // |boundingRects| may not get updated as quickly as |relativeFrame|, so just assume the 379 // |frame| will always be either at the top or bottom of |relativeFrame|. This means some 380 // calculations to make |boundingRects| relative to |relativeFrame| can be skipped or 381 // simplified. 382 // TODO(b/254128050): remove special treatment. 383 if (getType() == WindowInsets.Type.captionBar()) { 384 final ArrayList<Rect> validBoundingRects = new ArrayList<>(); 385 for (final Rect boundingRect : mBoundingRects) { 386 // Assume that the caption |frame| and |relativeFrame| perfectly align at the top 387 // or bottom, meaning that the provided |boundingRect|, which is relative to the 388 // |frame| either is already relative to |relativeFrame| (for top captionBar()), or 389 // just needs to be made relative to |relativeFrame| for bottom bars. 390 final int frameHeight = frame.height(); 391 mTmpBoundingRect.set(boundingRect); 392 if (getId() == ID_IME_CAPTION_BAR) { 393 mTmpBoundingRect.offset(0, relativeFrame.height() - frameHeight); 394 } 395 validBoundingRects.add(new Rect(mTmpBoundingRect)); 396 } 397 return validBoundingRects.toArray(new Rect[validBoundingRects.size()]); 398 } 399 400 // Regular treatment for non-captionBar inset types. 401 final ArrayList<Rect> validBoundingRects = new ArrayList<>(); 402 for (final Rect boundingRect : mBoundingRects) { 403 // |boundingRect| was provided relative to |frame|. Make it absolute to be in the same 404 // coordinate system as |frame|. 405 final Rect absBoundingRect = new Rect( 406 boundingRect.left + frame.left, 407 boundingRect.top + frame.top, 408 boundingRect.right + frame.left, 409 boundingRect.bottom + frame.top 410 ); 411 // Now find the intersection of that |absBoundingRect| with |relativeFrame|. In other 412 // words, whichever part of the bounding rect is inside the window frame. 413 if (!mTmpBoundingRect.setIntersect(absBoundingRect, relativeFrame)) { 414 // It's possible for this to be empty if the frame and bounding rects were larger 415 // than the |relativeFrame|, such as when a system window is wider than the app 416 // window width. Just ignore that rect since it will have no effect on the 417 // window insets. 418 continue; 419 } 420 // At this point, |mTmpBoundingRect| is a valid bounding rect located fully inside the 421 // window, convert it to be relative to the window so that apps don't need to know the 422 // location of the window to understand bounding rects. 423 validBoundingRects.add(new Rect( 424 mTmpBoundingRect.left - relativeFrame.left, 425 mTmpBoundingRect.top - relativeFrame.top, 426 mTmpBoundingRect.right - relativeFrame.left, 427 mTmpBoundingRect.bottom - relativeFrame.top)); 428 } 429 if (validBoundingRects.isEmpty()) { 430 return NO_BOUNDING_RECTS; 431 } 432 return validBoundingRects.toArray(new Rect[validBoundingRects.size()]); 433 } 434 435 /** 436 * Outputs the intersection of two rectangles. The shared edges will also be counted in the 437 * intersection. 438 * 439 * @param a The first rectangle being intersected with. 440 * @param b The second rectangle being intersected with. 441 * @param out The rectangle which represents the intersection. 442 * @return {@code true} if there is any intersection. 443 */ getIntersection(@onNull Rect a, @NonNull Rect b, @NonNull Rect out)444 private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) { 445 if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) { 446 out.left = Math.max(a.left, b.left); 447 out.top = Math.max(a.top, b.top); 448 out.right = Math.min(a.right, b.right); 449 out.bottom = Math.min(a.bottom, b.bottom); 450 return true; 451 } 452 out.setEmpty(); 453 return false; 454 } 455 456 /** 457 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b 458 * is set in order that this method returns a meaningful result. 459 */ getInsetSide(Insets insets)460 static @InternalInsetsSide int getInsetSide(Insets insets) { 461 if (Insets.NONE.equals(insets)) { 462 return SIDE_NONE; 463 } 464 if (insets.left != 0) { 465 return SIDE_LEFT; 466 } 467 if (insets.top != 0) { 468 return SIDE_TOP; 469 } 470 if (insets.right != 0) { 471 return SIDE_RIGHT; 472 } 473 if (insets.bottom != 0) { 474 return SIDE_BOTTOM; 475 } 476 return SIDE_UNKNOWN; 477 } 478 sideToString(@nternalInsetsSide int side)479 static String sideToString(@InternalInsetsSide int side) { 480 switch (side) { 481 case SIDE_NONE: 482 return "NONE"; 483 case SIDE_LEFT: 484 return "LEFT"; 485 case SIDE_TOP: 486 return "TOP"; 487 case SIDE_RIGHT: 488 return "RIGHT"; 489 case SIDE_BOTTOM: 490 return "BOTTOM"; 491 default: 492 return "UNKNOWN:" + side; 493 } 494 } 495 496 /** 497 * Creates an identifier of an {@link InsetsSource}. 498 * 499 * @param owner An object owned by the owner. Only the owner can modify its own sources. 500 * @param index An owner may have multiple sources with the same type. For example, the system 501 * server might have multiple display cutout sources. This is used to identify 502 * which one is which. The value must be in a range of [0, 2047]. 503 * @param type The {@link InsetsType type} of the source. 504 * @return a unique integer as the identifier. 505 */ createId(Object owner, @IntRange(from = 0, to = 2047) int index, @InsetsType int type)506 public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index, 507 @InsetsType int type) { 508 if (index < 0 || index >= 2048) { 509 throw new IllegalArgumentException(); 510 } 511 // owner takes top 16 bits; 512 // index takes 11 bits since the 6th bit; 513 // type takes bottom 5 bits. 514 return ((System.identityHashCode(owner) % (1 << 16)) << 16) 515 + (index << 5) 516 + WindowInsets.Type.indexOf(type); 517 } 518 519 /** 520 * Gets the index from the ID. 521 * 522 * @see #createId(Object, int, int) 523 */ getIndex(int id)524 public static int getIndex(int id) { 525 // start: ????????????????***********????? 526 // & 65535: 0000000000000000***********????? 527 // >> 5: 000000000000000000000*********** 528 return (id & 65535) >> 5; 529 } 530 531 /** 532 * Gets the {@link InsetsType} from the ID. 533 * 534 * @see #createId(Object, int, int) 535 * @see WindowInsets.Type#indexOf(int) 536 */ getType(int id)537 public static int getType(int id) { 538 // start: ???????????????????????????***** 539 // & 31: 000000000000000000000000000***** 540 // 1 <<: See WindowInsets.Type#indexOf 541 return 1 << (id & 31); 542 } 543 flagsToString(@lags int flags)544 public static String flagsToString(@Flags int flags) { 545 final StringJoiner joiner = new StringJoiner("|"); 546 if ((flags & FLAG_SUPPRESS_SCRIM) != 0) { 547 joiner.add("SUPPRESS_SCRIM"); 548 } 549 if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { 550 joiner.add("INSETS_ROUNDED_CORNER"); 551 } 552 if ((flags & FLAG_FORCE_CONSUMING) != 0) { 553 joiner.add("FORCE_CONSUMING"); 554 } 555 if ((flags & FLAG_ANIMATE_RESIZING) != 0) { 556 joiner.add("ANIMATE_RESIZING"); 557 } 558 return joiner.toString(); 559 } 560 561 /** 562 * Export the state of {@link InsetsSource} into a protocol buffer output stream. 563 * 564 * @param proto Stream to write the state to 565 * @param fieldId FieldId of InsetsSource as defined in the parent message 566 */ dumpDebug(ProtoOutputStream proto, long fieldId)567 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 568 final long token = proto.start(fieldId); 569 if (!android.os.Flags.androidOsBuildVanillaIceCream()) { 570 // Deprecated since V. 571 proto.write(TYPE, WindowInsets.Type.toString(mType)); 572 } 573 mFrame.dumpDebug(proto, FRAME); 574 if (mVisibleFrame != null) { 575 mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME); 576 } 577 proto.write(VISIBLE, mVisible); 578 proto.write(TYPE_NUMBER, mType); 579 proto.end(token); 580 } 581 dump(String prefix, PrintWriter pw)582 public void dump(String prefix, PrintWriter pw) { 583 pw.print(prefix); 584 pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId)); 585 pw.print(" type="); pw.print(WindowInsets.Type.toString(mType)); 586 pw.print(" frame="); pw.print(mFrame.toShortString()); 587 if (mVisibleFrame != null) { 588 pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); 589 } 590 pw.print(" visible="); pw.print(mVisible); 591 pw.print(" flags="); pw.print(flagsToString(mFlags)); 592 pw.print(" sideHint="); pw.print(sideToString(mSideHint)); 593 pw.print(" boundingRects="); pw.print(Arrays.toString(mBoundingRects)); 594 pw.println(); 595 } 596 597 @Override equals(@ullable Object o)598 public boolean equals(@Nullable Object o) { 599 return equals(o, false); 600 } 601 602 /** 603 * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored 604 * when IME is not visible. 605 */ equals(@ullable Object o, boolean excludeInvisibleImeFrames)606 public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) { 607 if (this == o) return true; 608 if (o == null || getClass() != o.getClass()) return false; 609 610 InsetsSource that = (InsetsSource) o; 611 612 if (mId != that.mId) return false; 613 if (mType != that.mType) return false; 614 if (mVisible != that.mVisible) return false; 615 if (mFlags != that.mFlags) return false; 616 if (mSideHint != that.mSideHint) return false; 617 if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true; 618 if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; 619 if (!mFrame.equals(that.mFrame)) return false; 620 return Arrays.equals(mBoundingRects, that.mBoundingRects); 621 } 622 623 @Override hashCode()624 public int hashCode() { 625 return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint, 626 Arrays.hashCode(mBoundingRects)); 627 } 628 InsetsSource(Parcel in)629 public InsetsSource(Parcel in) { 630 mId = in.readInt(); 631 mType = in.readInt(); 632 mFrame = Rect.CREATOR.createFromParcel(in); 633 if (in.readInt() != 0) { 634 mVisibleFrame = Rect.CREATOR.createFromParcel(in); 635 } else { 636 mVisibleFrame = null; 637 } 638 mVisible = in.readBoolean(); 639 mFlags = in.readInt(); 640 mSideHint = in.readInt(); 641 mBoundingRects = in.createTypedArray(Rect.CREATOR); 642 } 643 644 @Override describeContents()645 public int describeContents() { 646 return 0; 647 } 648 649 @Override writeToParcel(Parcel dest, int flags)650 public void writeToParcel(Parcel dest, int flags) { 651 dest.writeInt(mId); 652 dest.writeInt(mType); 653 mFrame.writeToParcel(dest, 0); 654 if (mVisibleFrame != null) { 655 dest.writeInt(1); 656 mVisibleFrame.writeToParcel(dest, 0); 657 } else { 658 dest.writeInt(0); 659 } 660 dest.writeBoolean(mVisible); 661 dest.writeInt(mFlags); 662 dest.writeInt(mSideHint); 663 dest.writeTypedArray(mBoundingRects, flags); 664 } 665 666 @Override toString()667 public String toString() { 668 return "InsetsSource: {" + Integer.toHexString(mId) 669 + " mType=" + WindowInsets.Type.toString(mType) 670 + " mFrame=" + mFrame.toShortString() 671 + " mVisible=" + mVisible 672 + " mFlags=" + flagsToString(mFlags) 673 + " mSideHint=" + sideToString(mSideHint) 674 + " mBoundingRects=" + Arrays.toString(mBoundingRects) 675 + "}"; 676 } 677 678 public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() { 679 680 public InsetsSource createFromParcel(Parcel in) { 681 return new InsetsSource(in); 682 } 683 684 public InsetsSource[] newArray(int size) { 685 return new InsetsSource[size]; 686 } 687 }; 688 } 689