package android.app.assist; import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR; import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR; import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.credentials.CredentialOption; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.GetCredentialResponse; import android.graphics.Matrix; import android.graphics.Rect; import android.net.Uri; import android.os.BadParcelableException; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; import android.os.OutcomeReceiver; import android.os.Parcel; import android.os.Parcelable; import android.os.PooledStringReader; import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; import android.service.autofill.FillRequest; import android.service.credentials.CredentialProviderService; import android.text.InputType; import android.text.Spanned; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.view.View; import android.view.View.AutofillImportance; import android.view.ViewRootImpl; import android.view.ViewStructure; import android.view.ViewStructure.HtmlInfo; import android.view.ViewStructure.HtmlInfo.Builder; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** *

This API automatically creates assist data from the platform's * implementation of assist and autofill. * *

The structure is used for assist purposes when created by * {@link android.app.Activity#onProvideAssistData}, {@link View#onProvideStructure(ViewStructure)}, * or {@link View#onProvideVirtualStructure(ViewStructure)}. * *

The structure is also used for autofill purposes when created by * {@link View#onProvideAutofillStructure(ViewStructure, int)}, * or {@link View#onProvideAutofillVirtualStructure(ViewStructure, int)}. * *

For performance reasons, some properties of the assist data might only be available for * assist or autofill purposes. In those cases, a property's availability will be documented * in its javadoc. * *

To learn about using Autofill in your app, read the * Autofill Framework guides. */ public class AssistStructure implements Parcelable { private static final String TAG = "AssistStructure"; private static final boolean DEBUG_PARCEL = false; private static final boolean DEBUG_PARCEL_CHILDREN = false; private static final boolean DEBUG_PARCEL_TREE = false; private static final int VALIDATE_WINDOW_TOKEN = 0x11111111; private static final int VALIDATE_VIEW_TOKEN = 0x22222222; private boolean mHaveData; // The task id and component of the activity which this assist structure is for private int mTaskId; private ComponentName mActivityComponent; private boolean mIsHomeActivity; private int mFlags; private int mAutofillFlags; private final ArrayList mWindowNodes = new ArrayList<>(); private final ArrayList mPendingAsyncChildren = new ArrayList<>(); private SendChannel mSendChannel; private IBinder mReceiveChannel; private Rect mTmpRect = new Rect(); private boolean mSanitizeOnWrite = false; private long mAcquisitionStartTime; private long mAcquisitionEndTime; private static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; private static final String DESCRIPTOR = "android.app.AssistStructure"; /** @hide */ public void setAcquisitionStartTime(long acquisitionStartTime) { mAcquisitionStartTime = acquisitionStartTime; } /** @hide */ public void setAcquisitionEndTime(long acquisitionEndTime) { mAcquisitionEndTime = acquisitionEndTime; } /** * @hide * Set the home activity flag. */ public void setHomeActivity(boolean isHomeActivity) { mIsHomeActivity = isHomeActivity; } /** * Returns the time when the activity started generating assist data to build the * AssistStructure. The time is as specified by {@link SystemClock#uptimeMillis()}. * * @see #getAcquisitionEndTime() * @return Returns the acquisition start time of the assist data, in milliseconds. */ public long getAcquisitionStartTime() { ensureData(); return mAcquisitionStartTime; } /** * Returns the time when the activity finished generating assist data to build the * AssistStructure. The time is as specified by {@link SystemClock#uptimeMillis()}. * * @see #getAcquisitionStartTime() * @return Returns the acquisition end time of the assist data, in milliseconds. */ public long getAcquisitionEndTime() { ensureData(); return mAcquisitionEndTime; } final static class SendChannel extends Binder { volatile AssistStructure mAssistStructure; SendChannel(AssistStructure as) { mAssistStructure = as; } @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code == TRANSACTION_XFER) { AssistStructure as = mAssistStructure; if (as == null) { return true; } data.enforceInterface(DESCRIPTOR); IBinder token = data.readStrongBinder(); if (DEBUG_PARCEL) Log.d(TAG, "Request for data on " + as + " using token " + token); if (token != null) { if (DEBUG_PARCEL) Log.d(TAG, "Resuming partial write of " + token); if (token instanceof ParcelTransferWriter) { ParcelTransferWriter xfer = (ParcelTransferWriter)token; xfer.writeToParcel(as, reply); return true; } Log.w(TAG, "Caller supplied bad token type: " + token); // Don't write anything; this is the end of the data. return true; } //long start = SystemClock.uptimeMillis(); ParcelTransferWriter xfer = new ParcelTransferWriter(as, reply); xfer.writeToParcel(as, reply); //Log.i(TAG, "Time to parcel: " + (SystemClock.uptimeMillis()-start) + "ms"); return true; } else { return super.onTransact(code, data, reply, flags); } } } final static class ViewStackEntry { ViewNode node; int curChild; int numChildren; } final static class ParcelTransferWriter extends Binder { final boolean mWriteStructure; int mCurWindow; int mNumWindows; final ArrayList mViewStack = new ArrayList<>(); ViewStackEntry mCurViewStackEntry; int mCurViewStackPos; int mNumWrittenWindows; int mNumWrittenViews; final float[] mTmpMatrix = new float[9]; final boolean mSanitizeOnWrite; ParcelTransferWriter(AssistStructure as, Parcel out) { mSanitizeOnWrite = as.mSanitizeOnWrite; mWriteStructure = as.waitForReady(); out.writeInt(as.mFlags); out.writeInt(as.mAutofillFlags); out.writeLong(as.mAcquisitionStartTime); out.writeLong(as.mAcquisitionEndTime); mNumWindows = as.mWindowNodes.size(); if (mWriteStructure && mNumWindows > 0) { out.writeInt(mNumWindows); } else { out.writeInt(0); } } void writeToParcel(AssistStructure as, Parcel out) { int start = out.dataPosition(); mNumWrittenWindows = 0; mNumWrittenViews = 0; boolean more = writeToParcelInner(as, out); Log.i(TAG, "Flattened " + (more ? "partial" : "final") + " assist data: " + (out.dataPosition() - start) + " bytes, containing " + mNumWrittenWindows + " windows, " + mNumWrittenViews + " views"); } boolean writeToParcelInner(AssistStructure as, Parcel out) { if (mNumWindows == 0) { return false; } if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringWriter @ " + out.dataPosition()); PooledStringWriter pwriter = new PooledStringWriter(out); while (writeNextEntryToParcel(as, out, pwriter)) { // If the parcel is above the IPC limit, then we are getting too // large for a single IPC so stop here and let the caller come back when it // is ready for more. if (out.dataSize() > IBinder.MAX_IPC_SIZE) { if (DEBUG_PARCEL) Log.d(TAG, "Assist data size is " + out.dataSize() + " @ pos " + out.dataPosition() + "; returning partial result"); out.writeInt(0); out.writeStrongBinder(this); if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ " + out.dataPosition() + ", size " + pwriter.getStringCount()); pwriter.finish(); return true; } } if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ " + out.dataPosition() + ", size " + pwriter.getStringCount()); pwriter.finish(); mViewStack.clear(); return false; } void pushViewStackEntry(ViewNode node, int pos) { ViewStackEntry entry; if (pos >= mViewStack.size()) { entry = new ViewStackEntry(); mViewStack.add(entry); if (DEBUG_PARCEL_TREE) Log.d(TAG, "New stack entry at " + pos + ": " + entry); } else { entry = mViewStack.get(pos); if (DEBUG_PARCEL_TREE) Log.d(TAG, "Existing stack entry at " + pos + ": " + entry); } entry.node = node; entry.numChildren = node.getChildCount(); entry.curChild = 0; mCurViewStackEntry = entry; } void writeView(ViewNode child, Parcel out, PooledStringWriter pwriter, int levelAdj) { if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition() + ", windows=" + mNumWrittenWindows + ", views=" + mNumWrittenViews + ", level=" + (mCurViewStackPos+levelAdj)); out.writeInt(VALIDATE_VIEW_TOKEN); int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite, mTmpMatrix, /*willWriteChildren=*/true); mNumWrittenViews++; // If the child has children, push it on the stack to write them next. if ((flags&ViewNode.FLAGS_HAS_CHILDREN) != 0) { if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) Log.d(TAG, "Preparing to write " + child.mChildren.length + " children: @ #" + mNumWrittenViews + ", level " + (mCurViewStackPos+levelAdj)); out.writeInt(child.mChildren.length); int pos = ++mCurViewStackPos; pushViewStackEntry(child, pos); } } boolean writeNextEntryToParcel(AssistStructure as, Parcel out, PooledStringWriter pwriter) { // Write next view node if appropriate. if (mCurViewStackEntry != null) { if (mCurViewStackEntry.curChild < mCurViewStackEntry.numChildren) { // Write the next child in the current view. if (DEBUG_PARCEL_TREE) Log.d(TAG, "Writing child #" + mCurViewStackEntry.curChild + " in " + mCurViewStackEntry.node); ViewNode child = mCurViewStackEntry.node.mChildren[mCurViewStackEntry.curChild]; mCurViewStackEntry.curChild++; writeView(child, out, pwriter, 1); return true; } // We are done writing children of the current view; pop off the stack. do { int pos = --mCurViewStackPos; if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with " + mCurViewStackEntry.node + "; popping up to " + pos); if (pos < 0) { // Reached the last view; step to next window. if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with view hierarchy!"); mCurViewStackEntry = null; break; } mCurViewStackEntry = mViewStack.get(pos); } while (mCurViewStackEntry.curChild >= mCurViewStackEntry.numChildren); return true; } // Write the next window if appropriate. int pos = mCurWindow; if (pos < mNumWindows) { WindowNode win = as.mWindowNodes.get(pos); mCurWindow++; if (DEBUG_PARCEL) Log.d(TAG, "write window #" + pos + ": at " + out.dataPosition() + ", windows=" + mNumWrittenWindows + ", views=" + mNumWrittenViews); out.writeInt(VALIDATE_WINDOW_TOKEN); win.writeSelfToParcel(out, pwriter, mTmpMatrix); mNumWrittenWindows++; ViewNode root = win.mRoot; mCurViewStackPos = 0; if (DEBUG_PARCEL_TREE) Log.d(TAG, "Writing initial root view " + root); writeView(root, out, pwriter, 0); return true; } return false; } } final class ParcelTransferReader { final float[] mTmpMatrix = new float[9]; PooledStringReader mStringReader; int mNumReadWindows; int mNumReadViews; private final IBinder mChannel; private IBinder mTransferToken; private Parcel mCurParcel; ParcelTransferReader(IBinder channel) { mChannel = channel; } void go() { fetchData(); mFlags = mCurParcel.readInt(); mAutofillFlags = mCurParcel.readInt(); mAcquisitionStartTime = mCurParcel.readLong(); mAcquisitionEndTime = mCurParcel.readLong(); final int N = mCurParcel.readInt(); if (N > 0) { if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ " + mCurParcel.dataPosition()); mStringReader = new PooledStringReader(mCurParcel); if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = " + mStringReader.getStringCount()); for (int i=0; i mGetCredentialCallback; @Nullable ResultReceiver mGetCredentialResultReceiver; AutofillValue mAutofillValue; CharSequence[] mAutofillOptions; boolean mSanitized; HtmlInfo mHtmlInfo; int mMinEms = -1; int mMaxEms = -1; int mMaxLength = -1; @Nullable String mTextIdEntry; @Nullable String mHintIdEntry; @AutofillImportance int mImportantForAutofill; // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. AutofillOverlay mAutofillOverlay; boolean mIsCredential; int mX; int mY; int mScrollX; int mScrollY; int mWidth; int mHeight; Matrix mMatrix; float mElevation; float mAlpha = 1.0f; // TODO: The FLAGS_* below have filled all bits, will need to be refactored. static final int FLAGS_DISABLED = 0x00000001; static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE; static final int FLAGS_FOCUSABLE = 0x00000010; static final int FLAGS_FOCUSED = 0x00000020; static final int FLAGS_SELECTED = 0x00000040; static final int FLAGS_ASSIST_BLOCKED = 0x00000080; static final int FLAGS_CHECKABLE = 0x00000100; static final int FLAGS_CHECKED = 0x00000200; static final int FLAGS_CLICKABLE = 0x00000400; static final int FLAGS_LONG_CLICKABLE = 0x00000800; static final int FLAGS_ACCESSIBILITY_FOCUSED = 0x00001000; static final int FLAGS_ACTIVATED = 0x00002000; static final int FLAGS_CONTEXT_CLICKABLE = 0x00004000; static final int FLAGS_OPAQUE = 0x00008000; // --IMPORTANT-- must update this flag if any below flags extend to further bits. // This flag is used to clear all FLAGS_HAS_* values in mFlags prior to parceling. static final int FLAGS_ALL_CONTROL = 0xffff0000; static final int FLAGS_HAS_MIME_TYPES = 0x80000000; static final int FLAGS_HAS_MATRIX = 0x40000000; static final int FLAGS_HAS_ALPHA = 0x20000000; static final int FLAGS_HAS_ELEVATION = 0x10000000; static final int FLAGS_HAS_SCROLL = 0x08000000; static final int FLAGS_HAS_LARGE_COORDS = 0x04000000; static final int FLAGS_HAS_CONTENT_DESCRIPTION = 0x02000000; static final int FLAGS_HAS_TEXT = 0x01000000; static final int FLAGS_HAS_COMPLEX_TEXT = 0x00800000; static final int FLAGS_HAS_EXTRAS = 0x00400000; static final int FLAGS_HAS_ID = 0x00200000; static final int FLAGS_HAS_CHILDREN = 0x00100000; static final int FLAGS_HAS_URL_DOMAIN = 0x00080000; static final int FLAGS_HAS_INPUT_TYPE = 0x00040000; static final int FLAGS_HAS_URL_SCHEME = 0x00020000; static final int FLAGS_HAS_LOCALE_LIST = 0x00010000; // --IMPORTANT END-- static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID = 0x0001; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID = 0x0002; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE = 0x0004; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE = 0x0008; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS = 0x0010; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS = 0x0020; static final int AUTOFILL_FLAGS_HAS_HTML_INFO = 0x0040; static final int AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY = 0x0080; static final int AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS = 0x0100; static final int AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS = 0x0200; static final int AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH = 0x0400; static final int AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID = 0x0800; static final int AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY = 0x1000; int mFlags; int mAutofillFlags; String mClassName; CharSequence mContentDescription; ViewNodeText mText; int mInputType; String mWebScheme; String mWebDomain; Bundle mExtras; LocaleList mLocaleList; String[] mReceiveContentMimeTypes; ViewNode[] mChildren; // TODO(b/111276913): temporarily made public / @hide until we decide what will be used by // COntent Capture. /** @hide */ @SystemApi public ViewNode() { } ViewNode(@NonNull Parcel in) { initializeFromParcelWithoutChildren(in, /*preader=*/null, /*tmpMatrix=*/null); } ViewNode(ParcelTransferReader reader, int nestingLevel) { final Parcel in = reader.readParcel(VALIDATE_VIEW_TOKEN, nestingLevel); reader.mNumReadViews++; initializeFromParcelWithoutChildren(in, Objects.requireNonNull(reader.mStringReader), Objects.requireNonNull(reader.mTmpMatrix)); if ((mFlags & FLAGS_HAS_CHILDREN) != 0) { final int numChildren = in.readInt(); if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) { Log.d(TAG, "Preparing to read " + numChildren + " children: @ #" + reader.mNumReadViews + ", level " + nestingLevel); } mChildren = new ViewNode[numChildren]; for (int i = 0; i < numChildren; i++) { mChildren[i] = new ViewNode(reader, nestingLevel + 1); } } } private static void writeString(@NonNull Parcel out, @Nullable PooledStringWriter pwriter, @Nullable String str) { if (pwriter != null) { pwriter.writeString(str); } else { out.writeString(str); } } @Nullable private static String readString(@NonNull Parcel in, @Nullable PooledStringReader preader) { if (preader != null) { return preader.readString(); } return in.readString(); } // This does not read the child nodes. void initializeFromParcelWithoutChildren(Parcel in, @Nullable PooledStringReader preader, @Nullable float[] tmpMatrix) { mClassName = readString(in, preader); mFlags = in.readInt(); final int flags = mFlags; mAutofillFlags = in.readInt(); final int autofillFlags = mAutofillFlags; if ((flags&FLAGS_HAS_ID) != 0) { mId = in.readInt(); if (mId != View.NO_ID) { mIdEntry = readString(in, preader); if (mIdEntry != null) { mIdType = readString(in, preader); mIdPackage = readString(in, preader); } } } if (autofillFlags != 0) { mSanitized = in.readInt() == 1; mIsCredential = in.readInt() == 1; mImportantForAutofill = in.readInt(); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { int autofillViewId = in.readInt(); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) { mAutofillId = new AutofillId(autofillViewId, in.readInt()); } else { mAutofillId = new AutofillId(autofillViewId); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID) != 0) { mAutofillId.setSessionId(in.readInt()); } } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) { mAutofillType = in.readInt(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS) != 0) { mAutofillHints = in.readStringArray(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) { mAutofillValue = in.readParcelable(null, android.view.autofill.AutofillValue.class); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { mAutofillOptions = in.readCharSequenceArray(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) { mHtmlInfo = in.readParcelable(null, android.view.ViewStructure.HtmlInfo.class); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) { mMinEms = in.readInt(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS) != 0) { mMaxEms = in.readInt(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH) != 0) { mMaxLength = in.readInt(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY) != 0) { mTextIdEntry = readString(in, preader); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY) != 0) { mHintIdEntry = readString(in, preader); } } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); mY = in.readInt(); mWidth = in.readInt(); mHeight = in.readInt(); } else { int val = in.readInt(); mX = val&0x7fff; mY = (val>>16)&0x7fff; val = in.readInt(); mWidth = val&0x7fff; mHeight = (val>>16)&0x7fff; } if ((flags&FLAGS_HAS_SCROLL) != 0) { mScrollX = in.readInt(); mScrollY = in.readInt(); } if ((flags&FLAGS_HAS_MATRIX) != 0) { mMatrix = new Matrix(); if (tmpMatrix == null) { tmpMatrix = new float[9]; } in.readFloatArray(tmpMatrix); mMatrix.setValues(tmpMatrix); } if ((flags&FLAGS_HAS_ELEVATION) != 0) { mElevation = in.readFloat(); } if ((flags&FLAGS_HAS_ALPHA) != 0) { mAlpha = in.readFloat(); } if ((flags&FLAGS_HAS_CONTENT_DESCRIPTION) != 0) { mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } if ((flags&FLAGS_HAS_TEXT) != 0) { mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0); } if ((flags&FLAGS_HAS_INPUT_TYPE) != 0) { mInputType = in.readInt(); } if ((flags&FLAGS_HAS_URL_SCHEME) != 0) { mWebScheme = in.readString(); } if ((flags&FLAGS_HAS_URL_DOMAIN) != 0) { mWebDomain = in.readString(); } if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { mLocaleList = in.readParcelable(null, android.os.LocaleList.class); } if ((flags & FLAGS_HAS_MIME_TYPES) != 0) { mReceiveContentMimeTypes = in.readStringArray(); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); } mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR); mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR); } /** * This does not write the child nodes. * * @param willWriteChildren whether child nodes will be written to the parcel or not after * calling this method. */ int writeSelfToParcel(@NonNull Parcel out, @Nullable PooledStringWriter pwriter, boolean sanitizeOnWrite, @Nullable float[] tmpMatrix, boolean willWriteChildren) { // Guard used to skip non-sanitized data when writing for autofill. boolean writeSensitive = true; int flags = mFlags & ~FLAGS_ALL_CONTROL; int autofillFlags = 0; if (mId != View.NO_ID) { flags |= FLAGS_HAS_ID; } if ((mX&~0x7fff) != 0 || (mY&~0x7fff) != 0 || (mWidth&~0x7fff) != 0 | (mHeight&~0x7fff) != 0) { flags |= FLAGS_HAS_LARGE_COORDS; } if (mScrollX != 0 || mScrollY != 0) { flags |= FLAGS_HAS_SCROLL; } if (mMatrix != null) { flags |= FLAGS_HAS_MATRIX; } if (mElevation != 0) { flags |= FLAGS_HAS_ELEVATION; } if (mAlpha != 1.0f) { flags |= FLAGS_HAS_ALPHA; } if (mContentDescription != null) { flags |= FLAGS_HAS_CONTENT_DESCRIPTION; } if (mText != null) { flags |= FLAGS_HAS_TEXT; if (!mText.isSimple()) { flags |= FLAGS_HAS_COMPLEX_TEXT; } } if (mInputType != 0) { flags |= FLAGS_HAS_INPUT_TYPE; } if (mWebScheme != null) { flags |= FLAGS_HAS_URL_SCHEME; } if (mWebDomain != null) { flags |= FLAGS_HAS_URL_DOMAIN; } if (mLocaleList != null) { flags |= FLAGS_HAS_LOCALE_LIST; } if (mReceiveContentMimeTypes != null) { flags |= FLAGS_HAS_MIME_TYPES; } if (mExtras != null) { flags |= FLAGS_HAS_EXTRAS; } if (mChildren != null && willWriteChildren) { flags |= FLAGS_HAS_CHILDREN; } if (mAutofillId != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID; if (mAutofillId.isVirtualInt()) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID; } if (mAutofillId.hasSession()) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID; } } if (mAutofillValue != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE; } if (mAutofillType != View.AUTOFILL_TYPE_NONE) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE; } if (mAutofillHints != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS; } if (mAutofillOptions != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS; } if (mHtmlInfo instanceof Parcelable) { autofillFlags |= AUTOFILL_FLAGS_HAS_HTML_INFO; } if (mMinEms > -1) { autofillFlags |= AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS; } if (mMaxEms > -1) { autofillFlags |= AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS; } if (mMaxLength > -1) { autofillFlags |= AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH; } if (mTextIdEntry != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY; } if (mHintIdEntry != null) { autofillFlags |= AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY; } writeString(out, pwriter, mClassName); int writtenFlags = flags; if (autofillFlags != 0 && (mSanitized || !sanitizeOnWrite)) { // Remove 'checked' from sanitized autofill request. writtenFlags = flags & ~FLAGS_CHECKED; } if (mAutofillOverlay != null) { if (mAutofillOverlay.focused) { writtenFlags |= ViewNode.FLAGS_FOCUSED; } else { writtenFlags &= ~ViewNode.FLAGS_FOCUSED; } } out.writeInt(writtenFlags); out.writeInt(autofillFlags); if ((flags&FLAGS_HAS_ID) != 0) { out.writeInt(mId); if (mId != View.NO_ID) { writeString(out, pwriter, mIdEntry); if (mIdEntry != null) { writeString(out, pwriter, mIdType); writeString(out, pwriter, mIdPackage); } } } if (autofillFlags != 0) { out.writeInt(mSanitized ? 1 : 0); out.writeInt(mIsCredential ? 1 : 0); out.writeInt(mImportantForAutofill); writeSensitive = mSanitized || !sanitizeOnWrite; if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { out.writeInt(mAutofillId.getViewId()); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) { out.writeInt(mAutofillId.getVirtualChildIntId()); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID) != 0) { out.writeInt(mAutofillId.getSessionId()); } } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) { out.writeInt(mAutofillType); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS) != 0) { out.writeStringArray(mAutofillHints); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) { final AutofillValue sanitizedValue; if (writeSensitive) { sanitizedValue = mAutofillValue; } else if (mAutofillOverlay != null && mAutofillOverlay.value != null) { sanitizedValue = mAutofillOverlay.value; } else { sanitizedValue = null; } out.writeParcelable(sanitizedValue, 0); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { out.writeCharSequenceArray(mAutofillOptions); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) { out.writeParcelable((Parcelable) mHtmlInfo, 0); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) { out.writeInt(mMinEms); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS) != 0) { out.writeInt(mMaxEms); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH) != 0) { out.writeInt(mMaxLength); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY) != 0) { writeString(out, pwriter, mTextIdEntry); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HINT_ID_ENTRY) != 0) { writeString(out, pwriter, mHintIdEntry); } } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); out.writeInt(mY); out.writeInt(mWidth); out.writeInt(mHeight); } else { out.writeInt((mY<<16) | mX); out.writeInt((mHeight<<16) | mWidth); } if ((flags&FLAGS_HAS_SCROLL) != 0) { out.writeInt(mScrollX); out.writeInt(mScrollY); } if ((flags&FLAGS_HAS_MATRIX) != 0) { if (tmpMatrix == null) { tmpMatrix = new float[9]; } mMatrix.getValues(tmpMatrix); out.writeFloatArray(tmpMatrix); } if ((flags&FLAGS_HAS_ELEVATION) != 0) { out.writeFloat(mElevation); } if ((flags&FLAGS_HAS_ALPHA) != 0) { out.writeFloat(mAlpha); } if ((flags&FLAGS_HAS_CONTENT_DESCRIPTION) != 0) { TextUtils.writeToParcel(mContentDescription, out, 0); } if ((flags&FLAGS_HAS_TEXT) != 0) { mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0, writeSensitive); } if ((flags&FLAGS_HAS_INPUT_TYPE) != 0) { out.writeInt(mInputType); } if ((flags & FLAGS_HAS_URL_SCHEME) != 0) { out.writeString(mWebScheme); } if ((flags&FLAGS_HAS_URL_DOMAIN) != 0) { out.writeString(mWebDomain); } if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { out.writeParcelable(mLocaleList, 0); } if ((flags & FLAGS_HAS_MIME_TYPES) != 0) { out.writeStringArray(mReceiveContentMimeTypes); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { out.writeBundle(mExtras); } out.writeTypedObject(mGetCredentialRequest, flags); out.writeTypedObject(mGetCredentialResultReceiver, flags); return flags; } /** * Returns the ID associated with this view, as per {@link View#getId() View.getId()}. */ public int getId() { return mId; } /** * If {@link #getId()} is a resource identifier, this is the package name of that * identifier. See {@link android.view.ViewStructure#setId ViewStructure.setId} * for more information. */ @Nullable public String getIdPackage() { return mIdPackage; } /** * If {@link #getId()} is a resource identifier, this is the type name of that * identifier. See {@link android.view.ViewStructure#setId ViewStructure.setId} * for more information. */ @Nullable public String getIdType() { return mIdType; } /** * If {@link #getId()} is a resource identifier, this is the entry name of that * identifier. See {@link android.view.ViewStructure#setId ViewStructure.setId} * for more information. */ @Nullable public String getIdEntry() { return mIdEntry; } /** * Gets the id that can be used to autofill the view contents. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes. * * @return id that can be used to autofill the view contents, or {@code null} if the * structure was created for assist purposes. */ @Nullable public AutofillId getAutofillId() { return mAutofillId; } /** * Gets the type of value that can be used to autofill the view contents. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes. * * @return autofill type as defined by {@link View#getAutofillType()}, * or {@link View#AUTOFILL_TYPE_NONE} if the structure was created for assist purposes. */ public @View.AutofillType int getAutofillType() { return mAutofillType; } /** * Describes the content of a view so that a autofill service can fill in the appropriate * data. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for Assist - see {@link View#getAutofillHints()} for more info. * * @return The autofill hints for this view, or {@code null} if the structure was created * for assist purposes. */ @Nullable public String[] getAutofillHints() { return mAutofillHints; } /** * Gets the value of this view. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. * * @return the autofill value of this view, or {@code null} if the structure was created * for assist purposes. */ @Nullable public AutofillValue getAutofillValue() { return mAutofillValue; } /** @hide **/ public void setAutofillOverlay(AutofillOverlay overlay) { mAutofillOverlay = overlay; } /** * Gets the options that can be used to autofill this view. * *

Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate * the meaning of each possible value in the list. * *

It's relevant when the {@link AssistStructure} is used for autofill purposes, not * for assist purposes. * * @return the options that can be used to autofill this view, or {@code null} if the * structure was created for assist purposes. */ @Nullable public CharSequence[] getAutofillOptions() { return mAutofillOptions; } /** * @return whether the node is a credential. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. * TODO(b/303677885): add TestApi * * @hide */ public boolean isCredential() { return mIsCredential; } /** * Returns the request associated with this node * @return * * @hide */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public GetCredentialRequest getPendingCredentialRequest() { return mGetCredentialRequest; } /** * @hide */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public ResultReceiver getPendingCredentialCallback() { return mGetCredentialResultReceiver; } /** * Gets the {@link android.text.InputType} bits of this structure. * * @return bits as defined by {@link android.text.InputType}. */ public int getInputType() { return mInputType; } /** @hide */ public boolean isSanitized() { return mSanitized; } /** * Updates the {@link AutofillValue} of this structure. * *

Should be used just before sending the structure to the * {@link android.service.autofill.AutofillService} for saving, since it will override the * initial value. * * @hide */ public void updateAutofillValue(AutofillValue value) { mAutofillValue = value; if (value.isText()) { if (mText == null) { mText = new ViewNodeText(); } mText.mText = value.getTextValue(); } } /** * Returns the left edge of this view, in pixels, relative to the left edge of its parent. */ public int getLeft() { return mX; } /** * Returns the top edge of this view, in pixels, relative to the top edge of its parent. */ public int getTop() { return mY; } /** * Returns the current X scroll offset of this view, as per * {@link android.view.View#getScrollX() View.getScrollX()}. */ public int getScrollX() { return mScrollX; } /** * Returns the current Y scroll offset of this view, as per * {@link android.view.View#getScrollX() View.getScrollY()}. */ public int getScrollY() { return mScrollY; } /** * Returns the width of this view, in pixels. */ public int getWidth() { return mWidth; } /** * Returns the height of this view, in pixels. */ public int getHeight() { return mHeight; } /** * Returns the transformation that has been applied to this view, such as a translation * or scaling. The returned Matrix object is owned by ViewNode; do not modify it. * Returns null if there is no transformation applied to the view. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public Matrix getTransformation() { return mMatrix; } /** * Returns the visual elevation of the view, used for shadowing and other visual * characterstics, as set by {@link ViewStructure#setElevation * ViewStructure.setElevation(float)}. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public float getElevation() { return mElevation; } /** * Returns the alpha transformation of the view, used to reduce the overall opacity * of the view's contents, as set by {@link ViewStructure#setAlpha * ViewStructure.setAlpha(float)}. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public float getAlpha() { return mAlpha; } /** * Returns the visibility mode of this view, as per * {@link android.view.View#getVisibility() View.getVisibility()}. */ public int getVisibility() { return mFlags&ViewNode.FLAGS_VISIBILITY_MASK; } /** * Returns true if assist data has been blocked starting at this node in the hierarchy. */ public boolean isAssistBlocked() { return (mFlags&ViewNode.FLAGS_ASSIST_BLOCKED) != 0; } /** * Returns true if this node is in an enabled state. */ public boolean isEnabled() { return (mFlags&ViewNode.FLAGS_DISABLED) == 0; } /** * Returns true if this node is clickable by the user. */ public boolean isClickable() { return (mFlags&ViewNode.FLAGS_CLICKABLE) != 0; } /** * Returns true if this node can take input focus. */ public boolean isFocusable() { return (mFlags&ViewNode.FLAGS_FOCUSABLE) != 0; } /** * Returns true if this node currently had input focus at the time that the * structure was collected. */ public boolean isFocused() { return (mFlags&ViewNode.FLAGS_FOCUSED) != 0; } /** * Returns true if this node currently had accessibility focus at the time that the * structure was collected. */ public boolean isAccessibilityFocused() { return (mFlags&ViewNode.FLAGS_ACCESSIBILITY_FOCUSED) != 0; } /** * Returns true if this node represents something that is checkable by the user. */ public boolean isCheckable() { return (mFlags&ViewNode.FLAGS_CHECKABLE) != 0; } /** * Returns true if this node is currently in a checked state. */ public boolean isChecked() { return (mFlags&ViewNode.FLAGS_CHECKED) != 0; } /** * Returns true if this node has currently been selected by the user. */ public boolean isSelected() { return (mFlags&ViewNode.FLAGS_SELECTED) != 0; } /** * Returns true if this node has currently been activated by the user. */ public boolean isActivated() { return (mFlags&ViewNode.FLAGS_ACTIVATED) != 0; } /** * Returns true if this node is opaque. */ public boolean isOpaque() { return (mFlags&ViewNode.FLAGS_OPAQUE) != 0; } /** * Returns true if this node is something the user can perform a long click/press on. */ public boolean isLongClickable() { return (mFlags&ViewNode.FLAGS_LONG_CLICKABLE) != 0; } /** * Returns true if this node is something the user can perform a context click on. */ public boolean isContextClickable() { return (mFlags&ViewNode.FLAGS_CONTEXT_CLICKABLE) != 0; } /** * Returns the class name of the node's implementation, indicating its behavior. * For example, a button will report "android.widget.Button" meaning it behaves * like a {@link android.widget.Button}. */ @Nullable public String getClassName() { return mClassName; } /** * Returns any content description associated with the node, which semantically describes * its purpose for accessibility and other uses. */ @Nullable public CharSequence getContentDescription() { return mContentDescription; } /** * Returns the domain of the HTML document represented by this view. * *

Typically used when the view associated with the view is a container for an HTML * document. * *

Warning: an autofill service cannot trust the value reported by this method * without verifing its authenticity—see the "Web security" section of * {@link android.service.autofill.AutofillService} for more details. * * @return domain-only part of the document. For example, if the full URL is * {@code https://example.com/login?user=my_user}, it returns {@code example.com}. */ @Nullable public String getWebDomain() { return mWebDomain; } /** * @hide */ public void setWebDomain(@Nullable String domain) { if (domain == null) return; Uri uri = Uri.parse(domain); if (uri == null) { // Cannot log domain because it could contain PII; Log.w(TAG, "Failed to parse web domain"); return; } mWebScheme = uri.getScheme(); if (mWebScheme == null) { uri = Uri.parse("http://" + domain); } mWebDomain = uri.getHost(); } /** * Returns the scheme of the HTML document represented by this view. * *

Typically used when the view associated with the view is a container for an HTML * document. * * @return scheme-only part of the document. For example, if the full URL is * {@code https://example.com/login?user=my_user}, it returns {@code https}. */ @Nullable public String getWebScheme() { return mWebScheme; } /** * Returns the HTML properties associated with this view. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. * * @return the HTML properties associated with this view, or {@code null} if the * structure was created for assist purposes. */ @Nullable public HtmlInfo getHtmlInfo() { return mHtmlInfo; } /** * Returns the list of locales associated with this view. */ @Nullable public LocaleList getLocaleList() { return mLocaleList; } /** * Returns the MIME types accepted by {@link View#performReceiveContent} for this view. See * {@link View#getReceiveContentMimeTypes()} for details. */ @Nullable @SuppressLint("NullableCollection") public String[] getReceiveContentMimeTypes() { return mReceiveContentMimeTypes; } /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. * *

The text will be stripped of any spans that could potentially contain reference to * the activity context, to avoid memory leak. If the text contained a span, a plain * string version of the text will be returned. */ @Nullable public CharSequence getText() { return mText != null ? mText.mText : null; } /** * If {@link #getText()} is non-null, this is where the current selection starts. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public int getTextSelectionStart() { return mText != null ? mText.mTextSelectionStart : -1; } /** * If {@link #getText()} is non-null, this is where the current selection starts. * If there is no selection, returns the same value as {@link #getTextSelectionStart()}, * indicating the cursor position. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public int getTextSelectionEnd() { return mText != null ? mText.mTextSelectionEnd : -1; } /** * If {@link #getText()} is non-null, this is the main text color associated with it. * If there is no text color, {@link #TEXT_COLOR_UNDEFINED} is returned. * Note that the text may also contain style spans that modify the color of specific * parts of the text. */ public int getTextColor() { return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED; } /** * If {@link #getText()} is non-null, this is the main text background color associated * with it. * If there is no text background color, {@link #TEXT_COLOR_UNDEFINED} is returned. * Note that the text may also contain style spans that modify the color of specific * parts of the text. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public int getTextBackgroundColor() { return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED; } /** * If {@link #getText()} is non-null, this is the main text size (in pixels) associated * with it. * Note that the text may also contain style spans that modify the size of specific * parts of the text. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public float getTextSize() { return mText != null ? mText.mTextSize : 0; } /** * If {@link #getText()} is non-null, this is the main text style associated * with it, containing a bit mask of {@link #TEXT_STYLE_BOLD}, * {@link #TEXT_STYLE_BOLD}, {@link #TEXT_STYLE_STRIKE_THRU}, and/or * {@link #TEXT_STYLE_UNDERLINE}. * Note that the text may also contain style spans that modify the style of specific * parts of the text. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ public int getTextStyle() { return mText != null ? mText.mTextStyle : 0; } /** * Return per-line offsets into the text returned by {@link #getText()}. Each entry * in the array is a formatted line of text, and the value it contains is the offset * into the text string where that line starts. May return null if there is no line * information. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ @Nullable public int[] getTextLineCharOffsets() { return mText != null ? mText.mLineCharOffsets : null; } /** * Return per-line baselines into the text returned by {@link #getText()}. Each entry * in the array is a formatted line of text, and the value it contains is the baseline * where that text appears in the view. May return null if there is no line * information. * *

It's only relevant when the {@link AssistStructure} is used for assist purposes, * not for autofill purposes. */ @Nullable public int[] getTextLineBaselines() { return mText != null ? mText.mLineBaselines : null; } /** * Gets the identifier used to set the text associated with this view. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. */ @Nullable public String getTextIdEntry() { return mTextIdEntry; } /** * Return additional hint text associated with the node; this is typically used with * a node that takes user input, describing to the user what the input means. */ @Nullable public String getHint() { return mText != null ? mText.mHint : null; } /** * Gets the identifier used to set the hint associated with this view. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. */ @Nullable public String getHintIdEntry() { return mHintIdEntry; } /** * Return a Bundle containing optional vendor-specific extension information. */ @Nullable public Bundle getExtras() { return mExtras; } /** * Return the number of children this node has. */ public int getChildCount() { return mChildren != null ? mChildren.length : 0; } /** * Return a child of this node, given an index value from 0 to * {@link #getChildCount()}-1. */ public ViewNode getChildAt(int index) { return mChildren[index]; } /** * Returns the minimum width in ems of the text associated with this node, or {@code -1} * if not supported by the node. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. */ public int getMinTextEms() { return mMinEms; } /** * Returns the maximum width in ems of the text associated with this node, or {@code -1} * if not supported by the node. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. */ public int getMaxTextEms() { return mMaxEms; } /** * Returns the maximum length of the text associated with this node, or {@code -1} if not * supported by the node or not set. System may set a default value if the text length is * not set. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. */ public int getMaxTextLength() { return mMaxLength; } /** * Gets the {@link View#setImportantForAutofill(int) importantForAutofill mode} of * the view associated with this node. * *

It's only relevant when the {@link AssistStructure} is used for autofill purposes. */ public @AutofillImportance int getImportantForAutofill() { return mImportantForAutofill; } } /** * A parcelable wrapper class around {@link ViewNode}. * *

This class, when parceled and unparceled, does not carry the child nodes. * * @hide */ public static final class ViewNodeParcelable implements Parcelable { @NonNull private final ViewNode mViewNode; public ViewNodeParcelable(@NonNull ViewNode viewNode) { mViewNode = viewNode; } public ViewNodeParcelable(@NonNull Parcel in) { mViewNode = new ViewNode(in); } @NonNull public ViewNode getViewNode() { return mViewNode; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { mViewNode.writeSelfToParcel(parcel, /*pwriter=*/null, /*sanitizeOnWrite=*/false, /*tmpMatrix*/null, /*willWriteChildren=*/ false); } @NonNull public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ViewNodeParcelable createFromParcel(@NonNull Parcel in) { return new ViewNodeParcelable(in); } @Override public ViewNodeParcelable[] newArray(int size) { return new ViewNodeParcelable[size]; } }; } /** * POJO used to override some autofill-related values when the node is parcelized. * * @hide */ static public class AutofillOverlay { public boolean focused; public AutofillValue value; } /** * @hide */ public static class ViewNodeBuilder extends ViewStructure { final AssistStructure mAssist; final ViewNode mNode; final boolean mAsync; private Handler mHandler; /** * Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated * to a properly created {@link AssistStructure}. */ public ViewNodeBuilder() { mAssist = new AssistStructure(); mNode = new ViewNode(); mAsync = false; } ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) { mAssist = assist; mNode = node; mAsync = async; } @NonNull public ViewNode getViewNode() { return mNode; } @Override public void setId(int id, String packageName, String typeName, String entryName) { mNode.mId = id; mNode.mIdPackage = packageName; mNode.mIdType = typeName; mNode.mIdEntry = entryName; } @Override public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) { mNode.mX = left; mNode.mY = top; mNode.mScrollX = scrollX; mNode.mScrollY = scrollY; mNode.mWidth = width; mNode.mHeight = height; } @Override public void setTransformation(Matrix matrix) { if (matrix == null) { mNode.mMatrix = null; } else { mNode.mMatrix = new Matrix(matrix); } } @Override public void setElevation(float elevation) { mNode.mElevation = elevation; } @Override public void setAlpha(float alpha) { mNode.mAlpha = alpha; } @Override public void setVisibility(int visibility) { mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_VISIBILITY_MASK) | (visibility & ViewNode.FLAGS_VISIBILITY_MASK); } @Override public void setAssistBlocked(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ASSIST_BLOCKED) | (state ? ViewNode.FLAGS_ASSIST_BLOCKED : 0); } @Override public void setEnabled(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_DISABLED) | (state ? 0 : ViewNode.FLAGS_DISABLED); } @Override public void setClickable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CLICKABLE) | (state ? ViewNode.FLAGS_CLICKABLE : 0); } @Override public void setLongClickable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_LONG_CLICKABLE) | (state ? ViewNode.FLAGS_LONG_CLICKABLE : 0); } @Override public void setContextClickable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CONTEXT_CLICKABLE) | (state ? ViewNode.FLAGS_CONTEXT_CLICKABLE : 0); } @Override public void setFocusable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE) | (state ? ViewNode.FLAGS_FOCUSABLE : 0); } @Override public void setFocused(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSED) | (state ? ViewNode.FLAGS_FOCUSED : 0); } @Override public void setAccessibilityFocused(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACCESSIBILITY_FOCUSED) | (state ? ViewNode.FLAGS_ACCESSIBILITY_FOCUSED : 0); } @Override public void setCheckable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKABLE) | (state ? ViewNode.FLAGS_CHECKABLE : 0); } @Override public void setChecked(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKED) | (state ? ViewNode.FLAGS_CHECKED : 0); } @Override public void setSelected(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_SELECTED) | (state ? ViewNode.FLAGS_SELECTED : 0); } @Override public void setActivated(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACTIVATED) | (state ? ViewNode.FLAGS_ACTIVATED : 0); } @Override public void setOpaque(boolean opaque) { mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_OPAQUE) | (opaque ? ViewNode.FLAGS_OPAQUE : 0); } @Override public void setClassName(String className) { mNode.mClassName = className; } @Override public void setContentDescription(CharSequence contentDescription) { mNode.mContentDescription = contentDescription; } private final ViewNodeText getNodeText() { if (mNode.mText != null) { return mNode.mText; } mNode.mText = new ViewNodeText(); return mNode.mText; } @Override public void setText(CharSequence text) { ViewNodeText t = getNodeText(); // Strip spans from the text to avoid memory leak t.mText = TextUtils.trimToParcelableSize(stripAllSpansFromText(text)); t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { ViewNodeText t = getNodeText(); // Strip spans from the text to avoid memory leak t.mText = stripAllSpansFromText(text); t.mTextSelectionStart = selectionStart; t.mTextSelectionEnd = selectionEnd; } @Override public void setTextStyle(float size, int fgColor, int bgColor, int style) { ViewNodeText t = getNodeText(); t.mTextColor = fgColor; t.mTextBackgroundColor = bgColor; t.mTextSize = size; t.mTextStyle = style; } @Override public void setTextLines(int[] charOffsets, int[] baselines) { ViewNodeText t = getNodeText(); t.mLineCharOffsets = charOffsets; t.mLineBaselines = baselines; } @Override public void setTextIdEntry(@NonNull String entryName) { mNode.mTextIdEntry = Objects.requireNonNull(entryName); } @Override public void setHint(CharSequence hint) { getNodeText().mHint = hint != null ? hint.toString() : null; } @Override public void setHintIdEntry(@NonNull String entryName) { mNode.mHintIdEntry = Objects.requireNonNull(entryName); } @Override public CharSequence getText() { return mNode.mText != null ? mNode.mText.mText : null; } @Override public int getTextSelectionStart() { return mNode.mText != null ? mNode.mText.mTextSelectionStart : -1; } @Override public int getTextSelectionEnd() { return mNode.mText != null ? mNode.mText.mTextSelectionEnd : -1; } @Override public CharSequence getHint() { return mNode.mText != null ? mNode.mText.mHint : null; } @Override public Bundle getExtras() { if (mNode.mExtras != null) { return mNode.mExtras; } mNode.mExtras = new Bundle(); return mNode.mExtras; } @Override public boolean hasExtras() { return mNode.mExtras != null; } @Override public void setChildCount(int num) { mNode.mChildren = new ViewNode[num]; } @Override public int addChildCount(int num) { if (mNode.mChildren == null) { setChildCount(num); return 0; } final int start = mNode.mChildren.length; ViewNode[] newArray = new ViewNode[start + num]; System.arraycopy(mNode.mChildren, 0, newArray, 0, start); mNode.mChildren = newArray; return start; } @Override public int getChildCount() { return mNode.mChildren != null ? mNode.mChildren.length : 0; } @Override public ViewStructure newChild(int index) { ViewNode node = new ViewNode(); mNode.mChildren[index] = node; return new ViewNodeBuilder(mAssist, node, false); } @Override public ViewStructure asyncNewChild(int index) { synchronized (mAssist) { ViewNode node = new ViewNode(); mNode.mChildren[index] = node; ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true); mAssist.mPendingAsyncChildren.add(builder); return builder; } } @Nullable @Override public GetCredentialRequest getPendingCredentialRequest() { return mNode.mGetCredentialRequest; } @Nullable @Override public OutcomeReceiver< GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() { return mNode.mGetCredentialCallback; } @Override public void asyncCommit() { synchronized (mAssist) { if (!mAsync) { throw new IllegalStateException("Child " + this + " was not created with ViewStructure.asyncNewChild"); } if (!mAssist.mPendingAsyncChildren.remove(this)) { throw new IllegalStateException("Child " + this + " already committed"); } mAssist.notifyAll(); } } @Override public Rect getTempRect() { return mAssist.mTmpRect; } @Override public void setAutofillId(@NonNull AutofillId id) { mNode.mAutofillId = id; } @Override public void setAutofillId(@NonNull AutofillId parentId, int virtualId) { mNode.mAutofillId = new AutofillId(parentId, virtualId); } @Override public AutofillId getAutofillId() { return mNode.mAutofillId; } @Override public void setAutofillType(@View.AutofillType int type) { mNode.mAutofillType = type; } @Override public void setAutofillHints(@Nullable String[] hints) { mNode.mAutofillHints = hints; } @Override public void setAutofillValue(AutofillValue value) { mNode.mAutofillValue = value; } @Override public void setAutofillOptions(CharSequence[] options) { mNode.mAutofillOptions = options; } @Override public void setImportantForAutofill(@AutofillImportance int mode) { mNode.mImportantForAutofill = mode; } @Override public void setIsCredential(boolean isCredential) { mNode.mIsCredential = isCredential; } @Override public void setPendingCredentialRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver callback) { mNode.mGetCredentialRequest = request; mNode.mGetCredentialCallback = callback; for (CredentialOption option : request.getCredentialOptions()) { ArrayList ids = option.getCandidateQueryData() .getParcelableArrayList( CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class); ids = ids != null ? ids : new ArrayList<>(); if (!ids.contains(getAutofillId())) { ids.add(getAutofillId()); } option.getCandidateQueryData() .putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids); } setUpResultReceiver(callback); } private void setUpResultReceiver( OutcomeReceiver callback) { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper(), null, true); } final ResultReceiver resultReceiver = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SUCCESS_CREDMAN_SELECTOR) { Slog.d(TAG, "onReceiveResult from Credential Manager"); GetCredentialResponse getCredentialResponse = resultData.getParcelable( CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, GetCredentialResponse.class); callback.onResult(getCredentialResponse); } else if (resultCode == FAILURE_CREDMAN_SELECTOR) { String[] exception = resultData.getStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); if (exception != null && exception.length >= 2) { Slog.w(TAG, "Credman bottom sheet from pinned " + "entry failed with: + " + exception[0] + " , " + exception[1]); callback.onError(new GetCredentialException( exception[0], exception[1])); } } else { Slog.d(TAG, "Unknown resultCode from credential " + "manager bottom sheet: " + resultCode); } } }; ResultReceiver ipcFriendlyResultReceiver = toIpcFriendlyResultReceiver(resultReceiver); mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver; } private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) { final Parcel parcel = Parcel.obtain(); resultReceiver.writeToParcel(parcel, 0); parcel.setDataPosition(0); final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel); parcel.recycle(); return ipcFriendly; } @Override public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) { mNode.mReceiveContentMimeTypes = mimeTypes; } @Override public void setInputType(int inputType) { mNode.mInputType = inputType; } @Override public void setMinTextEms(int minEms) { mNode.mMinEms = minEms; } @Override public void setMaxTextEms(int maxEms) { mNode.mMaxEms = maxEms; } @Override public void setMaxTextLength(int maxLength) { mNode.mMaxLength = maxLength; } @Override public void setDataIsSensitive(boolean sensitive) { mNode.mSanitized = !sensitive; } @Override public void setWebDomain(@Nullable String domain) { mNode.setWebDomain(domain); } @Override public void setLocaleList(LocaleList localeList) { mNode.mLocaleList = localeList; } @Override public HtmlInfo.Builder newHtmlInfoBuilder(@NonNull String tagName) { return new HtmlInfoNodeBuilder(tagName); } @Override public void setHtmlInfo(@NonNull HtmlInfo htmlInfo) { mNode.mHtmlInfo = htmlInfo; } private CharSequence stripAllSpansFromText(CharSequence text) { if (text instanceof Spanned) { return text.toString(); } return text; } } private static final class HtmlInfoNode extends HtmlInfo implements Parcelable { private final String mTag; private final String[] mNames; private final String[] mValues; // Not parcelable private ArrayList> mAttributes; private HtmlInfoNode(HtmlInfoNodeBuilder builder) { mTag = builder.mTag; if (builder.mNames == null) { mNames = null; mValues = null; } else { mNames = new String[builder.mNames.size()]; mValues = new String[builder.mValues.size()]; builder.mNames.toArray(mNames); builder.mValues.toArray(mValues); } } @Override public String getTag() { return mTag; } @Override public List> getAttributes() { if (mAttributes == null && mNames != null) { mAttributes = new ArrayList<>(mNames.length); for (int i = 0; i < mNames.length; i++) { final Pair pair = new Pair<>(mNames[i], mValues[i]); mAttributes.add(i, pair); } } return mAttributes; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mTag); parcel.writeStringArray(mNames); parcel.writeStringArray(mValues); } @SuppressWarnings("hiding") public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public HtmlInfoNode createFromParcel(Parcel parcel) { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. final String tag = parcel.readString(); final HtmlInfoNodeBuilder builder = new HtmlInfoNodeBuilder(tag); final String[] names = parcel.readStringArray(); final String[] values = parcel.readStringArray(); if (names != null && values != null) { if (names.length != values.length) { Log.w(TAG, "HtmlInfo attributes mismatch: names=" + names.length + ", values=" + values.length); } else { for (int i = 0; i < names.length; i++) { builder.addAttribute(names[i], values[i]); } } } return builder.build(); } @Override public HtmlInfoNode[] newArray(int size) { return new HtmlInfoNode[size]; } }; } private static final class HtmlInfoNodeBuilder extends HtmlInfo.Builder { private final String mTag; private ArrayList mNames; private ArrayList mValues; HtmlInfoNodeBuilder(String tag) { mTag = tag; } @Override public Builder addAttribute(String name, String value) { if (mNames == null) { mNames = new ArrayList<>(); mValues = new ArrayList<>(); } mNames.add(name); mValues.add(value); return this; } @Override public HtmlInfoNode build() { return new HtmlInfoNode(this); } } /** @hide */ public AssistStructure(Activity activity, boolean forAutoFill, int flags) { mHaveData = true; mFlags = flags; ArrayList views = WindowManagerGlobal.getInstance().getRootViews( activity.getActivityToken()); for (int i=0; iUsed just on autofill. * @hide */ public void sanitizeForParceling(boolean sanitize) { mSanitizeOnWrite = sanitize; } /** @hide */ public void dump(boolean showSensitive) { if (mActivityComponent == null) { Log.i(TAG, "dump(): calling ensureData() first"); ensureData(); } Log.i(TAG, "Task id: " + mTaskId); Log.i(TAG, "Activity: " + (mActivityComponent != null ? mActivityComponent.flattenToShortString() : null)); Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite); Log.i(TAG, "Flags: " + mFlags); final int N = getWindowNodeCount(); for (int i=0; i 0) { Log.i(TAG, prefix + " Children:"); String cprefix = prefix + " "; for (int i=0; i 0 && (now=SystemClock.uptimeMillis()) < endTime) { try { wait(endTime-now); } catch (InterruptedException e) { } } if (mPendingAsyncChildren.size() > 0) { // We waited too long, assume none of the assist structure is valid. Log.w(TAG, "Skipping assist structure, waiting too long for async children (have " + mPendingAsyncChildren.size() + " remaining"); skipStructure = true; } } return !skipStructure; } /** @hide */ public void clearSendChannel() { if (mSendChannel != null) { mSendChannel.mAssistStructure = null; } } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mTaskId); ComponentName.writeToParcel(mActivityComponent, out); out.writeInt(mIsHomeActivity ? 1 : 0); if (mHaveData) { // This object holds its data. We want to write a send channel that the // other side can use to retrieve that data. if (mSendChannel == null) { mSendChannel = new SendChannel(this); } out.writeStrongBinder(mSendChannel); } else { // This object doesn't hold its data, so just propagate along its receive channel. out.writeStrongBinder(mReceiveChannel); } } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public AssistStructure createFromParcel(Parcel in) { return new AssistStructure(in); } @Override public AssistStructure[] newArray(int size) { return new AssistStructure[size]; } }; private static final ArrayMap INPUT_TYPE_VARIATIONS = new ArrayMap<>(); static { INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, "EmailSubject"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS, "PostalAddress"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PERSON_NAME, "PersonName"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PASSWORD, "Password"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, "VisiblePassword"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_URI, "URI"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS, "WebEmailAddress"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD, "WebPassword"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE, "LongMessage"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE, "ShortMessage"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_MULTI_LINE, "MultiLine"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE, "ImeMultiLine"); INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_FILTER, "Filter"); } private static String getInputTypeString(int inputType) { StringBuilder sb = new StringBuilder(); sb.append(inputType); sb.append("(class=").append(inputType & InputType.TYPE_MASK_CLASS).append(')'); for (int variation : INPUT_TYPE_VARIATIONS.keySet()) { if ((variation & inputType) == variation) { sb.append('|').append(INPUT_TYPE_VARIATIONS.get(variation)); } } return sb.toString(); } }