content: 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];
          }
      };
  }
  