AccessibilityNodeInfo.java revision ee33ad24cdc31ed0d7f99e110e041b0a63c9b0f1
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.accessibility;
18
19import android.graphics.Rect;
20import android.os.Bundle;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.SparseLongArray;
24import android.view.View;
25
26import java.util.Collections;
27import java.util.List;
28
29/**
30 * This class represents a node of the window content as well as actions that
31 * can be requested from its source. From the point of view of an
32 * {@link android.accessibilityservice.AccessibilityService} a window content is
33 * presented as tree of accessibility node info which may or may not map one-to-one
34 * to the view hierarchy. In other words, a custom view is free to report itself as
35 * a tree of accessibility node info.
36 * </p>
37 * <p>
38 * Once an accessibility node info is delivered to an accessibility service it is
39 * made immutable and calling a state mutation method generates an error.
40 * </p>
41 * <p>
42 * Please refer to {@link android.accessibilityservice.AccessibilityService} for
43 * details about how to obtain a handle to window content as a tree of accessibility
44 * node info as well as familiarizing with the security model.
45 * </p>
46 * <div class="special reference">
47 * <h3>Developer Guides</h3>
48 * <p>For more information about making applications accessible, read the
49 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
50 * developer guide.</p>
51 * </div>
52 *
53 * @see android.accessibilityservice.AccessibilityService
54 * @see AccessibilityEvent
55 * @see AccessibilityManager
56 */
57public class AccessibilityNodeInfo implements Parcelable {
58
59    private static final boolean DEBUG = false;
60
61    /** @hide */
62    public static final int UNDEFINED = -1;
63
64    /** @hide */
65    public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED);
66
67    /** @hide */
68    public static final int ACTIVE_WINDOW_ID = UNDEFINED;
69
70    /** @hide */
71    public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
72
73    /** @hide */
74    public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
75
76    /** @hide */
77    public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
78
79    /** @hide */
80    public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
81
82    // Actions.
83
84    /**
85     * Action that gives input focus to the node.
86     */
87    public static final int ACTION_FOCUS =  0x00000001;
88
89    /**
90     * Action that clears input focus of the node.
91     */
92    public static final int ACTION_CLEAR_FOCUS = 0x00000002;
93
94    /**
95     * Action that selects the node.
96     */
97    public static final int ACTION_SELECT = 0x00000004;
98
99    /**
100     * Action that unselects the node.
101     */
102    public static final int ACTION_CLEAR_SELECTION = 0x00000008;
103
104    /**
105     * Action that clicks on the node info.
106     */
107    public static final int ACTION_CLICK = 0x00000010;
108
109    /**
110     * Action that long clicks on the node.
111     */
112    public static final int ACTION_LONG_CLICK = 0x00000020;
113
114    /**
115     * Action that gives accessibility focus to the node.
116     */
117    public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
118
119    /**
120     * Action that clears accessibility focus of the node.
121     */
122    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
123
124    /**
125     * Action that requests to go to the next entity in this node's text
126     * at a given movement granularity. For example, move to the next character,
127     * word, etc.
128     * <p>
129     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
130     * <strong>Example:</strong>
131     * <code><pre><p>
132     *   Bundle arguments = new Bundle();
133     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
134     *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
135     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
136     * </code></pre></p>
137     * </p>
138     *
139     * @see #setMovementGranularities(int)
140     * @see #getMovementGranularities()
141     *
142     * @see #MOVEMENT_GRANULARITY_CHARACTER
143     * @see #MOVEMENT_GRANULARITY_WORD
144     * @see #MOVEMENT_GRANULARITY_LINE
145     * @see #MOVEMENT_GRANULARITY_PARAGRAPH
146     * @see #MOVEMENT_GRANULARITY_PAGE
147     */
148    public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
149
150    /**
151     * Action that requests to go to the previous entity in this node's text
152     * at a given movement granularity. For example, move to the next character,
153     * word, etc.
154     * <p>
155     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
156     * <strong>Example:</strong>
157     * <code><pre><p>
158     *   Bundle arguments = new Bundle();
159     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
160     *           AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
161     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
162     *           arguments);
163     * </code></pre></p>
164     * </p>
165     *
166     * @see #setMovementGranularities(int)
167     * @see #getMovementGranularities()
168     *
169     * @see #MOVEMENT_GRANULARITY_CHARACTER
170     * @see #MOVEMENT_GRANULARITY_WORD
171     * @see #MOVEMENT_GRANULARITY_LINE
172     * @see #MOVEMENT_GRANULARITY_PARAGRAPH
173     * @see #MOVEMENT_GRANULARITY_PAGE
174     */
175    public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
176
177    /**
178     * Action to move to the next HTML element of a given type. For example, move
179     * to the BUTTON, INPUT, TABLE, etc.
180     * <p>
181     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
182     * <strong>Example:</strong>
183     * <code><pre><p>
184     *   Bundle arguments = new Bundle();
185     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
186     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
187     * </code></pre></p>
188     * </p>
189     */
190    public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
191
192    /**
193     * Action to move to the previous HTML element of a given type. For example, move
194     * to the BUTTON, INPUT, TABLE, etc.
195     * <p>
196     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
197     * <strong>Example:</strong>
198     * <code><pre><p>
199     *   Bundle arguments = new Bundle();
200     *   arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
201     *   info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments);
202     * </code></pre></p>
203     * </p>
204     */
205    public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
206
207    /**
208     * Action to scroll the node content forward.
209     */
210    public static final int ACTION_SCROLL_FORWARD = 0x00001000;
211
212    /**
213     * Action to scroll the node content backward.
214     */
215    public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
216
217    /**
218     * Argument for which movement granularity to be used when traversing the node text.
219     * <p>
220     * <strong>Type:</strong> int<br>
221     * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
222     * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
223     * </p>
224     */
225    public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
226        "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
227
228    /**
229     * Argument for which HTML element to get moving to the next/previous HTML element.
230     * <p>
231     * <strong>Type:</strong> String<br>
232     * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
233     *         {@link #ACTION_PREVIOUS_HTML_ELEMENT}
234     * </p>
235     */
236    public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
237        "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
238
239    /**
240     * The input focus.
241     */
242    public static final int FOCUS_INPUT = 1;
243
244    /**
245     * The accessibility focus.
246     */
247    public static final int FOCUS_ACCESSIBILITY = 2;
248
249    // Movement granularities
250
251    /**
252     * Movement granularity bit for traversing the text of a node by character.
253     */
254    public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001;
255
256    /**
257     * Movement granularity bit for traversing the text of a node by word.
258     */
259    public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002;
260
261    /**
262     * Movement granularity bit for traversing the text of a node by line.
263     */
264    public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004;
265
266    /**
267     * Movement granularity bit for traversing the text of a node by paragraph.
268     */
269    public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008;
270
271    /**
272     * Movement granularity bit for traversing the text of a node by page.
273     */
274    public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
275
276    // Boolean attributes.
277
278    private static final int PROPERTY_CHECKABLE = 0x00000001;
279
280    private static final int PROPERTY_CHECKED = 0x00000002;
281
282    private static final int PROPERTY_FOCUSABLE = 0x00000004;
283
284    private static final int PROPERTY_FOCUSED = 0x00000008;
285
286    private static final int PROPERTY_SELECTED = 0x00000010;
287
288    private static final int PROPERTY_CLICKABLE = 0x00000020;
289
290    private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
291
292    private static final int PROPERTY_ENABLED = 0x00000080;
293
294    private static final int PROPERTY_PASSWORD = 0x00000100;
295
296    private static final int PROPERTY_SCROLLABLE = 0x00000200;
297
298    private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
299
300    private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800;
301
302    /**
303     * Bits that provide the id of a virtual descendant of a view.
304     */
305    private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
306
307    /**
308     * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
309     * virtual descendant of a view. Such a descendant does not exist in the view
310     * hierarchy and is only reported via the accessibility APIs.
311     */
312    private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
313
314    /**
315     * Gets the accessibility view id which identifies a View in the view three.
316     *
317     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
318     * @return The accessibility view id part of the node id.
319     *
320     * @hide
321     */
322    public static int getAccessibilityViewId(long accessibilityNodeId) {
323        return (int) accessibilityNodeId;
324    }
325
326    /**
327     * Gets the virtual descendant id which identifies an imaginary view in a
328     * containing View.
329     *
330     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
331     * @return The virtual view id part of the node id.
332     *
333     * @hide
334     */
335    public static int getVirtualDescendantId(long accessibilityNodeId) {
336        return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
337                >> VIRTUAL_DESCENDANT_ID_SHIFT);
338    }
339
340    /**
341     * Makes a node id by shifting the <code>virtualDescendantId</code>
342     * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
343     * the bitwise or with the <code>accessibilityViewId</code>.
344     *
345     * @param accessibilityViewId A View accessibility id.
346     * @param virtualDescendantId A virtual descendant id.
347     * @return The node id.
348     *
349     * @hide
350     */
351    public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
352        return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
353    }
354
355    // Housekeeping.
356    private static final int MAX_POOL_SIZE = 50;
357    private static final Object sPoolLock = new Object();
358    private static AccessibilityNodeInfo sPool;
359    private static int sPoolSize;
360    private AccessibilityNodeInfo mNext;
361    private boolean mIsInPool;
362    private boolean mSealed;
363
364    // Data.
365    private int mWindowId = UNDEFINED;
366    private long mSourceNodeId = ROOT_NODE_ID;
367    private long mParentNodeId = ROOT_NODE_ID;
368
369    private int mBooleanProperties;
370    private final Rect mBoundsInParent = new Rect();
371    private final Rect mBoundsInScreen = new Rect();
372
373    private CharSequence mPackageName;
374    private CharSequence mClassName;
375    private CharSequence mText;
376    private CharSequence mContentDescription;
377
378    private final SparseLongArray mChildNodeIds = new SparseLongArray();
379    private int mActions;
380
381    private int mMovementGranularities;
382
383    private int mConnectionId = UNDEFINED;
384
385    // TODO: These are a workaround for 6623031. Remove when fixed.
386    private int mActualAndReportedWindowLeftDelta;
387    private int mActualAndReportedWindowTopDelta;
388
389    /**
390     * Hide constructor from clients.
391     */
392    private AccessibilityNodeInfo() {
393        /* do nothing */
394    }
395
396    /**
397     * Sets the source.
398     * <p>
399     *   <strong>Note:</strong> Cannot be called from an
400     *   {@link android.accessibilityservice.AccessibilityService}.
401     *   This class is made immutable before being delivered to an AccessibilityService.
402     * </p>
403     *
404     * @param source The info source.
405     */
406    public void setSource(View source) {
407        setSource(source, UNDEFINED);
408    }
409
410    /**
411     * Sets the source to be a virtual descendant of the given <code>root</code>.
412     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
413     * is set as the source.
414     * <p>
415     * A virtual descendant is an imaginary View that is reported as a part of the view
416     * hierarchy for accessibility purposes. This enables custom views that draw complex
417     * content to report themselves as a tree of virtual views, thus conveying their
418     * logical structure.
419     * </p>
420     * <p>
421     *   <strong>Note:</strong> Cannot be called from an
422     *   {@link android.accessibilityservice.AccessibilityService}.
423     *   This class is made immutable before being delivered to an AccessibilityService.
424     * </p>
425     *
426     * @param root The root of the virtual subtree.
427     * @param virtualDescendantId The id of the virtual descendant.
428     */
429    public void setSource(View root, int virtualDescendantId) {
430        enforceNotSealed();
431        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
432        final int rootAccessibilityViewId =
433            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
434        mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
435        if (root != null) {
436            mActualAndReportedWindowLeftDelta = root.getActualAndReportedWindowLeftDelta();
437            mActualAndReportedWindowTopDelta = root.getActualAndReportedWindowTopDelta();
438        }
439    }
440
441    /**
442     * Find the view that has the specified focus type. The search starts from
443     * the view represented by this node info.
444     *
445     * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
446     *         {@link #FOCUS_ACCESSIBILITY}.
447     * @return The node info of the focused view or null.
448     *
449     * @see #FOCUS_INPUT
450     * @see #FOCUS_ACCESSIBILITY
451     */
452    public AccessibilityNodeInfo findFocus(int focus) {
453        enforceSealed();
454        enforceValidFocusType(focus);
455        if (!canPerformRequestOverConnection(mSourceNodeId)) {
456            return null;
457        }
458        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
459                mSourceNodeId, focus);
460    }
461
462    /**
463     * Searches for the nearest view in the specified direction that can take
464     * the input focus.
465     *
466     * @param direction The direction. Can be one of:
467     *     {@link View#FOCUS_DOWN},
468     *     {@link View#FOCUS_UP},
469     *     {@link View#FOCUS_LEFT},
470     *     {@link View#FOCUS_RIGHT},
471     *     {@link View#FOCUS_FORWARD},
472     *     {@link View#FOCUS_BACKWARD},
473     *     {@link View#ACCESSIBILITY_FOCUS_FORWARD},
474     *     {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
475     *     {@link View#ACCESSIBILITY_FOCUS_UP},
476     *     {@link View#ACCESSIBILITY_FOCUS_RIGHT},
477     *     {@link View#ACCESSIBILITY_FOCUS_DOWN},
478     *     {@link View#ACCESSIBILITY_FOCUS_LEFT}.
479     *
480     * @return The node info for the view that can take accessibility focus.
481     */
482    public AccessibilityNodeInfo focusSearch(int direction) {
483        enforceSealed();
484        enforceValidFocusDirection(direction);
485        if (!canPerformRequestOverConnection(mSourceNodeId)) {
486            return null;
487        }
488        return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
489                mSourceNodeId, direction);
490    }
491
492    /**
493     * Gets the id of the window from which the info comes from.
494     *
495     * @return The window id.
496     */
497    public int getWindowId() {
498        return mWindowId;
499    }
500
501    /**
502     * @return The ids of the children.
503     *
504     * @hide
505     */
506    public SparseLongArray getChildNodeIds() {
507        return mChildNodeIds;
508    }
509
510    /**
511     * Gets the number of children.
512     *
513     * @return The child count.
514     */
515    public int getChildCount() {
516        return mChildNodeIds.size();
517    }
518
519    /**
520     * Get the child at given index.
521     * <p>
522     *   <strong>Note:</strong> It is a client responsibility to recycle the
523     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
524     *     to avoid creating of multiple instances.
525     * </p>
526     *
527     * @param index The child index.
528     * @return The child node.
529     *
530     * @throws IllegalStateException If called outside of an AccessibilityService.
531     *
532     */
533    public AccessibilityNodeInfo getChild(int index) {
534        enforceSealed();
535        if (!canPerformRequestOverConnection(mSourceNodeId)) {
536            return null;
537        }
538        final long childId = mChildNodeIds.get(index);
539        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
540        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
541                childId, FLAG_PREFETCH_DESCENDANTS);
542    }
543
544    /**
545     * Adds a child.
546     * <p>
547     * <strong>Note:</strong> Cannot be called from an
548     * {@link android.accessibilityservice.AccessibilityService}.
549     * This class is made immutable before being delivered to an AccessibilityService.
550     * </p>
551     *
552     * @param child The child.
553     *
554     * @throws IllegalStateException If called from an AccessibilityService.
555     */
556    public void addChild(View child) {
557        addChild(child, UNDEFINED);
558    }
559
560    /**
561     * Adds a virtual child which is a descendant of the given <code>root</code>.
562     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
563     * is added as a child.
564     * <p>
565     * A virtual descendant is an imaginary View that is reported as a part of the view
566     * hierarchy for accessibility purposes. This enables custom views that draw complex
567     * content to report them selves as a tree of virtual views, thus conveying their
568     * logical structure.
569     * </p>
570     *
571     * @param root The root of the virtual subtree.
572     * @param virtualDescendantId The id of the virtual child.
573     */
574    public void addChild(View root, int virtualDescendantId) {
575        enforceNotSealed();
576        final int index = mChildNodeIds.size();
577        final int rootAccessibilityViewId =
578            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
579        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
580        mChildNodeIds.put(index, childNodeId);
581    }
582
583    /**
584     * Gets the actions that can be performed on the node.
585     *
586     * @return The bit mask of with actions.
587     *
588     * @see AccessibilityNodeInfo#ACTION_FOCUS
589     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
590     * @see AccessibilityNodeInfo#ACTION_SELECT
591     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
592     * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
593     * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
594     * @see AccessibilityNodeInfo#ACTION_CLICK
595     * @see AccessibilityNodeInfo#ACTION_LONG_CLICK
596     * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
597     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
598     * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
599     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
600     * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
601     * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
602     */
603    public int getActions() {
604        return mActions;
605    }
606
607    /**
608     * Adds an action that can be performed on the node.
609     * <p>
610     *   <strong>Note:</strong> Cannot be called from an
611     *   {@link android.accessibilityservice.AccessibilityService}.
612     *   This class is made immutable before being delivered to an AccessibilityService.
613     * </p>
614     *
615     * @param action The action.
616     *
617     * @throws IllegalStateException If called from an AccessibilityService.
618     */
619    public void addAction(int action) {
620        enforceNotSealed();
621        mActions |= action;
622    }
623
624    /**
625     * Sets the movement granularities for traversing the text of this node.
626     * <p>
627     *   <strong>Note:</strong> Cannot be called from an
628     *   {@link android.accessibilityservice.AccessibilityService}.
629     *   This class is made immutable before being delivered to an AccessibilityService.
630     * </p>
631     *
632     * @param granularities The bit mask with granularities.
633     *
634     * @throws IllegalStateException If called from an AccessibilityService.
635     */
636    public void setMovementGranularities(int granularities) {
637        enforceNotSealed();
638        mMovementGranularities = granularities;
639    }
640
641    /**
642     * Gets the movement granularities for traversing the text of this node.
643     *
644     * @return The bit mask with granularities.
645     */
646    public int getMovementGranularities() {
647        return mMovementGranularities;
648    }
649
650    /**
651     * Performs an action on the node.
652     * <p>
653     *   <strong>Note:</strong> An action can be performed only if the request is made
654     *   from an {@link android.accessibilityservice.AccessibilityService}.
655     * </p>
656     *
657     * @param action The action to perform.
658     * @return True if the action was performed.
659     *
660     * @throws IllegalStateException If called outside of an AccessibilityService.
661     */
662    public boolean performAction(int action) {
663        enforceSealed();
664        if (!canPerformRequestOverConnection(mSourceNodeId)) {
665            return false;
666        }
667        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
668        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
669                action, null);
670    }
671
672    /**
673     * Performs an action on the node.
674     * <p>
675     *   <strong>Note:</strong> An action can be performed only if the request is made
676     *   from an {@link android.accessibilityservice.AccessibilityService}.
677     * </p>
678     *
679     * @param action The action to perform.
680     * @param arguments A bundle with additional arguments.
681     * @return True if the action was performed.
682     *
683     * @throws IllegalStateException If called outside of an AccessibilityService.
684     */
685    public boolean performAction(int action, Bundle arguments) {
686        enforceSealed();
687        if (!canPerformRequestOverConnection(mSourceNodeId)) {
688            return false;
689        }
690        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
691        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
692                action, arguments);
693    }
694
695    /**
696     * Finds {@link AccessibilityNodeInfo}s by text. The match is case
697     * insensitive containment. The search is relative to this info i.e.
698     * this info is the root of the traversed tree.
699     *
700     * <p>
701     *   <strong>Note:</strong> It is a client responsibility to recycle the
702     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
703     *     to avoid creating of multiple instances.
704     * </p>
705     *
706     * @param text The searched text.
707     * @return A list of node info.
708     */
709    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
710        enforceSealed();
711        if (!canPerformRequestOverConnection(mSourceNodeId)) {
712            return Collections.emptyList();
713        }
714        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
715        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
716                text);
717    }
718
719    /**
720     * Gets the parent.
721     * <p>
722     *   <strong>Note:</strong> It is a client responsibility to recycle the
723     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
724     *     to avoid creating of multiple instances.
725     * </p>
726     *
727     * @return The parent.
728     */
729    public AccessibilityNodeInfo getParent() {
730        enforceSealed();
731        if (!canPerformRequestOverConnection(mParentNodeId)) {
732            return null;
733        }
734        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
735        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
736                mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
737    }
738
739    /**
740     * @return The parent node id.
741     *
742     * @hide
743     */
744    public long getParentNodeId() {
745        return mParentNodeId;
746    }
747
748    /**
749     * Sets the parent.
750     * <p>
751     *   <strong>Note:</strong> Cannot be called from an
752     *   {@link android.accessibilityservice.AccessibilityService}.
753     *   This class is made immutable before being delivered to an AccessibilityService.
754     * </p>
755     *
756     * @param parent The parent.
757     *
758     * @throws IllegalStateException If called from an AccessibilityService.
759     */
760    public void setParent(View parent) {
761        setParent(parent, UNDEFINED);
762    }
763
764    /**
765     * Sets the parent to be a virtual descendant of the given <code>root</code>.
766     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
767     * is set as the parent.
768     * <p>
769     * A virtual descendant is an imaginary View that is reported as a part of the view
770     * hierarchy for accessibility purposes. This enables custom views that draw complex
771     * content to report them selves as a tree of virtual views, thus conveying their
772     * logical structure.
773     * </p>
774     * <p>
775     *   <strong>Note:</strong> Cannot be called from an
776     *   {@link android.accessibilityservice.AccessibilityService}.
777     *   This class is made immutable before being delivered to an AccessibilityService.
778     * </p>
779     *
780     * @param root The root of the virtual subtree.
781     * @param virtualDescendantId The id of the virtual descendant.
782     */
783    public void setParent(View root, int virtualDescendantId) {
784        enforceNotSealed();
785        final int rootAccessibilityViewId =
786            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
787        mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
788    }
789
790    /**
791     * Gets the node bounds in parent coordinates.
792     *
793     * @param outBounds The output node bounds.
794     */
795    public void getBoundsInParent(Rect outBounds) {
796        outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
797                mBoundsInParent.right, mBoundsInParent.bottom);
798    }
799
800    /**
801     * Sets the node bounds in parent coordinates.
802     * <p>
803     *   <strong>Note:</strong> Cannot be called from an
804     *   {@link android.accessibilityservice.AccessibilityService}.
805     *   This class is made immutable before being delivered to an AccessibilityService.
806     * </p>
807     *
808     * @param bounds The node bounds.
809     *
810     * @throws IllegalStateException If called from an AccessibilityService.
811     */
812    public void setBoundsInParent(Rect bounds) {
813        enforceNotSealed();
814        mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
815    }
816
817    /**
818     * Gets the node bounds in screen coordinates.
819     *
820     * @param outBounds The output node bounds.
821     */
822    public void getBoundsInScreen(Rect outBounds) {
823        outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
824                mBoundsInScreen.right, mBoundsInScreen.bottom);
825    }
826
827    /**
828     * Sets the node bounds in screen coordinates.
829     * <p>
830     *   <strong>Note:</strong> Cannot be called from an
831     *   {@link android.accessibilityservice.AccessibilityService}.
832     *   This class is made immutable before being delivered to an AccessibilityService.
833     * </p>
834     *
835     * @param bounds The node bounds.
836     *
837     * @throws IllegalStateException If called from an AccessibilityService.
838     */
839    public void setBoundsInScreen(Rect bounds) {
840        enforceNotSealed();
841        mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
842        mBoundsInScreen.offset(mActualAndReportedWindowLeftDelta, mActualAndReportedWindowTopDelta);
843    }
844
845    /**
846     * Gets whether this node is checkable.
847     *
848     * @return True if the node is checkable.
849     */
850    public boolean isCheckable() {
851        return getBooleanProperty(PROPERTY_CHECKABLE);
852    }
853
854    /**
855     * Sets whether this node is checkable.
856     * <p>
857     *   <strong>Note:</strong> Cannot be called from an
858     *   {@link android.accessibilityservice.AccessibilityService}.
859     *   This class is made immutable before being delivered to an AccessibilityService.
860     * </p>
861     *
862     * @param checkable True if the node is checkable.
863     *
864     * @throws IllegalStateException If called from an AccessibilityService.
865     */
866    public void setCheckable(boolean checkable) {
867        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
868    }
869
870    /**
871     * Gets whether this node is checked.
872     *
873     * @return True if the node is checked.
874     */
875    public boolean isChecked() {
876        return getBooleanProperty(PROPERTY_CHECKED);
877    }
878
879    /**
880     * Sets whether this node is checked.
881     * <p>
882     *   <strong>Note:</strong> Cannot be called from an
883     *   {@link android.accessibilityservice.AccessibilityService}.
884     *   This class is made immutable before being delivered to an AccessibilityService.
885     * </p>
886     *
887     * @param checked True if the node is checked.
888     *
889     * @throws IllegalStateException If called from an AccessibilityService.
890     */
891    public void setChecked(boolean checked) {
892        setBooleanProperty(PROPERTY_CHECKED, checked);
893    }
894
895    /**
896     * Gets whether this node is focusable.
897     *
898     * @return True if the node is focusable.
899     */
900    public boolean isFocusable() {
901        return getBooleanProperty(PROPERTY_FOCUSABLE);
902    }
903
904    /**
905     * Sets whether this node is focusable.
906     * <p>
907     *   <strong>Note:</strong> Cannot be called from an
908     *   {@link android.accessibilityservice.AccessibilityService}.
909     *   This class is made immutable before being delivered to an AccessibilityService.
910     * </p>
911     *
912     * @param focusable True if the node is focusable.
913     *
914     * @throws IllegalStateException If called from an AccessibilityService.
915     */
916    public void setFocusable(boolean focusable) {
917        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
918    }
919
920    /**
921     * Gets whether this node is focused.
922     *
923     * @return True if the node is focused.
924     */
925    public boolean isFocused() {
926        return getBooleanProperty(PROPERTY_FOCUSED);
927    }
928
929    /**
930     * Sets whether this node is focused.
931     * <p>
932     *   <strong>Note:</strong> Cannot be called from an
933     *   {@link android.accessibilityservice.AccessibilityService}.
934     *   This class is made immutable before being delivered to an AccessibilityService.
935     * </p>
936     *
937     * @param focused True if the node is focused.
938     *
939     * @throws IllegalStateException If called from an AccessibilityService.
940     */
941    public void setFocused(boolean focused) {
942        setBooleanProperty(PROPERTY_FOCUSED, focused);
943    }
944
945    /**
946     * Sets whether this node is visible to the user.
947     *
948     * @return Whether the node is visible to the user.
949     */
950    public boolean isVisibleToUser() {
951        return getBooleanProperty(PROPERTY_VISIBLE_TO_USER);
952    }
953
954    /**
955     * Sets whether this node is visible to the user.
956     * <p>
957     *   <strong>Note:</strong> Cannot be called from an
958     *   {@link android.accessibilityservice.AccessibilityService}.
959     *   This class is made immutable before being delivered to an AccessibilityService.
960     * </p>
961     *
962     * @param visibleToUser Whether the node is visible to the user.
963     *
964     * @throws IllegalStateException If called from an AccessibilityService.
965     */
966    public void setVisibleToUser(boolean visibleToUser) {
967        setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser);
968    }
969
970    /**
971     * Gets whether this node is accessibility focused.
972     *
973     * @return True if the node is accessibility focused.
974     */
975    public boolean isAccessibilityFocused() {
976        return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
977    }
978
979    /**
980     * Sets whether this node is accessibility focused.
981     * <p>
982     *   <strong>Note:</strong> Cannot be called from an
983     *   {@link android.accessibilityservice.AccessibilityService}.
984     *   This class is made immutable before being delivered to an AccessibilityService.
985     * </p>
986     *
987     * @param focused True if the node is accessibility focused.
988     *
989     * @throws IllegalStateException If called from an AccessibilityService.
990     */
991    public void setAccessibilityFocused(boolean focused) {
992        setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
993    }
994
995    /**
996     * Gets whether this node is selected.
997     *
998     * @return True if the node is selected.
999     */
1000    public boolean isSelected() {
1001        return getBooleanProperty(PROPERTY_SELECTED);
1002    }
1003
1004    /**
1005     * Sets whether this node is selected.
1006     * <p>
1007     *   <strong>Note:</strong> Cannot be called from an
1008     *   {@link android.accessibilityservice.AccessibilityService}.
1009     *   This class is made immutable before being delivered to an AccessibilityService.
1010     * </p>
1011     *
1012     * @param selected True if the node is selected.
1013     *
1014     * @throws IllegalStateException If called from an AccessibilityService.
1015     */
1016    public void setSelected(boolean selected) {
1017        setBooleanProperty(PROPERTY_SELECTED, selected);
1018    }
1019
1020    /**
1021     * Gets whether this node is clickable.
1022     *
1023     * @return True if the node is clickable.
1024     */
1025    public boolean isClickable() {
1026        return getBooleanProperty(PROPERTY_CLICKABLE);
1027    }
1028
1029    /**
1030     * Sets whether this node is clickable.
1031     * <p>
1032     *   <strong>Note:</strong> Cannot be called from an
1033     *   {@link android.accessibilityservice.AccessibilityService}.
1034     *   This class is made immutable before being delivered to an AccessibilityService.
1035     * </p>
1036     *
1037     * @param clickable True if the node is clickable.
1038     *
1039     * @throws IllegalStateException If called from an AccessibilityService.
1040     */
1041    public void setClickable(boolean clickable) {
1042        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
1043    }
1044
1045    /**
1046     * Gets whether this node is long clickable.
1047     *
1048     * @return True if the node is long clickable.
1049     */
1050    public boolean isLongClickable() {
1051        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
1052    }
1053
1054    /**
1055     * Sets whether this node is long clickable.
1056     * <p>
1057     *   <strong>Note:</strong> Cannot be called from an
1058     *   {@link android.accessibilityservice.AccessibilityService}.
1059     *   This class is made immutable before being delivered to an AccessibilityService.
1060     * </p>
1061     *
1062     * @param longClickable True if the node is long clickable.
1063     *
1064     * @throws IllegalStateException If called from an AccessibilityService.
1065     */
1066    public void setLongClickable(boolean longClickable) {
1067        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
1068    }
1069
1070    /**
1071     * Gets whether this node is enabled.
1072     *
1073     * @return True if the node is enabled.
1074     */
1075    public boolean isEnabled() {
1076        return getBooleanProperty(PROPERTY_ENABLED);
1077    }
1078
1079    /**
1080     * Sets whether this node is enabled.
1081     * <p>
1082     *   <strong>Note:</strong> Cannot be called from an
1083     *   {@link android.accessibilityservice.AccessibilityService}.
1084     *   This class is made immutable before being delivered to an AccessibilityService.
1085     * </p>
1086     *
1087     * @param enabled True if the node is enabled.
1088     *
1089     * @throws IllegalStateException If called from an AccessibilityService.
1090     */
1091    public void setEnabled(boolean enabled) {
1092        setBooleanProperty(PROPERTY_ENABLED, enabled);
1093    }
1094
1095    /**
1096     * Gets whether this node is a password.
1097     *
1098     * @return True if the node is a password.
1099     */
1100    public boolean isPassword() {
1101        return getBooleanProperty(PROPERTY_PASSWORD);
1102    }
1103
1104    /**
1105     * Sets whether this node is a password.
1106     * <p>
1107     *   <strong>Note:</strong> Cannot be called from an
1108     *   {@link android.accessibilityservice.AccessibilityService}.
1109     *   This class is made immutable before being delivered to an AccessibilityService.
1110     * </p>
1111     *
1112     * @param password True if the node is a password.
1113     *
1114     * @throws IllegalStateException If called from an AccessibilityService.
1115     */
1116    public void setPassword(boolean password) {
1117        setBooleanProperty(PROPERTY_PASSWORD, password);
1118    }
1119
1120    /**
1121     * Gets if the node is scrollable.
1122     *
1123     * @return True if the node is scrollable, false otherwise.
1124     */
1125    public boolean isScrollable() {
1126        return getBooleanProperty(PROPERTY_SCROLLABLE);
1127    }
1128
1129    /**
1130     * Sets if the node is scrollable.
1131     * <p>
1132     *   <strong>Note:</strong> Cannot be called from an
1133     *   {@link android.accessibilityservice.AccessibilityService}.
1134     *   This class is made immutable before being delivered to an AccessibilityService.
1135     * </p>
1136     *
1137     * @param scrollable True if the node is scrollable, false otherwise.
1138     *
1139     * @throws IllegalStateException If called from an AccessibilityService.
1140     */
1141    public void setScrollable(boolean scrollable) {
1142        enforceNotSealed();
1143        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
1144    }
1145
1146    /**
1147     * Gets the package this node comes from.
1148     *
1149     * @return The package name.
1150     */
1151    public CharSequence getPackageName() {
1152        return mPackageName;
1153    }
1154
1155    /**
1156     * Sets the package this node comes from.
1157     * <p>
1158     *   <strong>Note:</strong> Cannot be called from an
1159     *   {@link android.accessibilityservice.AccessibilityService}.
1160     *   This class is made immutable before being delivered to an AccessibilityService.
1161     * </p>
1162     *
1163     * @param packageName The package name.
1164     *
1165     * @throws IllegalStateException If called from an AccessibilityService.
1166     */
1167    public void setPackageName(CharSequence packageName) {
1168        enforceNotSealed();
1169        mPackageName = packageName;
1170    }
1171
1172    /**
1173     * Gets the class this node comes from.
1174     *
1175     * @return The class name.
1176     */
1177    public CharSequence getClassName() {
1178        return mClassName;
1179    }
1180
1181    /**
1182     * Sets the class this node comes from.
1183     * <p>
1184     *   <strong>Note:</strong> Cannot be called from an
1185     *   {@link android.accessibilityservice.AccessibilityService}.
1186     *   This class is made immutable before being delivered to an AccessibilityService.
1187     * </p>
1188     *
1189     * @param className The class name.
1190     *
1191     * @throws IllegalStateException If called from an AccessibilityService.
1192     */
1193    public void setClassName(CharSequence className) {
1194        enforceNotSealed();
1195        mClassName = className;
1196    }
1197
1198    /**
1199     * Gets the text of this node.
1200     *
1201     * @return The text.
1202     */
1203    public CharSequence getText() {
1204        return mText;
1205    }
1206
1207    /**
1208     * Sets the text of this node.
1209     * <p>
1210     *   <strong>Note:</strong> Cannot be called from an
1211     *   {@link android.accessibilityservice.AccessibilityService}.
1212     *   This class is made immutable before being delivered to an AccessibilityService.
1213     * </p>
1214     *
1215     * @param text The text.
1216     *
1217     * @throws IllegalStateException If called from an AccessibilityService.
1218     */
1219    public void setText(CharSequence text) {
1220        enforceNotSealed();
1221        mText = text;
1222    }
1223
1224    /**
1225     * Gets the content description of this node.
1226     *
1227     * @return The content description.
1228     */
1229    public CharSequence getContentDescription() {
1230        return mContentDescription;
1231    }
1232
1233    /**
1234     * Sets the content description of this node.
1235     * <p>
1236     *   <strong>Note:</strong> Cannot be called from an
1237     *   {@link android.accessibilityservice.AccessibilityService}.
1238     *   This class is made immutable before being delivered to an AccessibilityService.
1239     * </p>
1240     *
1241     * @param contentDescription The content description.
1242     *
1243     * @throws IllegalStateException If called from an AccessibilityService.
1244     */
1245    public void setContentDescription(CharSequence contentDescription) {
1246        enforceNotSealed();
1247        mContentDescription = contentDescription;
1248    }
1249
1250    /**
1251     * Gets the value of a boolean property.
1252     *
1253     * @param property The property.
1254     * @return The value.
1255     */
1256    private boolean getBooleanProperty(int property) {
1257        return (mBooleanProperties & property) != 0;
1258    }
1259
1260    /**
1261     * Sets a boolean property.
1262     *
1263     * @param property The property.
1264     * @param value The value.
1265     *
1266     * @throws IllegalStateException If called from an AccessibilityService.
1267     */
1268    private void setBooleanProperty(int property, boolean value) {
1269        enforceNotSealed();
1270        if (value) {
1271            mBooleanProperties |= property;
1272        } else {
1273            mBooleanProperties &= ~property;
1274        }
1275    }
1276
1277    /**
1278     * Sets the unique id of the IAccessibilityServiceConnection over which
1279     * this instance can send requests to the system.
1280     *
1281     * @param connectionId The connection id.
1282     *
1283     * @hide
1284     */
1285    public void setConnectionId(int connectionId) {
1286        enforceNotSealed();
1287        mConnectionId = connectionId;
1288    }
1289
1290    /**
1291     * {@inheritDoc}
1292     */
1293    public int describeContents() {
1294        return 0;
1295    }
1296
1297    /**
1298     * Gets the id of the source node.
1299     *
1300     * @return The id.
1301     *
1302     * @hide
1303     */
1304    public long getSourceNodeId() {
1305        return mSourceNodeId;
1306    }
1307
1308    /**
1309     * Sets if this instance is sealed.
1310     *
1311     * @param sealed Whether is sealed.
1312     *
1313     * @hide
1314     */
1315    public void setSealed(boolean sealed) {
1316        mSealed = sealed;
1317    }
1318
1319    /**
1320     * Gets if this instance is sealed.
1321     *
1322     * @return Whether is sealed.
1323     *
1324     * @hide
1325     */
1326    public boolean isSealed() {
1327        return mSealed;
1328    }
1329
1330    /**
1331     * Enforces that this instance is sealed.
1332     *
1333     * @throws IllegalStateException If this instance is not sealed.
1334     *
1335     * @hide
1336     */
1337    protected void enforceSealed() {
1338        if (!isSealed()) {
1339            throw new IllegalStateException("Cannot perform this "
1340                    + "action on a not sealed instance.");
1341        }
1342    }
1343
1344    private void enforceValidFocusDirection(int direction) {
1345        switch (direction) {
1346            case View.FOCUS_DOWN:
1347            case View.FOCUS_UP:
1348            case View.FOCUS_LEFT:
1349            case View.FOCUS_RIGHT:
1350            case View.FOCUS_FORWARD:
1351            case View.FOCUS_BACKWARD:
1352            case View.ACCESSIBILITY_FOCUS_DOWN:
1353            case View.ACCESSIBILITY_FOCUS_UP:
1354            case View.ACCESSIBILITY_FOCUS_LEFT:
1355            case View.ACCESSIBILITY_FOCUS_RIGHT:
1356            case View.ACCESSIBILITY_FOCUS_FORWARD:
1357            case View.ACCESSIBILITY_FOCUS_BACKWARD:
1358                return;
1359            default:
1360                throw new IllegalArgumentException("Unknown direction: " + direction);
1361        }
1362    }
1363
1364    private void enforceValidFocusType(int focusType) {
1365        switch (focusType) {
1366            case FOCUS_INPUT:
1367            case FOCUS_ACCESSIBILITY:
1368                return;
1369            default:
1370                throw new IllegalArgumentException("Unknown focus type: " + focusType);
1371        }
1372    }
1373
1374    /**
1375     * Enforces that this instance is not sealed.
1376     *
1377     * @throws IllegalStateException If this instance is sealed.
1378     *
1379     * @hide
1380     */
1381    protected void enforceNotSealed() {
1382        if (isSealed()) {
1383            throw new IllegalStateException("Cannot perform this "
1384                    + "action on a sealed instance.");
1385        }
1386    }
1387
1388    /**
1389     * Returns a cached instance if such is available otherwise a new one
1390     * and sets the source.
1391     *
1392     * @param source The source view.
1393     * @return An instance.
1394     *
1395     * @see #setSource(View)
1396     */
1397    public static AccessibilityNodeInfo obtain(View source) {
1398        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1399        info.setSource(source);
1400        return info;
1401    }
1402
1403    /**
1404     * Returns a cached instance if such is available otherwise a new one
1405     * and sets the source.
1406     *
1407     * @param root The root of the virtual subtree.
1408     * @param virtualDescendantId The id of the virtual descendant.
1409     * @return An instance.
1410     *
1411     * @see #setSource(View, int)
1412     */
1413    public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
1414        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1415        info.setSource(root, virtualDescendantId);
1416        return info;
1417    }
1418
1419    /**
1420     * Returns a cached instance if such is available otherwise a new one.
1421     *
1422     * @return An instance.
1423     */
1424    public static AccessibilityNodeInfo obtain() {
1425        synchronized (sPoolLock) {
1426            if (sPool != null) {
1427                AccessibilityNodeInfo info = sPool;
1428                sPool = sPool.mNext;
1429                sPoolSize--;
1430                info.mNext = null;
1431                info.mIsInPool = false;
1432                return info;
1433            }
1434            return new AccessibilityNodeInfo();
1435        }
1436    }
1437
1438    /**
1439     * Returns a cached instance if such is available or a new one is
1440     * create. The returned instance is initialized from the given
1441     * <code>info</code>.
1442     *
1443     * @param info The other info.
1444     * @return An instance.
1445     */
1446    public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
1447        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
1448        infoClone.init(info);
1449        return infoClone;
1450    }
1451
1452    /**
1453     * Return an instance back to be reused.
1454     * <p>
1455     * <strong>Note:</strong> You must not touch the object after calling this function.
1456     *
1457     * @throws IllegalStateException If the info is already recycled.
1458     */
1459    public void recycle() {
1460        if (mIsInPool) {
1461            throw new IllegalStateException("Info already recycled!");
1462        }
1463        clear();
1464        synchronized (sPoolLock) {
1465            if (sPoolSize <= MAX_POOL_SIZE) {
1466                mNext = sPool;
1467                sPool = this;
1468                mIsInPool = true;
1469                sPoolSize++;
1470            }
1471        }
1472    }
1473
1474    /**
1475     * {@inheritDoc}
1476     * <p>
1477     *   <strong>Note:</strong> After the instance is written to a parcel it
1478     *      is recycled. You must not touch the object after calling this function.
1479     * </p>
1480     */
1481    public void writeToParcel(Parcel parcel, int flags) {
1482        parcel.writeInt(isSealed() ? 1 : 0);
1483        parcel.writeLong(mSourceNodeId);
1484        parcel.writeInt(mWindowId);
1485        parcel.writeLong(mParentNodeId);
1486        parcel.writeInt(mConnectionId);
1487
1488        SparseLongArray childIds = mChildNodeIds;
1489        final int childIdsSize = childIds.size();
1490        parcel.writeInt(childIdsSize);
1491        for (int i = 0; i < childIdsSize; i++) {
1492            parcel.writeLong(childIds.valueAt(i));
1493        }
1494
1495        parcel.writeInt(mBoundsInParent.top);
1496        parcel.writeInt(mBoundsInParent.bottom);
1497        parcel.writeInt(mBoundsInParent.left);
1498        parcel.writeInt(mBoundsInParent.right);
1499
1500        parcel.writeInt(mBoundsInScreen.top);
1501        parcel.writeInt(mBoundsInScreen.bottom);
1502        parcel.writeInt(mBoundsInScreen.left);
1503        parcel.writeInt(mBoundsInScreen.right);
1504
1505        parcel.writeInt(mActions);
1506
1507        parcel.writeInt(mMovementGranularities);
1508
1509        parcel.writeInt(mBooleanProperties);
1510
1511        parcel.writeCharSequence(mPackageName);
1512        parcel.writeCharSequence(mClassName);
1513        parcel.writeCharSequence(mText);
1514        parcel.writeCharSequence(mContentDescription);
1515
1516        // Since instances of this class are fetched via synchronous i.e. blocking
1517        // calls in IPCs we always recycle as soon as the instance is marshaled.
1518        recycle();
1519    }
1520
1521    /**
1522     * Initializes this instance from another one.
1523     *
1524     * @param other The other instance.
1525     */
1526    @SuppressWarnings("unchecked")
1527    private void init(AccessibilityNodeInfo other) {
1528        mSealed = other.mSealed;
1529        mSourceNodeId = other.mSourceNodeId;
1530        mParentNodeId = other.mParentNodeId;
1531        mWindowId = other.mWindowId;
1532        mConnectionId = other.mConnectionId;
1533        mBoundsInParent.set(other.mBoundsInParent);
1534        mBoundsInScreen.set(other.mBoundsInScreen);
1535        mPackageName = other.mPackageName;
1536        mClassName = other.mClassName;
1537        mText = other.mText;
1538        mContentDescription = other.mContentDescription;
1539        mActions= other.mActions;
1540        mBooleanProperties = other.mBooleanProperties;
1541        mMovementGranularities = other.mMovementGranularities;
1542        final int otherChildIdCount = other.mChildNodeIds.size();
1543        for (int i = 0; i < otherChildIdCount; i++) {
1544            mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
1545        }
1546    }
1547
1548    /**
1549     * Creates a new instance from a {@link Parcel}.
1550     *
1551     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
1552     */
1553    private void initFromParcel(Parcel parcel) {
1554        mSealed = (parcel.readInt()  == 1);
1555        mSourceNodeId = parcel.readLong();
1556        mWindowId = parcel.readInt();
1557        mParentNodeId = parcel.readLong();
1558        mConnectionId = parcel.readInt();
1559
1560        SparseLongArray childIds = mChildNodeIds;
1561        final int childrenSize = parcel.readInt();
1562        for (int i = 0; i < childrenSize; i++) {
1563            final long childId = parcel.readLong();
1564            childIds.put(i, childId);
1565        }
1566
1567        mBoundsInParent.top = parcel.readInt();
1568        mBoundsInParent.bottom = parcel.readInt();
1569        mBoundsInParent.left = parcel.readInt();
1570        mBoundsInParent.right = parcel.readInt();
1571
1572        mBoundsInScreen.top = parcel.readInt();
1573        mBoundsInScreen.bottom = parcel.readInt();
1574        mBoundsInScreen.left = parcel.readInt();
1575        mBoundsInScreen.right = parcel.readInt();
1576
1577        mActions = parcel.readInt();
1578
1579        mMovementGranularities = parcel.readInt();
1580
1581        mBooleanProperties = parcel.readInt();
1582
1583        mPackageName = parcel.readCharSequence();
1584        mClassName = parcel.readCharSequence();
1585        mText = parcel.readCharSequence();
1586        mContentDescription = parcel.readCharSequence();
1587    }
1588
1589    /**
1590     * Clears the state of this instance.
1591     */
1592    private void clear() {
1593        mSealed = false;
1594        mSourceNodeId = ROOT_NODE_ID;
1595        mParentNodeId = ROOT_NODE_ID;
1596        mWindowId = UNDEFINED;
1597        mConnectionId = UNDEFINED;
1598        mMovementGranularities = 0;
1599        mChildNodeIds.clear();
1600        mBoundsInParent.set(0, 0, 0, 0);
1601        mBoundsInScreen.set(0, 0, 0, 0);
1602        mBooleanProperties = 0;
1603        mPackageName = null;
1604        mClassName = null;
1605        mText = null;
1606        mContentDescription = null;
1607        mActions = 0;
1608    }
1609
1610    /**
1611     * Gets the human readable action symbolic name.
1612     *
1613     * @param action The action.
1614     * @return The symbolic name.
1615     */
1616    private static String getActionSymbolicName(int action) {
1617        switch (action) {
1618            case ACTION_FOCUS:
1619                return "ACTION_FOCUS";
1620            case ACTION_CLEAR_FOCUS:
1621                return "ACTION_CLEAR_FOCUS";
1622            case ACTION_SELECT:
1623                return "ACTION_SELECT";
1624            case ACTION_CLEAR_SELECTION:
1625                return "ACTION_CLEAR_SELECTION";
1626            case ACTION_CLICK:
1627                return "ACTION_CLICK";
1628            case ACTION_LONG_CLICK:
1629                return "ACTION_LONG_CLICK";
1630            case ACTION_ACCESSIBILITY_FOCUS:
1631                return "ACTION_ACCESSIBILITY_FOCUS";
1632            case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
1633                return "ACTION_CLEAR_ACCESSIBILITY_FOCUS";
1634            case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
1635                return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY";
1636            case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
1637                return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY";
1638            case ACTION_NEXT_HTML_ELEMENT:
1639                return "ACTION_NEXT_HTML_ELEMENT";
1640            case ACTION_PREVIOUS_HTML_ELEMENT:
1641                return "ACTION_PREVIOUS_HTML_ELEMENT";
1642            case ACTION_SCROLL_FORWARD:
1643                return "ACTION_SCROLL_FORWARD";
1644            case ACTION_SCROLL_BACKWARD:
1645                return "ACTION_SCROLL_BACKWARD";
1646            default:
1647                throw new IllegalArgumentException("Unknown action: " + action);
1648        }
1649    }
1650
1651    /**
1652     * Gets the human readable movement granularity symbolic name.
1653     *
1654     * @param granularity The granularity.
1655     * @return The symbolic name.
1656     */
1657    private static String getMovementGranularitySymbolicName(int granularity) {
1658        switch (granularity) {
1659            case MOVEMENT_GRANULARITY_CHARACTER:
1660                return "MOVEMENT_GRANULARITY_CHARACTER";
1661            case MOVEMENT_GRANULARITY_WORD:
1662                return "MOVEMENT_GRANULARITY_WORD";
1663            case MOVEMENT_GRANULARITY_LINE:
1664                return "MOVEMENT_GRANULARITY_LINE";
1665            case MOVEMENT_GRANULARITY_PARAGRAPH:
1666                return "MOVEMENT_GRANULARITY_PARAGRAPH";
1667            case MOVEMENT_GRANULARITY_PAGE:
1668                return "MOVEMENT_GRANULARITY_PAGE";
1669            default:
1670                throw new IllegalArgumentException("Unknown movement granularity: " + granularity);
1671        }
1672    }
1673
1674    private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
1675        return (mWindowId != UNDEFINED
1676                && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
1677                && mConnectionId != UNDEFINED);
1678    }
1679
1680    @Override
1681    public boolean equals(Object object) {
1682        if (this == object) {
1683            return true;
1684        }
1685        if (object == null) {
1686            return false;
1687        }
1688        if (getClass() != object.getClass()) {
1689            return false;
1690        }
1691        AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
1692        if (mSourceNodeId != other.mSourceNodeId) {
1693            return false;
1694        }
1695        if (mWindowId != other.mWindowId) {
1696            return false;
1697        }
1698        return true;
1699    }
1700
1701    @Override
1702    public int hashCode() {
1703        final int prime = 31;
1704        int result = 1;
1705        result = prime * result + getAccessibilityViewId(mSourceNodeId);
1706        result = prime * result + getVirtualDescendantId(mSourceNodeId);
1707        result = prime * result + mWindowId;
1708        return result;
1709    }
1710
1711    @Override
1712    public String toString() {
1713        StringBuilder builder = new StringBuilder();
1714        builder.append(super.toString());
1715
1716        if (DEBUG) {
1717            builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
1718            builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
1719            builder.append("; mParentNodeId: " + mParentNodeId);
1720
1721            int granularities = mMovementGranularities;
1722            builder.append("; MovementGranularities: [");
1723            while (granularities != 0) {
1724                final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
1725                granularities &= ~granularity;
1726                builder.append(getMovementGranularitySymbolicName(granularity));
1727                if (granularities != 0) {
1728                    builder.append(", ");
1729                }
1730            }
1731            builder.append("]");
1732
1733            SparseLongArray childIds = mChildNodeIds;
1734            builder.append("; childAccessibilityIds: [");
1735            for (int i = 0, count = childIds.size(); i < count; i++) {
1736                builder.append(childIds.valueAt(i));
1737                if (i < count - 1) {
1738                    builder.append(", ");
1739                }
1740            }
1741            builder.append("]");
1742        }
1743
1744        builder.append("; boundsInParent: " + mBoundsInParent);
1745        builder.append("; boundsInScreen: " + mBoundsInScreen);
1746
1747        builder.append("; packageName: ").append(mPackageName);
1748        builder.append("; className: ").append(mClassName);
1749        builder.append("; text: ").append(mText);
1750        builder.append("; contentDescription: ").append(mContentDescription);
1751
1752        builder.append("; checkable: ").append(isCheckable());
1753        builder.append("; checked: ").append(isChecked());
1754        builder.append("; focusable: ").append(isFocusable());
1755        builder.append("; focused: ").append(isFocused());
1756        builder.append("; selected: ").append(isSelected());
1757        builder.append("; clickable: ").append(isClickable());
1758        builder.append("; longClickable: ").append(isLongClickable());
1759        builder.append("; enabled: ").append(isEnabled());
1760        builder.append("; password: ").append(isPassword());
1761        builder.append("; scrollable: " + isScrollable());
1762
1763        builder.append("; [");
1764        for (int actionBits = mActions; actionBits != 0;) {
1765            final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
1766            actionBits &= ~action;
1767            builder.append(getActionSymbolicName(action));
1768            if (actionBits != 0) {
1769                builder.append(", ");
1770            }
1771        }
1772        builder.append("]");
1773
1774        return builder.toString();
1775    }
1776
1777    /**
1778     * @see Parcelable.Creator
1779     */
1780    public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
1781            new Parcelable.Creator<AccessibilityNodeInfo>() {
1782        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
1783            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1784            info.initFromParcel(parcel);
1785            return info;
1786        }
1787
1788        public AccessibilityNodeInfo[] newArray(int size) {
1789            return new AccessibilityNodeInfo[size];
1790        }
1791    };
1792}
1793