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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; 21 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; 22 import static android.view.InsetsStateProto.DISPLAY_CUTOUT; 23 import static android.view.InsetsStateProto.DISPLAY_FRAME; 24 import static android.view.InsetsStateProto.SOURCES; 25 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 26 import static android.view.WindowInsets.Type.captionBar; 27 import static android.view.WindowInsets.Type.displayCutout; 28 import static android.view.WindowInsets.Type.ime; 29 import static android.view.WindowInsets.Type.indexOf; 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.FLAG_LAYOUT_NO_LIMITS; 34 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 35 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 36 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 37 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; 38 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 39 40 import android.annotation.IntDef; 41 import android.annotation.NonNull; 42 import android.annotation.Nullable; 43 import android.app.WindowConfiguration.ActivityType; 44 import android.graphics.Insets; 45 import android.graphics.Rect; 46 import android.os.Parcel; 47 import android.os.Parcelable; 48 import android.util.SparseArray; 49 import android.util.SparseIntArray; 50 import android.util.proto.ProtoOutputStream; 51 import android.view.WindowInsets.Type; 52 import android.view.WindowInsets.Type.InsetsType; 53 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 57 import java.io.PrintWriter; 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.util.Objects; 61 import java.util.StringJoiner; 62 63 /** 64 * Holder for state of system windows that cause window insets for all other windows in the system. 65 * @hide 66 */ 67 public class InsetsState implements Parcelable { 68 69 @Retention(RetentionPolicy.SOURCE) 70 @IntDef(prefix = "ISIDE", value = { 71 ISIDE_LEFT, 72 ISIDE_TOP, 73 ISIDE_RIGHT, 74 ISIDE_BOTTOM, 75 ISIDE_FLOATING, 76 ISIDE_UNKNOWN 77 }) 78 public @interface InternalInsetsSide {} 79 static final int ISIDE_LEFT = 0; 80 static final int ISIDE_TOP = 1; 81 static final int ISIDE_RIGHT = 2; 82 static final int ISIDE_BOTTOM = 3; 83 static final int ISIDE_FLOATING = 4; 84 static final int ISIDE_UNKNOWN = 5; 85 86 private final SparseArray<InsetsSource> mSources; 87 88 /** 89 * The frame of the display these sources are relative to. 90 */ 91 private final Rect mDisplayFrame = new Rect(); 92 93 /** The area cut from the display. */ 94 private final DisplayCutout.ParcelableWrapper mDisplayCutout = 95 new DisplayCutout.ParcelableWrapper(); 96 97 /** 98 * The frame that rounded corners are relative to. 99 * 100 * There are 2 cases that will draw fake rounded corners: 101 * 1. In split-screen mode 102 * 2. Devices with a task bar 103 * We need to report these fake rounded corners to apps by re-calculating based on this frame. 104 */ 105 private final Rect mRoundedCornerFrame = new Rect(); 106 107 /** The rounded corners on the display */ 108 private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; 109 110 /** The bounds of the Privacy Indicator */ 111 private PrivacyIndicatorBounds mPrivacyIndicatorBounds = 112 new PrivacyIndicatorBounds(); 113 114 /** The display shape */ 115 private DisplayShape mDisplayShape = DisplayShape.NONE; 116 InsetsState()117 public InsetsState() { 118 mSources = new SparseArray<>(); 119 } 120 InsetsState(InsetsState copy)121 public InsetsState(InsetsState copy) { 122 this(copy, false /* copySources */); 123 } 124 InsetsState(InsetsState copy, boolean copySources)125 public InsetsState(InsetsState copy, boolean copySources) { 126 mSources = new SparseArray<>(copy.mSources.size()); 127 set(copy, copySources); 128 } 129 130 /** 131 * Calculates {@link WindowInsets} based on the current source configuration. 132 * 133 * @param frame The frame to calculate the insets relative to. 134 * @param ignoringVisibilityState {@link InsetsState} used to calculate 135 * {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass 136 * {@code null} to use this state to calculate that information. 137 * @return The calculated insets. 138 */ calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, int windowType, @ActivityType int activityType, @Nullable @InternalInsetsSide SparseIntArray idSideMap)139 public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, 140 boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags, 141 int legacySystemUiFlags, int windowType, @ActivityType int activityType, 142 @Nullable @InternalInsetsSide SparseIntArray idSideMap) { 143 Insets[] typeInsetsMap = new Insets[Type.SIZE]; 144 Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; 145 boolean[] typeVisibilityMap = new boolean[Type.SIZE]; 146 final Rect relativeFrame = new Rect(frame); 147 final Rect relativeFrameMax = new Rect(frame); 148 @InsetsType int forceConsumingTypes = 0; 149 @InsetsType int suppressScrimTypes = 0; 150 for (int i = mSources.size() - 1; i >= 0; i--) { 151 final InsetsSource source = mSources.valueAt(i); 152 final @InsetsType int type = source.getType(); 153 154 if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) { 155 forceConsumingTypes |= type; 156 } 157 158 if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) { 159 suppressScrimTypes |= type; 160 } 161 162 processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, 163 idSideMap, typeVisibilityMap); 164 165 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME 166 // target. 167 if (type != WindowInsets.Type.ime()) { 168 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null 169 ? ignoringVisibilityState.peekSource(source.getId()) 170 : source; 171 if (ignoringVisibilitySource == null) { 172 continue; 173 } 174 processSource(ignoringVisibilitySource, relativeFrameMax, 175 true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */, 176 null /* typeVisibilityMap */); 177 } 178 } 179 final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; 180 181 @InsetsType int compatInsetsTypes = systemBars() | displayCutout(); 182 if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) { 183 compatInsetsTypes |= ime(); 184 } 185 if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { 186 compatInsetsTypes &= ~statusBars(); 187 } 188 if (clearsCompatInsets(windowType, legacyWindowFlags, activityType, forceConsumingTypes)) { 189 compatInsetsTypes = 0; 190 } 191 192 return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, 193 forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame), 194 calculateRelativeRoundedCorners(frame), 195 calculateRelativePrivacyIndicatorBounds(frame), 196 calculateRelativeDisplayShape(frame), 197 compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); 198 } 199 calculateRelativeCutout(Rect frame)200 private DisplayCutout calculateRelativeCutout(Rect frame) { 201 final DisplayCutout raw = mDisplayCutout.get(); 202 if (mDisplayFrame.equals(frame)) { 203 return raw; 204 } 205 if (frame == null) { 206 return DisplayCutout.NO_CUTOUT; 207 } 208 final int insetLeft = frame.left - mDisplayFrame.left; 209 final int insetTop = frame.top - mDisplayFrame.top; 210 final int insetRight = mDisplayFrame.right - frame.right; 211 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 212 if (insetLeft >= raw.getSafeInsetLeft() 213 && insetTop >= raw.getSafeInsetTop() 214 && insetRight >= raw.getSafeInsetRight() 215 && insetBottom >= raw.getSafeInsetBottom()) { 216 return DisplayCutout.NO_CUTOUT; 217 } 218 return raw.inset(insetLeft, insetTop, insetRight, insetBottom); 219 } 220 calculateRelativeRoundedCorners(Rect frame)221 private RoundedCorners calculateRelativeRoundedCorners(Rect frame) { 222 if (frame == null) { 223 return RoundedCorners.NO_ROUNDED_CORNERS; 224 } 225 // If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this 226 // frame. 227 final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame); 228 for (int i = mSources.size() - 1; i >= 0; i--) { 229 final InsetsSource source = mSources.valueAt(i); 230 if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) { 231 final Insets insets = source.calculateInsets(roundedCornerFrame, false); 232 roundedCornerFrame.inset(insets); 233 } 234 } 235 if (!roundedCornerFrame.isEmpty() && !roundedCornerFrame.equals(mDisplayFrame)) { 236 return mRoundedCorners.insetWithFrame(frame, roundedCornerFrame); 237 } 238 if (mDisplayFrame.equals(frame)) { 239 return mRoundedCorners; 240 } 241 final int insetLeft = frame.left - mDisplayFrame.left; 242 final int insetTop = frame.top - mDisplayFrame.top; 243 final int insetRight = mDisplayFrame.right - frame.right; 244 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 245 return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom); 246 } 247 calculateRelativePrivacyIndicatorBounds(Rect frame)248 private PrivacyIndicatorBounds calculateRelativePrivacyIndicatorBounds(Rect frame) { 249 if (mDisplayFrame.equals(frame)) { 250 return mPrivacyIndicatorBounds; 251 } 252 if (frame == null) { 253 return null; 254 } 255 final int insetLeft = frame.left - mDisplayFrame.left; 256 final int insetTop = frame.top - mDisplayFrame.top; 257 final int insetRight = mDisplayFrame.right - frame.right; 258 final int insetBottom = mDisplayFrame.bottom - frame.bottom; 259 return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom); 260 } 261 calculateRelativeDisplayShape(Rect frame)262 private DisplayShape calculateRelativeDisplayShape(Rect frame) { 263 if (mDisplayFrame.equals(frame)) { 264 return mDisplayShape; 265 } 266 if (frame == null) { 267 return DisplayShape.NONE; 268 } 269 return mDisplayShape.setOffset(-frame.left, -frame.top); 270 } 271 calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility)272 public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { 273 Insets insets = Insets.NONE; 274 for (int i = mSources.size() - 1; i >= 0; i--) { 275 final InsetsSource source = mSources.valueAt(i); 276 if ((source.getType() & types) == 0) { 277 continue; 278 } 279 insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets); 280 } 281 return insets; 282 } 283 calculateInsets(Rect frame, @InsetsType int types, @InsetsType int requestedVisibleTypes)284 public Insets calculateInsets(Rect frame, @InsetsType int types, 285 @InsetsType int requestedVisibleTypes) { 286 Insets insets = Insets.NONE; 287 for (int i = mSources.size() - 1; i >= 0; i--) { 288 final InsetsSource source = mSources.valueAt(i); 289 if ((source.getType() & types & requestedVisibleTypes) == 0) { 290 continue; 291 } 292 insets = Insets.max(source.calculateInsets(frame, true), insets); 293 } 294 return insets; 295 } 296 calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType, @SoftInputModeFlags int softInputMode, int windowFlags)297 public Insets calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType, 298 @SoftInputModeFlags int softInputMode, int windowFlags) { 299 final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST; 300 final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING 301 ? systemBars() | ime() 302 : systemBars(); 303 @InsetsType int forceConsumingTypes = 0; 304 Insets insets = Insets.NONE; 305 for (int i = mSources.size() - 1; i >= 0; i--) { 306 final InsetsSource source = mSources.valueAt(i); 307 if ((source.getType() & visibleInsetsTypes) == 0) { 308 continue; 309 } 310 if (source.hasFlags(FLAG_FORCE_CONSUMING)) { 311 forceConsumingTypes |= source.getType(); 312 } 313 insets = Insets.max(source.calculateVisibleInsets(frame), insets); 314 } 315 return clearsCompatInsets(windowType, windowFlags, activityType, forceConsumingTypes) 316 ? Insets.NONE 317 : insets; 318 } 319 320 /** 321 * Calculate which insets *cannot* be controlled, because the frame does not cover the 322 * respective side of the inset. 323 * 324 * If the frame of our window doesn't cover the entire inset, the control API makes very 325 * little sense, as we don't deal with negative insets. 326 */ 327 @InsetsType calculateUncontrollableInsetsFromFrame(Rect frame)328 public int calculateUncontrollableInsetsFromFrame(Rect frame) { 329 int blocked = 0; 330 for (int i = mSources.size() - 1; i >= 0; i--) { 331 final InsetsSource source = mSources.valueAt(i); 332 if (!canControlSource(frame, source)) { 333 blocked |= source.getType(); 334 } 335 } 336 return blocked; 337 } 338 canControlSource(Rect frame, InsetsSource source)339 private static boolean canControlSource(Rect frame, InsetsSource source) { 340 final Insets insets = source.calculateInsets(frame, true /* ignoreVisibility */); 341 final Rect sourceFrame = source.getFrame(); 342 final int sourceWidth = sourceFrame.width(); 343 final int sourceHeight = sourceFrame.height(); 344 return insets.left == sourceWidth || insets.right == sourceWidth 345 || insets.top == sourceHeight || insets.bottom == sourceHeight; 346 } 347 processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap)348 private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, 349 Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap, 350 @Nullable boolean[] typeVisibilityMap) { 351 Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); 352 353 final int type = source.getType(); 354 processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, 355 insets, type); 356 357 if (type == Type.MANDATORY_SYSTEM_GESTURES) { 358 // Mandatory system gestures are also system gestures. 359 // TODO: find a way to express this more generally. One option would be to define 360 // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the 361 // ability to set systemGestureInsets() independently from 362 // mandatorySystemGestureInsets() in the Builder. 363 processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, 364 insets, Type.SYSTEM_GESTURES); 365 } 366 if (type == Type.CAPTION_BAR) { 367 // Caption should also be gesture and tappable elements. This should not be needed when 368 // the caption is added from the shell, as the shell can add other types at the same 369 // time. 370 processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, 371 insets, Type.SYSTEM_GESTURES); 372 processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, 373 insets, Type.MANDATORY_SYSTEM_GESTURES); 374 processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, 375 insets, Type.TAPPABLE_ELEMENT); 376 } 377 } 378 processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, @InternalInsetsSide @Nullable SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type)379 private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, 380 @InternalInsetsSide @Nullable SparseIntArray idSideMap, 381 @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { 382 int index = indexOf(type); 383 384 // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered 385 // as non-equal while they provide the same insets of each type from WindowInsets#getInsets 386 // if one WindowInsets has Insets.NONE for a type and the other has null for the same type. 387 if (!Insets.NONE.equals(insets)) { 388 Insets existing = typeInsetsMap[index]; 389 if (existing == null) { 390 typeInsetsMap[index] = insets; 391 } else { 392 typeInsetsMap[index] = Insets.max(existing, insets); 393 } 394 } 395 396 if (typeVisibilityMap != null) { 397 typeVisibilityMap[index] = source.isVisible(); 398 } 399 400 if (idSideMap != null) { 401 @InternalInsetsSide int insetSide = getInsetSide(insets); 402 if (insetSide != ISIDE_UNKNOWN) { 403 idSideMap.put(source.getId(), insetSide); 404 } 405 } 406 } 407 408 /** 409 * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b 410 * is set in order that this method returns a meaningful result. 411 */ getInsetSide(Insets insets)412 static @InternalInsetsSide int getInsetSide(Insets insets) { 413 if (Insets.NONE.equals(insets)) { 414 return ISIDE_FLOATING; 415 } 416 if (insets.left != 0) { 417 return ISIDE_LEFT; 418 } 419 if (insets.top != 0) { 420 return ISIDE_TOP; 421 } 422 if (insets.right != 0) { 423 return ISIDE_RIGHT; 424 } 425 if (insets.bottom != 0) { 426 return ISIDE_BOTTOM; 427 } 428 return ISIDE_UNKNOWN; 429 } 430 431 /** 432 * Gets the source mapped from the ID, or creates one if no such mapping has been made. 433 */ getOrCreateSource(int id, int type)434 public InsetsSource getOrCreateSource(int id, int type) { 435 InsetsSource source = mSources.get(id); 436 if (source != null) { 437 return source; 438 } 439 source = new InsetsSource(id, type); 440 mSources.put(id, source); 441 return source; 442 } 443 444 /** 445 * Gets the source mapped from the ID, or <code>null</code> if no such mapping has been made. 446 */ peekSource(int id)447 public @Nullable InsetsSource peekSource(int id) { 448 return mSources.get(id); 449 } 450 451 /** 452 * Given an index in the range <code>0...sourceSize()-1</code>, returns the source ID from the 453 * <code>index</code>th ID-source mapping that this state stores. 454 */ sourceIdAt(int index)455 public int sourceIdAt(int index) { 456 return mSources.keyAt(index); 457 } 458 459 /** 460 * Given an index in the range <code>0...sourceSize()-1</code>, returns the source from the 461 * <code>index</code>th ID-source mapping that this state stores. 462 */ sourceAt(int index)463 public InsetsSource sourceAt(int index) { 464 return mSources.valueAt(index); 465 } 466 467 /** 468 * Returns the amount of the sources. 469 */ sourceSize()470 public int sourceSize() { 471 return mSources.size(); 472 } 473 474 /** 475 * Returns if the source is visible or the type is default visible and the source doesn't exist. 476 * 477 * @param id The ID of the source. 478 * @param type The {@link InsetsType} to see if it is default visible. 479 * @return {@code true} if the source is visible or the type is default visible and the source 480 * doesn't exist. 481 */ isSourceOrDefaultVisible(int id, @InsetsType int type)482 public boolean isSourceOrDefaultVisible(int id, @InsetsType int type) { 483 final InsetsSource source = mSources.get(id); 484 return source != null ? source.isVisible() : (type & Type.defaultVisible()) != 0; 485 } 486 setDisplayFrame(Rect frame)487 public void setDisplayFrame(Rect frame) { 488 mDisplayFrame.set(frame); 489 } 490 getDisplayFrame()491 public Rect getDisplayFrame() { 492 return mDisplayFrame; 493 } 494 setDisplayCutout(DisplayCutout cutout)495 public void setDisplayCutout(DisplayCutout cutout) { 496 mDisplayCutout.set(cutout); 497 } 498 getDisplayCutout()499 public DisplayCutout getDisplayCutout() { 500 return mDisplayCutout.get(); 501 } 502 getDisplayCutoutSafe(Rect outBounds)503 public void getDisplayCutoutSafe(Rect outBounds) { 504 outBounds.set( 505 WindowLayout.MIN_X, WindowLayout.MIN_Y, WindowLayout.MAX_X, WindowLayout.MAX_Y); 506 final DisplayCutout cutout = mDisplayCutout.get(); 507 final Rect displayFrame = mDisplayFrame; 508 if (!cutout.isEmpty()) { 509 if (cutout.getSafeInsetLeft() > 0) { 510 outBounds.left = displayFrame.left + cutout.getSafeInsetLeft(); 511 } 512 if (cutout.getSafeInsetTop() > 0) { 513 outBounds.top = displayFrame.top + cutout.getSafeInsetTop(); 514 } 515 if (cutout.getSafeInsetRight() > 0) { 516 outBounds.right = displayFrame.right - cutout.getSafeInsetRight(); 517 } 518 if (cutout.getSafeInsetBottom() > 0) { 519 outBounds.bottom = displayFrame.bottom - cutout.getSafeInsetBottom(); 520 } 521 } 522 } 523 setRoundedCorners(RoundedCorners roundedCorners)524 public void setRoundedCorners(RoundedCorners roundedCorners) { 525 mRoundedCorners = roundedCorners; 526 } 527 getRoundedCorners()528 public RoundedCorners getRoundedCorners() { 529 return mRoundedCorners; 530 } 531 532 /** 533 * Set the frame that will be used to calculate the rounded corners. 534 * 535 * @see #mRoundedCornerFrame 536 */ setRoundedCornerFrame(Rect frame)537 public void setRoundedCornerFrame(Rect frame) { 538 mRoundedCornerFrame.set(frame); 539 } 540 setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds)541 public void setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds) { 542 mPrivacyIndicatorBounds = bounds; 543 } 544 getPrivacyIndicatorBounds()545 public PrivacyIndicatorBounds getPrivacyIndicatorBounds() { 546 return mPrivacyIndicatorBounds; 547 } 548 setDisplayShape(DisplayShape displayShape)549 public void setDisplayShape(DisplayShape displayShape) { 550 mDisplayShape = displayShape; 551 } 552 getDisplayShape()553 public DisplayShape getDisplayShape() { 554 return mDisplayShape; 555 } 556 557 /** 558 * Removes the source which has the ID from this state, if there was any. 559 * 560 * @param id The ID of the source to remove. 561 */ removeSource(int id)562 public void removeSource(int id) { 563 mSources.delete(id); 564 } 565 566 /** 567 * Removes the source at the specified index. 568 * 569 * @param index The index of the source to remove. 570 */ removeSourceAt(int index)571 public void removeSourceAt(int index) { 572 mSources.removeAt(index); 573 } 574 575 /** 576 * A shortcut for setting the visibility of the source. 577 * 578 * @param id The ID of the source to set the visibility 579 * @param visible {@code true} for visible 580 */ setSourceVisible(int id, boolean visible)581 public void setSourceVisible(int id, boolean visible) { 582 final InsetsSource source = mSources.get(id); 583 if (source != null) { 584 source.setVisible(visible); 585 } 586 } 587 588 /** 589 * Scales the frame and the visible frame (if there is one) of each source. 590 * 591 * @param scale the scale to be applied 592 */ scale(float scale)593 public void scale(float scale) { 594 mDisplayFrame.scale(scale); 595 mDisplayCutout.scale(scale); 596 mRoundedCorners = mRoundedCorners.scale(scale); 597 mRoundedCornerFrame.scale(scale); 598 mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale); 599 mDisplayShape = mDisplayShape.setScale(scale); 600 for (int i = mSources.size() - 1; i >= 0; i--) { 601 final InsetsSource source = mSources.valueAt(i); 602 source.getFrame().scale(scale); 603 final Rect visibleFrame = source.getVisibleFrame(); 604 if (visibleFrame != null) { 605 visibleFrame.scale(scale); 606 } 607 } 608 } 609 set(InsetsState other)610 public void set(InsetsState other) { 611 set(other, false /* copySources */); 612 } 613 set(InsetsState other, boolean copySources)614 public void set(InsetsState other, boolean copySources) { 615 mDisplayFrame.set(other.mDisplayFrame); 616 mDisplayCutout.set(other.mDisplayCutout); 617 mRoundedCorners = other.getRoundedCorners(); 618 mRoundedCornerFrame.set(other.mRoundedCornerFrame); 619 mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); 620 mDisplayShape = other.getDisplayShape(); 621 mSources.clear(); 622 for (int i = 0, size = other.mSources.size(); i < size; i++) { 623 final InsetsSource otherSource = other.mSources.valueAt(i); 624 mSources.append(otherSource.getId(), copySources 625 ? new InsetsSource(otherSource) 626 : otherSource); 627 } 628 } 629 630 /** 631 * Sets the values from the other InsetsState. But for sources, only specific types of source 632 * would be set. 633 * 634 * @param other the other InsetsState. 635 * @param types the only types of sources would be set. 636 */ set(InsetsState other, @InsetsType int types)637 public void set(InsetsState other, @InsetsType int types) { 638 mDisplayFrame.set(other.mDisplayFrame); 639 mDisplayCutout.set(other.mDisplayCutout); 640 mRoundedCorners = other.getRoundedCorners(); 641 mRoundedCornerFrame.set(other.mRoundedCornerFrame); 642 mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); 643 mDisplayShape = other.getDisplayShape(); 644 if (types == 0) { 645 return; 646 } 647 for (int i = mSources.size() - 1; i >= 0; i--) { 648 final InsetsSource source = mSources.valueAt(i); 649 if ((source.getType() & types) != 0) { 650 mSources.removeAt(i); 651 } 652 } 653 for (int i = other.mSources.size() - 1; i >= 0; i--) { 654 final InsetsSource otherSource = other.mSources.valueAt(i); 655 if ((otherSource.getType() & types) != 0) { 656 mSources.put(otherSource.getId(), otherSource); 657 } 658 } 659 } 660 addSource(InsetsSource source)661 public void addSource(InsetsSource source) { 662 mSources.put(source.getId(), source); 663 } 664 clearsCompatInsets(int windowType, int windowFlags, @ActivityType int activityType, @InsetsType int forceConsumingTypes)665 public static boolean clearsCompatInsets(int windowType, int windowFlags, 666 @ActivityType int activityType, @InsetsType int forceConsumingTypes) { 667 return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0 668 // For compatibility reasons, this excludes the wallpaper, the system error windows, 669 // and the app windows while any system bar is forcibly consumed. 670 && windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR 671 // This ensures the app content won't be obscured by compat insets even if the app 672 // has FLAG_LAYOUT_NO_LIMITS. 673 && (forceConsumingTypes == 0 || activityType != ACTIVITY_TYPE_STANDARD); 674 } 675 dump(String prefix, PrintWriter pw)676 public void dump(String prefix, PrintWriter pw) { 677 final String newPrefix = prefix + " "; 678 pw.println(prefix + "InsetsState"); 679 pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame); 680 pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get()); 681 pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners); 682 pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame); 683 pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds); 684 pw.println(newPrefix + "mDisplayShape=" + mDisplayShape); 685 for (int i = 0, size = mSources.size(); i < size; i++) { 686 mSources.valueAt(i).dump(newPrefix + " ", pw); 687 } 688 } 689 dumpDebug(ProtoOutputStream proto, long fieldId)690 void dumpDebug(ProtoOutputStream proto, long fieldId) { 691 final long token = proto.start(fieldId); 692 final InsetsSource source = mSources.get(InsetsSource.ID_IME); 693 if (source != null) { 694 source.dumpDebug(proto, SOURCES); 695 } 696 mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME); 697 mDisplayCutout.get().dumpDebug(proto, DISPLAY_CUTOUT); 698 proto.end(token); 699 } 700 701 @Override equals(@ullable Object o)702 public boolean equals(@Nullable Object o) { 703 return equals(o, false, false); 704 } 705 706 /** 707 * An equals method can exclude the caption insets. This is useful because we assemble the 708 * caption insets information on the client side, and when we communicate with server, it's 709 * excluded. 710 * @param excludesCaptionBar If {@link Type#captionBar()}} should be ignored. 711 * @param excludesInvisibleIme If {@link WindowInsets.Type#ime()} should be ignored when IME is 712 * not visible. 713 * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. 714 */ 715 @VisibleForTesting equals(@ullable Object o, boolean excludesCaptionBar, boolean excludesInvisibleIme)716 public boolean equals(@Nullable Object o, boolean excludesCaptionBar, 717 boolean excludesInvisibleIme) { 718 if (this == o) { return true; } 719 if (o == null || getClass() != o.getClass()) { return false; } 720 721 InsetsState state = (InsetsState) o; 722 723 if (!mDisplayFrame.equals(state.mDisplayFrame) 724 || !mDisplayCutout.equals(state.mDisplayCutout) 725 || !mRoundedCorners.equals(state.mRoundedCorners) 726 || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame) 727 || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds) 728 || !mDisplayShape.equals(state.mDisplayShape)) { 729 return false; 730 } 731 732 final SparseArray<InsetsSource> thisSources = mSources; 733 final SparseArray<InsetsSource> thatSources = state.mSources; 734 if (!excludesCaptionBar && !excludesInvisibleIme) { 735 return thisSources.contentEquals(thatSources); 736 } else { 737 final int thisSize = thisSources.size(); 738 final int thatSize = thatSources.size(); 739 int thisIndex = 0; 740 int thatIndex = 0; 741 while (thisIndex < thisSize || thatIndex < thatSize) { 742 InsetsSource thisSource = thisIndex < thisSize 743 ? thisSources.valueAt(thisIndex) 744 : null; 745 746 // Seek to the next non-excluding source of ours. 747 while (thisSource != null 748 && (excludesCaptionBar && thisSource.getType() == captionBar() 749 || excludesInvisibleIme && thisSource.getType() == ime() 750 && !thisSource.isVisible())) { 751 thisIndex++; 752 thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null; 753 } 754 755 InsetsSource thatSource = thatIndex < thatSize 756 ? thatSources.valueAt(thatIndex) 757 : null; 758 759 // Seek to the next non-excluding source of theirs. 760 while (thatSource != null 761 && (excludesCaptionBar && thatSource.getType() == captionBar() 762 || excludesInvisibleIme && thatSource.getType() == ime() 763 && !thatSource.isVisible())) { 764 thatIndex++; 765 thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null; 766 } 767 768 if (!Objects.equals(thisSource, thatSource)) { 769 return false; 770 } 771 772 thisIndex++; 773 thatIndex++; 774 } 775 return true; 776 } 777 } 778 779 @Override 780 public int hashCode() { 781 return Objects.hash(mDisplayFrame, mDisplayCutout, mSources.contentHashCode(), 782 mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape); 783 } 784 785 public InsetsState(Parcel in) { 786 mSources = readFromParcel(in); 787 } 788 789 @Override 790 public int describeContents() { 791 return 0; 792 } 793 794 @Override 795 public void writeToParcel(Parcel dest, int flags) { 796 mDisplayFrame.writeToParcel(dest, flags); 797 mDisplayCutout.writeToParcel(dest, flags); 798 dest.writeTypedObject(mRoundedCorners, flags); 799 mRoundedCornerFrame.writeToParcel(dest, flags); 800 dest.writeTypedObject(mPrivacyIndicatorBounds, flags); 801 dest.writeTypedObject(mDisplayShape, flags); 802 final int size = mSources.size(); 803 dest.writeInt(size); 804 for (int i = 0; i < size; i++) { 805 dest.writeTypedObject(mSources.valueAt(i), flags); 806 } 807 } 808 809 public static final @NonNull Creator<InsetsState> CREATOR = new Creator<>() { 810 811 public InsetsState createFromParcel(Parcel in) { 812 return new InsetsState(in); 813 } 814 815 public InsetsState[] newArray(int size) { 816 return new InsetsState[size]; 817 } 818 }; 819 820 public SparseArray<InsetsSource> readFromParcel(Parcel in) { 821 mDisplayFrame.readFromParcel(in); 822 mDisplayCutout.readFromParcel(in); 823 mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); 824 mRoundedCornerFrame.readFromParcel(in); 825 mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); 826 mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); 827 final int size = in.readInt(); 828 final SparseArray<InsetsSource> sources; 829 if (mSources == null) { 830 // We are constructing this InsetsState. 831 sources = new SparseArray<>(size); 832 } else { 833 sources = mSources; 834 sources.clear(); 835 } 836 for (int i = 0; i < size; i++) { 837 final InsetsSource source = in.readTypedObject(InsetsSource.CREATOR); 838 sources.append(source.getId(), source); 839 } 840 return sources; 841 } 842 843 @Override 844 public String toString() { 845 final StringJoiner joiner = new StringJoiner(", "); 846 for (int i = 0, size = mSources.size(); i < size; i++) { 847 joiner.add(mSources.valueAt(i).toString()); 848 } 849 return "InsetsState: {" 850 + "mDisplayFrame=" + mDisplayFrame 851 + ", mDisplayCutout=" + mDisplayCutout 852 + ", mRoundedCorners=" + mRoundedCorners 853 + " mRoundedCornerFrame=" + mRoundedCornerFrame 854 + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds 855 + ", mDisplayShape=" + mDisplayShape 856 + ", mSources= { " + joiner 857 + " }"; 858 } 859 860 /** 861 * Traverses sources in two {@link InsetsState}s and calls back when events defined in 862 * {@link OnTraverseCallbacks} happen. This is optimized for {@link SparseArray} that we avoid 863 * triggering the binary search while getting the key or the value. 864 * 865 * This can be used to copy attributes of sources from one InsetsState to the other one, or to 866 * remove sources existing in one InsetsState but not in the other one. 867 * 868 * @param state1 The first {@link InsetsState} to be traversed. 869 * @param state2 The second {@link InsetsState} to be traversed. 870 * @param cb The {@link OnTraverseCallbacks} to call back to the caller. 871 */ 872 public static void traverse(InsetsState state1, InsetsState state2, OnTraverseCallbacks cb) { 873 cb.onStart(state1, state2); 874 final int size1 = state1.sourceSize(); 875 final int size2 = state2.sourceSize(); 876 int index1 = 0; 877 int index2 = 0; 878 while (index1 < size1 && index2 < size2) { 879 int id1 = state1.sourceIdAt(index1); 880 int id2 = state2.sourceIdAt(index2); 881 while (id1 != id2) { 882 if (id1 < id2) { 883 cb.onIdNotFoundInState2(index1, state1.sourceAt(index1)); 884 index1++; 885 if (index1 < size1) { 886 id1 = state1.sourceIdAt(index1); 887 } else { 888 break; 889 } 890 } else { 891 cb.onIdNotFoundInState1(index2, state2.sourceAt(index2)); 892 index2++; 893 if (index2 < size2) { 894 id2 = state2.sourceIdAt(index2); 895 } else { 896 break; 897 } 898 } 899 } 900 if (index1 >= size1 || index2 >= size2) { 901 break; 902 } 903 final InsetsSource source1 = state1.sourceAt(index1); 904 final InsetsSource source2 = state2.sourceAt(index2); 905 cb.onIdMatch(source1, source2); 906 index1++; 907 index2++; 908 } 909 while (index2 < size2) { 910 cb.onIdNotFoundInState1(index2, state2.sourceAt(index2)); 911 index2++; 912 } 913 while (index1 < size1) { 914 cb.onIdNotFoundInState2(index1, state1.sourceAt(index1)); 915 index1++; 916 } 917 cb.onFinish(state1, state2); 918 } 919 920 /** 921 * Used with {@link #traverse(InsetsState, InsetsState, OnTraverseCallbacks)} to call back when 922 * certain events happen. 923 */ 924 public interface OnTraverseCallbacks { 925 926 /** 927 * Called at the beginning of the traverse. 928 * 929 * @param state1 same as the state1 supplied to {@link #traverse} 930 * @param state2 same as the state2 supplied to {@link #traverse} 931 */ 932 default void onStart(InsetsState state1, InsetsState state2) { } 933 934 /** 935 * Called when finding two IDs from two InsetsStates are the same. 936 * 937 * @param source1 the source in state1. 938 * @param source2 the source in state2. 939 */ 940 default void onIdMatch(InsetsSource source1, InsetsSource source2) { } 941 942 /** 943 * Called when finding an ID in state2 but not in state1. 944 * 945 * @param index2 the index of the ID in state2. 946 * @param source2 the source which has the ID in state2. 947 */ 948 default void onIdNotFoundInState1(int index2, InsetsSource source2) { } 949 950 /** 951 * Called when finding an ID in state1 but not in state2. 952 * 953 * @param index1 the index of the ID in state1. 954 * @param source1 the source which has the ID in state1. 955 */ 956 default void onIdNotFoundInState2(int index1, InsetsSource source1) { } 957 958 /** 959 * Called at the end of the traverse. 960 * 961 * @param state1 same as the state1 supplied to {@link #traverse} 962 * @param state2 same as the state2 supplied to {@link #traverse} 963 */ 964 default void onFinish(InsetsState state1, InsetsState state2) { } 965 } 966 } 967 968