| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.view; |
| |
| import static android.view.InsetsSourceProto.FRAME; |
| import static android.view.InsetsSourceProto.TYPE; |
| import static android.view.InsetsSourceProto.TYPE_NUMBER; |
| import static android.view.InsetsSourceProto.VISIBLE; |
| import static android.view.InsetsSourceProto.VISIBLE_FRAME; |
| import static android.view.WindowInsets.Type.captionBar; |
| import static android.view.WindowInsets.Type.ime; |
| |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.graphics.Insets; |
| import android.graphics.Rect; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.WindowInsets.Type.InsetsType; |
| |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Objects; |
| import java.util.StringJoiner; |
| |
| /** |
| * Represents the state of a single entity generating insets for clients. |
| * @hide |
| */ |
| public class InsetsSource implements Parcelable { |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = "SIDE_", value = { |
| SIDE_NONE, |
| SIDE_LEFT, |
| SIDE_TOP, |
| SIDE_RIGHT, |
| SIDE_BOTTOM, |
| SIDE_UNKNOWN |
| }) |
| public @interface InternalInsetsSide {} |
| |
| static final int SIDE_NONE = 0; |
| static final int SIDE_LEFT = 1; |
| static final int SIDE_TOP = 2; |
| static final int SIDE_RIGHT = 3; |
| static final int SIDE_BOTTOM = 4; |
| static final int SIDE_UNKNOWN = 5; |
| |
| /** The insets source ID of IME */ |
| public static final int ID_IME = createId(null, 0, ime()); |
| |
| /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */ |
| public static final int ID_IME_CAPTION_BAR = |
| InsetsSource.createId(null /* owner */, 1 /* index */, captionBar()); |
| |
| /** |
| * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't |
| * draw a semi-transparent scrim behind the system bar area even when the bar contrast is |
| * enforced. |
| * |
| * @see android.R.styleable#Window_enforceStatusBarContrast |
| * @see android.R.styleable#Window_enforceNavigationBarContrast |
| */ |
| public static final int FLAG_SUPPRESS_SCRIM = 1; |
| |
| /** |
| * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the |
| * insets frame size when calculating the rounded corner insets to other windows. |
| * |
| * For example, task bar will draw fake rounded corners above itself, so we need to move the |
| * rounded corner up by the task bar insets size to make other windows see a rounded corner |
| * above the task bar. |
| */ |
| public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; |
| |
| /** |
| * Controls whether the insets provided by this source should be forcibly consumed. |
| */ |
| public static final int FLAG_FORCE_CONSUMING = 1 << 2; |
| |
| /** |
| * Controls whether the insets source will play an animation when resizing. |
| */ |
| public static final int FLAG_ANIMATE_RESIZING = 1 << 3; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(flag = true, prefix = "FLAG_", value = { |
| FLAG_SUPPRESS_SCRIM, |
| FLAG_INSETS_ROUNDED_CORNER, |
| FLAG_FORCE_CONSUMING, |
| FLAG_ANIMATE_RESIZING, |
| }) |
| public @interface Flags {} |
| |
| /** |
| * Used when there are no bounding rects to describe an inset, which is only possible when the |
| * insets itself is {@link Insets#NONE}. |
| */ |
| private static final Rect[] NO_BOUNDING_RECTS = new Rect[0]; |
| |
| private @Flags int mFlags; |
| |
| /** |
| * An unique integer to identify this source across processes. |
| */ |
| private final int mId; |
| |
| private final @InsetsType int mType; |
| |
| /** Frame of the source in screen coordinate space */ |
| private final Rect mFrame; |
| private @Nullable Rect mVisibleFrame; |
| private @Nullable Rect[] mBoundingRects; |
| |
| private boolean mVisible; |
| |
| /** |
| * Used to decide which side of the relative frame should receive insets when the frame fully |
| * covers the relative frame. |
| */ |
| private @InternalInsetsSide int mSideHint = SIDE_NONE; |
| |
| private final Rect mTmpFrame = new Rect(); |
| private final Rect mTmpBoundingRect = new Rect(); |
| |
| public InsetsSource(int id, @InsetsType int type) { |
| mId = id; |
| mType = type; |
| mFrame = new Rect(); |
| mVisible = (WindowInsets.Type.defaultVisible() & type) != 0; |
| } |
| |
| public InsetsSource(InsetsSource other) { |
| mId = other.mId; |
| mType = other.mType; |
| mFrame = new Rect(other.mFrame); |
| mVisible = other.mVisible; |
| mVisibleFrame = other.mVisibleFrame != null |
| ? new Rect(other.mVisibleFrame) |
| : null; |
| mFlags = other.mFlags; |
| mSideHint = other.mSideHint; |
| mBoundingRects = other.mBoundingRects != null |
| ? other.mBoundingRects.clone() |
| : null; |
| } |
| |
| public void set(InsetsSource other) { |
| mFrame.set(other.mFrame); |
| mVisible = other.mVisible; |
| mVisibleFrame = other.mVisibleFrame != null |
| ? new Rect(other.mVisibleFrame) |
| : null; |
| mFlags = other.mFlags; |
| mSideHint = other.mSideHint; |
| mBoundingRects = other.mBoundingRects != null |
| ? other.mBoundingRects.clone() |
| : null; |
| } |
| |
| public InsetsSource setFrame(int left, int top, int right, int bottom) { |
| mFrame.set(left, top, right, bottom); |
| return this; |
| } |
| |
| public InsetsSource setFrame(Rect frame) { |
| mFrame.set(frame); |
| return this; |
| } |
| |
| public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) { |
| mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null; |
| return this; |
| } |
| |
| public InsetsSource setVisible(boolean visible) { |
| mVisible = visible; |
| return this; |
| } |
| |
| public InsetsSource setFlags(@Flags int flags) { |
| mFlags = flags; |
| return this; |
| } |
| |
| public InsetsSource setFlags(@Flags int flags, @Flags int mask) { |
| mFlags = (mFlags & ~mask) | (flags & mask); |
| return this; |
| } |
| |
| /** |
| * Updates the side hint which is used to decide which side of the relative frame should receive |
| * insets when the frame fully covers the relative frame. |
| * |
| * @param bounds A rectangle which contains the frame. It will be used to calculate the hint. |
| */ |
| public InsetsSource updateSideHint(Rect bounds) { |
| mSideHint = getInsetSide( |
| calculateInsets(bounds, mFrame, true /* ignoreVisibility */)); |
| return this; |
| } |
| |
| /** |
| * Set the bounding rectangles of this source. They are expected to be relative to the source |
| * frame. |
| */ |
| public InsetsSource setBoundingRects(@Nullable Rect[] rects) { |
| mBoundingRects = rects != null ? rects.clone() : null; |
| return this; |
| } |
| |
| public int getId() { |
| return mId; |
| } |
| |
| public @InsetsType int getType() { |
| return mType; |
| } |
| |
| public Rect getFrame() { |
| return mFrame; |
| } |
| |
| public @Nullable Rect getVisibleFrame() { |
| return mVisibleFrame; |
| } |
| |
| public boolean isVisible() { |
| return mVisible; |
| } |
| |
| public @Flags int getFlags() { |
| return mFlags; |
| } |
| |
| public boolean hasFlags(int flags) { |
| return (mFlags & flags) == flags; |
| } |
| |
| /** |
| * Returns the bounding rectangles of this source. |
| */ |
| public @Nullable Rect[] getBoundingRects() { |
| return mBoundingRects; |
| } |
| |
| /** |
| * Calculates the insets this source will cause to a client window. |
| * |
| * @param relativeFrame The frame to calculate the insets relative to. |
| * @param ignoreVisibility If true, always reports back insets even if source isn't visible. |
| * @return The resulting insets. The contract is that only one side will be occupied by a |
| * source. |
| */ |
| public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { |
| return calculateInsets(relativeFrame, mFrame, ignoreVisibility); |
| } |
| |
| /** |
| * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. |
| */ |
| public Insets calculateVisibleInsets(Rect relativeFrame) { |
| return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, |
| false /* ignoreVisibility */); |
| } |
| |
| private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { |
| if (!ignoreVisibility && !mVisible) { |
| return Insets.NONE; |
| } |
| // During drag-move and drag-resizing, the caption insets position may not get updated |
| // before the app frame get updated. To layout the app content correctly during drag events, |
| // we always return the insets with the corresponding height covering the top. |
| // However, with the "fake" IME navigation bar treated as a caption bar, we return the |
| // insets with the corresponding height the bottom. |
| if (getType() == WindowInsets.Type.captionBar()) { |
| return getId() == ID_IME_CAPTION_BAR |
| ? Insets.of(0, 0, 0, frame.height()) |
| : Insets.of(0, frame.height(), 0, 0); |
| } |
| // Checks for whether there is shared edge with insets for 0-width/height window. |
| final boolean hasIntersection = relativeFrame.isEmpty() |
| ? getIntersection(frame, relativeFrame, mTmpFrame) |
| : mTmpFrame.setIntersect(frame, relativeFrame); |
| if (!hasIntersection) { |
| return Insets.NONE; |
| } |
| |
| // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. |
| // However, we should let the policy decide from the server. |
| if (getType() == WindowInsets.Type.ime()) { |
| return Insets.of(0, 0, 0, mTmpFrame.height()); |
| } |
| |
| if (mTmpFrame.equals(relativeFrame)) { |
| // Covering all sides |
| switch (mSideHint) { |
| default: |
| case SIDE_LEFT: |
| return Insets.of(mTmpFrame.width(), 0, 0, 0); |
| case SIDE_TOP: |
| return Insets.of(0, mTmpFrame.height(), 0, 0); |
| case SIDE_RIGHT: |
| return Insets.of(0, 0, mTmpFrame.width(), 0); |
| case SIDE_BOTTOM: |
| return Insets.of(0, 0, 0, mTmpFrame.height()); |
| } |
| } else if (mTmpFrame.width() == relativeFrame.width()) { |
| // Intersecting at top/bottom |
| if (mTmpFrame.top == relativeFrame.top) { |
| return Insets.of(0, mTmpFrame.height(), 0, 0); |
| } else if (mTmpFrame.bottom == relativeFrame.bottom) { |
| return Insets.of(0, 0, 0, mTmpFrame.height()); |
| } |
| // TODO: remove when insets are shell-customizable. |
| // This is a hack that says "if this is a top-inset (eg statusbar), always apply it |
| // to the top". It is used when adjusting primary split for IME. |
| if (mTmpFrame.top == 0) { |
| return Insets.of(0, mTmpFrame.height(), 0, 0); |
| } |
| } else if (mTmpFrame.height() == relativeFrame.height()) { |
| // Intersecting at left/right |
| if (mTmpFrame.left == relativeFrame.left) { |
| return Insets.of(mTmpFrame.width(), 0, 0, 0); |
| } else if (mTmpFrame.right == relativeFrame.right) { |
| return Insets.of(0, 0, mTmpFrame.width(), 0); |
| } |
| } |
| return Insets.NONE; |
| } |
| |
| /** |
| * Calculates the bounding rects the source will cause to a client window. |
| */ |
| public @NonNull Rect[] calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility) { |
| if (!ignoreVisibility && !mVisible) { |
| return NO_BOUNDING_RECTS; |
| } |
| |
| final Rect frame = getFrame(); |
| if (mBoundingRects == null) { |
| // No bounding rects set, make a single bounding rect that covers the intersection of |
| // the |frame| and the |relativeFrame|. Also make it relative to the window origin. |
| return mTmpBoundingRect.setIntersect(frame, relativeFrame) |
| ? new Rect[]{ |
| new Rect( |
| mTmpBoundingRect.left - relativeFrame.left, |
| mTmpBoundingRect.top - relativeFrame.top, |
| mTmpBoundingRect.right - relativeFrame.left, |
| mTmpBoundingRect.bottom - relativeFrame.top |
| ) |
| } |
| : NO_BOUNDING_RECTS; |
| } |
| |
| // Special treatment for captionBar inset type. During drag-resizing, the |frame| and |
| // |boundingRects| may not get updated as quickly as |relativeFrame|, so just assume the |
| // |frame| will always be either at the top or bottom of |relativeFrame|. This means some |
| // calculations to make |boundingRects| relative to |relativeFrame| can be skipped or |
| // simplified. |
| // TODO(b/254128050): remove special treatment. |
| if (getType() == WindowInsets.Type.captionBar()) { |
| final ArrayList<Rect> validBoundingRects = new ArrayList<>(); |
| for (final Rect boundingRect : mBoundingRects) { |
| // Assume that the caption |frame| and |relativeFrame| perfectly align at the top |
| // or bottom, meaning that the provided |boundingRect|, which is relative to the |
| // |frame| either is already relative to |relativeFrame| (for top captionBar()), or |
| // just needs to be made relative to |relativeFrame| for bottom bars. |
| final int frameHeight = frame.height(); |
| mTmpBoundingRect.set(boundingRect); |
| if (getId() == ID_IME_CAPTION_BAR) { |
| mTmpBoundingRect.offset(0, relativeFrame.height() - frameHeight); |
| } |
| validBoundingRects.add(new Rect(mTmpBoundingRect)); |
| } |
| return validBoundingRects.toArray(new Rect[validBoundingRects.size()]); |
| } |
| |
| // Regular treatment for non-captionBar inset types. |
| final ArrayList<Rect> validBoundingRects = new ArrayList<>(); |
| for (final Rect boundingRect : mBoundingRects) { |
| // |boundingRect| was provided relative to |frame|. Make it absolute to be in the same |
| // coordinate system as |frame|. |
| final Rect absBoundingRect = new Rect( |
| boundingRect.left + frame.left, |
| boundingRect.top + frame.top, |
| boundingRect.right + frame.left, |
| boundingRect.bottom + frame.top |
| ); |
| // Now find the intersection of that |absBoundingRect| with |relativeFrame|. In other |
| // words, whichever part of the bounding rect is inside the window frame. |
| if (!mTmpBoundingRect.setIntersect(absBoundingRect, relativeFrame)) { |
| // It's possible for this to be empty if the frame and bounding rects were larger |
| // than the |relativeFrame|, such as when a system window is wider than the app |
| // window width. Just ignore that rect since it will have no effect on the |
| // window insets. |
| continue; |
| } |
| // At this point, |mTmpBoundingRect| is a valid bounding rect located fully inside the |
| // window, convert it to be relative to the window so that apps don't need to know the |
| // location of the window to understand bounding rects. |
| validBoundingRects.add(new Rect( |
| mTmpBoundingRect.left - relativeFrame.left, |
| mTmpBoundingRect.top - relativeFrame.top, |
| mTmpBoundingRect.right - relativeFrame.left, |
| mTmpBoundingRect.bottom - relativeFrame.top)); |
| } |
| if (validBoundingRects.isEmpty()) { |
| return NO_BOUNDING_RECTS; |
| } |
| return validBoundingRects.toArray(new Rect[validBoundingRects.size()]); |
| } |
| |
| /** |
| * Outputs the intersection of two rectangles. The shared edges will also be counted in the |
| * intersection. |
| * |
| * @param a The first rectangle being intersected with. |
| * @param b The second rectangle being intersected with. |
| * @param out The rectangle which represents the intersection. |
| * @return {@code true} if there is any intersection. |
| */ |
| private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) { |
| if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) { |
| out.left = Math.max(a.left, b.left); |
| out.top = Math.max(a.top, b.top); |
| out.right = Math.min(a.right, b.right); |
| out.bottom = Math.min(a.bottom, b.bottom); |
| return true; |
| } |
| out.setEmpty(); |
| return false; |
| } |
| |
| /** |
| * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b |
| * is set in order that this method returns a meaningful result. |
| */ |
| static @InternalInsetsSide int getInsetSide(Insets insets) { |
| if (Insets.NONE.equals(insets)) { |
| return SIDE_NONE; |
| } |
| if (insets.left != 0) { |
| return SIDE_LEFT; |
| } |
| if (insets.top != 0) { |
| return SIDE_TOP; |
| } |
| if (insets.right != 0) { |
| return SIDE_RIGHT; |
| } |
| if (insets.bottom != 0) { |
| return SIDE_BOTTOM; |
| } |
| return SIDE_UNKNOWN; |
| } |
| |
| static String sideToString(@InternalInsetsSide int side) { |
| switch (side) { |
| case SIDE_NONE: |
| return "NONE"; |
| case SIDE_LEFT: |
| return "LEFT"; |
| case SIDE_TOP: |
| return "TOP"; |
| case SIDE_RIGHT: |
| return "RIGHT"; |
| case SIDE_BOTTOM: |
| return "BOTTOM"; |
| default: |
| return "UNKNOWN:" + side; |
| } |
| } |
| |
| /** |
| * Creates an identifier of an {@link InsetsSource}. |
| * |
| * @param owner An object owned by the owner. Only the owner can modify its own sources. |
| * @param index An owner may have multiple sources with the same type. For example, the system |
| * server might have multiple display cutout sources. This is used to identify |
| * which one is which. The value must be in a range of [0, 2047]. |
| * @param type The {@link InsetsType type} of the source. |
| * @return a unique integer as the identifier. |
| */ |
| public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index, |
| @InsetsType int type) { |
| if (index < 0 || index >= 2048) { |
| throw new IllegalArgumentException(); |
| } |
| // owner takes top 16 bits; |
| // index takes 11 bits since the 6th bit; |
| // type takes bottom 5 bits. |
| return ((System.identityHashCode(owner) % (1 << 16)) << 16) |
| + (index << 5) |
| + WindowInsets.Type.indexOf(type); |
| } |
| |
| /** |
| * Gets the index from the ID. |
| * |
| * @see #createId(Object, int, int) |
| */ |
| public static int getIndex(int id) { |
| // start: ????????????????***********????? |
| // & 65535: 0000000000000000***********????? |
| // >> 5: 000000000000000000000*********** |
| return (id & 65535) >> 5; |
| } |
| |
| /** |
| * Gets the {@link InsetsType} from the ID. |
| * |
| * @see #createId(Object, int, int) |
| * @see WindowInsets.Type#indexOf(int) |
| */ |
| public static int getType(int id) { |
| // start: ???????????????????????????***** |
| // & 31: 000000000000000000000000000***** |
| // 1 <<: See WindowInsets.Type#indexOf |
| return 1 << (id & 31); |
| } |
| |
| public static String flagsToString(@Flags int flags) { |
| final StringJoiner joiner = new StringJoiner("|"); |
| if ((flags & FLAG_SUPPRESS_SCRIM) != 0) { |
| joiner.add("SUPPRESS_SCRIM"); |
| } |
| if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { |
| joiner.add("INSETS_ROUNDED_CORNER"); |
| } |
| if ((flags & FLAG_FORCE_CONSUMING) != 0) { |
| joiner.add("FORCE_CONSUMING"); |
| } |
| if ((flags & FLAG_ANIMATE_RESIZING) != 0) { |
| joiner.add("ANIMATE_RESIZING"); |
| } |
| return joiner.toString(); |
| } |
| |
| /** |
| * Export the state of {@link InsetsSource} into a protocol buffer output stream. |
| * |
| * @param proto Stream to write the state to |
| * @param fieldId FieldId of InsetsSource as defined in the parent message |
| */ |
| public void dumpDebug(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| if (!android.os.Flags.androidOsBuildVanillaIceCream()) { |
| // Deprecated since V. |
| proto.write(TYPE, WindowInsets.Type.toString(mType)); |
| } |
| mFrame.dumpDebug(proto, FRAME); |
| if (mVisibleFrame != null) { |
| mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME); |
| } |
| proto.write(VISIBLE, mVisible); |
| proto.write(TYPE_NUMBER, mType); |
| proto.end(token); |
| } |
| |
| public void dump(String prefix, PrintWriter pw) { |
| pw.print(prefix); |
| pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId)); |
| pw.print(" type="); pw.print(WindowInsets.Type.toString(mType)); |
| pw.print(" frame="); pw.print(mFrame.toShortString()); |
| if (mVisibleFrame != null) { |
| pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); |
| } |
| pw.print(" visible="); pw.print(mVisible); |
| pw.print(" flags="); pw.print(flagsToString(mFlags)); |
| pw.print(" sideHint="); pw.print(sideToString(mSideHint)); |
| pw.print(" boundingRects="); pw.print(Arrays.toString(mBoundingRects)); |
| pw.println(); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object o) { |
| return equals(o, false); |
| } |
| |
| /** |
| * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored |
| * when IME is not visible. |
| */ |
| public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| InsetsSource that = (InsetsSource) o; |
| |
| if (mId != that.mId) return false; |
| if (mType != that.mType) return false; |
| if (mVisible != that.mVisible) return false; |
| if (mFlags != that.mFlags) return false; |
| if (mSideHint != that.mSideHint) return false; |
| if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true; |
| if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; |
| if (!mFrame.equals(that.mFrame)) return false; |
| return Arrays.equals(mBoundingRects, that.mBoundingRects); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint, |
| Arrays.hashCode(mBoundingRects)); |
| } |
| |
| public InsetsSource(Parcel in) { |
| mId = in.readInt(); |
| mType = in.readInt(); |
| mFrame = Rect.CREATOR.createFromParcel(in); |
| if (in.readInt() != 0) { |
| mVisibleFrame = Rect.CREATOR.createFromParcel(in); |
| } else { |
| mVisibleFrame = null; |
| } |
| mVisible = in.readBoolean(); |
| mFlags = in.readInt(); |
| mSideHint = in.readInt(); |
| mBoundingRects = in.createTypedArray(Rect.CREATOR); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mId); |
| dest.writeInt(mType); |
| mFrame.writeToParcel(dest, 0); |
| if (mVisibleFrame != null) { |
| dest.writeInt(1); |
| mVisibleFrame.writeToParcel(dest, 0); |
| } else { |
| dest.writeInt(0); |
| } |
| dest.writeBoolean(mVisible); |
| dest.writeInt(mFlags); |
| dest.writeInt(mSideHint); |
| dest.writeTypedArray(mBoundingRects, flags); |
| } |
| |
| @Override |
| public String toString() { |
| return "InsetsSource: {" + Integer.toHexString(mId) |
| + " mType=" + WindowInsets.Type.toString(mType) |
| + " mFrame=" + mFrame.toShortString() |
| + " mVisible=" + mVisible |
| + " mFlags=" + flagsToString(mFlags) |
| + " mSideHint=" + sideToString(mSideHint) |
| + " mBoundingRects=" + Arrays.toString(mBoundingRects) |
| + "}"; |
| } |
| |
| public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() { |
| |
| public InsetsSource createFromParcel(Parcel in) { |
| return new InsetsSource(in); |
| } |
| |
| public InsetsSource[] newArray(int size) { |
| return new InsetsSource[size]; |
| } |
| }; |
| } |