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