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