summaryrefslogtreecommitdiff
path: root/android/view/accessibility/AccessibilityNodeInfo.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/view/accessibility/AccessibilityNodeInfo.java')
-rw-r--r--android/view/accessibility/AccessibilityNodeInfo.java4681
1 files changed, 4681 insertions, 0 deletions
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
new file mode 100644
index 00000000..9bdd3ffc
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -0,0 +1,4681 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import static com.android.internal.util.BitUtils.bitAt;
+import static com.android.internal.util.BitUtils.isBitSet;
+
+import static java.util.Collections.EMPTY_LIST;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.util.ArraySet;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a node of the window content as well as actions that
+ * can be requested from its source. From the point of view of an
+ * {@link android.accessibilityservice.AccessibilityService} a window's content is
+ * presented as a tree of accessibility node infos, which may or may not map one-to-one
+ * to the view hierarchy. In other words, a custom view is free to report itself as
+ * a tree of accessibility node info.
+ * </p>
+ * <p>
+ * Once an accessibility node info is delivered to an accessibility service it is
+ * made immutable and calling a state mutation method generates an error.
+ * </p>
+ * <p>
+ * Please refer to {@link android.accessibilityservice.AccessibilityService} for
+ * details about how to obtain a handle to window content as a tree of accessibility
+ * node info as well as details about the security model.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ */
+public class AccessibilityNodeInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ /** @hide */
+ public static final int UNDEFINED_CONNECTION_ID = -1;
+
+ /** @hide */
+ public static final int UNDEFINED_SELECTION_INDEX = -1;
+
+ /** @hide */
+ public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;
+
+ /** @hide */
+ public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
+
+ /** @hide */
+ public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
+
+ /** @hide */
+ public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+
+ /** @hide */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+
+ // Actions.
+
+ /**
+ * Action that gives input focus to the node.
+ */
+ public static final int ACTION_FOCUS = 0x00000001;
+
+ /**
+ * Action that clears input focus of the node.
+ */
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+
+ /**
+ * Action that selects the node.
+ */
+ public static final int ACTION_SELECT = 0x00000004;
+
+ /**
+ * Action that deselects the node.
+ */
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ /**
+ * Action that clicks on the node info.
+ *
+ * See {@link AccessibilityAction#ACTION_CLICK}
+ */
+ public static final int ACTION_CLICK = 0x00000010;
+
+ /**
+ * Action that long clicks on the node.
+ */
+ public static final int ACTION_LONG_CLICK = 0x00000020;
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
+
+ /**
+ * Action that requests to go to the next entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see #setMovementGranularities(int)
+ * @see #getMovementGranularities()
+ *
+ * @see #MOVEMENT_GRANULARITY_CHARACTER
+ * @see #MOVEMENT_GRANULARITY_WORD
+ * @see #MOVEMENT_GRANULARITY_LINE
+ * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see #MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
+
+ /**
+ * Action that requests to go to the previous entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see #setMovementGranularities(int)
+ * @see #getMovementGranularities()
+ *
+ * @see #MOVEMENT_GRANULARITY_CHARACTER
+ * @see #MOVEMENT_GRANULARITY_WORD
+ * @see #MOVEMENT_GRANULARITY_LINE
+ * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see #MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
+
+ /**
+ * Action to move to the next HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
+
+ /**
+ * Action to move to the previous HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
+
+ /**
+ * Action to scroll the node content forward.
+ */
+ public static final int ACTION_SCROLL_FORWARD = 0x00001000;
+
+ /**
+ * Action to scroll the node content backward.
+ */
+ public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
+
+ /**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final int ACTION_COPY = 0x00004000;
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final int ACTION_PASTE = 0x00008000;
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final int ACTION_CUT = 0x00010000;
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_SELECTION_START_INT
+ * @see #ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final int ACTION_SET_SELECTION = 0x00020000;
+
+ /**
+ * Action to expand an expandable node.
+ */
+ public static final int ACTION_EXPAND = 0x00040000;
+
+ /**
+ * Action to collapse an expandable node.
+ */
+ public static final int ACTION_COLLAPSE = 0x00080000;
+
+ /**
+ * Action to dismiss a dismissable node.
+ */
+ public static final int ACTION_DISMISS = 0x00100000;
+
+ /**
+ * Action that sets the text of the node. Performing the action without argument, using <code>
+ * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
+ * cursor at the end of text.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ * "android");
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+ * </code></pre></p>
+ */
+ public static final int ACTION_SET_TEXT = 0x00200000;
+
+ /** @hide */
+ public static final int LAST_LEGACY_STANDARD_ACTION = ACTION_SET_TEXT;
+
+ /**
+ * Mask to see if the value is larger than the largest ACTION_ constant
+ */
+ private static final int ACTION_TYPE_MASK = 0xFF000000;
+
+ // Action arguments
+
+ /**
+ * Argument for which movement granularity to be used when traversing the node text.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
+ * </p>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
+ "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+
+ /**
+ * Argument for which HTML element to get moving to the next/previous HTML element.
+ * <p>
+ * <strong>Type:</strong> String<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_HTML_ELEMENT}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT}</li>
+ * </ul>
+ * </p>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_HTML_ELEMENT
+ * @see AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT
+ */
+ public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
+ "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+
+ /**
+ * Argument for whether when moving at granularity to extend the selection
+ * or to move it otherwise.
+ * <p>
+ * <strong>Type:</strong> boolean<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
+ "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+
+ /**
+ * Argument for specifying the selection start.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_START_INT =
+ "ACTION_ARGUMENT_SELECTION_START_INT";
+
+ /**
+ * Argument for specifying the selection end.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_END_INT =
+ "ACTION_ARGUMENT_SELECTION_END_INT";
+
+ /**
+ * Argument for specifying the text content to set.
+ * <p>
+ * <strong>Type:</strong> CharSequence<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_TEXT}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_TEXT
+ */
+ public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+ "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
+ /**
+ * Argument for specifying the collection row to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_ROW_INT =
+ "android.view.accessibility.action.ARGUMENT_ROW_INT";
+
+ /**
+ * Argument for specifying the collection column to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_COLUMN_INT =
+ "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+
+ /**
+ * Argument for specifying the progress value to set.
+ * <p>
+ * <strong>Type:</strong> float<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_PROGRESS}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_PROGRESS
+ */
+ public static final String ACTION_ARGUMENT_PROGRESS_VALUE =
+ "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+
+ /**
+ * Argument for specifying the x coordinate to which to move a window.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_MOVE_WINDOW
+ */
+ public static final String ACTION_ARGUMENT_MOVE_WINDOW_X =
+ "ACTION_ARGUMENT_MOVE_WINDOW_X";
+
+ /**
+ * Argument for specifying the y coordinate to which to move a window.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_MOVE_WINDOW
+ */
+ public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y =
+ "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+
+ /**
+ * Argument to pass the {@link AccessibilityClickableSpan}.
+ * For use with R.id.accessibilityActionClickOnClickableSpan
+ * @hide
+ */
+ public static final String ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN =
+ "android.view.accessibility.action.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN";
+
+ // Focus types
+
+ /**
+ * The input focus.
+ */
+ public static final int FOCUS_INPUT = 1;
+
+ /**
+ * The accessibility focus.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 2;
+
+ // Movement granularities
+
+ /**
+ * Movement granularity bit for traversing the text of a node by character.
+ */
+ public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by word.
+ */
+ public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by line.
+ */
+ public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by paragraph.
+ */
+ public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by page.
+ */
+ public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
+
+ /**
+ * Key used to request and locate extra data for text character location. This key requests that
+ * an array of {@link android.graphics.RectF}s be added to the extras. This request is made with
+ * {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by this request are two
+ * integers: {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+ * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index must be valid
+ * inside the CharSequence returned by {@link #getText()}, and the length must be positive.
+ * <p>
+ * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
+ * string as a key for {@link Bundle#getParcelableArray(String)}. The
+ * {@link android.graphics.RectF} will be null for characters that either do not exist or are
+ * off the screen.
+ *
+ * {@see #refreshWithExtraData(String, Bundle)}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+
+ /**
+ * Integer argument specifying the start index of the requested text location data. Must be
+ * valid inside the CharSequence returned by {@link #getText()}.
+ *
+ * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+
+ /**
+ * Integer argument specifying the end index of the requested text location data. Must be
+ * positive.
+ *
+ * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+
+ /** @hide */
+ public static final String EXTRA_DATA_REQUESTED_KEY =
+ "android.view.accessibility.AccessibilityNodeInfo.extra_data_requested";
+
+ // Boolean attributes.
+
+ private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
+
+ private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
+
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
+
+ private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
+
+ private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
+
+ private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
+
+ private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
+
+ private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
+
+ private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
+
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+
+ private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
+
+ private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
+
+ private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 0x00002000;
+
+ private static final int BOOLEAN_PROPERTY_DISMISSABLE = 0x00004000;
+
+ private static final int BOOLEAN_PROPERTY_MULTI_LINE = 0x00008000;
+
+ private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
+
+ private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+
+ private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+
+ private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+
+ /**
+ * Bits that provide the id of a virtual descendant of a view.
+ */
+ private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
+ /**
+ * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
+ * virtual descendant of a view. Such a descendant does not exist in the view
+ * hierarchy and is only reported via the accessibility APIs.
+ */
+ private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
+
+ private static AtomicInteger sNumInstancesInUse;
+
+ /**
+ * Gets the accessibility view id which identifies a View in the view three.
+ *
+ * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+ * @return The accessibility view id part of the node id.
+ *
+ * @hide
+ */
+ public static int getAccessibilityViewId(long accessibilityNodeId) {
+ return (int) accessibilityNodeId;
+ }
+
+ /**
+ * Gets the virtual descendant id which identifies an imaginary view in a
+ * containing View.
+ *
+ * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+ * @return The virtual view id part of the node id.
+ *
+ * @hide
+ */
+ public static int getVirtualDescendantId(long accessibilityNodeId) {
+ return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
+ >> VIRTUAL_DESCENDANT_ID_SHIFT);
+ }
+
+ /**
+ * Makes a node id by shifting the <code>virtualDescendantId</code>
+ * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
+ * the bitwise or with the <code>accessibilityViewId</code>.
+ *
+ * @param accessibilityViewId A View accessibility id.
+ * @param virtualDescendantId A virtual descendant id.
+ * @return The node id.
+ *
+ * @hide
+ */
+ public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
+ return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
+ }
+
+ // Housekeeping.
+ private static final int MAX_POOL_SIZE = 50;
+ private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo();
+
+ private boolean mSealed;
+
+ // Data.
+ private int mWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ private long mSourceNodeId = UNDEFINED_NODE_ID;
+ private long mParentNodeId = UNDEFINED_NODE_ID;
+ private long mLabelForId = UNDEFINED_NODE_ID;
+ private long mLabeledById = UNDEFINED_NODE_ID;
+ private long mTraversalBefore = UNDEFINED_NODE_ID;
+ private long mTraversalAfter = UNDEFINED_NODE_ID;
+
+ private int mBooleanProperties;
+ private final Rect mBoundsInParent = new Rect();
+ private final Rect mBoundsInScreen = new Rect();
+ private int mDrawingOrderInParent;
+
+ private CharSequence mPackageName;
+ private CharSequence mClassName;
+ // Hidden, unparceled value used to hold the original value passed to setText
+ private CharSequence mOriginalText;
+ private CharSequence mText;
+ private CharSequence mHintText;
+ private CharSequence mError;
+ private CharSequence mContentDescription;
+ private String mViewIdResourceName;
+ private ArrayList<String> mExtraDataKeys;
+
+ private LongArray mChildNodeIds;
+ private ArrayList<AccessibilityAction> mActions;
+
+ private int mMaxTextLength = -1;
+ private int mMovementGranularities;
+
+ private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
+ private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
+ private int mInputType = InputType.TYPE_NULL;
+ private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
+
+ private Bundle mExtras;
+
+ private int mConnectionId = UNDEFINED_CONNECTION_ID;
+
+ private RangeInfo mRangeInfo;
+ private CollectionInfo mCollectionInfo;
+ private CollectionItemInfo mCollectionItemInfo;
+
+ /**
+ * Hide constructor from clients.
+ */
+ private AccessibilityNodeInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Sets the source.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param source The info source.
+ */
+ public void setSource(View source) {
+ setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the source to be a virtual descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+ * is set as the source.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setSource(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED_ITEM_ID;
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Find the view that has the specified focus type. The search starts from
+ * the view represented by this node info.
+ *
+ * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
+ * {@link #FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see #FOCUS_INPUT
+ * @see #FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ enforceSealed();
+ enforceValidFocusType(focus);
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
+ mSourceNodeId, focus);
+ }
+
+ /**
+ * Searches for the nearest view in the specified direction that can take
+ * the input focus.
+ *
+ * @param direction The direction. Can be one of:
+ * {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_UP},
+ * {@link View#FOCUS_LEFT},
+ * {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD}.
+ *
+ * @return The node info for the view that can take accessibility focus.
+ */
+ public AccessibilityNodeInfo focusSearch(int direction) {
+ enforceSealed();
+ enforceValidFocusDirection(direction);
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
+ mSourceNodeId, direction);
+ }
+
+ /**
+ * Gets the id of the window from which the info comes from.
+ *
+ * @return The window id.
+ */
+ public int getWindowId() {
+ return mWindowId;
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents.
+ * <p>
+ * <strong>Note:</strong> If this method returns false this info is obsolete
+ * since it represents a view that is no longer in the view tree and should
+ * be recycled.
+ * </p>
+ *
+ * @param bypassCache Whether to bypass the cache.
+ * @return Whether the refresh succeeded.
+ *
+ * @hide
+ */
+ public boolean refresh(Bundle arguments, boolean bypassCache) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
+ mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0, arguments);
+ if (refreshedInfo == null) {
+ return false;
+ }
+ // Hard-to-reproduce bugs seem to be due to some tools recycling a node on another
+ // thread. If that happens, the init will re-seal the node, which then is in a bad state
+ // when it is obtained. Enforce sealing again before we init to fail when a node has been
+ // recycled during a refresh to catch such errors earlier.
+ enforceSealed();
+ init(refreshedInfo);
+ refreshedInfo.recycle();
+ return true;
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents.
+ *
+ * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+ * by this node is no longer in the view tree (and thus this node is obsolete and should be
+ * recycled).
+ */
+ public boolean refresh() {
+ return refresh(null, true);
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents, and request new
+ * data be added by the View.
+ *
+ * @param extraDataKey A bitmask of the extra data requested. Data that must be requested
+ * with this mechanism is generally expensive to retrieve, so should only be
+ * requested when needed. See
+ * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY} and
+ * {@link #getAvailableExtraData()}.
+ * @param args A bundle of arguments for the request. These depend on the particular request.
+ *
+ * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+ * by this node is no longer in the view tree (and thus this node is obsolete and should be
+ * recycled).
+ */
+ public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
+ args.putString(EXTRA_DATA_REQUESTED_KEY, extraDataKey);
+ return refresh(args, true);
+ }
+
+ /**
+ * Returns the array containing the IDs of this node's children.
+ *
+ * @hide
+ */
+ public LongArray getChildNodeIds() {
+ return mChildNodeIds;
+ }
+
+ /**
+ * Returns the id of the child at the specified index.
+ *
+ * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt;=
+ * getChildCount()
+ * @hide
+ */
+ public long getChildId(int index) {
+ if (mChildNodeIds == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mChildNodeIds.get(index);
+ }
+
+ /**
+ * Gets the number of children.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return mChildNodeIds == null ? 0 : mChildNodeIds.size();
+ }
+
+ /**
+ * Get the child at given index.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @param index The child index.
+ * @return The child node.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ */
+ public AccessibilityNodeInfo getChild(int index) {
+ enforceSealed();
+ if (mChildNodeIds == null) {
+ return null;
+ }
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ final long childId = mChildNodeIds.get(index);
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
+ childId, false, FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
+ /**
+ * Adds a child.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param child The child.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addChild(View child) {
+ addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);
+ }
+
+ /**
+ * Unchecked version of {@link #addChild(View)} that does not verify
+ * uniqueness. For framework use only.
+ *
+ * @hide
+ */
+ public void addChildUnchecked(View child) {
+ addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, false);
+ }
+
+ /**
+ * Removes a child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param child The child.
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeChild(View child) {
+ return removeChild(child, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Adds a virtual child which is a descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+ * is added as a child.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual child.
+ */
+ public void addChild(View root, int virtualDescendantId) {
+ addChildInternal(root, virtualDescendantId, true);
+ }
+
+ private void addChildInternal(View root, int virtualDescendantId, boolean checked) {
+ enforceNotSealed();
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ // If we're checking uniqueness and the ID already exists, abort.
+ if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(childNodeId);
+ }
+
+ /**
+ * Removes a virtual child which is a descendant of the given
+ * <code>root</code>. If the child was not previously added to the node,
+ * calling this method has no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual child.
+ * @return true if the child was present
+ * @see #addChild(View, int)
+ */
+ public boolean removeChild(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ final int index = childIds.indexOf(childNodeId);
+ if (index < 0) {
+ return false;
+ }
+ childIds.remove(index);
+ return true;
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ */
+ public List<AccessibilityAction> getActionList() {
+ return CollectionUtils.emptyIfNull(mActions);
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ *
+ * @return The bit mask of with actions.
+ *
+ * @see AccessibilityNodeInfo#ACTION_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_SELECT
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
+ * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLICK
+ * @see AccessibilityNodeInfo#ACTION_LONG_CLICK
+ * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
+ * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
+ * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
+ * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
+ *
+ * @deprecated Use {@link #getActionList()}.
+ */
+ @Deprecated
+ public int getActions() {
+ int returnValue = 0;
+
+ if (mActions == null) {
+ return returnValue;
+ }
+
+ final int actionSize = mActions.size();
+ for (int i = 0; i < actionSize; i++) {
+ int actionId = mActions.get(i).getId();
+ if (actionId <= LAST_LEGACY_STANDARD_ACTION) {
+ returnValue |= actionId;
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * To add a standard action use the static constants on {@link AccessibilityAction}.
+ * To add a custom action create a new {@link AccessibilityAction} by passing in a
+ * resource id from your application as the action id and an optional label that
+ * describes the action. To override one of the standard actions use as the action
+ * id of a standard action id such as {@link #ACTION_CLICK} and an optional label that
+ * describes the action.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ addActionUnchecked(action);
+ }
+
+ private void addActionUnchecked(AccessibilityAction action) {
+ if (action == null) {
+ return;
+ }
+
+ if (mActions == null) {
+ mActions = new ArrayList<>();
+ }
+
+ mActions.remove(action);
+ mActions.add(action);
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @throws IllegalArgumentException If the argument is not one of the standard actions.
+ *
+ * @deprecated This has been deprecated for {@link #addAction(AccessibilityAction)}
+ */
+ @Deprecated
+ public void addAction(int action) {
+ enforceNotSealed();
+
+ if ((action & ACTION_TYPE_MASK) != 0) {
+ throw new IllegalArgumentException("Action is not a combination of the standard " +
+ "actions: " + action);
+ }
+
+ addStandardActions(action);
+ }
+
+ /**
+ * Removes an action that can be performed on the node. If the action was
+ * not already added to the node, calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action to be removed.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #removeAction(AccessibilityAction)}
+ */
+ @Deprecated
+ public void removeAction(int action) {
+ enforceNotSealed();
+
+ removeAction(getActionSingleton(action));
+ }
+
+ /**
+ * Removes an action that can be performed on the node. If the action was
+ * not already added to the node, calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action to be removed.
+ * @return The action removed from the list of actions.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ if (mActions == null || action == null) {
+ return false;
+ }
+
+ return mActions.remove(action);
+ }
+
+ /**
+ * Removes all actions.
+ *
+ * @hide
+ */
+ public void removeAllActions() {
+ if (mActions != null) {
+ mActions.clear();
+ }
+ }
+
+ /**
+ * Gets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalBefore(android.view.View)
+ * @see #setTraversalBefore(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalBefore() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalBefore);
+ }
+
+ /**
+ * Sets the view before whose node this one should be visited during traversal. A
+ * screen-reader must visit the content of this node before the content of the one
+ * it precedes.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param view The view providing the preceding node.
+ *
+ * @see #getTraversalBefore()
+ */
+ public void setTraversalBefore(View view) {
+ setTraversalBefore(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ * The successor is a virtual descendant of the given <code>root</code>. If
+ * <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root is set
+ * as the successor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalBefore(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalAfter(android.view.View)
+ * @see #setTraversalAfter(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalAfter() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalAfter);
+ }
+
+ /**
+ * Sets the view whose node is visited after this one in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param view The previous view.
+ *
+ * @see #getTraversalAfter()
+ */
+ public void setTraversalAfter(View view) {
+ setTraversalAfter(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one. If <code>virtualDescendantId</code> equals to {@link View#NO_ID}
+ * the root is set as the predecessor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalAfter(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Get the extra data available for this node.
+ * <p>
+ * Some data that is useful for some accessibility services is expensive to compute, and would
+ * place undue overhead on apps to compute all the time. That data can be requested with
+ * {@link #refreshWithExtraData(String, Bundle)}.
+ *
+ * @return An unmodifiable list of keys corresponding to extra data that can be requested.
+ * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+ */
+ public List<String> getAvailableExtraData() {
+ if (mExtraDataKeys != null) {
+ return Collections.unmodifiableList(mExtraDataKeys);
+ } else {
+ return EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Set the extra data available for this node.
+ * <p>
+ * <strong>Note:</strong> When a {@code View} passes in a non-empty list, it promises that
+ * it will populate the node's extras with corresponding pieces of information in
+ * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)}.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ *
+ * @param extraDataKeys A list of types of extra data that are available.
+ * @see #getAvailableExtraData()
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAvailableExtraData(List<String> extraDataKeys) {
+ enforceNotSealed();
+ mExtraDataKeys = new ArrayList<>(extraDataKeys);
+ }
+
+ /**
+ * Sets the maximum text length, or -1 for no limit.
+ * <p>
+ * Typically used to indicate that an editable text field has a limit on
+ * the number of characters entered.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ *
+ * @param max The maximum text length.
+ * @see #getMaxTextLength()
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setMaxTextLength(int max) {
+ enforceNotSealed();
+ mMaxTextLength = max;
+ }
+
+ /**
+ * Returns the maximum text length for this node.
+ *
+ * @return The maximum text length, or -1 for no limit.
+ * @see #setMaxTextLength(int)
+ */
+ public int getMaxTextLength() {
+ return mMaxTextLength;
+ }
+
+ /**
+ * Sets the movement granularities for traversing the text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param granularities The bit mask with granularities.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setMovementGranularities(int granularities) {
+ enforceNotSealed();
+ mMovementGranularities = granularities;
+ }
+
+ /**
+ * Gets the movement granularities for traversing the text of this node.
+ *
+ * @return The bit mask with granularities.
+ */
+ public int getMovementGranularities() {
+ return mMovementGranularities;
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * <strong>Note:</strong> An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param action The action to perform.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+ action, null);
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * <strong>Note:</strong> An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param action The action to perform.
+ * @param arguments A bundle with additional arguments.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action, Bundle arguments) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+ action, arguments);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by text. The match is case
+ * insensitive containment. The search is relative to this info i.e.
+ * this info is the root of the traversed tree.
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @param text The searched text.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
+ text);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
+ * Gets the window to which this node belongs.
+ *
+ * @return The window.
+ *
+ * @see android.accessibilityservice.AccessibilityService#getWindows()
+ */
+ public AccessibilityWindowInfo getWindow() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, mWindowId);
+ }
+
+ /**
+ * Gets the parent.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The parent.
+ */
+ public AccessibilityNodeInfo getParent() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mParentNodeId);
+ }
+
+ /**
+ * @return The parent node id.
+ *
+ * @hide
+ */
+ public long getParentNodeId() {
+ return mParentNodeId;
+ }
+
+ /**
+ * Sets the parent.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param parent The parent.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParent(View parent) {
+ setParent(parent, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the parent to be a virtual descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
+ * is set as the parent.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setParent(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node bounds in parent coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBoundsInParent(Rect outBounds) {
+ outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
+ mBoundsInParent.right, mBoundsInParent.bottom);
+ }
+
+ /**
+ * Sets the node bounds in parent coordinates.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBoundsInParent(Rect bounds) {
+ enforceNotSealed();
+ mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets the node bounds in screen coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBoundsInScreen(Rect outBounds) {
+ outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
+ mBoundsInScreen.right, mBoundsInScreen.bottom);
+ }
+
+ /**
+ * Returns the actual rect containing the node bounds in screen coordinates.
+ *
+ * @hide Not safe to expose outside the framework.
+ */
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ /**
+ * Sets the node bounds in screen coordinates.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBoundsInScreen(Rect bounds) {
+ enforceNotSealed();
+ mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets whether this node is checkable.
+ *
+ * @return True if the node is checkable.
+ */
+ public boolean isCheckable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
+ }
+
+ /**
+ * Sets whether this node is checkable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param checkable True if the node is checkable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCheckable(boolean checkable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
+ }
+
+ /**
+ * Gets whether this node is checked.
+ *
+ * @return True if the node is checked.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets whether this node is checked.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param checked True if the node is checked.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean checked) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
+ }
+
+ /**
+ * Gets whether this node is focusable.
+ *
+ * @return True if the node is focusable.
+ */
+ public boolean isFocusable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this node is focusable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focusable True if the node is focusable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocusable(boolean focusable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
+ }
+
+ /**
+ * Gets whether this node is focused.
+ *
+ * @return True if the node is focused.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is focused.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focused True if the node is focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is visible to the user.
+ *
+ * @return Whether the node is visible to the user.
+ */
+ public boolean isVisibleToUser() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
+ }
+
+ /**
+ * Sets whether this node is visible to the user.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param visibleToUser Whether the node is visible to the user.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setVisibleToUser(boolean visibleToUser) {
+ setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
+ }
+
+ /**
+ * Gets whether this node is accessibility focused.
+ *
+ * @return True if the node is accessibility focused.
+ */
+ public boolean isAccessibilityFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is accessibility focused.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focused True if the node is accessibility focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAccessibilityFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is selected.
+ *
+ * @return True if the node is selected.
+ */
+ public boolean isSelected() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
+ }
+
+ /**
+ * Sets whether this node is selected.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param selected True if the node is selected.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSelected(boolean selected) {
+ setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
+ }
+
+ /**
+ * Gets whether this node is clickable.
+ *
+ * @return True if the node is clickable.
+ */
+ public boolean isClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param clickable True if the node is clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClickable(boolean clickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
+ }
+
+ /**
+ * Gets whether this node is long clickable.
+ *
+ * @return True if the node is long clickable.
+ */
+ public boolean isLongClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is long clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param longClickable True if the node is long clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setLongClickable(boolean longClickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
+ }
+
+ /**
+ * Gets whether this node is enabled.
+ *
+ * @return True if the node is enabled.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets whether this node is enabled.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param enabled True if the node is enabled.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean enabled) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
+ }
+
+ /**
+ * Gets whether this node is a password.
+ *
+ * @return True if the node is a password.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets whether this node is a password.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param password True if the node is a password.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean password) {
+ setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
+ }
+
+ /**
+ * Gets if the node is scrollable.
+ *
+ * @return True if the node is scrollable, false otherwise.
+ */
+ public boolean isScrollable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+ }
+
+ /**
+ * Sets if the node is scrollable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param scrollable True if the node is scrollable, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setScrollable(boolean scrollable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+ }
+
+ /**
+ * Gets if the node is editable.
+ *
+ * @return True if the node is editable, false otherwise.
+ */
+ public boolean isEditable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+ }
+
+ /**
+ * Sets whether this node is editable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param editable True if the node is editable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEditable(boolean editable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
+ }
+
+ /**
+ * Get the drawing order of the view corresponding it this node.
+ * <p>
+ * Drawing order is determined only within the node's parent, so this index is only relative
+ * to its siblings.
+ * <p>
+ * In some cases, the drawing order is essentially simultaneous, so it is possible for two
+ * siblings to return the same value. It is also possible that values will be skipped.
+ *
+ * @return The drawing position of the view corresponding to this node relative to its siblings.
+ */
+ public int getDrawingOrder() {
+ return mDrawingOrderInParent;
+ }
+
+ /**
+ * Set the drawing order of the view corresponding it this node.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param drawingOrderInParent
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setDrawingOrder(int drawingOrderInParent) {
+ enforceNotSealed();
+ mDrawingOrderInParent = drawingOrderInParent;
+ }
+
+ /**
+ * Gets the collection info if the node is a collection. A collection
+ * child is always a collection item.
+ *
+ * @return The collection info.
+ */
+ public CollectionInfo getCollectionInfo() {
+ return mCollectionInfo;
+ }
+
+ /**
+ * Sets the collection info if the node is a collection. A collection
+ * child is always a collection item.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param collectionInfo The collection info.
+ */
+ public void setCollectionInfo(CollectionInfo collectionInfo) {
+ enforceNotSealed();
+ mCollectionInfo = collectionInfo;
+ }
+
+ /**
+ * Gets the collection item info if the node is a collection item. A collection
+ * item is always a child of a collection.
+ *
+ * @return The collection item info.
+ */
+ public CollectionItemInfo getCollectionItemInfo() {
+ return mCollectionItemInfo;
+ }
+
+ /**
+ * Sets the collection item info if the node is a collection item. A collection
+ * item is always a child of a collection.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ */
+ public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
+ enforceNotSealed();
+ mCollectionItemInfo = collectionItemInfo;
+ }
+
+ /**
+ * Gets the range info if this node is a range.
+ *
+ * @return The range.
+ */
+ public RangeInfo getRangeInfo() {
+ return mRangeInfo;
+ }
+
+ /**
+ * Sets the range info if this node is a range.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param rangeInfo The range info.
+ */
+ public void setRangeInfo(RangeInfo rangeInfo) {
+ enforceNotSealed();
+ mRangeInfo = rangeInfo;
+ }
+
+ /**
+ * Gets if the content of this node is invalid. For example,
+ * a date is not well-formed.
+ *
+ * @return If the node content is invalid.
+ */
+ public boolean isContentInvalid() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID);
+ }
+
+ /**
+ * Sets if the content of this node is invalid. For example,
+ * a date is not well-formed.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contentInvalid If the node content is invalid.
+ */
+ public void setContentInvalid(boolean contentInvalid) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID, contentInvalid);
+ }
+
+ /**
+ * Gets whether this node is context clickable.
+ *
+ * @return True if the node is context clickable.
+ */
+ public boolean isContextClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is context clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}. This class is made immutable
+ * before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contextClickable True if the node is context clickable.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContextClickable(boolean contextClickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
+ }
+
+ /**
+ * Gets the node's live region mode.
+ * <p>
+ * A live region is a node that contains information that is important for
+ * the user and when it changes the user should be notified. For example,
+ * in a login screen with a TextView that displays an "incorrect password"
+ * notification, that view should be marked as a live region with mode
+ * {@link View#ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * <p>
+ * It is the responsibility of the accessibility service to monitor
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events indicating
+ * changes to live region nodes and their children.
+ *
+ * @return The live region mode, or
+ * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+ * live region.
+ * @see android.view.View#getAccessibilityLiveRegion()
+ */
+ public int getLiveRegion() {
+ return mLiveRegion;
+ }
+
+ /**
+ * Sets the node's live region mode.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}. This class is
+ * made immutable before being delivered to an AccessibilityService.
+ *
+ * @param mode The live region mode, or
+ * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+ * live region.
+ * @see android.view.View#setAccessibilityLiveRegion(int)
+ */
+ public void setLiveRegion(int mode) {
+ enforceNotSealed();
+ mLiveRegion = mode;
+ }
+
+ /**
+ * Gets if the node is a multi line editable text.
+ *
+ * @return True if the node is multi line.
+ */
+ public boolean isMultiLine() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE);
+ }
+
+ /**
+ * Sets if the node is a multi line editable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param multiLine True if the node is multi line.
+ */
+ public void setMultiLine(boolean multiLine) {
+ setBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE, multiLine);
+ }
+
+ /**
+ * Gets if this node opens a popup or a dialog.
+ *
+ * @return If the the node opens a popup.
+ */
+ public boolean canOpenPopup() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP);
+ }
+
+ /**
+ * Sets if this node opens a popup or a dialog.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param opensPopup If the the node opens a popup.
+ */
+ public void setCanOpenPopup(boolean opensPopup) {
+ enforceNotSealed();
+ setBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP, opensPopup);
+ }
+
+ /**
+ * Gets if the node can be dismissed.
+ *
+ * @return If the node can be dismissed.
+ */
+ public boolean isDismissable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+ }
+
+ /**
+ * Sets if the node can be dismissed.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param dismissable If the node can be dismissed.
+ */
+ public void setDismissable(boolean dismissable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
+ }
+
+ /**
+ * Returns whether the node originates from a view considered important for accessibility.
+ *
+ * @return {@code true} if the node originates from a view considered important for
+ * accessibility, {@code false} otherwise
+ *
+ * @see View#isImportantForAccessibility()
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE);
+ }
+
+ /**
+ * Sets whether the node is considered important for accessibility.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param important {@code true} if the node is considered important for accessibility,
+ * {@code false} otherwise
+ */
+ public void setImportantForAccessibility(boolean important) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE, important);
+ }
+
+ /**
+ * Returns whether the node's text represents a hint for the user to enter text. It should only
+ * be {@code true} if the node has editable text.
+ *
+ * @return {@code true} if the text in the node represents a hint to the user, {@code false}
+ * otherwise.
+ */
+ public boolean isShowingHintText() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT);
+ }
+
+ /**
+ * Sets whether the node's text represents a hint for the user to enter text. It should only
+ * be {@code true} if the node has editable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param showingHintText {@code true} if the text in the node represents a hint to the user,
+ * {@code false} otherwise.
+ */
+ public void setShowingHintText(boolean showingHintText) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT, showingHintText);
+ }
+
+ /**
+ * Gets the package this node comes from.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package this node comes from.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the class this node comes from.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class this node comes from.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param className The class name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of this node.
+ * <p>
+ * <strong>Note:</strong> If the text contains {@link ClickableSpan}s or {@link URLSpan}s,
+ * these spans will have been replaced with ones whose {@link ClickableSpan#onClick(View)}
+ * can be called from an {@link AccessibilityService}. When called from a service, the
+ * {@link View} argument is ignored and the corresponding span will be found on the view that
+ * this {@code AccessibilityNodeInfo} represents and called with that view as its argument.
+ * <p>
+ * This treatment of {@link ClickableSpan}s means that the text returned from this method may
+ * different slightly one passed to {@link #setText(CharSequence)}, although they will be
+ * equivalent according to {@link TextUtils#equals(CharSequence, CharSequence)}. The
+ * {@link ClickableSpan#onClick(View)} of any spans, however, will generally not work outside
+ * of an accessibility service.
+ * </p>
+ *
+ * @return The text.
+ */
+ public CharSequence getText() {
+ // Attach this node to any spans that need it
+ if (mText instanceof Spanned) {
+ Spanned spanned = (Spanned) mText;
+ AccessibilityClickableSpan[] clickableSpans =
+ spanned.getSpans(0, mText.length(), AccessibilityClickableSpan.class);
+ for (int i = 0; i < clickableSpans.length; i++) {
+ clickableSpans[i].copyConnectionDataFrom(this);
+ }
+ AccessibilityURLSpan[] urlSpans =
+ spanned.getSpans(0, mText.length(), AccessibilityURLSpan.class);
+ for (int i = 0; i < urlSpans.length; i++) {
+ urlSpans[i].copyConnectionDataFrom(this);
+ }
+ }
+ return mText;
+ }
+
+ /**
+ * Get the text passed to setText before any changes to the spans.
+ * @hide
+ */
+ public CharSequence getOriginalText() {
+ return mOriginalText;
+ }
+
+ /**
+ * Sets the text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param text The text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setText(CharSequence text) {
+ enforceNotSealed();
+ mOriginalText = text;
+ // Replace any ClickableSpans in mText with placeholders
+ if (text instanceof Spanned) {
+ ClickableSpan[] spans =
+ ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+ if (spans.length > 0) {
+ Spannable spannable = new SpannableStringBuilder(text);
+ for (int i = 0; i < spans.length; i++) {
+ ClickableSpan span = spans[i];
+ if ((span instanceof AccessibilityClickableSpan)
+ || (span instanceof AccessibilityURLSpan)) {
+ // We've already done enough
+ break;
+ }
+ int spanToReplaceStart = spannable.getSpanStart(span);
+ int spanToReplaceEnd = spannable.getSpanEnd(span);
+ int spanToReplaceFlags = spannable.getSpanFlags(span);
+ spannable.removeSpan(span);
+ ClickableSpan replacementSpan = (span instanceof URLSpan)
+ ? new AccessibilityURLSpan((URLSpan) span)
+ : new AccessibilityClickableSpan(span.getId());
+ spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
+ spanToReplaceFlags);
+ }
+ mText = spannable;
+ return;
+ }
+ }
+ mText = (text == null) ? null : text.subSequence(0, text.length());
+ }
+
+ /**
+ * Gets the hint text of this node. Only applies to nodes where text can be entered.
+ *
+ * @return The hint text.
+ */
+ public CharSequence getHintText() {
+ return mHintText;
+ }
+
+ /**
+ * Sets the hint text of this node. Only applies to nodes where text can be entered.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param hintText The hint text for this mode.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setHintText(CharSequence hintText) {
+ enforceNotSealed();
+ mHintText = (hintText == null) ? null : hintText.subSequence(0, hintText.length());
+ }
+
+ /**
+ * Sets the error text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param error The error text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setError(CharSequence error) {
+ enforceNotSealed();
+ mError = (error == null) ? null : error.subSequence(0, error.length());
+ }
+
+ /**
+ * Gets the error text of this node.
+ *
+ * @return The error text.
+ */
+ public CharSequence getError() {
+ return mError;
+ }
+
+ /**
+ * Gets the content description of this node.
+ *
+ * @return The content description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the content description of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contentDescription The content description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = (contentDescription == null) ? null
+ : contentDescription.subSequence(0, contentDescription.length());
+ }
+
+ /**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes.
+ *
+ * @param labeled The view for which this info serves as a label.
+ */
+ public void setLabelFor(View labeled) {
+ setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the labeled.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant serves as a label.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabelFor(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info for which the view represented by this info serves as
+ * a label for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The labeled info.
+ */
+ public AccessibilityNodeInfo getLabelFor() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mLabelForId);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @param label The view that labels this node's source.
+ */
+ public void setLabeledBy(View label) {
+ setLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabeledBy(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The label.
+ */
+ public AccessibilityNodeInfo getLabeledBy() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mLabeledById);
+ }
+
+ /**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param viewIdResName The id resource name.
+ */
+ public void setViewIdResourceName(String viewIdResName) {
+ enforceNotSealed();
+ mViewIdResourceName = viewIdResName;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public String getViewIdResourceName() {
+ return mViewIdResourceName;
+ }
+
+ /**
+ * Gets the text selection start or the cursor position.
+ * <p>
+ * If no text is selected, both this method and
+ * {@link AccessibilityNodeInfo#getTextSelectionEnd()} return the same value:
+ * the current location of the cursor.
+ * </p>
+ *
+ * @return The text selection start, the cursor location if there is no selection, or -1 if
+ * there is no text selection and no cursor.
+ */
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ /**
+ * Gets the text selection end if text is selected.
+ * <p>
+ * If no text is selected, both this method and
+ * {@link AccessibilityNodeInfo#getTextSelectionStart()} return the same value:
+ * the current location of the cursor.
+ * </p>
+ *
+ * @return The text selection end, the cursor location if there is no selection, or -1 if
+ * there is no text selection and no cursor.
+ */
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ /**
+ * Sets the text selection start and end.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param start The text selection start.
+ * @param end The text selection end.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelection(int start, int end) {
+ enforceNotSealed();
+ mTextSelectionStart = start;
+ mTextSelectionEnd = end;
+ }
+
+ /**
+ * Gets the input type of the source as defined by {@link InputType}.
+ *
+ * @return The input type.
+ */
+ public int getInputType() {
+ return mInputType;
+ }
+
+ /**
+ * Sets the input type of the source as defined by {@link InputType}.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an
+ * AccessibilityService.
+ * </p>
+ *
+ * @param inputType The input type.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setInputType(int inputType) {
+ enforceNotSealed();
+ mInputType = inputType;
+ }
+
+ /**
+ * Gets an optional bundle with extra data. The bundle
+ * is lazily created and never <code>null</code>.
+ * <p>
+ * <strong>Note:</strong> It is recommended to use the package
+ * name of your application as a prefix for the keys to avoid
+ * collisions which may confuse an accessibility service if the
+ * same key has different meaning when emitted from different
+ * applications.
+ * </p>
+ *
+ * @return The bundle.
+ */
+ public Bundle getExtras() {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ return mExtras;
+ }
+
+ /**
+ * Check if a node has an extras bundle
+ * @hide
+ */
+ public boolean hasExtras() {
+ return mExtras != null;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ enforceNotSealed();
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Sets the unique id of the IAccessibilityServiceConnection over which
+ * this instance can send requests to the system.
+ *
+ * @param connectionId The connection id.
+ *
+ * @hide
+ */
+ public void setConnectionId(int connectionId) {
+ enforceNotSealed();
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Get the connection ID.
+ *
+ * @return The connection id
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Sets the id of the source node.
+ *
+ * @param sourceId The id.
+ * @param windowId The window id.
+ *
+ * @hide
+ */
+ public void setSourceNodeId(long sourceId, int windowId) {
+ enforceNotSealed();
+ mSourceNodeId = sourceId;
+ mWindowId = windowId;
+ }
+
+ /**
+ * Gets the id of the source node.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public long getSourceNodeId() {
+ return mSourceNodeId;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ private void enforceValidFocusDirection(int direction) {
+ switch (direction) {
+ case View.FOCUS_DOWN:
+ case View.FOCUS_UP:
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ return;
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ }
+
+ private void enforceValidFocusType(int focusType) {
+ switch (focusType) {
+ case FOCUS_INPUT:
+ case FOCUS_ACCESSIBILITY:
+ return;
+ default:
+ throw new IllegalArgumentException("Unknown focus type: " + focusType);
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a sealed instance.");
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @param source The source view.
+ * @return An instance.
+ *
+ * @see #setSource(View)
+ */
+ public static AccessibilityNodeInfo obtain(View source) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(source);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ * @return An instance.
+ *
+ * @see #setSource(View, int)
+ */
+ public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(root, virtualDescendantId);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityNodeInfo obtain() {
+ AccessibilityNodeInfo info = sPool.acquire();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.incrementAndGet();
+ }
+ return (info != null) ? info : new AccessibilityNodeInfo();
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * create. The returned instance is initialized from the given
+ * <code>info</code>.
+ *
+ * @param info The other info.
+ * @return An instance.
+ */
+ public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
+ AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
+ infoClone.init(info);
+ return infoClone;
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <strong>Note:</strong> You must not touch the object after calling this function.
+ *
+ * @throws IllegalStateException If the info is already recycled.
+ */
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.decrementAndGet();
+ }
+ }
+
+ /**
+ * Specify a counter that will be incremented on obtain() and decremented on recycle()
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+ sNumInstancesInUse = counter;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Note:</strong> After the instance is written to a parcel it
+ * is recycled. You must not touch the object after calling this function.
+ * </p>
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ // Write bit set of indices of fields with values differing from default
+ long nonDefaultFields = 0;
+ int fieldIndex = 0; // index of the current field
+ if (isSealed() != DEFAULT.isSealed()) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mSourceNodeId != DEFAULT.mSourceNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mWindowId != DEFAULT.mWindowId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mParentNodeId != DEFAULT.mParentNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabelForId != DEFAULT.mLabelForId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mChildNodeIds, DEFAULT.mChildNodeIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInParent, DEFAULT.mBoundsInParent)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInScreen, DEFAULT.mBoundsInScreen)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mActions, DEFAULT.mActions)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMaxTextLength != DEFAULT.mMaxTextLength) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMovementGranularities != DEFAULT.mMovementGranularities) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mBooleanProperties != DEFAULT.mBooleanProperties) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mPackageName, DEFAULT.mPackageName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mClassName, DEFAULT.mClassName)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mText, DEFAULT.mText)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mHintText, DEFAULT.mHintText)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mError, DEFAULT.mError)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionStart != DEFAULT.mTextSelectionStart) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionEnd != DEFAULT.mTextSelectionEnd) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mInputType != DEFAULT.mInputType) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLiveRegion != DEFAULT.mLiveRegion) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mDrawingOrderInParent != DEFAULT.mDrawingOrderInParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtraDataKeys, DEFAULT.mExtraDataKeys)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtras, DEFAULT.mExtras)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mRangeInfo, DEFAULT.mRangeInfo)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mCollectionInfo, DEFAULT.mCollectionInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mCollectionItemInfo, DEFAULT.mCollectionItemInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ int totalFields = fieldIndex;
+ parcel.writeLong(nonDefaultFields);
+
+ fieldIndex = 0;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(isSealed() ? 1 : 0);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mSourceNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mWindowId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeLong(childIds.get(i));
+ }
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInParent.top);
+ parcel.writeInt(mBoundsInParent.bottom);
+ parcel.writeInt(mBoundsInParent.left);
+ parcel.writeInt(mBoundsInParent.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInScreen.top);
+ parcel.writeInt(mBoundsInScreen.bottom);
+ parcel.writeInt(mBoundsInScreen.left);
+ parcel.writeInt(mBoundsInScreen.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ if (mActions != null && !mActions.isEmpty()) {
+ final int actionCount = mActions.size();
+
+ int nonStandardActionCount = 0;
+ int defaultStandardActions = 0;
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (isDefaultStandardAction(action)) {
+ defaultStandardActions |= action.mSerializationFlag;
+ } else {
+ nonStandardActionCount++;
+ }
+ }
+ parcel.writeInt(defaultStandardActions);
+
+ parcel.writeInt(nonStandardActionCount);
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (!isDefaultStandardAction(action)) {
+ parcel.writeInt(action.getId());
+ parcel.writeCharSequence(action.getLabel());
+ }
+ }
+ } else {
+ parcel.writeInt(0);
+ parcel.writeInt(0);
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMaxTextLength);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMovementGranularities);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mBooleanProperties);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPackageName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mClassName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mHintText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mError);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeCharSequence(mContentDescription);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mLiveRegion);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mDrawingOrderInParent);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeStringList(mExtraDataKeys);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeBundle(mExtras);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mRangeInfo.getType());
+ parcel.writeFloat(mRangeInfo.getMin());
+ parcel.writeFloat(mRangeInfo.getMax());
+ parcel.writeFloat(mRangeInfo.getCurrent());
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mCollectionInfo.getRowCount());
+ parcel.writeInt(mCollectionInfo.getColumnCount());
+ parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
+ parcel.writeInt(mCollectionInfo.getSelectionMode());
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mCollectionItemInfo.getRowIndex());
+ parcel.writeInt(mCollectionItemInfo.getRowSpan());
+ parcel.writeInt(mCollectionItemInfo.getColumnIndex());
+ parcel.writeInt(mCollectionItemInfo.getColumnSpan());
+ parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
+ parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
+ }
+
+ if (DEBUG) {
+ fieldIndex--;
+ if (totalFields != fieldIndex) {
+ throw new IllegalStateException("Number of fields mismatch: " + totalFields
+ + " vs " + fieldIndex);
+ }
+ }
+
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /**
+ * Initializes this instance from another one.
+ *
+ * @param other The other instance.
+ */
+ private void init(AccessibilityNodeInfo other) {
+ mSealed = other.mSealed;
+ mSourceNodeId = other.mSourceNodeId;
+ mParentNodeId = other.mParentNodeId;
+ mLabelForId = other.mLabelForId;
+ mLabeledById = other.mLabeledById;
+ mTraversalBefore = other.mTraversalBefore;
+ mTraversalAfter = other.mTraversalAfter;
+ mWindowId = other.mWindowId;
+ mConnectionId = other.mConnectionId;
+ mBoundsInParent.set(other.mBoundsInParent);
+ mBoundsInScreen.set(other.mBoundsInScreen);
+ mPackageName = other.mPackageName;
+ mClassName = other.mClassName;
+ mText = other.mText;
+ mHintText = other.mHintText;
+ mError = other.mError;
+ mContentDescription = other.mContentDescription;
+ mViewIdResourceName = other.mViewIdResourceName;
+
+ if (mActions != null) mActions.clear();
+ final ArrayList<AccessibilityAction> otherActions = other.mActions;
+ if (otherActions != null && otherActions.size() > 0) {
+ if (mActions == null) {
+ mActions = new ArrayList(otherActions);
+ } else {
+ mActions.addAll(other.mActions);
+ }
+ }
+
+ mBooleanProperties = other.mBooleanProperties;
+ mMaxTextLength = other.mMaxTextLength;
+ mMovementGranularities = other.mMovementGranularities;
+
+
+ if (mChildNodeIds != null) mChildNodeIds.clear();
+ final LongArray otherChildNodeIds = other.mChildNodeIds;
+ if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
+ if (mChildNodeIds == null) {
+ mChildNodeIds = otherChildNodeIds.clone();
+ } else {
+ mChildNodeIds.addAll(otherChildNodeIds);
+ }
+ }
+
+ mTextSelectionStart = other.mTextSelectionStart;
+ mTextSelectionEnd = other.mTextSelectionEnd;
+ mInputType = other.mInputType;
+ mLiveRegion = other.mLiveRegion;
+ mDrawingOrderInParent = other.mDrawingOrderInParent;
+
+ mExtraDataKeys = other.mExtraDataKeys;
+
+ mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
+ mRangeInfo = (other.mRangeInfo != null)
+ ? RangeInfo.obtain(other.mRangeInfo) : null;
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
+ mCollectionInfo = (other.mCollectionInfo != null)
+ ? CollectionInfo.obtain(other.mCollectionInfo) : null;
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+ mCollectionItemInfo = (other.mCollectionItemInfo != null)
+ ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
+ */
+ private void initFromParcel(Parcel parcel) {
+ // Bit mask of non-default-valued field indices
+ long nonDefaultFields = parcel.readLong();
+ int fieldIndex = 0;
+ final boolean sealed = isBitSet(nonDefaultFields, fieldIndex++)
+ ? (parcel.readInt() == 1)
+ : DEFAULT.mSealed;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mSourceNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mWindowId = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int childrenSize = parcel.readInt();
+ if (childrenSize <= 0) {
+ mChildNodeIds = null;
+ } else {
+ mChildNodeIds = new LongArray(childrenSize);
+ for (int i = 0; i < childrenSize; i++) {
+ final long childId = parcel.readLong();
+ mChildNodeIds.add(childId);
+ }
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInParent.top = parcel.readInt();
+ mBoundsInParent.bottom = parcel.readInt();
+ mBoundsInParent.left = parcel.readInt();
+ mBoundsInParent.right = parcel.readInt();
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInScreen.top = parcel.readInt();
+ mBoundsInScreen.bottom = parcel.readInt();
+ mBoundsInScreen.left = parcel.readInt();
+ mBoundsInScreen.right = parcel.readInt();
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int standardActions = parcel.readInt();
+ addStandardActions(standardActions);
+ final int nonStandardActionCount = parcel.readInt();
+ for (int i = 0; i < nonStandardActionCount; i++) {
+ final AccessibilityAction action = new AccessibilityAction(
+ parcel.readInt(), parcel.readCharSequence());
+ addActionUnchecked(action);
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMaxTextLength = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMovementGranularities = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mBooleanProperties = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPackageName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mClassName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mHintText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mError = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mContentDescription = parcel.readCharSequence();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionEnd = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mInputType = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLiveRegion = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mDrawingOrderInParent = parcel.readInt();
+
+ mExtraDataKeys = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.createStringArrayList()
+ : null;
+
+ mExtras = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.readBundle()
+ : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
+ mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? RangeInfo.obtain(
+ parcel.readInt(),
+ parcel.readFloat(),
+ parcel.readFloat(),
+ parcel.readFloat())
+ : null;
+
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
+ mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt())
+ : null;
+
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+ mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionItemInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt() == 1)
+ : null;
+
+ mSealed = sealed;
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ private void clear() {
+ init(DEFAULT);
+ }
+
+ private static boolean isDefaultStandardAction(AccessibilityAction action) {
+ return action.mSerializationFlag != -1 && TextUtils.isEmpty(action.getLabel());
+ }
+
+ private static AccessibilityAction getActionSingleton(int actionId) {
+ final int actions = AccessibilityAction.sStandardActions.size();
+ for (int i = 0; i < actions; i++) {
+ AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+ if (actionId == currentAction.getId()) {
+ return currentAction;
+ }
+ }
+
+ return null;
+ }
+
+ private static AccessibilityAction getActionSingletonBySerializationFlag(int flag) {
+ final int actions = AccessibilityAction.sStandardActions.size();
+ for (int i = 0; i < actions; i++) {
+ AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+ if (flag == currentAction.mSerializationFlag) {
+ return currentAction;
+ }
+ }
+
+ return null;
+ }
+
+ private void addStandardActions(int serializationIdMask) {
+ int remainingIds = serializationIdMask;
+ while (remainingIds > 0) {
+ final int id = 1 << Integer.numberOfTrailingZeros(remainingIds);
+ remainingIds &= ~id;
+ AccessibilityAction action = getActionSingletonBySerializationFlag(id);
+ addAction(action);
+ }
+ }
+
+ /**
+ * Gets the human readable action symbolic name.
+ *
+ * @param action The action.
+ * @return The symbolic name.
+ */
+ private static String getActionSymbolicName(int action) {
+ switch (action) {
+ case ACTION_FOCUS:
+ return "ACTION_FOCUS";
+ case ACTION_CLEAR_FOCUS:
+ return "ACTION_CLEAR_FOCUS";
+ case ACTION_SELECT:
+ return "ACTION_SELECT";
+ case ACTION_CLEAR_SELECTION:
+ return "ACTION_CLEAR_SELECTION";
+ case ACTION_CLICK:
+ return "ACTION_CLICK";
+ case ACTION_LONG_CLICK:
+ return "ACTION_LONG_CLICK";
+ case ACTION_ACCESSIBILITY_FOCUS:
+ return "ACTION_ACCESSIBILITY_FOCUS";
+ case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return "ACTION_CLEAR_ACCESSIBILITY_FOCUS";
+ case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY";
+ case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY";
+ case ACTION_NEXT_HTML_ELEMENT:
+ return "ACTION_NEXT_HTML_ELEMENT";
+ case ACTION_PREVIOUS_HTML_ELEMENT:
+ return "ACTION_PREVIOUS_HTML_ELEMENT";
+ case ACTION_SCROLL_FORWARD:
+ return "ACTION_SCROLL_FORWARD";
+ case ACTION_SCROLL_BACKWARD:
+ return "ACTION_SCROLL_BACKWARD";
+ case ACTION_CUT:
+ return "ACTION_CUT";
+ case ACTION_COPY:
+ return "ACTION_COPY";
+ case ACTION_PASTE:
+ return "ACTION_PASTE";
+ case ACTION_SET_SELECTION:
+ return "ACTION_SET_SELECTION";
+ case ACTION_EXPAND:
+ return "ACTION_EXPAND";
+ case ACTION_COLLAPSE:
+ return "ACTION_COLLAPSE";
+ case ACTION_DISMISS:
+ return "ACTION_DISMISS";
+ case ACTION_SET_TEXT:
+ return "ACTION_SET_TEXT";
+ case R.id.accessibilityActionShowOnScreen:
+ return "ACTION_SHOW_ON_SCREEN";
+ case R.id.accessibilityActionScrollToPosition:
+ return "ACTION_SCROLL_TO_POSITION";
+ case R.id.accessibilityActionScrollUp:
+ return "ACTION_SCROLL_UP";
+ case R.id.accessibilityActionScrollLeft:
+ return "ACTION_SCROLL_LEFT";
+ case R.id.accessibilityActionScrollDown:
+ return "ACTION_SCROLL_DOWN";
+ case R.id.accessibilityActionScrollRight:
+ return "ACTION_SCROLL_RIGHT";
+ case R.id.accessibilityActionSetProgress:
+ return "ACTION_SET_PROGRESS";
+ case R.id.accessibilityActionContextClick:
+ return "ACTION_CONTEXT_CLICK";
+ default:
+ return "ACTION_UNKNOWN";
+ }
+ }
+
+ /**
+ * Gets the human readable movement granularity symbolic name.
+ *
+ * @param granularity The granularity.
+ * @return The symbolic name.
+ */
+ private static String getMovementGranularitySymbolicName(int granularity) {
+ switch (granularity) {
+ case MOVEMENT_GRANULARITY_CHARACTER:
+ return "MOVEMENT_GRANULARITY_CHARACTER";
+ case MOVEMENT_GRANULARITY_WORD:
+ return "MOVEMENT_GRANULARITY_WORD";
+ case MOVEMENT_GRANULARITY_LINE:
+ return "MOVEMENT_GRANULARITY_LINE";
+ case MOVEMENT_GRANULARITY_PARAGRAPH:
+ return "MOVEMENT_GRANULARITY_PARAGRAPH";
+ case MOVEMENT_GRANULARITY_PAGE:
+ return "MOVEMENT_GRANULARITY_PAGE";
+ default:
+ throw new IllegalArgumentException("Unknown movement granularity: " + granularity);
+ }
+ }
+
+ private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
+ return ((mWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ && (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
+ && (mConnectionId != UNDEFINED_CONNECTION_ID));
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
+ if (getClass() != object.getClass()) {
+ return false;
+ }
+ AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
+ if (mSourceNodeId != other.mSourceNodeId) {
+ return false;
+ }
+ if (mWindowId != other.mWindowId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getAccessibilityViewId(mSourceNodeId);
+ result = prime * result + getVirtualDescendantId(mSourceNodeId);
+ result = prime * result + mWindowId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(super.toString());
+
+ if (DEBUG) {
+ builder.append("; sourceNodeId: " + mSourceNodeId);
+ builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
+ builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
+ builder.append("; mParentNodeId: " + mParentNodeId);
+ builder.append("; traversalBefore: ").append(mTraversalBefore);
+ builder.append("; traversalAfter: ").append(mTraversalAfter);
+
+ int granularities = mMovementGranularities;
+ builder.append("; MovementGranularities: [");
+ while (granularities != 0) {
+ final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
+ granularities &= ~granularity;
+ builder.append(getMovementGranularitySymbolicName(granularity));
+ if (granularities != 0) {
+ builder.append(", ");
+ }
+ }
+ builder.append("]");
+
+ builder.append("; childAccessibilityIds: [");
+ final LongArray childIds = mChildNodeIds;
+ if (childIds != null) {
+ for (int i = 0, count = childIds.size(); i < count; i++) {
+ builder.append(childIds.get(i));
+ if (i < count - 1) {
+ builder.append(", ");
+ }
+ }
+ }
+ builder.append("]");
+ }
+
+ builder.append("; boundsInParent: " + mBoundsInParent);
+ builder.append("; boundsInScreen: " + mBoundsInScreen);
+
+ builder.append("; packageName: ").append(mPackageName);
+ builder.append("; className: ").append(mClassName);
+ builder.append("; text: ").append(mText);
+ builder.append("; error: ").append(mError);
+ builder.append("; maxTextLength: ").append(mMaxTextLength);
+ builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewIdResName: ").append(mViewIdResourceName);
+
+ builder.append("; checkable: ").append(isCheckable());
+ builder.append("; checked: ").append(isChecked());
+ builder.append("; focusable: ").append(isFocusable());
+ builder.append("; focused: ").append(isFocused());
+ builder.append("; selected: ").append(isSelected());
+ builder.append("; clickable: ").append(isClickable());
+ builder.append("; longClickable: ").append(isLongClickable());
+ builder.append("; contextClickable: ").append(isContextClickable());
+ builder.append("; enabled: ").append(isEnabled());
+ builder.append("; password: ").append(isPassword());
+ builder.append("; scrollable: ").append(isScrollable());
+ builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
+ builder.append("; actions: ").append(mActions);
+
+ return builder.toString();
+ }
+
+ private AccessibilityNodeInfo getNodeForAccessibilityId(long accessibilityId) {
+ if (!canPerformRequestOverConnection(accessibilityId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ }
+
+ /**
+ * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
+ * Each action has a unique id that is mandatory and optional data.
+ * <p>
+ * There are three categories of actions:
+ * <ul>
+ * <li><strong>Standard actions</strong> - These are actions that are reported and
+ * handled by the standard UI widgets in the platform. For each standard action
+ * there is a static constant defined in this class, e.g. {@link #ACTION_FOCUS}.
+ * These actions will have {@code null} labels.
+ * </li>
+ * <li><strong>Custom actions action</strong> - These are actions that are reported
+ * and handled by custom widgets. i.e. ones that are not part of the UI toolkit. For
+ * example, an application may define a custom action for clearing the user history.
+ * </li>
+ * <li><strong>Overriden standard actions</strong> - These are actions that override
+ * standard actions to customize them. For example, an app may add a label to the
+ * standard {@link #ACTION_CLICK} action to announce that this action clears browsing history.
+ * </ul>
+ * </p>
+ * <p>
+ * Actions are typically added to an {@link AccessibilityNodeInfo} by using
+ * {@link AccessibilityNodeInfo#addAction(AccessibilityAction)} within
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
+ * within {@link View#performAccessibilityAction(int, Bundle)}.
+ * </p>
+ * <p class="note">
+ * <strong>Note:</strong> Views which support these actions should invoke
+ * {@link View#setImportantForAccessibility(int)} with
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
+ * can discover the set of supported actions.
+ * </p>
+ */
+ public static final class AccessibilityAction {
+
+ /** @hide */
+ public static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>();
+
+ /**
+ * Action that gives input focus to the node.
+ */
+ public static final AccessibilityAction ACTION_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
+
+ /**
+ * Action that clears input focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+
+ /**
+ * Action that selects the node.
+ */
+ public static final AccessibilityAction ACTION_SELECT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SELECT);
+
+ /**
+ * Action that deselects the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_SELECTION =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+ /**
+ * Action that clicks on the node info.
+ */
+ public static final AccessibilityAction ACTION_CLICK =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ /**
+ * Action that long clicks on the node.
+ */
+ public static final AccessibilityAction ACTION_LONG_CLICK =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+
+ /**
+ * Action that requests to go to the next entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+
+ /**
+ * Action that requests to go to the previous entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.setMovementGranularities(int)
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+
+ /**
+ * Action to move to the next HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_NEXT_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_NEXT_HTML_ELEMENT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+
+ /**
+ * Action to move to the previous HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+
+ /**
+ * Action to scroll the node content forward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_FORWARD =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+
+ /**
+ * Action to scroll the node content backward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+
+ /**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_COPY =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_COPY);
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final AccessibilityAction ACTION_PASTE =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_PASTE);
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_CUT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CUT);
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityAction.ACTION_SET_SELECTION.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final AccessibilityAction ACTION_SET_SELECTION =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+
+ /**
+ * Action to expand an expandable node.
+ */
+ public static final AccessibilityAction ACTION_EXPAND =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_EXPAND);
+
+ /**
+ * Action to collapse an expandable node.
+ */
+ public static final AccessibilityAction ACTION_COLLAPSE =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+
+ /**
+ * Action to dismiss a dismissable node.
+ */
+ public static final AccessibilityAction ACTION_DISMISS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_DISMISS);
+
+ /**
+ * Action that sets the text of the node. Performing the action without argument,
+ * using <code> null</code> or empty {@link CharSequence} will clear the text. This
+ * action will also put the cursor at the end of text.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ * "android");
+ * info.performAction(AccessibilityAction.ACTION_SET_TEXT.getId(), arguments);
+ * </code></pre></p>
+ */
+ public static final AccessibilityAction ACTION_SET_TEXT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
+
+ /**
+ * Action that requests the node make its bounding rectangle visible
+ * on the screen, scrolling if necessary just enough.
+ *
+ * @see View#requestRectangleOnScreen(Rect)
+ */
+ public static final AccessibilityAction ACTION_SHOW_ON_SCREEN =
+ new AccessibilityAction(R.id.accessibilityActionShowOnScreen);
+
+ /**
+ * Action that scrolls the node to make the specified collection
+ * position visible on screen.
+ * <p>
+ * <strong>Arguments:</strong>
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
+ * <ul>
+ *
+ * @see AccessibilityNodeInfo#getCollectionInfo()
+ */
+ public static final AccessibilityAction ACTION_SCROLL_TO_POSITION =
+ new AccessibilityAction(R.id.accessibilityActionScrollToPosition);
+
+ /**
+ * Action to scroll the node content up.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_UP =
+ new AccessibilityAction(R.id.accessibilityActionScrollUp);
+
+ /**
+ * Action to scroll the node content left.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_LEFT =
+ new AccessibilityAction(R.id.accessibilityActionScrollLeft);
+
+ /**
+ * Action to scroll the node content down.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_DOWN =
+ new AccessibilityAction(R.id.accessibilityActionScrollDown);
+
+ /**
+ * Action to scroll the node content right.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_RIGHT =
+ new AccessibilityAction(R.id.accessibilityActionScrollRight);
+
+ /**
+ * Action that context clicks the node.
+ */
+ public static final AccessibilityAction ACTION_CONTEXT_CLICK =
+ new AccessibilityAction(R.id.accessibilityActionContextClick);
+
+ /**
+ * Action that sets progress between {@link RangeInfo#getMin() RangeInfo.getMin()} and
+ * {@link RangeInfo#getMax() RangeInfo.getMax()}. It should use the same value type as
+ * {@link RangeInfo#getType() RangeInfo.getType()}
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_PROGRESS_VALUE}
+ *
+ * @see RangeInfo
+ */
+ public static final AccessibilityAction ACTION_SET_PROGRESS =
+ new AccessibilityAction(R.id.accessibilityActionSetProgress);
+
+ /**
+ * Action to move a window to a new location.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_X}
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_Y}
+ */
+ public static final AccessibilityAction ACTION_MOVE_WINDOW =
+ new AccessibilityAction(R.id.accessibilityActionMoveWindow);
+
+ private final int mActionId;
+ private final CharSequence mLabel;
+
+ /** @hide */
+ public int mSerializationFlag = -1;
+
+ /**
+ * Creates a new AccessibilityAction. For adding a standard action without a specific label,
+ * use the static constants.
+ *
+ * You can also override the description for one the standard actions. Below is an example
+ * how to override the standard click action by adding a custom label:
+ * <pre>
+ * AccessibilityAction action = new AccessibilityAction(
+ * AccessibilityAction.ACTION_CLICK.getId(), getLocalizedLabel());
+ * node.addAction(action);
+ * </pre>
+ *
+ * @param actionId The id for this action. This should either be one of the
+ * standard actions or a specific action for your app. In that case it is
+ * required to use a resource identifier.
+ * @param label The label for the new AccessibilityAction.
+ */
+ public AccessibilityAction(int actionId, @Nullable CharSequence label) {
+ if ((actionId & ACTION_TYPE_MASK) == 0 && Integer.bitCount(actionId) != 1) {
+ throw new IllegalArgumentException("Invalid standard action id");
+ }
+
+ mActionId = actionId;
+ mLabel = label;
+ }
+
+ /**
+ * Constructor for a {@link #sStandardActions standard} action
+ */
+ private AccessibilityAction(int standardActionId) {
+ this(standardActionId, null);
+
+ mSerializationFlag = (int) bitAt(sStandardActions.size());
+ sStandardActions.add(this);
+ }
+
+ /**
+ * Gets the id for this action.
+ *
+ * @return The action id.
+ */
+ public int getId() {
+ return mActionId;
+ }
+
+ /**
+ * Gets the label for this action. Its purpose is to describe the
+ * action to user.
+ *
+ * @return The label.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public int hashCode() {
+ return mActionId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (other == this) {
+ return true;
+ }
+
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+
+ return mActionId == ((AccessibilityAction)other).mActionId;
+ }
+
+ @Override
+ public String toString() {
+ return "AccessibilityAction: " + getActionSymbolicName(mActionId) + " - " + mLabel;
+ }
+ }
+
+ /**
+ * Class with information if a node is a range. Use
+ * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. Recycling is
+ * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ */
+ public static final class RangeInfo {
+ private static final int MAX_POOL_SIZE = 10;
+
+ /** Range type: integer. */
+ public static final int RANGE_TYPE_INT = 0;
+ /** Range type: float. */
+ public static final int RANGE_TYPE_FLOAT = 1;
+ /** Range type: percent with values from zero to one.*/
+ public static final int RANGE_TYPE_PERCENT = 2;
+
+ private static final SynchronizedPool<RangeInfo> sPool =
+ new SynchronizedPool<AccessibilityNodeInfo.RangeInfo>(MAX_POOL_SIZE);
+
+ private int mType;
+ private float mMin;
+ private float mMax;
+ private float mCurrent;
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ *
+ * @hide
+ */
+ public static RangeInfo obtain(RangeInfo other) {
+ return obtain(other.mType, other.mMin, other.mMax, other.mCurrent);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param type The type of the range.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
+ * @param current The current value.
+ */
+ public static RangeInfo obtain(int type, float min, float max, float current) {
+ RangeInfo info = sPool.acquire();
+ if (info == null) {
+ return new RangeInfo(type, min, max, current);
+ }
+
+ info.mType = type;
+ info.mMin = min;
+ info.mMax = max;
+ info.mCurrent = current;
+ return info;
+ }
+
+ /**
+ * Creates a new range.
+ *
+ * @param type The type of the range.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
+ * @param current The current value.
+ */
+ private RangeInfo(int type, float min, float max, float current) {
+ mType = type;
+ mMin = min;
+ mMax = max;
+ mCurrent = current;
+ }
+
+ /**
+ * Gets the range type.
+ *
+ * @return The range type.
+ *
+ * @see #RANGE_TYPE_INT
+ * @see #RANGE_TYPE_FLOAT
+ * @see #RANGE_TYPE_PERCENT
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the minimum value.
+ *
+ * @return The minimum value, or {@code Float.NEGATIVE_INFINITY} if no minimum exists.
+ */
+ public float getMin() {
+ return mMin;
+ }
+
+ /**
+ * Gets the maximum value.
+ *
+ * @return The maximum value, or {@code Float.POSITIVE_INFINITY} if no maximum exists.
+ */
+ public float getMax() {
+ return mMax;
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return The current value.
+ */
+ public float getCurrent() {
+ return mCurrent;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mType = 0;
+ mMin = 0;
+ mMax = 0;
+ mCurrent = 0;
+ }
+ }
+
+ /**
+ * Class with information if a node is a collection. Use
+ * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. Recycling is
+ * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ * <p>
+ * A collection of items has rows and columns and may be hierarchical.
+ * For example, a horizontal list is a collection with one column, as
+ * many rows as the list items, and is not hierarchical; A table is a
+ * collection with several rows, several columns, and is not hierarchical;
+ * A vertical tree is a hierarchical collection with one column and
+ * as many rows as the first level children.
+ * </p>
+ */
+ public static final class CollectionInfo {
+ /** Selection mode where items are not selectable. */
+ public static final int SELECTION_MODE_NONE = 0;
+
+ /** Selection mode where a single item may be selected. */
+ public static final int SELECTION_MODE_SINGLE = 1;
+
+ /** Selection mode where multiple items may be selected. */
+ public static final int SELECTION_MODE_MULTIPLE = 2;
+
+ private static final int MAX_POOL_SIZE = 20;
+
+ private static final SynchronizedPool<CollectionInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ private int mRowCount;
+ private int mColumnCount;
+ private boolean mHierarchical;
+ private int mSelectionMode;
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ * @hide
+ */
+ public static CollectionInfo obtain(CollectionInfo other) {
+ return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+ other.mSelectionMode);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical) {
+ return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical, int selectionMode) {
+ final CollectionInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
+ }
+
+ info.mRowCount = rowCount;
+ info.mColumnCount = columnCount;
+ info.mHierarchical = hierarchical;
+ info.mSelectionMode = selectionMode;
+ return info;
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode.
+ */
+ private CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+ int selectionMode) {
+ mRowCount = rowCount;
+ mColumnCount = columnCount;
+ mHierarchical = hierarchical;
+ mSelectionMode = selectionMode;
+ }
+
+ /**
+ * Gets the number of rows.
+ *
+ * @return The row count.
+ */
+ public int getRowCount() {
+ return mRowCount;
+ }
+
+ /**
+ * Gets the number of columns.
+ *
+ * @return The column count.
+ */
+ public int getColumnCount() {
+ return mColumnCount;
+ }
+
+ /**
+ * Gets if the collection is a hierarchically ordered.
+ *
+ * @return Whether the collection is hierarchical.
+ */
+ public boolean isHierarchical() {
+ return mHierarchical;
+ }
+
+ /**
+ * Gets the collection's selection mode.
+ *
+ * @return The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mRowCount = 0;
+ mColumnCount = 0;
+ mHierarchical = false;
+ mSelectionMode = SELECTION_MODE_NONE;
+ }
+ }
+
+ /**
+ * Class with information if a node is a collection item. Use
+ * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
+ * to get an instance. Recycling is handled by the {@link AccessibilityNodeInfo} to which this
+ * object is attached.
+ * <p>
+ * A collection item is contained in a collection, it starts at
+ * a given row and column in the collection, and spans one or
+ * more rows and columns. For example, a header of two related
+ * table columns starts at the first row and the first column,
+ * spans one row and two columns.
+ * </p>
+ */
+ public static final class CollectionItemInfo {
+ private static final int MAX_POOL_SIZE = 20;
+
+ private static final SynchronizedPool<CollectionItemInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ * @hide
+ */
+ public static CollectionItemInfo obtain(CollectionItemInfo other) {
+ return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
+ other.mColumnSpan, other.mHeading, other.mSelected);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading) {
+ return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ * @param selected Whether the item is selected.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading, boolean selected) {
+ final CollectionItemInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionItemInfo(
+ rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+ }
+
+ info.mRowIndex = rowIndex;
+ info.mRowSpan = rowSpan;
+ info.mColumnIndex = columnIndex;
+ info.mColumnSpan = columnSpan;
+ info.mHeading = heading;
+ info.mSelected = selected;
+ return info;
+ }
+
+ private boolean mHeading;
+ private int mColumnIndex;
+ private int mRowIndex;
+ private int mColumnSpan;
+ private int mRowSpan;
+ private boolean mSelected;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ */
+ private CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+ boolean heading, boolean selected) {
+ mRowIndex = rowIndex;
+ mRowSpan = rowSpan;
+ mColumnIndex = columnIndex;
+ mColumnSpan = columnSpan;
+ mHeading = heading;
+ mSelected = selected;
+ }
+
+ /**
+ * Gets the column index at which the item is located.
+ *
+ * @return The column index.
+ */
+ public int getColumnIndex() {
+ return mColumnIndex;
+ }
+
+ /**
+ * Gets the row index at which the item is located.
+ *
+ * @return The row index.
+ */
+ public int getRowIndex() {
+ return mRowIndex;
+ }
+
+ /**
+ * Gets the number of columns the item spans.
+ *
+ * @return The column span.
+ */
+ public int getColumnSpan() {
+ return mColumnSpan;
+ }
+
+ /**
+ * Gets the number of rows the item spans.
+ *
+ * @return The row span.
+ */
+ public int getRowSpan() {
+ return mRowSpan;
+ }
+
+ /**
+ * Gets if the collection item is a heading. For example, section
+ * heading, table header, etc.
+ *
+ * @return If the item is a heading.
+ */
+ public boolean isHeading() {
+ return mHeading;
+ }
+
+ /**
+ * Gets if the collection item is selected.
+ *
+ * @return If the item is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mColumnIndex = 0;
+ mColumnSpan = 0;
+ mRowIndex = 0;
+ mRowSpan = 0;
+ mHeading = false;
+ mSelected = false;
+ }
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityNodeInfo>() {
+ @Override
+ public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ @Override
+ public AccessibilityNodeInfo[] newArray(int size) {
+ return new AccessibilityNodeInfo[size];
+ }
+ };
+}