/* * Copyright (C) 2017 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.autofill; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.view.View; import java.util.Objects; /** * A unique identifier for an autofill node inside an {@link android.app.Activity}. */ public final class AutofillId implements Parcelable { /** @hide */ public static final int NO_SESSION = 0; private static final int FLAG_IS_VIRTUAL_INT = 0x1; private static final int FLAG_IS_VIRTUAL_LONG = 0x2; private static final int FLAG_HAS_SESSION = 0x4; private final int mViewId; private int mFlags; private final int mVirtualIntId; private final long mVirtualLongId; private int mSessionId; /** @hide */ @TestApi public AutofillId(int id) { this(/* flags= */ 0, id, View.NO_ID, NO_SESSION); } /** @hide */ @TestApi public AutofillId(@NonNull AutofillId hostId, int virtualChildId) { this(FLAG_IS_VIRTUAL_INT, hostId.mViewId, virtualChildId, NO_SESSION); } /** @hide */ @TestApi public AutofillId(int hostId, int virtualChildId) { this(FLAG_IS_VIRTUAL_INT, hostId, virtualChildId, NO_SESSION); } /** @hide */ @TestApi public AutofillId(@NonNull AutofillId hostId, long virtualChildId, int sessionId) { this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, hostId.mViewId, virtualChildId, sessionId); } private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) { mFlags = flags; mViewId = parentId; mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID; mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID; mSessionId = sessionId; } /** @hide */ @NonNull public static final AutofillId NO_AUTOFILL_ID = new AutofillId(0); /** * Creates an {@link AutofillId} with the virtual id. * * This method is used by a {@link View} that contains the virtual view hierarchy. Use this * method to create the {@link AutofillId} for each virtual view. * * @param host the view hosting the virtual view hierarchy which is used to show autofill * suggestions. * @param virtualId id identifying the virtual view inside the host view. * @return an {@link AutofillId} for the virtual view */ @NonNull public static AutofillId create(@NonNull View host, int virtualId) { Objects.requireNonNull(host); return new AutofillId(host.getAutofillId(), virtualId); } /** @hide */ @NonNull @TestApi public static AutofillId withoutSession(@NonNull AutofillId id) { final int flags = id.mFlags & ~FLAG_HAS_SESSION; final long virtualChildId = ((id.mFlags & FLAG_IS_VIRTUAL_LONG) != 0) ? id.mVirtualLongId : id.mVirtualIntId; return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION); } /** @hide */ public int getViewId() { return mViewId; } /** * Gets the virtual child id. * *

Should only be used on subsystems where such id is represented by an {@code int} * (Assist and Autofill). * * @hide */ public int getVirtualChildIntId() { return mVirtualIntId; } /** * Gets the virtual child id. * *

Should only be used on subsystems where such id is represented by a {@code long} * (ContentCapture). * * @hide */ public long getVirtualChildLongId() { return mVirtualLongId; } /** * Checks whether this node represents a virtual child, whose id is represented by an * {@code int}. * *

Should only be used on subsystems where such id is represented by an {@code int} * (Assist and Autofill). * * @hide */ public boolean isVirtualInt() { return (mFlags & FLAG_IS_VIRTUAL_INT) != 0; } /** * Checks whether this node represents a virtual child, whose id is represented by an * {@code long}. * *

Should only be used on subsystems where such id is represented by a {@code long} * (ContentCapture). * * @hide */ public boolean isVirtualLong() { return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0; } /** * Checks whether this node represents a non-virtual child. * * @hide */ @TestApi public boolean isNonVirtual() { return !isVirtualInt() && !isVirtualLong(); } /** @hide */ public boolean hasSession() { return (mFlags & FLAG_HAS_SESSION) != 0; } /** @hide */ public int getSessionId() { return mSessionId; } /** @hide */ public void setSessionId(int sessionId) { mFlags |= FLAG_HAS_SESSION; mSessionId = sessionId; } /** @hide */ public void resetSessionId() { mFlags &= ~FLAG_HAS_SESSION; mSessionId = NO_SESSION; } ///////////////////////////////// // Object "contract" methods. // ///////////////////////////////// @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + mViewId; result = prime * result + mVirtualIntId; result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32)); result = prime * result + mSessionId; return result; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final AutofillId other = (AutofillId) obj; if (mViewId != other.mViewId) return false; if (mVirtualIntId != other.mVirtualIntId) return false; if (mVirtualLongId != other.mVirtualLongId) return false; if (mSessionId != other.mSessionId) return false; return true; } /** @hide */ @TestApi public boolean equalsIgnoreSession(@Nullable AutofillId other) { if (this == other) return true; if (other == null) return false; if (mViewId != other.mViewId) return false; if (mVirtualIntId != other.mVirtualIntId) return false; if (mVirtualLongId != other.mVirtualLongId) return false; return true; } @Override public String toString() { final StringBuilder builder = new StringBuilder().append(mViewId); if (isVirtualInt()) { builder.append(':').append(mVirtualIntId); } else if (isVirtualLong()) { builder.append(':').append(mVirtualLongId); } if (hasSession()) { builder.append('@').append(mSessionId); } return builder.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mViewId); parcel.writeInt(mFlags); if (hasSession()) { parcel.writeInt(mSessionId); } if (isVirtualInt()) { parcel.writeInt(mVirtualIntId); } else if (isVirtualLong()) { parcel.writeLong(mVirtualLongId); } } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public AutofillId createFromParcel(Parcel source) { final int viewId = source.readInt(); final int flags = source.readInt(); final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION; if ((flags & FLAG_IS_VIRTUAL_INT) != 0) { return new AutofillId(flags, viewId, source.readInt(), sessionId); } if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) { return new AutofillId(flags, viewId, source.readLong(), sessionId); } return new AutofillId(flags, viewId, View.NO_ID, sessionId); } @Override public AutofillId[] newArray(int size) { return new AutofillId[size]; } }; }