package android.app.assist; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.content.ComponentName; 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.IBinder; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.os.PooledStringReader; import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.FillRequest; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.View; 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; /** * Assist data automatically created by 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 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 be available just for assist * or autofill purposes; in those case, the property availability will be document in its javadoc. */ public class AssistStructure implements Parcelable { static final String TAG = "AssistStructure"; static final boolean DEBUG_PARCEL = false; static final boolean DEBUG_PARCEL_CHILDREN = false; static final boolean DEBUG_PARCEL_TREE = false; static final int VALIDATE_WINDOW_TOKEN = 0x11111111; static final int VALIDATE_VIEW_TOKEN = 0x22222222; boolean mHaveData; ComponentName mActivityComponent; private boolean mIsHomeActivity; private int mFlags; final ArrayList mWindowNodes = new ArrayList<>(); final ArrayList mPendingAsyncChildren = new ArrayList<>(); SendChannel mSendChannel; IBinder mReceiveChannel; Rect mTmpRect = new Rect(); boolean mSanitizeOnWrite = false; private long mAcquisitionStartTime; private long mAcquisitionEndTime; static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; 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(); ComponentName.writeToParcel(as.mActivityComponent, out); out.writeInt(as.mFlags); 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); 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(); mActivityComponent = ComponentName.readFromParcel(mCurParcel); mFlags = 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>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(); in.readFloatArray(reader.mTmpMatrix); mMatrix.setValues(reader.mTmpMatrix); } 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) != 0) { mWebDomain = in.readString(); } if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { mLocaleList = in.readParcelable(null); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); } if ((flags&FLAGS_HAS_CHILDREN) != 0) { final int NCHILDREN = in.readInt(); if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) Log.d(TAG, "Preparing to read " + NCHILDREN + " children: @ #" + reader.mNumReadViews + ", level " + nestingLevel); mChildren = new ViewNode[NCHILDREN]; for (int i=0; iIt'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 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 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; } /** * 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}. */ public String getClassName() { return mClassName; } /** * Returns any content description associated with the node, which semantically describes * its purpose for accessibility and other uses. */ 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: a {@link android.service.autofill.AutofillService} should only * use this domain for autofill purposes when it trusts the app generating it (i.e., the app * defined by {@link AssistStructure#getActivityComponent()}). * * @return domain-only part of the document. For example, if the full URL is * {@code http://my.site/login?user=my_user}, it returns {@code my.site}. */ @Nullable public String getWebDomain() { return mWebDomain; } /** * 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 the list of locales associated with this view. */ @Nullable public LocaleList getLocaleList() { return mLocaleList; } /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. */ 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. */ 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. */ public int[] getTextLineBaselines() { return mText != null ? mText.mLineBaselines : null; } /** * 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. */ public String getHint() { return mText != null ? mText.mHint : null; } /** * Return a Bundle containing optional vendor-specific extension information. */ 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]; } } /** * POJO used to override some autofill-related values when the node is parcelized. * * @hide */ static public class AutofillOverlay { public boolean focused; public AutofillValue value; } static class ViewNodeBuilder extends ViewStructure { final AssistStructure mAssist; final ViewNode mNode; final boolean mAsync; ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) { mAssist = assist; mNode = node; mAsync = async; } @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; } @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(); t.mText = TextUtils.trimNoCopySpans(text); t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { ViewNodeText t = getNodeText(); t.mText = TextUtils.trimNoCopySpans(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 setHint(CharSequence hint) { getNodeText().mHint = hint != null ? hint.toString() : null; } @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; } } @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 setInputType(int inputType) { mNode.mInputType = inputType; } @Override public void setDataIsSensitive(boolean sensitive) { mNode.mSanitized = !sensitive; } @Override public void setWebDomain(@Nullable String domain) { if (domain == null) { mNode.mWebDomain = null; return; } mNode.mWebDomain = Uri.parse(domain).getHost(); } @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 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 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; mActivityComponent = activity.getComponentName(); 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, "Activity: " + mActivityComponent.flattenToShortString()); Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite); Log.i(TAG, "Flags: " + mFlags); final int N = getWindowNodeCount(); 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(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 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]; } }; }