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.VISIBLE; 22 import static android.view.InsetsSourceProto.VISIBLE_FRAME; 23 import static android.view.WindowInsets.Type.captionBar; 24 import static android.view.WindowInsets.Type.ime; 25 26 import android.annotation.IntDef; 27 import android.annotation.IntRange; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.graphics.Insets; 31 import android.graphics.Rect; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.util.proto.ProtoOutputStream; 35 import android.view.WindowInsets.Type.InsetsType; 36 37 import java.io.PrintWriter; 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.Objects; 41 import java.util.StringJoiner; 42 43 /** 44 * Represents the state of a single entity generating insets for clients. 45 * @hide 46 */ 47 public class InsetsSource implements Parcelable { 48 49 /** The insets source ID of IME */ 50 public static final int ID_IME = createId(null, 0, ime()); 51 /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */ 52 static final int ID_IME_CAPTION_BAR = 53 InsetsSource.createId(null /* owner */, 1 /* index */, captionBar()); 54 55 /** 56 * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't 57 * draw a semi-transparent scrim behind the system bar area even when the bar contrast is 58 * enforced. 59 * 60 * @see android.R.styleable#Window_enforceStatusBarContrast 61 * @see android.R.styleable#Window_enforceNavigationBarContrast 62 */ 63 public static final int FLAG_SUPPRESS_SCRIM = 1; 64 65 /** 66 * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the 67 * insets frame size when calculating the rounded corner insets to other windows. 68 * 69 * For example, task bar will draw fake rounded corners above itself, so we need to move the 70 * rounded corner up by the task bar insets size to make other windows see a rounded corner 71 * above the task bar. 72 */ 73 public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; 74 75 /** 76 * Controls whether the insets provided by this source should be forcibly consumed. 77 */ 78 public static final int FLAG_FORCE_CONSUMING = 1 << 2; 79 80 @Retention(RetentionPolicy.SOURCE) 81 @IntDef(flag = true, prefix = "FLAG_", value = { 82 FLAG_SUPPRESS_SCRIM, 83 FLAG_INSETS_ROUNDED_CORNER, 84 FLAG_FORCE_CONSUMING, 85 }) 86 public @interface Flags {} 87 88 private @Flags int mFlags; 89 90 /** 91 * An unique integer to identify this source across processes. 92 */ 93 private final int mId; 94 95 private final @InsetsType int mType; 96 97 /** Frame of the source in screen coordinate space */ 98 private final Rect mFrame; 99 private @Nullable Rect mVisibleFrame; 100 101 private boolean mVisible; 102 103 private final Rect mTmpFrame = new Rect(); 104 InsetsSource(int id, @InsetsType int type)105 public InsetsSource(int id, @InsetsType int type) { 106 mId = id; 107 mType = type; 108 mFrame = new Rect(); 109 mVisible = (WindowInsets.Type.defaultVisible() & type) != 0; 110 } 111 InsetsSource(InsetsSource other)112 public InsetsSource(InsetsSource other) { 113 mId = other.mId; 114 mType = other.mType; 115 mFrame = new Rect(other.mFrame); 116 mVisible = other.mVisible; 117 mVisibleFrame = other.mVisibleFrame != null 118 ? new Rect(other.mVisibleFrame) 119 : null; 120 mFlags = other.mFlags; 121 } 122 set(InsetsSource other)123 public void set(InsetsSource other) { 124 mFrame.set(other.mFrame); 125 mVisible = other.mVisible; 126 mVisibleFrame = other.mVisibleFrame != null 127 ? new Rect(other.mVisibleFrame) 128 : null; 129 mFlags = other.mFlags; 130 } 131 setFrame(int left, int top, int right, int bottom)132 public InsetsSource setFrame(int left, int top, int right, int bottom) { 133 mFrame.set(left, top, right, bottom); 134 return this; 135 } 136 setFrame(Rect frame)137 public InsetsSource setFrame(Rect frame) { 138 mFrame.set(frame); 139 return this; 140 } 141 setVisibleFrame(@ullable Rect visibleFrame)142 public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) { 143 mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null; 144 return this; 145 } 146 setVisible(boolean visible)147 public InsetsSource setVisible(boolean visible) { 148 mVisible = visible; 149 return this; 150 } 151 setFlags(@lags int flags)152 public InsetsSource setFlags(@Flags int flags) { 153 mFlags = flags; 154 return this; 155 } 156 setFlags(@lags int flags, @Flags int mask)157 public InsetsSource setFlags(@Flags int flags, @Flags int mask) { 158 mFlags = (mFlags & ~mask) | (flags & mask); 159 return this; 160 } 161 getId()162 public int getId() { 163 return mId; 164 } 165 getType()166 public @InsetsType int getType() { 167 return mType; 168 } 169 getFrame()170 public Rect getFrame() { 171 return mFrame; 172 } 173 getVisibleFrame()174 public @Nullable Rect getVisibleFrame() { 175 return mVisibleFrame; 176 } 177 isVisible()178 public boolean isVisible() { 179 return mVisible; 180 } 181 getFlags()182 public @Flags int getFlags() { 183 return mFlags; 184 } 185 hasFlags(int flags)186 public boolean hasFlags(int flags) { 187 return (mFlags & flags) == flags; 188 } 189 isUserControllable()190 boolean isUserControllable() { 191 // If mVisibleFrame is null, it will be the same area as mFrame. 192 return mVisibleFrame == null || !mVisibleFrame.isEmpty(); 193 } 194 195 /** 196 * Calculates the insets this source will cause to a client window. 197 * 198 * @param relativeFrame The frame to calculate the insets relative to. 199 * @param ignoreVisibility If true, always reports back insets even if source isn't visible. 200 * @return The resulting insets. The contract is that only one side will be occupied by a 201 * source. 202 */ calculateInsets(Rect relativeFrame, boolean ignoreVisibility)203 public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { 204 return calculateInsets(relativeFrame, mFrame, ignoreVisibility); 205 } 206 207 /** 208 * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. 209 */ calculateVisibleInsets(Rect relativeFrame)210 public Insets calculateVisibleInsets(Rect relativeFrame) { 211 return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, 212 false /* ignoreVisibility */); 213 } 214 calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility)215 private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { 216 if (!ignoreVisibility && !mVisible) { 217 return Insets.NONE; 218 } 219 // During drag-move and drag-resizing, the caption insets position may not get updated 220 // before the app frame get updated. To layout the app content correctly during drag events, 221 // we always return the insets with the corresponding height covering the top. 222 // However, with the "fake" IME navigation bar treated as a caption bar, we return the 223 // insets with the corresponding height the bottom. 224 if (getType() == WindowInsets.Type.captionBar()) { 225 return getId() == ID_IME_CAPTION_BAR 226 ? Insets.of(0, 0, 0, frame.height()) 227 : Insets.of(0, frame.height(), 0, 0); 228 } 229 // Checks for whether there is shared edge with insets for 0-width/height window. 230 final boolean hasIntersection = relativeFrame.isEmpty() 231 ? getIntersection(frame, relativeFrame, mTmpFrame) 232 : mTmpFrame.setIntersect(frame, relativeFrame); 233 if (!hasIntersection) { 234 return Insets.NONE; 235 } 236 237 // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. 238 // However, we should let the policy decide from the server. 239 if (getType() == WindowInsets.Type.ime()) { 240 return Insets.of(0, 0, 0, mTmpFrame.height()); 241 } 242 243 // Intersecting at top/bottom 244 if (mTmpFrame.width() == relativeFrame.width()) { 245 if (mTmpFrame.top == relativeFrame.top) { 246 return Insets.of(0, mTmpFrame.height(), 0, 0); 247 } else if (mTmpFrame.bottom == relativeFrame.bottom) { 248 return Insets.of(0, 0, 0, mTmpFrame.height()); 249 } 250 // TODO: remove when insets are shell-customizable. 251 // This is a hack that says "if this is a top-inset (eg statusbar), always apply it 252 // to the top". It is used when adjusting primary split for IME. 253 if (mTmpFrame.top == 0) { 254 return Insets.of(0, mTmpFrame.height(), 0, 0); 255 } 256 } 257 // Intersecting at left/right 258 else if (mTmpFrame.height() == relativeFrame.height()) { 259 if (mTmpFrame.left == relativeFrame.left) { 260 return Insets.of(mTmpFrame.width(), 0, 0, 0); 261 } else if (mTmpFrame.right == relativeFrame.right) { 262 return Insets.of(0, 0, mTmpFrame.width(), 0); 263 } 264 } 265 return Insets.NONE; 266 } 267 268 /** 269 * Outputs the intersection of two rectangles. The shared edges will also be counted in the 270 * intersection. 271 * 272 * @param a The first rectangle being intersected with. 273 * @param b The second rectangle being intersected with. 274 * @param out The rectangle which represents the intersection. 275 * @return {@code true} if there is any intersection. 276 */ getIntersection(@onNull Rect a, @NonNull Rect b, @NonNull Rect out)277 private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) { 278 if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) { 279 out.left = Math.max(a.left, b.left); 280 out.top = Math.max(a.top, b.top); 281 out.right = Math.min(a.right, b.right); 282 out.bottom = Math.min(a.bottom, b.bottom); 283 return true; 284 } 285 out.setEmpty(); 286 return false; 287 } 288 289 /** 290 * Creates an identifier of an {@link InsetsSource}. 291 * 292 * @param owner An object owned by the owner. Only the owner can modify its own sources. 293 * @param index An owner may have multiple sources with the same type. For example, the system 294 * server might have multiple display cutout sources. This is used to identify 295 * which one is which. The value must be in a range of [0, 2047]. 296 * @param type The {@link InsetsType type} of the source. 297 * @return a unique integer as the identifier. 298 */ createId(Object owner, @IntRange(from = 0, to = 2047) int index, @InsetsType int type)299 public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index, 300 @InsetsType int type) { 301 if (index < 0 || index >= 2048) { 302 throw new IllegalArgumentException(); 303 } 304 // owner takes top 16 bits; 305 // index takes 11 bits since the 6th bit; 306 // type takes bottom 5 bits. 307 return ((System.identityHashCode(owner) % (1 << 16)) << 16) 308 + (index << 5) 309 + WindowInsets.Type.indexOf(type); 310 } 311 312 /** 313 * Gets the index from the ID. 314 * 315 * @see #createId(Object, int, int) 316 */ getIndex(int id)317 public static int getIndex(int id) { 318 // start: ????????????????***********????? 319 // & 65535: 0000000000000000***********????? 320 // >> 5: 000000000000000000000*********** 321 return (id & 65535) >> 5; 322 } 323 324 /** 325 * Gets the {@link InsetsType} from the ID. 326 * 327 * @see #createId(Object, int, int) 328 * @see WindowInsets.Type#indexOf(int) 329 */ getType(int id)330 public static int getType(int id) { 331 // start: ???????????????????????????***** 332 // & 31: 000000000000000000000000000***** 333 // 1 <<: See WindowInsets.Type#indexOf 334 return 1 << (id & 31); 335 } 336 flagsToString(@lags int flags)337 public static String flagsToString(@Flags int flags) { 338 final StringJoiner joiner = new StringJoiner(" "); 339 if ((flags & FLAG_SUPPRESS_SCRIM) != 0) { 340 joiner.add("SUPPRESS_SCRIM"); 341 } 342 if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { 343 joiner.add("INSETS_ROUNDED_CORNER"); 344 } 345 if ((flags & FLAG_FORCE_CONSUMING) != 0) { 346 joiner.add("FORCE_CONSUMING"); 347 } 348 return joiner.toString(); 349 } 350 351 /** 352 * Export the state of {@link InsetsSource} into a protocol buffer output stream. 353 * 354 * @param proto Stream to write the state to 355 * @param fieldId FieldId of InsetsSource as defined in the parent message 356 */ dumpDebug(ProtoOutputStream proto, long fieldId)357 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 358 final long token = proto.start(fieldId); 359 proto.write(TYPE, WindowInsets.Type.toString(mType)); 360 mFrame.dumpDebug(proto, FRAME); 361 if (mVisibleFrame != null) { 362 mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME); 363 } 364 proto.write(VISIBLE, mVisible); 365 proto.end(token); 366 } 367 dump(String prefix, PrintWriter pw)368 public void dump(String prefix, PrintWriter pw) { 369 pw.print(prefix); 370 pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId)); 371 pw.print(" type="); pw.print(WindowInsets.Type.toString(mType)); 372 pw.print(" frame="); pw.print(mFrame.toShortString()); 373 if (mVisibleFrame != null) { 374 pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); 375 } 376 pw.print(" visible="); pw.print(mVisible); 377 pw.print(" flags="); pw.print(flagsToString(mFlags)); 378 pw.println(); 379 } 380 381 @Override equals(@ullable Object o)382 public boolean equals(@Nullable Object o) { 383 return equals(o, false); 384 } 385 386 /** 387 * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored 388 * when IME is not visible. 389 */ equals(@ullable Object o, boolean excludeInvisibleImeFrames)390 public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) { 391 if (this == o) return true; 392 if (o == null || getClass() != o.getClass()) return false; 393 394 InsetsSource that = (InsetsSource) o; 395 396 if (mId != that.mId) return false; 397 if (mType != that.mType) return false; 398 if (mVisible != that.mVisible) return false; 399 if (mFlags != that.mFlags) return false; 400 if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true; 401 if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; 402 return mFrame.equals(that.mFrame); 403 } 404 405 @Override hashCode()406 public int hashCode() { 407 return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags); 408 } 409 InsetsSource(Parcel in)410 public InsetsSource(Parcel in) { 411 mId = in.readInt(); 412 mType = in.readInt(); 413 mFrame = Rect.CREATOR.createFromParcel(in); 414 if (in.readInt() != 0) { 415 mVisibleFrame = Rect.CREATOR.createFromParcel(in); 416 } else { 417 mVisibleFrame = null; 418 } 419 mVisible = in.readBoolean(); 420 mFlags = in.readInt(); 421 } 422 423 @Override describeContents()424 public int describeContents() { 425 return 0; 426 } 427 428 @Override writeToParcel(Parcel dest, int flags)429 public void writeToParcel(Parcel dest, int flags) { 430 dest.writeInt(mId); 431 dest.writeInt(mType); 432 mFrame.writeToParcel(dest, 0); 433 if (mVisibleFrame != null) { 434 dest.writeInt(1); 435 mVisibleFrame.writeToParcel(dest, 0); 436 } else { 437 dest.writeInt(0); 438 } 439 dest.writeBoolean(mVisible); 440 dest.writeInt(mFlags); 441 } 442 443 @Override toString()444 public String toString() { 445 return "InsetsSource: {" + Integer.toHexString(mId) 446 + " mType=" + WindowInsets.Type.toString(mType) 447 + " mFrame=" + mFrame.toShortString() 448 + " mVisible=" + mVisible 449 + " mFlags=[" + flagsToString(mFlags) + "]" 450 + "}"; 451 } 452 453 public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() { 454 455 public InsetsSource createFromParcel(Parcel in) { 456 return new InsetsSource(in); 457 } 458 459 public InsetsSource[] newArray(int size) { 460 return new InsetsSource[size]; 461 } 462 }; 463 } 464