blob: 8bc7f3c91d8c53223d717ea226f0fc9a9567f66b [file] [log] [blame]
/*
* 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.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
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.AccessibilityReplacementSpan;
import android.text.style.AccessibilityURLSpan;
import android.text.style.ClickableSpan;
import android.text.style.ReplacementSpan;
import android.text.style.URLSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
import android.util.Size;
import android.util.TypedValue;
import android.view.SurfaceView;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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 String TAG = "AccessibilityNodeInfo";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) && Build.IS_DEBUGGABLE;
/** @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 int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
/** @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 long LEASHED_NODE_ID = makeNodeId(LEASHED_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_PREFETCH_MASK = 0x00000007;
/** @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";
/**
* Argument to represent the duration in milliseconds to press and hold a node.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong>
* <ul>
* <li>{@link AccessibilityAction#ACTION_PRESS_AND_HOLD}</li>
* </ul>
*
* @see AccessibilityAction#ACTION_PRESS_AND_HOLD
*/
public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT =
"android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_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;
/**
* 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";
/**
* Key used to request extra data for the rendering information.
* The key requests that a {@link AccessibilityNodeInfo.ExtraRenderingInfo} be added to this
* info. This request is made with {@link #refreshWithExtraData(String, Bundle)} without
* argument.
* <p>
* The data can be retrieved from the {@link ExtraRenderingInfo} returned by
* {@link #getExtraRenderingInfo()} using {@link ExtraRenderingInfo#getLayoutSize},
* {@link ExtraRenderingInfo#getTextSizeInPx()} and
* {@link ExtraRenderingInfo#getTextSizeUnit()}. For layout params, it is supported by both
* {@link TextView} and {@link ViewGroup}. For text size and unit, it is only supported by
* {@link TextView}.
*
* @see #refreshWithExtraData(String, Bundle)
*/
public static final String EXTRA_DATA_RENDERING_INFO_KEY =
"android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
/** @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_SCREEN_READER_FOCUSABLE = 0x0080000;
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
/**
* 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;
// TODO(b/129300068): Remove sNumInstancesInUse.
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
*/
@UnsupportedAppUsage
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
*/
@UnsupportedAppUsage
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();
@UnsupportedAppUsage
private boolean mSealed;
// Data.
private int mWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@UnsupportedAppUsage
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 mPaneTitle;
private CharSequence mStateDescription;
private CharSequence mContentDescription;
private CharSequence mTooltipText;
private String mViewIdResourceName;
private ArrayList<String> mExtraDataKeys;
@UnsupportedAppUsage
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;
private TouchDelegateInfo mTouchDelegateInfo;
private ExtraRenderingInfo mExtraRenderingInfo;
private IBinder mLeashedChild;
private IBinder mLeashedParent;
private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
/**
* Creates a new {@link AccessibilityNodeInfo}.
*/
public AccessibilityNodeInfo() {
}
/**
* Creates a new {@link AccessibilityNodeInfo} with the given <code>source</code>.
*
* @param source The source view.
*/
public AccessibilityNodeInfo(@NonNull View source) {
setSource(source);
}
/**
* Creates a new {@link AccessibilityNodeInfo} with the given <code>source</code>.
*
* @param root The root of the virtual subtree.
* @param virtualDescendantId The id of the virtual descendant.
*/
public AccessibilityNodeInfo(@NonNull View root, int virtualDescendantId) {
setSource(root, virtualDescendantId);
}
/**
* Copy constructor. Creates a new {@link AccessibilityNodeInfo}, and this new instance is
* initialized from the given <code>info</code>.
*
* @param info The other info.
*/
public AccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
init(info, false /* usePoolingInfo */);
}
/**
* 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.
*
* <p>
* <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
* view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
* this API won't be able to find the node for the view on the embedded view hierarchy. It's
* because views don't know about the embedded hierarchies. Instead, you could traverse all
* the children to find the node. Or, use {@link AccessibilityService#findFocus(int)} for
* {@link #FOCUS_ACCESSIBILITY} only since it has no such limitation.
* </p>
*
* @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(mConnectionId, mWindowId, 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.
*
* <p>
* <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
* view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
* this API won't be able to find the node for the view in the specified direction on the
* embedded view hierarchy. It's because views don't know about the embedded hierarchies.
* Instead, you could traverse all the children to find the node.
* </p>
*
* @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(mConnectionId, mWindowId, 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
*/
@UnsupportedAppUsage
public boolean refresh(Bundle arguments, boolean bypassCache) {
enforceSealed();
if (!canPerformRequestOverConnection(mConnectionId, mWindowId, 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, true /* usePoolingInfo */);
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 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_RENDERING_INFO_KEY},
* {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY},
* {@link #getAvailableExtraData()} and {@link #getExtraRenderingInfo()}.
* @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(mConnectionId, mWindowId, mSourceNodeId)) {
return null;
}
final long childId = mChildNodeIds.get(index);
final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
}
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.
* Note that a view cannot be made its own child.
* </p>
*
* @param child The child.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void addChild(View child) {
addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);
}
/**
* Adds a view root from leashed content as a child. This method is used to embedded another
* view hierarchy.
* <p>
* <strong>Note:</strong> Only one leashed child is permitted.
* </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.
* Note that a view cannot be made its own child.
* </p>
*
* @param token The token to which a view root is added.
*
* @throws IllegalStateException If called from an AccessibilityService.
* @hide
*/
@TestApi
public void addChild(@NonNull IBinder token) {
enforceNotSealed();
if (token == null) {
return;
}
if (mChildNodeIds == null) {
mChildNodeIds = new LongArray();
}
mLeashedChild = token;
// Checking uniqueness.
// Since only one leashed child is permitted, skip adding ID if the ID already exists.
if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
return;
}
mChildNodeIds.add(LEASHED_NODE_ID);
}
/**
* 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);
}
/**
* Removes a leashed 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 token The token of the leashed child
* @return true if the child was present
*
* @throws IllegalStateException If called from an AccessibilityService.
* @hide
*/
public boolean removeChild(IBinder token) {
enforceNotSealed();
if (mChildNodeIds == null || mLeashedChild == null) {
return false;
}
if (!mLeashedChild.equals(token)) {
return false;
}
final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
mLeashedChild = null;
if (index < 0) {
return false;
}
mChildNodeIds.remove(index);
return true;
}
/**
* 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.
* Note that a view cannot be made its own child.
* </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 (childNodeId == mSourceNodeId) {
Log.e(TAG, "Rejecting attempt to make a View its own child");
return;
}
// 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(mConnectionId, mWindowId, 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(mConnectionId, mWindowId, 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_RENDERING_INFO_KEY
* @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(mConnectionId, mWindowId, mSourceNodeId)) {
return false;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
Bundle arguments = null;
if (mExtras != null) {
arguments = mExtras;
}
return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
action, arguments);
}
/**
* 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(mConnectionId, mWindowId, 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>
* <p>
* <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
* view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
* this API won't be able to find the node for the view on the embedded view hierarchy. It's
* because views don't know about the embedded hierarchies. Instead, you could traverse all
* the children to find the node.
* </p>
*
* @param text The searched text.
* @return A list of node info.
*/
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
enforceSealed();
if (!canPerformRequestOverConnection(mConnectionId, mWindowId, 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 the {@link android.accessibilityservice.AccessibilityService}.
* </p>
* <p>
* <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
* view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
* this API won't be able to find the node for the view on the embedded view hierarchy. It's
* because views don't know about the embedded hierarchies. Instead, you could traverse all
* the children to find the node.
* </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(mConnectionId, mWindowId, 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(mConnectionId, mWindowId, 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();
if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
}
return getNodeForAccessibilityId(mConnectionId, mWindowId, 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 the viewParent's coordinates.
* {@link #getParent()} does not represent the source's viewParent.
* Instead it represents the result of {@link View#getParentForAccessibility()},
* which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
* So this method is not reliable.
* <p>
* When magnification is enabled, the bounds in parent are also scaled up by magnification
* scale. For example, it returns Rect(20, 20, 200, 200) for original bounds
* Rect(10, 10, 100, 100), when the magnification scale is 2.
* <p/>
*
* @param outBounds The output node bounds.
* @deprecated Use {@link #getBoundsInScreen(Rect)} instead.
*
*/
@Deprecated
public void getBoundsInParent(Rect outBounds) {
outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
mBoundsInParent.right, mBoundsInParent.bottom);
}
/**
* Sets the node bounds in the viewParent's coordinates.
* {@link #getParent()} does not represent the source's viewParent.
* Instead it represents the result of {@link View#getParentForAccessibility()},
* which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
* So this method is not reliable.
*
* <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.
* @deprecated Accessibility services should not care about these bounds.
*/
@Deprecated
public void setBoundsInParent(Rect bounds) {
enforceNotSealed();
mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
/**
* Gets the node bounds in screen coordinates.
* <p>
* When magnification is enabled, the bounds in screen are scaled up by magnification scale
* and the positions are also adjusted according to the offset of magnification viewport.
* For example, it returns Rect(-180, -180, 0, 0) for original bounds Rect(10, 10, 100, 100),
* when the magnification scale is 2 and offsets for X and Y are both 200.
* <p/>
*
* @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.
* <p>
* Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
* {@link Build.VERSION_CODES#Q API 29}, this method may incorrectly return false when
* magnification is enabled. On other versions, a node is considered visible even if it is not
* on the screen because magnification is active.
* </p>
*
* @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);
}
/**
* If this node represents a visually distinct region of the screen that may update separately
* from the rest of the window, it is considered a pane. Set the pane title to indicate that
* the node is a pane, and to provide a title for it.
* <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 paneTitle The title of the pane represented by this node.
*/
public void setPaneTitle(@Nullable CharSequence paneTitle) {
enforceNotSealed();
mPaneTitle = (paneTitle == null)
? null : paneTitle.subSequence(0, paneTitle.length());
}
/**
* Get the title of the pane represented by this node.
*
* @return The title of the pane represented by this node, or {@code null} if this node does
* not represent a pane.
*/
public @Nullable CharSequence getPaneTitle() {
return mPaneTitle;
}
/**
* 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 the {@link ExtraRenderingInfo extra rendering info} if the node is meant to be
* refreshed with extra data to examine rendering related accessibility issues.
*
* @return The {@link ExtraRenderingInfo extra rendering info}.
*
* @see #EXTRA_DATA_RENDERING_INFO_KEY
* @see #refreshWithExtraData(String, Bundle)
*/
@Nullable
public ExtraRenderingInfo getExtraRenderingInfo() {
return mExtraRenderingInfo;
}
/**
* Sets the extra rendering info, <code>extraRenderingInfo<code/>, if the node is meant to be
* refreshed with extra data.
* <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 extraRenderingInfo The {@link ExtraRenderingInfo extra rendering info}.
* @hide
*/
public void setExtraRenderingInfo(@NonNull ExtraRenderingInfo extraRenderingInfo) {
enforceNotSealed();
mExtraRenderingInfo = extraRenderingInfo;
}
/**
* 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 is explicitly marked as a focusable unit by a screen reader. Note
* that {@code false} indicates that it is not explicitly marked, not that the node is not
* a focusable unit. Screen readers should generally use other signals, such as
* {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
* focus.
*
* @return {@code true} if the node is specifically marked as a focusable unit for screen
* readers, {@code false} otherwise.
*
* @see View#isScreenReaderFocusable()
*/
public boolean isScreenReaderFocusable() {
return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE);
}
/**
* Sets whether the node should be considered a focusable unit by a screen reader.
* <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 screenReaderFocusable {@code true} if the node is a focusable unit for screen readers,
* {@code false} otherwise.
*/
public void setScreenReaderFocusable(boolean screenReaderFocusable) {
setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable);
}
/**
* 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);
}
/**
* Returns whether node represents a heading.
* <p><strong>Note:</strong> Returns {@code true} if either {@link #setHeading(boolean)}
* marks this node as a heading or if the node has a {@link CollectionItemInfo} that marks
* it as such, to accomodate apps that use the now-deprecated API.</p>
*
* @return {@code true} if the node is a heading, {@code false} otherwise.
*/
public boolean isHeading() {
if (getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING)) return true;
CollectionItemInfo itemInfo = getCollectionItemInfo();
return ((itemInfo != null) && itemInfo.mHeading);
}
/**
* Sets whether the node represents a heading.
*
* <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 isHeading {@code true} if the node is a heading, {@code false} otherwise.
*/
public void setHeading(boolean isHeading) {
setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading);
}
/**
* Returns whether node represents a text entry key that is part of a keyboard or keypad.
*
* @return {@code true} if the node is a text entry key., {@code false} otherwise.
*/
public boolean isTextEntryKey() {
return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY);
}
/**
* Sets whether the node represents a text entry key that is part of a keyboard or keypad.
*
* <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 isTextEntryKey {@code true} if the node is a text entry key, {@code false} otherwise.
*/
public void setTextEntryKey(boolean isTextEntryKey) {
setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY, isTextEntryKey);
}
/**
* 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;
if (text instanceof Spanned) {
CharSequence tmpText = text;
tmpText = replaceClickableSpan(tmpText);
tmpText = replaceReplacementSpan(tmpText);
mText = tmpText;
return;
}
mText = (text == null) ? null : text.subSequence(0, text.length());
}
/**
* Replaces any ClickableSpan in the given {@code text} with placeholders.
*
* @param text The text.
*
* @return The spannable with ClickableSpan replacement.
*/
private CharSequence replaceClickableSpan(CharSequence text) {
ClickableSpan[] clickableSpans =
((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
Spannable spannable = new SpannableStringBuilder(text);
if (clickableSpans.length == 0) {
return text;
}
for (int i = 0; i < clickableSpans.length; i++) {
ClickableSpan span = clickableSpans[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);
}
return spannable;
}
/**
* Replaces any ReplacementSpan in the given {@code text} if the object has content description.
*
* @param text The text.
*
* @return The spannable with ReplacementSpan replacement.
*/
private CharSequence replaceReplacementSpan(CharSequence text) {
ReplacementSpan[] replacementSpans =
((Spanned) text).getSpans(0, text.length(), ReplacementSpan.class);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
if (replacementSpans.length == 0) {
return text;
}
for (int i = 0; i < replacementSpans.length; i++) {
ReplacementSpan span = replacementSpans[i];
CharSequence replacementText = span.getContentDescription();
if (span instanceof AccessibilityReplacementSpan) {
// We've already done enough
break;
}
if (replacementText == null) {
continue;
}
int spanToReplaceStart = spannable.getSpanStart(span);
int spanToReplaceEnd = spannable.getSpanEnd(span);
int spanToReplaceFlags = spannable.getSpanFlags(span);
spannable.removeSpan(span);
ReplacementSpan replacementSpan = new AccessibilityReplacementSpan(replacementText);
spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
spanToReplaceFlags);
}
return spannable;
}
/**
* 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;
}
/**
* Get the state description of this node.
*
* @return the state description
*/
public @Nullable CharSequence getStateDescription() {
return mStateDescription;
}
/**
* Gets the content description of this node.
*
* @return The content description.
*/
public CharSequence getContentDescription() {
return mContentDescription;
}
/**
* Sets the state 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 stateDescription the state description of this node.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setStateDescription(@Nullable CharSequence stateDescription) {
enforceNotSealed();
mStateDescription = (stateDescription == null) ? null
: stateDescription.subSequence(0, stateDescription.length());
}
/**
* 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());
}
/**
* Gets the tooltip text of this node.
*
* @return The tooltip text.
*/
@Nullable
public CharSequence getTooltipText() {
return mTooltipText;
}
/**
* Sets the tooltip 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 tooltipText The tooltip text.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setTooltipText(@Nullable CharSequence tooltipText) {
enforceNotSealed();
mTooltipText = (tooltipText == null) ? null
: tooltipText.subSequence(0, tooltipText.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(mConnectionId, mWindowId, 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(mConnectionId, mWindowId, 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 the {@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;
}
/**
* Get the {@link TouchDelegateInfo} for touch delegate behavior with the represented view.
* It is possible for the same node to be pointed to by several regions. Use
* {@link TouchDelegateInfo#getRegionAt(int)} to get touch delegate target {@link Region}, and
* {@link TouchDelegateInfo#getTargetForRegion(Region)} for {@link AccessibilityNodeInfo} from
* the given region.
*
* @return {@link TouchDelegateInfo} or {@code null} if there are no touch delegates.
*/
@Nullable
public TouchDelegateInfo getTouchDelegateInfo() {
if (mTouchDelegateInfo != null) {
mTouchDelegateInfo.setConnectionId(mConnectionId);
mTouchDelegateInfo.setWindowId(mWindowId);
}
return mTouchDelegateInfo;
}
/**
* Set touch delegate info if the represented view has a {@link TouchDelegate}.
* <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 delegatedInfo {@link TouchDelegateInfo} returned from
* {@link TouchDelegate#getTouchDelegateInfo()}.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setTouchDelegateInfo(@NonNull TouchDelegateInfo delegatedInfo) {
enforceNotSealed();
mTouchDelegateInfo = delegatedInfo;
}
/**
* 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
*/
@UnsupportedAppUsage
@TestApi
public long getSourceNodeId() {
return mSourceNodeId;
}
/**
* Sets the token and node id of the leashed parent.
*
* @param token The token.
* @param viewId The accessibility view id.
* @hide
*/
@TestApi
public void setLeashedParent(@Nullable IBinder token, int viewId) {
enforceNotSealed();
mLeashedParent = token;
mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
}
/**
* Gets the token of the leashed parent.
*
* @return The token.
* @hide
*/
public @Nullable IBinder getLeashedParent() {
return mLeashedParent;
}
/**
* Gets the node id of the leashed parent.
*
* @return The accessibility node id.
* @hide
*/
public long getLeashedParentNodeId() {
return mLeashedParentNodeId;
}
/**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
*
* @hide
*/
@UnsupportedAppUsage
public void setSealed(boolean sealed) {
mSealed = sealed;
}
/**
* Gets if this instance is sealed.
*
* @return Whether is sealed.
*
* @hide
*/
@UnsupportedAppUsage
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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link #AccessibilityNodeInfo(View)} instead.
*
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link #AccessibilityNodeInfo(View, int)} instead.
*
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link #AccessibilityNodeInfo()} instead.
*
* @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>.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link #AccessibilityNodeInfo(AccessibilityNodeInfo)} instead.
*
* @param info The other info.
* @return An instance.
*/
public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
infoClone.init(info, true /* usePoolingInfo */);
return infoClone;
}
/**
* Return an instance back to be reused.
* <p>
* <strong>Note:</strong> You must not touch the object after calling this function.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
*
* @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) {
writeToParcelNoRecycle(parcel, flags);
// 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();
}
/** @hide */
@TestApi
public void writeToParcelNoRecycle(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 (!LongArray.elementsEqual(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(mStateDescription, DEFAULT.mStateDescription)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) {
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);
}
fieldIndex++;
if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (!Objects.equals(mExtraRenderingInfo, DEFAULT.mExtraRenderingInfo)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (mLeashedChild != DEFAULT.mLeashedChild) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (mLeashedParent != DEFAULT.mLeashedParent) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
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;
long defaultStandardActions = 0;
for (int i = 0; i < actionCount; i++) {
AccessibilityAction action = mActions.get(i);
if (isDefaultStandardAction(action)) {
defaultStandardActions |= action.mSerializationFlag;
} else {
nonStandardActionCount++;
}
}
parcel.writeLong(defaultStandardActions);
parcel.writeInt(nonStandardActionCount);
for (int i = 0; i < actionCount; i++) {
AccessibilityAction action = mActions.get(i);
if (!isDefaultStandardAction(action)) {
action.writeToParcel(parcel, flags);
}
}
} else {
parcel.writeLong(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(mStateDescription);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeCharSequence(mContentDescription);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
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 (isBitSet(nonDefaultFields, fieldIndex++)) {
mTouchDelegateInfo.writeToParcel(parcel, flags);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeValue(mExtraRenderingInfo.getLayoutSize());
parcel.writeFloat(mExtraRenderingInfo.getTextSizeInPx());
parcel.writeInt(mExtraRenderingInfo.getTextSizeUnit());
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeStrongBinder(mLeashedChild);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeStrongBinder(mLeashedParent);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeLong(mLeashedParentNodeId);
}
if (DEBUG) {
fieldIndex--;
if (totalFields != fieldIndex) {
throw new IllegalStateException("Number of fields mismatch: " + totalFields
+ " vs " + fieldIndex);
}
}
}
/**
* Initializes this instance from another one.
*
* @param other The other instance.
* @param usePoolingInfos whether using pooled object internally or not
*/
private void init(AccessibilityNodeInfo other, boolean usePoolingInfos) {
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;
mOriginalText = other.mOriginalText;
mHintText = other.mHintText;
mError = other.mError;
mStateDescription = other.mStateDescription;
mContentDescription = other.mContentDescription;
mPaneTitle = other.mPaneTitle;
mTooltipText = other.mTooltipText;
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 (usePoolingInfos) {
initPoolingInfos(other);
} else {
initCopyInfos(other);
}
final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
mTouchDelegateInfo = (otherInfo != null)
? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
mLeashedChild = other.mLeashedChild;
mLeashedParent = other.mLeashedParent;
mLeashedParentNodeId = other.mLeashedParentNodeId;
}
private void initPoolingInfos(AccessibilityNodeInfo other) {
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;
if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
mExtraRenderingInfo = (other.mExtraRenderingInfo != null)
? ExtraRenderingInfo.obtain(other.mExtraRenderingInfo) : null;
}
private void initCopyInfos(AccessibilityNodeInfo other) {
RangeInfo ri = other.mRangeInfo;
mRangeInfo = (ri == null) ? null
: new RangeInfo(ri.mType, ri.mMin, ri.mMax, ri.mCurrent);
CollectionInfo ci = other.mCollectionInfo;
mCollectionInfo = (ci == null) ? null
: new CollectionInfo(ci.mRowCount, ci.mColumnCount,
ci.mHierarchical, ci.mSelectionMode);
CollectionItemInfo cii = other.mCollectionItemInfo;
mCollectionItemInfo = (cii == null) ? null
: new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex,
cii.mColumnSpan, cii.mHeading, cii.mSelected);
ExtraRenderingInfo ti = other.mExtraRenderingInfo;
mExtraRenderingInfo = (ti == null) ? null
: new ExtraRenderingInfo(ti);
}
/**
* 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 long standardActions = parcel.readLong();
addStandardActions(standardActions);
final int nonStandardActionCount = parcel.readInt();
for (int i = 0; i < nonStandardActionCount; i++) {
final AccessibilityAction action =
AccessibilityAction.CREATOR.createFromParcel(parcel);
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++)) mStateDescription = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mContentDescription = parcel.readCharSequence();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = 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;
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle();
mExtraRenderingInfo = ExtraRenderingInfo.obtain();
mExtraRenderingInfo.mLayoutSize = (Size) parcel.readValue(null);
mExtraRenderingInfo.mTextSizeInPx = parcel.readFloat();
mExtraRenderingInfo.mTextSizeUnit = parcel.readInt();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mLeashedChild = parcel.readStrongBinder();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mLeashedParent = parcel.readStrongBinder();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mLeashedParentNodeId = parcel.readLong();
}
mSealed = sealed;
}
/**
* Clears the state of this instance.
*/
private void clear() {
init(DEFAULT, true /* usePoolingInfo */);
}
private static boolean isDefaultStandardAction(AccessibilityAction action) {
return (action.mSerializationFlag != -1L) && 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(long 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(long serializationIdMask) {
long remainingIds = serializationIdMask;
while (remainingIds > 0) {
final long id = 1L << Long.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.accessibilityActionPageDown:
return "ACTION_PAGE_DOWN";
case R.id.accessibilityActionPageUp:
return "ACTION_PAGE_UP";
case R.id.accessibilityActionPageLeft:
return "ACTION_PAGE_LEFT";
case R.id.accessibilityActionPageRight:
return "ACTION_PAGE_RIGHT";
case R.id.accessibilityActionSetProgress:
return "ACTION_SET_PROGRESS";
case R.id.accessibilityActionContextClick:
return "ACTION_CONTEXT_CLICK";
case R.id.accessibilityActionShowTooltip:
return "ACTION_SHOW_TOOLTIP";
case R.id.accessibilityActionHideTooltip:
return "ACTION_HIDE_TOOLTIP";
case R.id.accessibilityActionPressAndHold:
return "ACTION_PRESS_AND_HOLD";
case R.id.accessibilityActionImeEnter:
return "ACTION_IME_ENTER";
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 static boolean canPerformRequestOverConnection(int connectionId,
int windowId, long accessibilityNodeId) {
return ((windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
&& (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
&& (connectionId != 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: 0x").append(Long.toHexString(mSourceNodeId));
builder.append("; windowId: 0x").append(Long.toHexString(mWindowId));
builder.append("; accessibilityViewId: 0x")
.append(Long.toHexString(getAccessibilityViewId(mSourceNodeId)));
builder.append("; virtualDescendantId: 0x")
.append(Long.toHexString(getVirtualDescendantId(mSourceNodeId)));
builder.append("; mParentNodeId: 0x").append(Long.toHexString(mParentNodeId));
builder.append("; traversalBefore: 0x").append(Long.toHexString(mTraversalBefore));
builder.append("; traversalAfter: 0x").append(Long.toHexString(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("0x").append(Long.toHexString(childIds.get(i)));
if (i < count - 1) {
builder.append(", ");
}
}
}
builder.append("]");
}
builder.append("; boundsInParent: ").append(mBoundsInParent);
builder.append("; boundsInScreen: ").append(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("; stateDescription: ").append(mStateDescription);
builder.append("; contentDescription: ").append(mContentDescription);
builder.append("; tooltipText: ").append(mTooltipText);
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("; visible: ").append(isVisibleToUser());
builder.append("; actions: ").append(mActions);
return builder.toString();
}
private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
int windowId, long accessibilityId) {
if (!canPerformRequestOverConnection(connectionId, windowId, accessibilityId)) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
windowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
| FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
}
private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
IBinder leashToken, long accessibilityId) {
if (!((leashToken != null)
&& (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
&& (connectionId != UNDEFINED_CONNECTION_ID))) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
| FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
}
/** @hide */
public static String idToString(long accessibilityId) {
int accessibilityViewId = getAccessibilityViewId(accessibilityId);
int virtualDescendantId = getVirtualDescendantId(accessibilityId);
return virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID
? idItemToString(accessibilityViewId)
: idItemToString(accessibilityViewId) + ":" + idItemToString(virtualDescendantId);
}
private static String idItemToString(int item) {
switch (item) {
case ROOT_ITEM_ID: return "ROOT";
case UNDEFINED_ITEM_ID: return "UNDEFINED";
case AccessibilityNodeProvider.HOST_VIEW_ID: return "HOST";
default: return "" + item;
}
}
/**
* 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 indicate to the user 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 implements Parcelable {
/** @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 to move to the page above.
*/
public static final AccessibilityAction ACTION_PAGE_UP =
new AccessibilityAction(R.id.accessibilityActionPageUp);
/**
* Action to move to the page below.
*/
public static final AccessibilityAction ACTION_PAGE_DOWN =
new AccessibilityAction(R.id.accessibilityActionPageDown);
/**
* Action to move to the page left.
*/
public static final AccessibilityAction ACTION_PAGE_LEFT =
new AccessibilityAction(R.id.accessibilityActionPageLeft);
/**
* Action to move to the page right.
*/
public static final AccessibilityAction ACTION_PAGE_RIGHT =
new AccessibilityAction(R.id.accessibilityActionPageRight);
/**
* 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);
/**
* Action to show a tooltip. A node should expose this action only for views with tooltip
* text that but are not currently showing a tooltip.
*/
public static final AccessibilityAction ACTION_SHOW_TOOLTIP =
new AccessibilityAction(R.id.accessibilityActionShowTooltip);
/**
* Action to hide a tooltip. A node should expose this action only for views that are
* currently showing a tooltip.
*/
public static final AccessibilityAction ACTION_HIDE_TOOLTIP =
new AccessibilityAction(R.id.accessibilityActionHideTooltip);
/**
* Action that presses and holds a node.
* <p>
* This action is for nodes that have distinct behavior that depends on how long a press is
* held. Nodes having a single action for long press should use {@link #ACTION_LONG_CLICK}
* instead of this action, and nodes should not expose both actions.
* <p>
* When calling {@code performAction(ACTION_PRESS_AND_HOLD, bundle}, use
* {@link #ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT} to specify how long the
* node is pressed. The first time an accessibility service performs ACTION_PRES_AND_HOLD
* on a node, it must specify 0 as ACTION_ARGUMENT_PRESS_AND_HOLD, so the application is
* notified that the held state has started. To ensure reasonable behavior, the values
* must be increased incrementally and may not exceed 10,000. UIs requested
* to hold for times outside of this range should ignore the action.
* <p>
* The total time the element is held could be specified by an accessibility user up-front,
* or may depend on what happens on the UI as the user continues to request the hold.
* <p>
* <strong>Note:</strong> The time between dispatching the action and it arriving in the
* UI process is not guaranteed. It is possible on a busy system for the time to expire
* unexpectedly. For the case of holding down a key for a repeating action, a delayed
* arrival should be benign. Please do not use this sort of action in cases where such
* delays will lead to unexpected UI behavior.
* <p>
*/
@NonNull public static final AccessibilityAction ACTION_PRESS_AND_HOLD =
new AccessibilityAction(R.id.accessibilityActionPressAndHold);
/**
* Action to send an ime actionId which is from
* {@link android.view.inputmethod.EditorInfo#actionId}. This ime actionId sets by
* {@link TextView#setImeActionLabel(CharSequence, int)}, or it would be
* {@link android.view.inputmethod.EditorInfo#IME_ACTION_UNSPECIFIED} if no specific
* actionId has set. A node should expose this action only for views that are currently
* with input focus and editable.
*/
@NonNull public static final AccessibilityAction ACTION_IME_ENTER =
new AccessibilityAction(R.id.accessibilityActionImeEnter);
private final int mActionId;
private final CharSequence mLabel;
/** @hide */
public long mSerializationFlag = -1L;
/**
* 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) {
mActionId = actionId;
mLabel = label;
}
/**
* Constructor for a {@link #sStandardActions standard} action
*/
private AccessibilityAction(int standardActionId) {
this(standardActionId, null);
mSerializationFlag = 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;
}
/**
* {@inheritDoc}
*/
@Override
public int describeContents() {
return 0;
}
/**
* Write data into a parcel.
*/
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mActionId);
out.writeCharSequence(mLabel);
}
public static final @NonNull Parcelable.Creator<AccessibilityAction> CREATOR =
new Parcelable.Creator<AccessibilityAction>() {
public AccessibilityAction createFromParcel(Parcel in) {
return new AccessibilityAction(in);
}
public AccessibilityAction[] newArray(int size) {
return new AccessibilityAction[size];
}
};
private AccessibilityAction(Parcel in) {
mActionId = in.readInt();
mLabel = in.readCharSequence();
}
}
/**
* 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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
* float, float, float)} instead.
*
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int,
* float, float, float)} instead.
*
* @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.
*/
public 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.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
*/
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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionInfo#CollectionInfo} instead.
*
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
* boolean)} instead.
*
* @param rowCount The number of rows, or -1 if count is unknown.
* @param columnCount The number of columns, or -1 if count is unknown.
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int,
* boolean, int)} instead.
*
* @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.
*/
public CollectionInfo(int rowCount, int columnCount, boolean hierarchical) {
this(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
}
/**
* 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.
*/
public 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, or -1 if count is unknown.
*/
public int getRowCount() {
return mRowCount;
}
/**
* Gets the number of columns.
*
* @return The column count, or -1 if count is unknown.
*/
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.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
*/
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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo}
* instead.
*
* @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.
*
* <p>In most situations object pooling is not beneficial. Create a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
* int, int, int, boolean)} instead.
*
* @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. (Prefer
* {@link AccessibilityNodeInfo#setHeading(boolean)}).
*/
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.
*
* <p>In most situations object pooling is not beneficial. Creates a new instance using the
* constructor {@link
* AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
* int, int, int, boolean, boolean)} instead.
*
* @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. (Prefer
* {@link AccessibilityNodeInfo#setHeading(boolean)})
* @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.
*/
public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
boolean heading) {
this(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
}
/**
* 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.
* @param selected Whether the item is selected.
*/
public 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.
* @deprecated Use {@link AccessibilityNodeInfo#isHeading()}
*/
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.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
*/
void recycle() {
clear();
sPool.release(this);
}
private void clear() {
mColumnIndex = 0;
mColumnSpan = 0;
mRowIndex = 0;
mRowSpan = 0;
mHeading = false;
mSelected = false;
}
}
/**
* Class with information of touch delegated views and regions from {@link TouchDelegate} for
* the {@link AccessibilityNodeInfo}.
*
* @see AccessibilityNodeInfo#setTouchDelegateInfo(TouchDelegateInfo)
*/
public static final class TouchDelegateInfo implements Parcelable {
private ArrayMap<Region, Long> mTargetMap;
// Two ids are initialized lazily in AccessibilityNodeInfo#getTouchDelegateInfo
private int mConnectionId;
private int mWindowId;
/**
* Create a new instance of {@link TouchDelegateInfo}.
*
* @param targetMap A map from regions (in view coordinates) to delegated views.
* @throws IllegalArgumentException if targetMap is empty or {@code null} in
* Regions or Views.
*/
public TouchDelegateInfo(@NonNull Map<Region, View> targetMap) {
Preconditions.checkArgument(!targetMap.isEmpty()
&& !targetMap.containsKey(null) && !targetMap.containsValue(null));
mTargetMap = new ArrayMap<>(targetMap.size());
for (final Region region : targetMap.keySet()) {
final View view = targetMap.get(region);
mTargetMap.put(region, (long) view.getAccessibilityViewId());
}
}
/**
* Create a new instance from target map.
*
* @param targetMap A map from regions (in view coordinates) to delegated views'
* accessibility id.
* @param doCopy True if shallow copy targetMap.
* @throws IllegalArgumentException if targetMap is empty or {@code null} in
* Regions or Views.
*/
TouchDelegateInfo(@NonNull ArrayMap<Region, Long> targetMap, boolean doCopy) {
Preconditions.checkArgument(!targetMap.isEmpty()
&& !targetMap.containsKey(null) && !targetMap.containsValue(null));
if (doCopy) {
mTargetMap = new ArrayMap<>(targetMap.size());
mTargetMap.putAll(targetMap);
} else {
mTargetMap = targetMap;
}
}
/**
* Set the connection ID.
*
* @param connectionId The connection id.
*/
private void setConnectionId(int connectionId) {
mConnectionId = connectionId;
}
/**
* Set the window ID.
*
* @param windowId The window id.
*/
private void setWindowId(int windowId) {
mWindowId = windowId;
}
/**
* Returns the number of touch delegate target region.
*
* @return Number of touch delegate target region.
*/
public int getRegionCount() {
return mTargetMap.size();
}
/**
* Return the {@link Region} at the given index in the {@link TouchDelegateInfo}.
*
* @param index The desired index, must be between 0 and {@link #getRegionCount()}-1.
* @return Returns the {@link Region} stored at the given index.
*/
@NonNull
public Region getRegionAt(int index) {
return mTargetMap.keyAt(index);
}
/**
* Return the target {@link AccessibilityNodeInfo} for the given {@link Region}.
* <p>
* <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
* </p>
* <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 region The region retrieved from {@link #getRegionAt(int)}.
* @return The target node associates with the given region.
*/
@Nullable
public AccessibilityNodeInfo getTargetForRegion(@NonNull Region region) {
return getNodeForAccessibilityId(mConnectionId, mWindowId, mTargetMap.get(region));
}
/**
* Return the accessibility id of target node.
*
* @param region The region retrieved from {@link #getRegionAt(int)}.
* @return The accessibility id of target node.
*
* @hide
*/
@TestApi
public long getAccessibilityIdForRegion(@NonNull Region region) {
return mTargetMap.get(region);
}
/**
* {@inheritDoc}
*/
@Override
public int describeContents() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mTargetMap.size());
for (int i = 0; i < mTargetMap.size(); i++) {
final Region region = mTargetMap.keyAt(i);
final Long accessibilityId = mTargetMap.valueAt(i);
region.writeToParcel(dest, flags);
dest.writeLong(accessibilityId);
}
}
/**
* @see android.os.Parcelable.Creator
*/
public static final @android.annotation.NonNull Parcelable.Creator<TouchDelegateInfo> CREATOR =
new Parcelable.Creator<TouchDelegateInfo>() {
@Override
public TouchDelegateInfo createFromParcel(Parcel parcel) {
final int size = parcel.readInt();
if (size == 0) {
return null;
}
final ArrayMap<Region, Long> targetMap = new ArrayMap<>(size);
for (int i = 0; i < size; i++) {
final Region region = Region.CREATOR.createFromParcel(parcel);
final long accessibilityId = parcel.readLong();
targetMap.put(region, accessibilityId);
}
final TouchDelegateInfo touchDelegateInfo = new TouchDelegateInfo(
targetMap, false);
return touchDelegateInfo;
}
@Override
public TouchDelegateInfo[] newArray(int size) {
return new TouchDelegateInfo[size];
}
};
}
/**
* Class with information of a view useful to evaluate accessibility needs. Developers can
* refresh the node with the key {@link #EXTRA_DATA_RENDERING_INFO_KEY} to fetch the text size
* and unit if it is {@link TextView} and the height and the width of layout params from
* {@link ViewGroup} or {@link TextView}.
*
* @see #EXTRA_DATA_RENDERING_INFO_KEY
* @see #refreshWithExtraData(String, Bundle)
*/
public static final class ExtraRenderingInfo {
private static final int UNDEFINED_VALUE = -1;
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<ExtraRenderingInfo> sPool =
new SynchronizedPool<>(MAX_POOL_SIZE);
private Size mLayoutSize;
private float mTextSizeInPx = UNDEFINED_VALUE;
private int mTextSizeUnit = UNDEFINED_VALUE;
/**
* Obtains a pooled instance.
* @hide
*/
@NonNull
public static ExtraRenderingInfo obtain() {
final ExtraRenderingInfo info = sPool.acquire();
if (info == null) {
return new ExtraRenderingInfo(null);
}
return info;
}
/** Obtains a pooled instance that is a clone of another one. */
private static ExtraRenderingInfo obtain(ExtraRenderingInfo other) {
ExtraRenderingInfo extraRenderingInfo = ExtraRenderingInfo.obtain();
extraRenderingInfo.mLayoutSize = other.mLayoutSize;
extraRenderingInfo.mTextSizeInPx = other.mTextSizeInPx;
extraRenderingInfo.mTextSizeUnit = other.mTextSizeUnit;
return extraRenderingInfo;
}
/**
* Creates a new rendering info of a view, and this new instance is initialized from
* the given <code>other</code>.
*
* @param other The instance to clone.
*/
private ExtraRenderingInfo(@Nullable ExtraRenderingInfo other) {
if (other != null) {
mLayoutSize = other.mLayoutSize;
mTextSizeInPx = other.mTextSizeInPx;
mTextSizeUnit = other.mTextSizeUnit;
}
}
/**
* Gets the size object containing the height and the width of
* {@link android.view.ViewGroup.LayoutParams} if the node is a {@link ViewGroup} or
* a {@link TextView}, or null otherwise. Useful for some accessibility services to
* understand whether the text is scalable and fits the view or not.
*
* @return a {@link Size} stores layout height and layout width of the view, or null
* otherwise. And the size value may be in pixels,
* {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
* or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
*/
public @Nullable Size getLayoutSize() {
return mLayoutSize;
}
/**
* Sets layout width and layout height of the view.
*
* @param width The layout width.
* @param height The layout height.
* @hide
*/
public void setLayoutSize(int width, int height) {
mLayoutSize = new Size(width, height);
}
/**
* Gets the text size if the node is a {@link TextView}, or -1 otherwise. Useful for some
* accessibility services to understand whether the text is scalable and fits the view or
* not.
*
* @return the text size of a {@code TextView}, or -1 otherwise.
*/
public float getTextSizeInPx() {
return mTextSizeInPx;
}
/**
* Sets text size of the view.
*
* @param textSizeInPx The text size in pixels.
* @hide
*/
public void setTextSizeInPx(float textSizeInPx) {
mTextSizeInPx = textSizeInPx;
}
/**
* Gets the text size unit if the node is a {@link TextView}, or -1 otherwise.
* Text size returned from {@link #getTextSizeInPx} in raw pixels may scale by factors and
* convert from other units. Useful for some accessibility services to understand whether
* the text is scalable and fits the view or not.
*
* @return the text size unit which type is {@link TypedValue#TYPE_DIMENSION} of a
* {@code TextView}, or -1 otherwise.
*
* @see TypedValue#TYPE_DIMENSION
*/
public int getTextSizeUnit() {
return mTextSizeUnit;
}
/**
* Sets text size unit of the view.
*
* @param textSizeUnit The text size unit.
* @hide
*/
public void setTextSizeUnit(int textSizeUnit) {
mTextSizeUnit = textSizeUnit;
}
/**
* Recycles this instance.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
*/
void recycle() {
clear();
sPool.release(this);
}
private void clear() {
mLayoutSize = null;
mTextSizeInPx = UNDEFINED_VALUE;
mTextSizeUnit = UNDEFINED_VALUE;
}
}
/**
* @see android.os.Parcelable.Creator
*/
public static final @android.annotation.NonNull 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];
}
};
}