blob: 3fca3118c9a001286f7bf5af70ef142c44693f76 [file] [log] [blame]
package android.app.assist;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
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 {
private static final String TAG = "AssistStructure";
private static final boolean DEBUG_PARCEL = false;
private static final boolean DEBUG_PARCEL_CHILDREN = false;
private static final boolean DEBUG_PARCEL_TREE = false;
private static final int VALIDATE_WINDOW_TOKEN = 0x11111111;
private static final int VALIDATE_VIEW_TOKEN = 0x22222222;
private boolean mHaveData;
// The task id and component of the activity which this assist structure is for
private int mTaskId;
private ComponentName mActivityComponent;
private boolean mIsHomeActivity;
private int mFlags;
private int mAutofillFlags;
private final ArrayList<WindowNode> mWindowNodes = new ArrayList<>();
private final ArrayList<ViewNodeBuilder> mPendingAsyncChildren = new ArrayList<>();
private SendChannel mSendChannel;
private IBinder mReceiveChannel;
private Rect mTmpRect = new Rect();
private boolean mSanitizeOnWrite = false;
private long mAcquisitionStartTime;
private long mAcquisitionEndTime;
private static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1;
private static final String DESCRIPTOR = "android.app.AssistStructure";
/** @hide */
public void setAcquisitionStartTime(long acquisitionStartTime) {
mAcquisitionStartTime = acquisitionStartTime;
}
/** @hide */
public void setAcquisitionEndTime(long acquisitionEndTime) {
mAcquisitionEndTime = acquisitionEndTime;
}
/**
* @hide
* Set the home activity flag.
*/
public void setHomeActivity(boolean isHomeActivity) {
mIsHomeActivity = isHomeActivity;
}
/**
* Returns the time when the activity started generating assist data to build the
* AssistStructure. The time is as specified by {@link SystemClock#uptimeMillis()}.
*
* @see #getAcquisitionEndTime()
* @return Returns the acquisition start time of the assist data, in milliseconds.
*/
public long getAcquisitionStartTime() {
ensureData();
return mAcquisitionStartTime;
}
/**
* Returns the time when the activity finished generating assist data to build the
* AssistStructure. The time is as specified by {@link SystemClock#uptimeMillis()}.
*
* @see #getAcquisitionStartTime()
* @return Returns the acquisition end time of the assist data, in milliseconds.
*/
public long getAcquisitionEndTime() {
ensureData();
return mAcquisitionEndTime;
}
final static class SendChannel extends Binder {
volatile AssistStructure mAssistStructure;
SendChannel(AssistStructure as) {
mAssistStructure = as;
}
@Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
if (code == TRANSACTION_XFER) {
AssistStructure as = mAssistStructure;
if (as == null) {
return true;
}
data.enforceInterface(DESCRIPTOR);
IBinder token = data.readStrongBinder();
if (DEBUG_PARCEL) Log.d(TAG, "Request for data on " + as
+ " using token " + token);
if (token != null) {
if (DEBUG_PARCEL) Log.d(TAG, "Resuming partial write of " + token);
if (token instanceof ParcelTransferWriter) {
ParcelTransferWriter xfer = (ParcelTransferWriter)token;
xfer.writeToParcel(as, reply);
return true;
}
Log.w(TAG, "Caller supplied bad token type: " + token);
// Don't write anything; this is the end of the data.
return true;
}
//long start = SystemClock.uptimeMillis();
ParcelTransferWriter xfer = new ParcelTransferWriter(as, reply);
xfer.writeToParcel(as, reply);
//Log.i(TAG, "Time to parcel: " + (SystemClock.uptimeMillis()-start) + "ms");
return true;
} else {
return super.onTransact(code, data, reply, flags);
}
}
}
final static class ViewStackEntry {
ViewNode node;
int curChild;
int numChildren;
}
final static class ParcelTransferWriter extends Binder {
final boolean mWriteStructure;
int mCurWindow;
int mNumWindows;
final ArrayList<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();
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();
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;
static final int AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID = 0x800;
int mFlags;
int mAutofillFlags;
String mClassName;
CharSequence mContentDescription;
ViewNodeText mText;
int mInputType;
String mWebScheme;
String mWebDomain;
Bundle mExtras;
LocaleList mLocaleList;
ViewNode[] mChildren;
// TODO(b/111276913): temporarily made public / @hide until we decide what will be used by
// COntent Capture.
/** @hide */
@SystemApi
@TestApi
public 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_SESSION_ID) != 0) {
mAutofillId.setSessionId(in.readInt());
}
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
mAutofillType = in.readInt();
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS) != 0) {
mAutofillHints = in.readStringArray();
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) {
mAutofillValue = in.readParcelable(null);
}
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.isVirtualInt()) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID;
}
if (mAutofillId.hasSession()) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID;
}
}
if (mAutofillValue != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE;
}
if (mAutofillType != View.AUTOFILL_TYPE_NONE) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE;
}
if (mAutofillHints != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS;
}
if (mAutofillOptions != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS;
}
if (mHtmlInfo instanceof Parcelable) {
autofillFlags |= AUTOFILL_FLAGS_HAS_HTML_INFO;
}
if (mMinEms > -1) {
autofillFlags |= AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS;
}
if (mMaxEms > -1) {
autofillFlags |= AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS;
}
if (mMaxLength > -1) {
autofillFlags |= AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH;
}
if (mTextIdEntry != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY;
}
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.getVirtualChildIntId());
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_SESSION_ID) != 0) {
out.writeInt(mAutofillId.getSessionId());
}
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
out.writeInt(mAutofillType);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_HINTS) != 0) {
out.writeStringArray(mAutofillHints);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) {
final AutofillValue sanitizedValue;
if (writeSensitive) {
sanitizedValue = mAutofillValue;
} else if (mAutofillOverlay != null && mAutofillOverlay.value != null) {
sanitizedValue = mAutofillOverlay.value;
} else {
sanitizedValue = null;
}
out.writeParcelable(sanitizedValue, 0);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
out.writeCharSequenceArray(mAutofillOptions);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) {
out.writeParcelable((Parcelable) mHtmlInfo, 0);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) {
out.writeInt(mMinEms);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_EMS) != 0) {
out.writeInt(mMaxEms);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
out.writeInt(mMaxLength);
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
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&mdash;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 & ViewNode.FLAGS_VISIBILITY_MASK);
}
@Override
public void setAssistBlocked(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ASSIST_BLOCKED)
| (state ? ViewNode.FLAGS_ASSIST_BLOCKED : 0);
}
@Override
public void setEnabled(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_DISABLED)
| (state ? 0 : ViewNode.FLAGS_DISABLED);
}
@Override
public void setClickable(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CLICKABLE)
| (state ? ViewNode.FLAGS_CLICKABLE : 0);
}
@Override
public void setLongClickable(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_LONG_CLICKABLE)
| (state ? ViewNode.FLAGS_LONG_CLICKABLE : 0);
}
@Override
public void setContextClickable(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CONTEXT_CLICKABLE)
| (state ? ViewNode.FLAGS_CONTEXT_CLICKABLE : 0);
}
@Override
public void setFocusable(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE)
| (state ? ViewNode.FLAGS_FOCUSABLE : 0);
}
@Override
public void setFocused(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSED)
| (state ? ViewNode.FLAGS_FOCUSED : 0);
}
@Override
public void setAccessibilityFocused(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACCESSIBILITY_FOCUSED)
| (state ? ViewNode.FLAGS_ACCESSIBILITY_FOCUSED : 0);
}
@Override
public void setCheckable(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKABLE)
| (state ? ViewNode.FLAGS_CHECKABLE : 0);
}
@Override
public void setChecked(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKED)
| (state ? ViewNode.FLAGS_CHECKED : 0);
}
@Override
public void setSelected(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_SELECTED)
| (state ? ViewNode.FLAGS_SELECTED : 0);
}
@Override
public void setActivated(boolean state) {
mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACTIVATED)
| (state ? ViewNode.FLAGS_ACTIVATED : 0);
}
@Override
public void setOpaque(boolean opaque) {
mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_OPAQUE)
| (opaque ? ViewNode.FLAGS_OPAQUE : 0);
}
@Override
public void setClassName(String className) {
mNode.mClassName = className;
}
@Override
public void setContentDescription(CharSequence contentDescription) {
mNode.mContentDescription = contentDescription;
}
private final ViewNodeText getNodeText() {
if (mNode.mText != null) {
return mNode.mText;
}
mNode.mText = new ViewNodeText();
return mNode.mText;
}
@Override
public void setText(CharSequence text) {
ViewNodeText t = getNodeText();
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 @android.annotation.NonNull 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;
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;
mFlags = 0;
}
/** @hide */
public AssistStructure(Parcel in) {
mTaskId = in.readInt();
mActivityComponent = ComponentName.readFromParcel(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, "Task id: " + mTaskId);
Log.i(TAG, "Activity: " + (mActivityComponent != null
? mActivityComponent.flattenToShortString()
: null));
Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite);
Log.i(TAG, "Flags: " + mFlags);
final int N = getWindowNodeCount();
for (int i=0; i<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);
}
}
}
/**
* Sets the task id is associated with the activity from which this AssistStructure was
* generated.
* @hide
*/
public void setTaskId(int taskId) {
mTaskId = taskId;
}
/**
* @return The task id for the associated activity.
*
* @hide
*/
public int getTaskId() {
return mTaskId;
}
/**
* Sets the activity that is associated with this AssistStructure.
* @hide
*/
public void setActivityComponent(ComponentName componentName) {
mActivityComponent = componentName;
}
/**
* Return the activity this AssistStructure came from.
*/
public ComponentName getActivityComponent() {
return mActivityComponent;
}
/** @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(mTaskId);
ComponentName.writeToParcel(mActivityComponent, out);
out.writeInt(mIsHomeActivity ? 1 : 0);
if (mHaveData) {
// This object holds its data. We want to write a send channel that the
// other side can use to retrieve that data.
if (mSendChannel == null) {
mSendChannel = new SendChannel(this);
}
out.writeStrongBinder(mSendChannel);
} else {
// This object doesn't hold its data, so just propagate along its receive channel.
out.writeStrongBinder(mReceiveChannel);
}
}
public static final @android.annotation.NonNull Parcelable.Creator<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];
}
};
}