| package android.app.assist; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.Activity; |
| import android.content.ComponentName; |
| import android.content.Context; |
| 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.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 com.android.internal.util.Preconditions; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * <p>This API automatically creates assist data from the platform's |
| * implementation of assist and autofill. |
| * |
| * <p>The structure is used for assist purposes when created by |
| * {@link android.app.Activity#onProvideAssistData}, {@link View#onProvideStructure(ViewStructure)}, |
| * or {@link View#onProvideVirtualStructure(ViewStructure)}. |
| * |
| * <p>The structure is also used for autofill purposes when created by |
| * {@link View#onProvideAutofillStructure(ViewStructure, int)}, |
| * or {@link View#onProvideAutofillVirtualStructure(ViewStructure, int)}. |
| * |
| * <p>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. |
| * |
| * <p>To learn about using Autofill in your app, read the |
| * <a href="/guide/topics/text/autofill">Autofill Framework</a> guides. |
| */ |
| 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; |
| private int mAutofillFlags; |
| |
| final ArrayList<WindowNode> mWindowNodes = new ArrayList<>(); |
| |
| final ArrayList<ViewNodeBuilder> 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<ViewStackEntry> 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.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); |
| 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(); |
| 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<N; i++) { |
| mWindowNodes.add(new WindowNode(this)); |
| } |
| } |
| if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition() |
| + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows |
| + ", views=" + mNumReadViews); |
| mCurParcel.recycle(); |
| mCurParcel = null; // Parcel cannot be used after recycled. |
| } |
| |
| Parcel readParcel(int validateToken, int level) { |
| if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition() |
| + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows |
| + ", views=" + mNumReadViews + ", level=" + level); |
| int token = mCurParcel.readInt(); |
| if (token != 0) { |
| if (token != validateToken) { |
| throw new BadParcelableException("Got token " + Integer.toHexString(token) |
| + ", expected token " + Integer.toHexString(validateToken)); |
| } |
| return mCurParcel; |
| } |
| // We have run out of partial data, need to read another batch. |
| mTransferToken = mCurParcel.readStrongBinder(); |
| if (mTransferToken == null) { |
| throw new IllegalStateException( |
| "Reached end of partial data without transfer token"); |
| } |
| if (DEBUG_PARCEL) Log.d(TAG, "Ran out of partial data at " |
| + mCurParcel.dataPosition() + ", token " + mTransferToken); |
| fetchData(); |
| if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ " |
| + mCurParcel.dataPosition()); |
| mStringReader = new PooledStringReader(mCurParcel); |
| if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = " |
| + mStringReader.getStringCount()); |
| if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition() |
| + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows |
| + ", views=" + mNumReadViews); |
| mCurParcel.readInt(); |
| return mCurParcel; |
| } |
| |
| private void fetchData() { |
| Parcel data = Parcel.obtain(); |
| try { |
| data.writeInterfaceToken(DESCRIPTOR); |
| data.writeStrongBinder(mTransferToken); |
| if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken); |
| if (mCurParcel != null) { |
| mCurParcel.recycle(); |
| } |
| mCurParcel = Parcel.obtain(); |
| try { |
| mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failure reading AssistStructure data", e); |
| throw new IllegalStateException("Failure reading AssistStructure data: " + e); |
| } |
| } finally { |
| data.recycle(); |
| } |
| mNumReadWindows = mNumReadViews = 0; |
| } |
| } |
| |
| final static class ViewNodeText { |
| CharSequence mText; |
| float mTextSize; |
| int mTextStyle; |
| int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED; |
| int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED; |
| int mTextSelectionStart; |
| int mTextSelectionEnd; |
| int[] mLineCharOffsets; |
| int[] mLineBaselines; |
| String mHint; |
| |
| ViewNodeText() { |
| } |
| |
| boolean isSimple() { |
| return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED |
| && mTextSelectionStart == 0 && mTextSelectionEnd == 0 |
| && mLineCharOffsets == null && mLineBaselines == null && mHint == null; |
| } |
| |
| ViewNodeText(Parcel in, boolean simple) { |
| mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); |
| mTextSize = in.readFloat(); |
| mTextStyle = in.readInt(); |
| mTextColor = in.readInt(); |
| if (!simple) { |
| mTextBackgroundColor = in.readInt(); |
| mTextSelectionStart = in.readInt(); |
| mTextSelectionEnd = in.readInt(); |
| mLineCharOffsets = in.createIntArray(); |
| mLineBaselines = in.createIntArray(); |
| mHint = in.readString(); |
| } |
| } |
| |
| void writeToParcel(Parcel out, boolean simple, boolean writeSensitive) { |
| TextUtils.writeToParcel(writeSensitive ? mText : "", out, 0); |
| out.writeFloat(mTextSize); |
| out.writeInt(mTextStyle); |
| out.writeInt(mTextColor); |
| if (!simple) { |
| out.writeInt(mTextBackgroundColor); |
| out.writeInt(mTextSelectionStart); |
| out.writeInt(mTextSelectionEnd); |
| out.writeIntArray(mLineCharOffsets); |
| out.writeIntArray(mLineBaselines); |
| out.writeString(mHint); |
| } |
| } |
| } |
| |
| /** |
| * Describes a window in the assist data. |
| */ |
| static public class WindowNode { |
| final int mX; |
| final int mY; |
| final int mWidth; |
| final int mHeight; |
| final CharSequence mTitle; |
| final int mDisplayId; |
| final ViewNode mRoot; |
| |
| WindowNode(AssistStructure assist, ViewRootImpl root, boolean forAutoFill, int flags) { |
| View view = root.getView(); |
| Rect rect = new Rect(); |
| view.getBoundsOnScreen(rect); |
| mX = rect.left - view.getLeft(); |
| mY = rect.top - view.getTop(); |
| mWidth = rect.width(); |
| mHeight = rect.height(); |
| mTitle = root.getTitle(); |
| mDisplayId = root.getDisplayId(); |
| mRoot = new ViewNode(); |
| |
| ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false); |
| if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) { |
| if (forAutoFill) { |
| final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags); |
| view.onProvideAutofillStructure(builder, viewFlags); |
| } else { |
| // This is a secure window, so it doesn't want a screenshot, and that |
| // means we should also not copy out its view hierarchy for Assist |
| view.onProvideStructure(builder); |
| builder.setAssistBlocked(true); |
| return; |
| } |
| } |
| if (forAutoFill) { |
| final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags); |
| view.dispatchProvideAutofillStructure(builder, viewFlags); |
| } else { |
| view.dispatchProvideStructure(builder); |
| } |
| } |
| |
| WindowNode(ParcelTransferReader reader) { |
| Parcel in = reader.readParcel(VALIDATE_WINDOW_TOKEN, 0); |
| reader.mNumReadWindows++; |
| mX = in.readInt(); |
| mY = in.readInt(); |
| mWidth = in.readInt(); |
| mHeight = in.readInt(); |
| mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); |
| mDisplayId = in.readInt(); |
| mRoot = new ViewNode(reader, 0); |
| } |
| |
| int resolveViewAutofillFlags(Context context, int fillRequestFlags) { |
| return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0 |
| || context.isAutofillCompatibilityEnabled() |
| ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0; |
| } |
| |
| void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) { |
| out.writeInt(mX); |
| out.writeInt(mY); |
| out.writeInt(mWidth); |
| out.writeInt(mHeight); |
| TextUtils.writeToParcel(mTitle, out, 0); |
| out.writeInt(mDisplayId); |
| } |
| |
| /** |
| * Returns the left edge of the window, in pixels, relative to the left |
| * edge of the screen. |
| */ |
| public int getLeft() { |
| return mX; |
| } |
| |
| /** |
| * Returns the top edge of the window, in pixels, relative to the top |
| * edge of the screen. |
| */ |
| public int getTop() { |
| return mY; |
| } |
| |
| /** |
| * Returns the total width of the window in pixels. |
| */ |
| public int getWidth() { |
| return mWidth; |
| } |
| |
| /** |
| * Returns the total height of the window in pixels. |
| */ |
| public int getHeight() { |
| return mHeight; |
| } |
| |
| /** |
| * Returns the title associated with the window, if it has one. |
| */ |
| public CharSequence getTitle() { |
| return mTitle; |
| } |
| |
| /** |
| * Returns the ID of the display this window is on, for use with |
| * {@link android.hardware.display.DisplayManager#getDisplay DisplayManager.getDisplay()}. |
| */ |
| public int getDisplayId() { |
| return mDisplayId; |
| } |
| |
| /** |
| * Returns the {@link ViewNode} containing the root content of the window. |
| */ |
| public ViewNode getRootViewNode() { |
| return mRoot; |
| } |
| } |
| |
| /** |
| * Describes a single view in the assist data. |
| */ |
| static public class ViewNode { |
| /** |
| * Magic value for text color that has not been defined, which is very unlikely |
| * to be confused with a real text color. |
| */ |
| public static final int TEXT_COLOR_UNDEFINED = 1; |
| |
| public static final int TEXT_STYLE_BOLD = 1<<0; |
| public static final int TEXT_STYLE_ITALIC = 1<<1; |
| public static final int TEXT_STYLE_UNDERLINE = 1<<2; |
| public static final int TEXT_STYLE_STRIKE_THRU = 1<<3; |
| |
| int mId = View.NO_ID; |
| String mIdPackage; |
| String mIdType; |
| String mIdEntry; |
| |
| AutofillId mAutofillId; |
| @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE; |
| @Nullable String[] mAutofillHints; |
| AutofillValue mAutofillValue; |
| CharSequence[] mAutofillOptions; |
| boolean mSanitized; |
| HtmlInfo mHtmlInfo; |
| int mMinEms = -1; |
| int mMaxEms = -1; |
| int mMaxLength = -1; |
| @Nullable String mTextIdEntry; |
| @AutofillImportance int mImportantForAutofill; |
| |
| // POJO used to override some autofill-related values when the node is parcelized. |
| // Not written to parcel. |
| AutofillOverlay mAutofillOverlay; |
| |
| int mX; |
| int mY; |
| int mScrollX; |
| int mScrollY; |
| int mWidth; |
| int mHeight; |
| Matrix mMatrix; |
| float mElevation; |
| float mAlpha = 1.0f; |
| |
| 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; |
| |
| 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 = 0x00080000; |
| static final int FLAGS_HAS_INPUT_TYPE = 0x00040000; |
| static final int FLAGS_HAS_LOCALE_LIST = 0x00010000; |
| static final int FLAGS_ALL_CONTROL = 0xfff00000; |
| |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID = 0x001; |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID = 0x002; |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE = 0x004; |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE = 0x008; |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS = 0x010; |
| static final int AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS = 0x020; |
| static final int AUTOFILL_FLAGS_HAS_HTML_INFO = 0x040; |
| static final int AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY = 0x080; |
| static final int AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS = 0x100; |
| static final int AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS = 0x200; |
| static final int AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH = 0x400; |
| |
| int mFlags; |
| int mAutofillFlags; |
| |
| String mClassName; |
| CharSequence mContentDescription; |
| |
| ViewNodeText mText; |
| int mInputType; |
| String mWebScheme; |
| String mWebDomain; |
| Bundle mExtras; |
| LocaleList mLocaleList; |
| |
| ViewNode[] mChildren; |
| |
| ViewNode() { |
| } |
| |
| ViewNode(ParcelTransferReader reader, int nestingLevel) { |
| final Parcel in = reader.readParcel(VALIDATE_VIEW_TOKEN, nestingLevel); |
| reader.mNumReadViews++; |
| final PooledStringReader preader = reader.mStringReader; |
| mClassName = preader.readString(); |
| 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 = preader.readString(); |
| if (mIdEntry != null) { |
| mIdType = preader.readString(); |
| mIdPackage = preader.readString(); |
| } |
| } |
| } |
| |
| if (autofillFlags != 0) { |
| mSanitized = 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_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); |
| } |
| if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { |
| mAutofillOptions = in.readCharSequenceArray(); |
| } |
| if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) { |
| mHtmlInfo = in.readParcelable(null); |
| } |
| 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 = preader.readString(); |
| } |
| } |
| 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(); |
| 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) { |
| mWebScheme = in.readString(); |
| 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; i<NCHILDREN; i++) { |
| mChildren[i] = new ViewNode(reader, nestingLevel + 1); |
| } |
| } |
| } |
| |
| int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, boolean sanitizeOnWrite, |
| float[] tmpMatrix) { |
| // 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 || mWebDomain != null) { |
| flags |= FLAGS_HAS_URL; |
| } |
| if (mLocaleList != null) { |
| flags |= FLAGS_HAS_LOCALE_LIST; |
| } |
| if (mExtras != null) { |
| flags |= FLAGS_HAS_EXTRAS; |
| } |
| if (mChildren != null) { |
| flags |= FLAGS_HAS_CHILDREN; |
| } |
| if (mAutofillId != null) { |
| autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID; |
| if (mAutofillId.isVirtual()) { |
| autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_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; |
| } |
| |
| pwriter.writeString(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) { |
| pwriter.writeString(mIdEntry); |
| if (mIdEntry != null) { |
| pwriter.writeString(mIdType); |
| pwriter.writeString(mIdPackage); |
| } |
| } |
| } |
| |
| if (autofillFlags != 0) { |
| out.writeInt(mSanitized ? 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.getVirtualChildId()); |
| } |
| } |
| 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) { |
| pwriter.writeString(mTextIdEntry); |
| } |
| } |
| 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) { |
| 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) != 0) { |
| out.writeString(mWebScheme); |
| out.writeString(mWebDomain); |
| } |
| if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { |
| out.writeParcelable(mLocaleList, 0); |
| } |
| if ((flags&FLAGS_HAS_EXTRAS) != 0) { |
| out.writeBundle(mExtras); |
| } |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| public String getIdEntry() { |
| return mIdEntry; |
| } |
| |
| /** |
| * Gets the id that can be used to autofill the view contents. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate |
| * the meaning of each possible value in the list. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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)}. |
| * |
| * <p>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)}. |
| * |
| * <p>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. |
| * |
| * <p>Typically used when the view associated with the view is a container for an HTML |
| * document. |
| * |
| * <p><b>Warning:</b> 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; |
| |
| final 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(); |
| mWebDomain = uri.getHost(); |
| } |
| |
| /** |
| * Returns the scheme of the HTML document represented by this view. |
| * |
| * <p>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. |
| * |
| * <p>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 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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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; |
| } |
| |
| /** |
| * Gets the identifier used to set the text associated with this view. |
| * |
| * <p>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. |
| */ |
| 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]; |
| } |
| |
| /** |
| * Returns the minimum width in ems of the text associated with this node, or {@code -1} |
| * if not supported by the node. |
| * |
| * <p>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. |
| * |
| * <p>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 node, or {@code -1} |
| * if not supported by the node or not set. |
| * |
| * <p>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. |
| * |
| * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes. |
| */ |
| public @AutofillImportance int getImportantForAutofill() { |
| return mImportantForAutofill; |
| } |
| } |
| |
| /** |
| * 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 setTextIdEntry(@NonNull String entryName) { |
| mNode.mTextIdEntry = Preconditions.checkNotNull(entryName); |
| } |
| |
| @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 setImportantForAutofill(@AutofillImportance int mode) { |
| mNode.mImportantForAutofill = mode; |
| } |
| |
| @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 static final class HtmlInfoNode extends HtmlInfo implements Parcelable { |
| private final String mTag; |
| private final String[] mNames; |
| private final String[] mValues; |
| |
| // Not parcelable |
| private ArrayList<Pair<String, String>> 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<Pair<String, String>> getAttributes() { |
| if (mAttributes == null && mNames != null) { |
| mAttributes = new ArrayList<>(mNames.length); |
| for (int i = 0; i < mNames.length; i++) { |
| final Pair<String, String> 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<HtmlInfoNode> CREATOR = new Creator<HtmlInfoNode>() { |
| @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<String> mNames; |
| private ArrayList<String> 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<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( |
| activity.getActivityToken()); |
| for (int i=0; i<views.size(); i++) { |
| ViewRootImpl root = views.get(i); |
| if (root.getView() == null) { |
| Log.w(TAG, "Skipping window with dettached view: " + root.getTitle()); |
| continue; |
| } |
| mWindowNodes.add(new WindowNode(this, root, forAutoFill, flags)); |
| } |
| } |
| |
| public AssistStructure() { |
| mHaveData = true; |
| mActivityComponent = null; |
| mFlags = 0; |
| } |
| |
| /** @hide */ |
| public AssistStructure(Parcel in) { |
| mIsHomeActivity = in.readInt() == 1; |
| mReceiveChannel = in.readStrongBinder(); |
| } |
| |
| /** |
| * Helper method used to sanitize the structure before it's written to a parcel. |
| * |
| * <p>Used 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<N; i++) { |
| WindowNode node = getWindowNodeAt(i); |
| Log.i(TAG, "Window #" + i + " [" + node.getLeft() + "," + node.getTop() |
| + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getTitle()); |
| dump(" ", node.getRootViewNode(), showSensitive); |
| } |
| } |
| |
| void dump(String prefix, ViewNode node, boolean showSensitive) { |
| Log.i(TAG, prefix + "View [" + node.getLeft() + "," + node.getTop() |
| + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getClassName()); |
| int id = node.getId(); |
| if (id != 0) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(prefix); sb.append(" ID: #"); sb.append(Integer.toHexString(id)); |
| String entry = node.getIdEntry(); |
| if (entry != null) { |
| String type = node.getIdType(); |
| String pkg = node.getIdPackage(); |
| sb.append(" "); sb.append(pkg); sb.append(":"); sb.append(type); |
| sb.append("/"); sb.append(entry); |
| } |
| Log.i(TAG, sb.toString()); |
| } |
| int scrollX = node.getScrollX(); |
| int scrollY = node.getScrollY(); |
| if (scrollX != 0 || scrollY != 0) { |
| Log.i(TAG, prefix + " Scroll: " + scrollX + "," + scrollY); |
| } |
| Matrix matrix = node.getTransformation(); |
| if (matrix != null) { |
| Log.i(TAG, prefix + " Transformation: " + matrix); |
| } |
| float elevation = node.getElevation(); |
| if (elevation != 0) { |
| Log.i(TAG, prefix + " Elevation: " + elevation); |
| } |
| float alpha = node.getAlpha(); |
| if (alpha != 0) { |
| Log.i(TAG, prefix + " Alpha: " + elevation); |
| } |
| CharSequence contentDescription = node.getContentDescription(); |
| if (contentDescription != null) { |
| Log.i(TAG, prefix + " Content description: " + contentDescription); |
| } |
| CharSequence text = node.getText(); |
| if (text != null) { |
| final String safeText = node.isSanitized() || showSensitive ? text.toString() |
| : "REDACTED[" + text.length() + " chars]"; |
| Log.i(TAG, prefix + " Text (sel " + node.getTextSelectionStart() + "-" |
| + node.getTextSelectionEnd() + "): " + safeText); |
| Log.i(TAG, prefix + " Text size: " + node.getTextSize() + " , style: #" |
| + node.getTextStyle()); |
| Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor()) |
| + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor())); |
| Log.i(TAG, prefix + " Input type: " + node.getInputType()); |
| Log.i(TAG, prefix + " Resource id: " + node.getTextIdEntry()); |
| } |
| String webDomain = node.getWebDomain(); |
| if (webDomain != null) { |
| Log.i(TAG, prefix + " Web domain: " + webDomain); |
| } |
| HtmlInfo htmlInfo = node.getHtmlInfo(); |
| if (htmlInfo != null) { |
| Log.i(TAG, prefix + " HtmlInfo: tag=" + htmlInfo.getTag() |
| + ", attr="+ htmlInfo.getAttributes()); |
| } |
| |
| LocaleList localeList = node.getLocaleList(); |
| if (localeList != null) { |
| Log.i(TAG, prefix + " LocaleList: " + localeList); |
| } |
| String hint = node.getHint(); |
| if (hint != null) { |
| Log.i(TAG, prefix + " Hint: " + hint); |
| } |
| Bundle extras = node.getExtras(); |
| if (extras != null) { |
| Log.i(TAG, prefix + " Extras: " + extras); |
| } |
| if (node.isAssistBlocked()) { |
| Log.i(TAG, prefix + " BLOCKED"); |
| } |
| AutofillId autofillId = node.getAutofillId(); |
| if (autofillId == null) { |
| Log.i(TAG, prefix + " NO autofill ID"); |
| } else { |
| Log.i(TAG, prefix + " Autofill info: id= " + autofillId |
| + ", type=" + node.getAutofillType() |
| + ", options=" + Arrays.toString(node.getAutofillOptions()) |
| + ", hints=" + Arrays.toString(node.getAutofillHints()) |
| + ", value=" + node.getAutofillValue() |
| + ", sanitized=" + node.isSanitized() |
| + ", important=" + node.getImportantForAutofill()); |
| } |
| |
| final int NCHILDREN = node.getChildCount(); |
| if (NCHILDREN > 0) { |
| Log.i(TAG, prefix + " Children:"); |
| String cprefix = prefix + " "; |
| for (int i=0; i<NCHILDREN; i++) { |
| ViewNode cnode = node.getChildAt(i); |
| dump(cprefix, cnode, showSensitive); |
| } |
| } |
| } |
| |
| /** |
| * Return the activity this AssistStructure came from. |
| */ |
| public ComponentName getActivityComponent() { |
| ensureData(); |
| return mActivityComponent; |
| } |
| |
| /** |
| * Called by Autofill server when app forged a different value. |
| * |
| * @hide |
| */ |
| public void setActivityComponent(ComponentName componentName) { |
| ensureData(); |
| mActivityComponent = componentName; |
| } |
| |
| /** @hide */ |
| public int getFlags() { |
| return mFlags; |
| } |
| |
| /** |
| * Returns whether the activity associated with this AssistStructure was the home activity |
| * (Launcher) at the time the assist data was acquired. |
| * @return Whether the activity was the home activity. |
| * @see android.content.Intent#CATEGORY_HOME |
| */ |
| public boolean isHomeActivity() { |
| return mIsHomeActivity; |
| } |
| |
| /** |
| * Return the number of window contents that have been collected in this assist data. |
| */ |
| public int getWindowNodeCount() { |
| ensureData(); |
| return mWindowNodes.size(); |
| } |
| |
| /** |
| * Return one of the windows in the assist data. |
| * @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1. |
| */ |
| public WindowNode getWindowNodeAt(int index) { |
| ensureData(); |
| return mWindowNodes.get(index); |
| } |
| |
| // TODO(b/35708678): temporary method that disable one-way warning flag on binder. |
| /** @hide */ |
| public void ensureDataForAutofill() { |
| if (mHaveData) { |
| return; |
| } |
| mHaveData = true; |
| Binder.allowBlocking(mReceiveChannel); |
| try { |
| ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel); |
| reader.go(); |
| } finally { |
| Binder.defaultBlocking(mReceiveChannel); |
| } |
| } |
| |
| /** @hide */ |
| public void ensureData() { |
| if (mHaveData) { |
| return; |
| } |
| mHaveData = true; |
| ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel); |
| reader.go(); |
| } |
| |
| boolean waitForReady() { |
| boolean skipStructure = false; |
| synchronized (this) { |
| long endTime = SystemClock.uptimeMillis() + 5000; |
| long now; |
| while (mPendingAsyncChildren.size() > 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<AssistStructure> CREATOR |
| = new Parcelable.Creator<AssistStructure>() { |
| @Override |
| public AssistStructure createFromParcel(Parcel in) { |
| return new AssistStructure(in); |
| } |
| |
| @Override |
| public AssistStructure[] newArray(int size) { |
| return new AssistStructure[size]; |
| } |
| }; |
| } |