/* * 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 android.accessibilityservice.AccessibilityServiceInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.InputType; import android.util.Pools.SynchronizedPool; import android.util.SparseLongArray; import android.view.View; import java.util.Collections; import java.util.List; /** * 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 content is * presented as tree of accessibility node info 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. *

*

* Once an accessibility node info is delivered to an accessibility service it is * made immutable and calling a state mutation method generates an error. *

*

* 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 familiarizing with the security model. *

*
*

Developer Guides

*

For more information about making applications accessible, read the * Accessibility * developer guide.

*
* * @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 = -1; /** @hide */ public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED); /** @hide */ public static final int ACTIVE_WINDOW_ID = UNDEFINED; /** @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 unselects the node. */ public static final int ACTION_CLEAR_SELECTION = 0x00000008; /** * Action that clicks on the node info. */ 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. *

* Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}
* Example: Move to the previous character and do not extend selection. *

* 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); *

*

* * @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. *

* Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}
* Example: Move to the next character and do not extend selection. *

* 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); *

*

* * @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. *

* Arguments: {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}
* Example: *

* Bundle arguments = new Bundle(); * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments); *

*

*/ 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. *

* Arguments: {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}
* Example: *

* Bundle arguments = new Bundle(); * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments); *

*

*/ 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. *

* Arguments: {@link #ACTION_ARGUMENT_SELECTION_START_INT}, * {@link #ACTION_ARGUMENT_SELECTION_END_INT}
* Example: *

* 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); *

*

* * @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 arguments /** * Argument for which movement granularity to be used when traversing the node text. *

* Type: int
* Actions: {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} *

* * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY * @see #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. *

* Type: String
* Actions: {@link #ACTION_NEXT_HTML_ELEMENT}, * {@link #ACTION_PREVIOUS_HTML_ELEMENT} *

* * @see #ACTION_NEXT_HTML_ELEMENT * @see #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. *

* Type: boolean
* Actions: {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} *

* * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY * @see #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. *

* Type: int
* Actions: {@link #ACTION_SET_SELECTION} *

* * @see #ACTION_SET_SELECTION */ public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT"; /** * Argument for specifying the selection end. *

* Type: int
* Actions: {@link #ACTION_SET_SELECTION} *

* * @see #ACTION_SET_SELECTION */ public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; // 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; // 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; /** * 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; /** * 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 virtualDescendantId * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking * the bitwise or with the accessibilityViewId. * * @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 sPool = new SynchronizedPool(MAX_POOL_SIZE); private boolean mSealed; // Data. private int mWindowId = UNDEFINED; private long mSourceNodeId = ROOT_NODE_ID; private long mParentNodeId = ROOT_NODE_ID; private long mLabelForId = ROOT_NODE_ID; private long mLabeledById = ROOT_NODE_ID; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); private final Rect mBoundsInScreen = new Rect(); private CharSequence mPackageName; private CharSequence mClassName; private CharSequence mText; private CharSequence mContentDescription; private String mViewIdResourceName; private final SparseLongArray mChildNodeIds = new SparseLongArray(); private int mActions; private int mMovementGranularities; private int mTextSelectionStart = UNDEFINED; private int mTextSelectionEnd = UNDEFINED; private int mInputType = InputType.TYPE_NULL; private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; private Bundle mExtras; private int mConnectionId = UNDEFINED; private RangeInfo mRangeInfo; private CollectionInfo mCollectionInfo; private CollectionItemInfo mCollectionItemInfo; /** * Hide constructor from clients. */ private AccessibilityNodeInfo() { /* do nothing */ } /** * Sets the source. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param source The info source. */ public void setSource(View source) { setSource(source, UNDEFINED); } /** * Sets the source to be a virtual descendant of the given root. * If virtualDescendantId is {@link View#NO_ID} the root * is set as the source. *

* 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. *

*

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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; final int rootAccessibilityViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 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. *

* Note: 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. *

* * @param bypassCache Whether to bypass the cache. * @return Whether the refresh succeeded. * * @hide */ public boolean refresh(boolean bypassCache) { enforceSealed(); if (!canPerformRequestOverConnection(mSourceNodeId)) { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId( mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0); if (refreshedInfo == null) { return false; } init(refreshedInfo); refreshedInfo.recycle(); return true; } /** * Refreshes this info with the latest state of the view it represents. *

* Note: 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. *

* @return Whether the refresh succeeded. */ public boolean refresh() { return refresh(false); } /** * @return The ids of the children. * * @hide */ public SparseLongArray getChildNodeIds() { return mChildNodeIds; } /** * Gets the number of children. * * @return The child count. */ public int getChildCount() { return mChildNodeIds.size(); } /** * Get the child at given index. *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* * @param index The child index. * @return The child node. * * @throws IllegalStateException If called outside of an AccessibilityService. * */ public AccessibilityNodeInfo getChild(int index) { enforceSealed(); 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); } /** * Adds a child. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param child The child. * * @throws IllegalStateException If called from an AccessibilityService. */ public void addChild(View child) { addChild(child, UNDEFINED); } /** * Adds a virtual child which is a descendant of the given root. * If virtualDescendantId is {@link View#NO_ID} the root * is added as a child. *

* 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. *

* * @param root The root of the virtual subtree. * @param virtualDescendantId The id of the virtual child. */ public void addChild(View root, int virtualDescendantId) { enforceNotSealed(); final int index = mChildNodeIds.size(); final int rootAccessibilityViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED; final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); mChildNodeIds.put(index, childNodeId); } /** * 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 */ public int getActions() { return mActions; } /** * Adds an action that can be performed on the node. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param action The action. * * @throws IllegalStateException If called from an AccessibilityService. */ public void addAction(int action) { enforceNotSealed(); mActions |= action; } /** * Sets the movement granularities for traversing the text of this node. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: An action can be performed only if the request is made * from an {@link android.accessibilityservice.AccessibilityService}. *

* * @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. *

* Note: An action can be performed only if the request is made * from an {@link android.accessibilityservice.AccessibilityService}. *

* * @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. * *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* * @param text The searched text. * @return A list of node info. */ public List 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". * *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

*

* Note: 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}. *

* * @param viewId The fully qualified resource name of the view id to find. * @return A list of node info. */ public List findAccessibilityNodeInfosByViewId(String viewId) { enforceSealed(); if (!canPerformRequestOverConnection(mSourceNodeId)) { return Collections.emptyList(); } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId, viewId); } /** * Gets the parent. *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* * @return The parent. */ public AccessibilityNodeInfo getParent() { enforceSealed(); if (!canPerformRequestOverConnection(mParentNodeId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, mParentNodeId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** * @return The parent node id. * * @hide */ public long getParentNodeId() { return mParentNodeId; } /** * Sets the parent. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param parent The parent. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setParent(View parent) { setParent(parent, UNDEFINED); } /** * Sets the parent to be a virtual descendant of the given root. * If virtualDescendantId equals to {@link View#NO_ID} the root * is set as the parent. *

* 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. *

*

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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; 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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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); } /** * Sets the node bounds in screen coordinates. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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); } /** * Sets 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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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); } /** * 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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @return collectionItem True if the node is an item. */ 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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param contentInvalid If the node content is invalid. */ public void setContentInvalid(boolean contentInvalid) { setBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID, contentInvalid); } /** * Gets the node's live region mode. *

* 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}. *

* 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. *

* Note: 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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param dismissable If the node can be dismissed. */ public void setDismissable(boolean dismissable) { setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable); } /** * Gets the package this node comes from. * * @return The package name. */ public CharSequence getPackageName() { return mPackageName; } /** * Sets the package this node comes from. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. * * @return The text. */ public CharSequence getText() { return mText; } /** * Sets the text of this node. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param text The text. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setText(CharSequence text) { enforceNotSealed(); mText = text; } /** * Gets the content description of this node. * * @return The content description. */ public CharSequence getContentDescription() { return mContentDescription; } /** * Sets the content description of this node. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @param contentDescription The content description. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setContentDescription(CharSequence contentDescription) { enforceNotSealed(); mContentDescription = contentDescription; } /** * 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, UNDEFINED); } /** * Sets the view for which the view represented by this info serves as a * label for accessibility purposes. If virtualDescendantId * is {@link View#NO_ID} the root is set as the labeled. *

* 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. *

*

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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; mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } /** * Gets the node info for which the view represented by this info serves as * a label for accessibility purposes. *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* * @return The labeled info. */ public AccessibilityNodeInfo getLabelFor() { enforceSealed(); if (!canPerformRequestOverConnection(mLabelForId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, mLabelForId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** * 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, UNDEFINED); } /** * Sets the view which serves as the label of the view represented by * this info for accessibility purposes. If virtualDescendantId * is {@link View#NO_ID} the root is set as the label. *

* 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. *

*

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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; mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } /** * Gets the node info which serves as the label of the view represented by * this info for accessibility purposes. *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* * @return The label. */ public AccessibilityNodeInfo getLabeledBy() { enforceSealed(); if (!canPerformRequestOverConnection(mLabeledById)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, mLabeledById, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** * Sets the fully qualified resource name of the source view's id. * *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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. * *

* Note: 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}. *

* @return The id resource name. */ public String getViewIdResourceName() { return mViewIdResourceName; } /** * Gets the text selection start. * * @return The text selection start if there is selection or -1. */ public int getTextSelectionStart() { return mTextSelectionStart; } /** * Gets the text selection end. * * @return The text selection end if there is selection or -1. */ public int getTextSelectionEnd() { return mTextSelectionEnd; } /** * Sets the text selection start and end. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an AccessibilityService. *

* * @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}. *

* Note: Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. * This class is made immutable before being delivered to an * AccessibilityService. *

* * @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 null. *

* Note: 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. *

* * @return The bundle. */ public Bundle getExtras() { if (mExtras == null) { mExtras = new Bundle(); } return mExtras; } /** * 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; } /** * {@inheritDoc} */ public int describeContents() { return 0; } /** * 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(); 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 * info. * * @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. *

* Note: 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); } /** * {@inheritDoc} *

* Note: After the instance is written to a parcel it * is recycled. You must not touch the object after calling this function. *

*/ public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(isSealed() ? 1 : 0); parcel.writeLong(mSourceNodeId); parcel.writeInt(mWindowId); parcel.writeLong(mParentNodeId); parcel.writeLong(mLabelForId); parcel.writeLong(mLabeledById); parcel.writeInt(mConnectionId); SparseLongArray childIds = mChildNodeIds; final int childIdsSize = childIds.size(); parcel.writeInt(childIdsSize); for (int i = 0; i < childIdsSize; i++) { parcel.writeLong(childIds.valueAt(i)); } parcel.writeInt(mBoundsInParent.top); parcel.writeInt(mBoundsInParent.bottom); parcel.writeInt(mBoundsInParent.left); parcel.writeInt(mBoundsInParent.right); parcel.writeInt(mBoundsInScreen.top); parcel.writeInt(mBoundsInScreen.bottom); parcel.writeInt(mBoundsInScreen.left); parcel.writeInt(mBoundsInScreen.right); parcel.writeInt(mActions); parcel.writeInt(mMovementGranularities); parcel.writeInt(mBooleanProperties); parcel.writeCharSequence(mPackageName); parcel.writeCharSequence(mClassName); parcel.writeCharSequence(mText); parcel.writeCharSequence(mContentDescription); parcel.writeString(mViewIdResourceName); parcel.writeInt(mTextSelectionStart); parcel.writeInt(mTextSelectionEnd); parcel.writeInt(mInputType); parcel.writeInt(mLiveRegion); if (mExtras != null) { parcel.writeInt(1); parcel.writeBundle(mExtras); } else { parcel.writeInt(0); } if (mRangeInfo != null) { parcel.writeInt(1); parcel.writeInt(mRangeInfo.getType()); parcel.writeFloat(mRangeInfo.getMin()); parcel.writeFloat(mRangeInfo.getMax()); parcel.writeFloat(mRangeInfo.getCurrent()); } else { parcel.writeInt(0); } if (mCollectionInfo != null) { parcel.writeInt(1); parcel.writeInt(mCollectionInfo.getRowCount()); parcel.writeInt(mCollectionInfo.getColumnCount()); parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0); } else { parcel.writeInt(0); } if (mCollectionItemInfo != null) { parcel.writeInt(1); parcel.writeInt(mCollectionItemInfo.getColumnIndex()); parcel.writeInt(mCollectionItemInfo.getColumnSpan()); parcel.writeInt(mCollectionItemInfo.getRowIndex()); parcel.writeInt(mCollectionItemInfo.getRowSpan()); parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0); } else { parcel.writeInt(0); } // 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; mWindowId = other.mWindowId; mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); mBoundsInScreen.set(other.mBoundsInScreen); mPackageName = other.mPackageName; mClassName = other.mClassName; mText = other.mText; mContentDescription = other.mContentDescription; mViewIdResourceName = other.mViewIdResourceName; mActions= other.mActions; mBooleanProperties = other.mBooleanProperties; mMovementGranularities = other.mMovementGranularities; final int otherChildIdCount = other.mChildNodeIds.size(); for (int i = 0; i < otherChildIdCount; i++) { mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); } mTextSelectionStart = other.mTextSelectionStart; mTextSelectionEnd = other.mTextSelectionEnd; mInputType = other.mInputType; mLiveRegion = other.mLiveRegion; if (other.mExtras != null && !other.mExtras.isEmpty()) { getExtras().putAll(other.mExtras); } mRangeInfo = (other.mRangeInfo != null) ? RangeInfo.obtain(other.mRangeInfo) : null; mCollectionInfo = (other.mCollectionInfo != null) ? CollectionInfo.obtain(other.mCollectionInfo) : null; 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) { mSealed = (parcel.readInt() == 1); mSourceNodeId = parcel.readLong(); mWindowId = parcel.readInt(); mParentNodeId = parcel.readLong(); mLabelForId = parcel.readLong(); mLabeledById = parcel.readLong(); mConnectionId = parcel.readInt(); SparseLongArray childIds = mChildNodeIds; final int childrenSize = parcel.readInt(); for (int i = 0; i < childrenSize; i++) { final long childId = parcel.readLong(); childIds.put(i, childId); } mBoundsInParent.top = parcel.readInt(); mBoundsInParent.bottom = parcel.readInt(); mBoundsInParent.left = parcel.readInt(); mBoundsInParent.right = parcel.readInt(); mBoundsInScreen.top = parcel.readInt(); mBoundsInScreen.bottom = parcel.readInt(); mBoundsInScreen.left = parcel.readInt(); mBoundsInScreen.right = parcel.readInt(); mActions = parcel.readInt(); mMovementGranularities = parcel.readInt(); mBooleanProperties = parcel.readInt(); mPackageName = parcel.readCharSequence(); mClassName = parcel.readCharSequence(); mText = parcel.readCharSequence(); mContentDescription = parcel.readCharSequence(); mViewIdResourceName = parcel.readString(); mTextSelectionStart = parcel.readInt(); mTextSelectionEnd = parcel.readInt(); mInputType = parcel.readInt(); mLiveRegion = parcel.readInt(); if (parcel.readInt() == 1) { getExtras().putAll(parcel.readBundle()); } if (parcel.readInt() == 1) { mRangeInfo = RangeInfo.obtain( parcel.readInt(), parcel.readFloat(), parcel.readFloat(), parcel.readFloat()); } if (parcel.readInt() == 1) { mCollectionInfo = CollectionInfo.obtain( parcel.readInt(), parcel.readInt(), parcel.readInt() == 1); } if (parcel.readInt() == 1) { mCollectionItemInfo = CollectionItemInfo.obtain( parcel.readInt(), parcel.readInt(), parcel.readInt(), parcel.readInt(), parcel.readInt() == 1); } } /** * Clears the state of this instance. */ private void clear() { mSealed = false; mSourceNodeId = ROOT_NODE_ID; mParentNodeId = ROOT_NODE_ID; mLabelForId = ROOT_NODE_ID; mLabeledById = ROOT_NODE_ID; mWindowId = UNDEFINED; mConnectionId = UNDEFINED; mMovementGranularities = 0; mChildNodeIds.clear(); mBoundsInParent.set(0, 0, 0, 0); mBoundsInScreen.set(0, 0, 0, 0); mBooleanProperties = 0; mPackageName = null; mClassName = null; mText = null; mContentDescription = null; mViewIdResourceName = null; mActions = 0; mTextSelectionStart = UNDEFINED; mTextSelectionEnd = UNDEFINED; mInputType = InputType.TYPE_NULL; mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; if (mExtras != null) { mExtras.clear(); } if (mRangeInfo != null) { mRangeInfo.recycle(); mRangeInfo = null; } if (mCollectionInfo != null) { mCollectionInfo.recycle(); mCollectionInfo = null; } if (mCollectionItemInfo != null) { mCollectionItemInfo.recycle(); mCollectionItemInfo = null; } } /** * 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"; 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 != UNDEFINED && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED && mConnectionId != UNDEFINED); } @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("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); 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("]"); SparseLongArray childIds = mChildNodeIds; builder.append("; childAccessibilityIds: ["); for (int i = 0, count = childIds.size(); i < count; i++) { builder.append(childIds.valueAt(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("; 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("; enabled: ").append(isEnabled()); builder.append("; password: ").append(isPassword()); builder.append("; scrollable: " + isScrollable()); builder.append("; ["); for (int actionBits = mActions; actionBits != 0;) { final int action = 1 << Integer.numberOfTrailingZeros(actionBits); actionBits &= ~action; builder.append(getActionSymbolicName(action)); if (actionBits != 0) { builder.append(", "); } } builder.append("]"); return builder.toString(); } /** * Class with information if a node is a range. Use * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. */ 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 sPool = new SynchronizedPool(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 min value. * @param max The max value. * @param current The current value. */ public static RangeInfo obtain(int type, float min, float max, float current) { RangeInfo info = sPool.acquire(); return (info != null) ? info : new RangeInfo(type, min, max, current); } /** * Creates a new range. * * @param type The type of the range. * @param min The min value. * @param max The max value. * @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 min value. * * @return The min value. */ public float getMin() { return mMin; } /** * Gets the max value. * * @return The max value. */ 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. *

* 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. *

*/ public static final class CollectionInfo { private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool sPool = new SynchronizedPool(MAX_POOL_SIZE); private int mRowCount; private int mColumnCount; private boolean mHierarchical; /** * 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); } /** * 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) { CollectionInfo info = sPool.acquire(); return (info != null) ? info : new CollectionInfo(rowCount, columnCount, hierarchical); } /** * Creates a new instance. * * @param rowCount The number of rows. * @param columnCount The number of columns. * @param hierarchical Whether the collection is hierarchical. */ private CollectionInfo(int rowCount, int columnCount, boolean hierarchical) { mRowCount = rowCount; mColumnCount = columnCount; mHierarchical = hierarchical; } /** * 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; } /** * Recycles this instance. */ void recycle() { clear(); sPool.release(this); } private void clear() { mRowCount = 0; mColumnCount = 0; mHierarchical = false; } } /** * Class with information if a node is a collection item. Use * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)} * to get an instance. *

* 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. *

*/ public static final class CollectionItemInfo { private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool 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); } /** * 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) { CollectionItemInfo info = sPool.acquire(); return (info != null) ? info : new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading); } private boolean mHeading; private int mColumnIndex; private int mRowIndex; private int mColumnSpan; private int mRowSpan; /** * 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) { mRowIndex = rowIndex; mRowSpan = rowSpan; mColumnIndex = columnIndex; mColumnSpan = columnSpan; mHeading = heading; } /** * 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; } /** * Recycles this instance. */ void recycle() { clear(); sPool.release(this); } private void clear() { mColumnIndex = 0; mColumnSpan = 0; mRowIndex = 0; mRowSpan = 0; mHeading = false; } } /** * @see Parcelable.Creator */ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public AccessibilityNodeInfo createFromParcel(Parcel parcel) { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); info.initFromParcel(parcel); return info; } public AccessibilityNodeInfo[] newArray(int size) { return new AccessibilityNodeInfo[size]; } }; }