AccessibilityNodeInfo.java revision 2ef6905003c20010032ee993dfcc5899ad9be6f8
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    /**
386     * Hide constructor from clients.
387     */
388    private AccessibilityNodeInfo() {
389        /* do nothing */
390    }
391
392    /**
393     * Sets the source.
394     * <p>
395     *   <strong>Note:</strong> Cannot be called from an
396     *   {@link android.accessibilityservice.AccessibilityService}.
397     *   This class is made immutable before being delivered to an AccessibilityService.
398     * </p>
399     *
400     * @param source The info source.
401     */
402    public void setSource(View source) {
403        setSource(source, UNDEFINED);
404    }
405
406    /**
407     * Sets the source to be a virtual descendant of the given <code>root</code>.
408     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
409     * is set as the source.
410     * <p>
411     * A virtual descendant is an imaginary View that is reported as a part of the view
412     * hierarchy for accessibility purposes. This enables custom views that draw complex
413     * content to report themselves as a tree of virtual views, thus conveying their
414     * logical structure.
415     * </p>
416     * <p>
417     *   <strong>Note:</strong> Cannot be called from an
418     *   {@link android.accessibilityservice.AccessibilityService}.
419     *   This class is made immutable before being delivered to an AccessibilityService.
420     * </p>
421     *
422     * @param root The root of the virtual subtree.
423     * @param virtualDescendantId The id of the virtual descendant.
424     */
425    public void setSource(View root, int virtualDescendantId) {
426        enforceNotSealed();
427        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
428        final int rootAccessibilityViewId =
429            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
430        mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
431    }
432
433    /**
434     * Find the view that has the specified focus type. The search starts from
435     * the view represented by this node info.
436     *
437     * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
438     *         {@link #FOCUS_ACCESSIBILITY}.
439     * @return The node info of the focused view or null.
440     *
441     * @see #FOCUS_INPUT
442     * @see #FOCUS_ACCESSIBILITY
443     */
444    public AccessibilityNodeInfo findFocus(int focus) {
445        enforceSealed();
446        enforceValidFocusType(focus);
447        if (!canPerformRequestOverConnection(mSourceNodeId)) {
448            return null;
449        }
450        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
451                mSourceNodeId, focus);
452    }
453
454    /**
455     * Searches for the nearest view in the specified direction that can take
456     * the input focus.
457     *
458     * @param direction The direction. Can be one of:
459     *     {@link View#FOCUS_DOWN},
460     *     {@link View#FOCUS_UP},
461     *     {@link View#FOCUS_LEFT},
462     *     {@link View#FOCUS_RIGHT},
463     *     {@link View#FOCUS_FORWARD},
464     *     {@link View#FOCUS_BACKWARD},
465     *     {@link View#ACCESSIBILITY_FOCUS_FORWARD},
466     *     {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
467     *     {@link View#ACCESSIBILITY_FOCUS_UP},
468     *     {@link View#ACCESSIBILITY_FOCUS_RIGHT},
469     *     {@link View#ACCESSIBILITY_FOCUS_DOWN},
470     *     {@link View#ACCESSIBILITY_FOCUS_LEFT}.
471     *
472     * @return The node info for the view that can take accessibility focus.
473     */
474    public AccessibilityNodeInfo focusSearch(int direction) {
475        enforceSealed();
476        enforceValidFocusDirection(direction);
477        if (!canPerformRequestOverConnection(mSourceNodeId)) {
478            return null;
479        }
480        return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
481                mSourceNodeId, direction);
482    }
483
484    /**
485     * Gets the id of the window from which the info comes from.
486     *
487     * @return The window id.
488     */
489    public int getWindowId() {
490        return mWindowId;
491    }
492
493    /**
494     * @return The ids of the children.
495     *
496     * @hide
497     */
498    public SparseLongArray getChildNodeIds() {
499        return mChildNodeIds;
500    }
501
502    /**
503     * Gets the number of children.
504     *
505     * @return The child count.
506     */
507    public int getChildCount() {
508        return mChildNodeIds.size();
509    }
510
511    /**
512     * Get the child at given index.
513     * <p>
514     *   <strong>Note:</strong> It is a client responsibility to recycle the
515     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
516     *     to avoid creating of multiple instances.
517     * </p>
518     *
519     * @param index The child index.
520     * @return The child node.
521     *
522     * @throws IllegalStateException If called outside of an AccessibilityService.
523     *
524     */
525    public AccessibilityNodeInfo getChild(int index) {
526        enforceSealed();
527        if (!canPerformRequestOverConnection(mSourceNodeId)) {
528            return null;
529        }
530        final long childId = mChildNodeIds.get(index);
531        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
532        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
533                childId, FLAG_PREFETCH_DESCENDANTS);
534    }
535
536    /**
537     * Adds a child.
538     * <p>
539     * <strong>Note:</strong> Cannot be called from an
540     * {@link android.accessibilityservice.AccessibilityService}.
541     * This class is made immutable before being delivered to an AccessibilityService.
542     * </p>
543     *
544     * @param child The child.
545     *
546     * @throws IllegalStateException If called from an AccessibilityService.
547     */
548    public void addChild(View child) {
549        addChild(child, UNDEFINED);
550    }
551
552    /**
553     * Adds a virtual child which is a descendant of the given <code>root</code>.
554     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
555     * is added as a child.
556     * <p>
557     * A virtual descendant is an imaginary View that is reported as a part of the view
558     * hierarchy for accessibility purposes. This enables custom views that draw complex
559     * content to report them selves as a tree of virtual views, thus conveying their
560     * logical structure.
561     * </p>
562     *
563     * @param root The root of the virtual subtree.
564     * @param virtualDescendantId The id of the virtual child.
565     */
566    public void addChild(View root, int virtualDescendantId) {
567        enforceNotSealed();
568        final int index = mChildNodeIds.size();
569        final int rootAccessibilityViewId =
570            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
571        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
572        mChildNodeIds.put(index, childNodeId);
573    }
574
575    /**
576     * Gets the actions that can be performed on the node.
577     *
578     * @return The bit mask of with actions.
579     *
580     * @see AccessibilityNodeInfo#ACTION_FOCUS
581     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
582     * @see AccessibilityNodeInfo#ACTION_SELECT
583     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
584     * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
585     * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
586     * @see AccessibilityNodeInfo#ACTION_CLICK
587     * @see AccessibilityNodeInfo#ACTION_LONG_CLICK
588     * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
589     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
590     * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
591     * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
592     * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
593     * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
594     */
595    public int getActions() {
596        return mActions;
597    }
598
599    /**
600     * Adds an action that can be performed on the node.
601     * <p>
602     *   <strong>Note:</strong> Cannot be called from an
603     *   {@link android.accessibilityservice.AccessibilityService}.
604     *   This class is made immutable before being delivered to an AccessibilityService.
605     * </p>
606     *
607     * @param action The action.
608     *
609     * @throws IllegalStateException If called from an AccessibilityService.
610     */
611    public void addAction(int action) {
612        enforceNotSealed();
613        mActions |= action;
614    }
615
616    /**
617     * Sets the movement granularities for traversing the text of this node.
618     * <p>
619     *   <strong>Note:</strong> Cannot be called from an
620     *   {@link android.accessibilityservice.AccessibilityService}.
621     *   This class is made immutable before being delivered to an AccessibilityService.
622     * </p>
623     *
624     * @param granularities The bit mask with granularities.
625     *
626     * @throws IllegalStateException If called from an AccessibilityService.
627     */
628    public void setMovementGranularities(int granularities) {
629        enforceNotSealed();
630        mMovementGranularities = granularities;
631    }
632
633    /**
634     * Gets the movement granularities for traversing the text of this node.
635     *
636     * @return The bit mask with granularities.
637     */
638    public int getMovementGranularities() {
639        return mMovementGranularities;
640    }
641
642    /**
643     * Performs an action on the node.
644     * <p>
645     *   <strong>Note:</strong> An action can be performed only if the request is made
646     *   from an {@link android.accessibilityservice.AccessibilityService}.
647     * </p>
648     *
649     * @param action The action to perform.
650     * @return True if the action was performed.
651     *
652     * @throws IllegalStateException If called outside of an AccessibilityService.
653     */
654    public boolean performAction(int action) {
655        enforceSealed();
656        if (!canPerformRequestOverConnection(mSourceNodeId)) {
657            return false;
658        }
659        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
660        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
661                action, null);
662    }
663
664    /**
665     * Performs an action on the node.
666     * <p>
667     *   <strong>Note:</strong> An action can be performed only if the request is made
668     *   from an {@link android.accessibilityservice.AccessibilityService}.
669     * </p>
670     *
671     * @param action The action to perform.
672     * @param arguments A bundle with additional arguments.
673     * @return True if the action was performed.
674     *
675     * @throws IllegalStateException If called outside of an AccessibilityService.
676     */
677    public boolean performAction(int action, Bundle arguments) {
678        enforceSealed();
679        if (!canPerformRequestOverConnection(mSourceNodeId)) {
680            return false;
681        }
682        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
683        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
684                action, arguments);
685    }
686
687    /**
688     * Finds {@link AccessibilityNodeInfo}s by text. The match is case
689     * insensitive containment. The search is relative to this info i.e.
690     * this info is the root of the traversed tree.
691     *
692     * <p>
693     *   <strong>Note:</strong> It is a client responsibility to recycle the
694     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
695     *     to avoid creating of multiple instances.
696     * </p>
697     *
698     * @param text The searched text.
699     * @return A list of node info.
700     */
701    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
702        enforceSealed();
703        if (!canPerformRequestOverConnection(mSourceNodeId)) {
704            return Collections.emptyList();
705        }
706        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
707        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
708                text);
709    }
710
711    /**
712     * Gets the parent.
713     * <p>
714     *   <strong>Note:</strong> It is a client responsibility to recycle the
715     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
716     *     to avoid creating of multiple instances.
717     * </p>
718     *
719     * @return The parent.
720     */
721    public AccessibilityNodeInfo getParent() {
722        enforceSealed();
723        if (!canPerformRequestOverConnection(mParentNodeId)) {
724            return null;
725        }
726        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
727        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
728                mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
729    }
730
731    /**
732     * @return The parent node id.
733     *
734     * @hide
735     */
736    public long getParentNodeId() {
737        return mParentNodeId;
738    }
739
740    /**
741     * Sets the parent.
742     * <p>
743     *   <strong>Note:</strong> Cannot be called from an
744     *   {@link android.accessibilityservice.AccessibilityService}.
745     *   This class is made immutable before being delivered to an AccessibilityService.
746     * </p>
747     *
748     * @param parent The parent.
749     *
750     * @throws IllegalStateException If called from an AccessibilityService.
751     */
752    public void setParent(View parent) {
753        setParent(parent, UNDEFINED);
754    }
755
756    /**
757     * Sets the parent to be a virtual descendant of the given <code>root</code>.
758     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
759     * is set as the parent.
760     * <p>
761     * A virtual descendant is an imaginary View that is reported as a part of the view
762     * hierarchy for accessibility purposes. This enables custom views that draw complex
763     * content to report them selves as a tree of virtual views, thus conveying their
764     * logical structure.
765     * </p>
766     * <p>
767     *   <strong>Note:</strong> Cannot be called from an
768     *   {@link android.accessibilityservice.AccessibilityService}.
769     *   This class is made immutable before being delivered to an AccessibilityService.
770     * </p>
771     *
772     * @param root The root of the virtual subtree.
773     * @param virtualDescendantId The id of the virtual descendant.
774     */
775    public void setParent(View root, int virtualDescendantId) {
776        enforceNotSealed();
777        final int rootAccessibilityViewId =
778            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
779        mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
780    }
781
782    /**
783     * Gets the node bounds in parent coordinates.
784     *
785     * @param outBounds The output node bounds.
786     */
787    public void getBoundsInParent(Rect outBounds) {
788        outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
789                mBoundsInParent.right, mBoundsInParent.bottom);
790    }
791
792    /**
793     * Sets the node bounds in parent coordinates.
794     * <p>
795     *   <strong>Note:</strong> Cannot be called from an
796     *   {@link android.accessibilityservice.AccessibilityService}.
797     *   This class is made immutable before being delivered to an AccessibilityService.
798     * </p>
799     *
800     * @param bounds The node bounds.
801     *
802     * @throws IllegalStateException If called from an AccessibilityService.
803     */
804    public void setBoundsInParent(Rect bounds) {
805        enforceNotSealed();
806        mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
807    }
808
809    /**
810     * Gets the node bounds in screen coordinates.
811     *
812     * @param outBounds The output node bounds.
813     */
814    public void getBoundsInScreen(Rect outBounds) {
815        outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
816                mBoundsInScreen.right, mBoundsInScreen.bottom);
817    }
818
819    /**
820     * Sets the node bounds in screen coordinates.
821     * <p>
822     *   <strong>Note:</strong> Cannot be called from an
823     *   {@link android.accessibilityservice.AccessibilityService}.
824     *   This class is made immutable before being delivered to an AccessibilityService.
825     * </p>
826     *
827     * @param bounds The node bounds.
828     *
829     * @throws IllegalStateException If called from an AccessibilityService.
830     */
831    public void setBoundsInScreen(Rect bounds) {
832        enforceNotSealed();
833        mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
834    }
835
836    /**
837     * Gets whether this node is checkable.
838     *
839     * @return True if the node is checkable.
840     */
841    public boolean isCheckable() {
842        return getBooleanProperty(PROPERTY_CHECKABLE);
843    }
844
845    /**
846     * Sets whether this node is checkable.
847     * <p>
848     *   <strong>Note:</strong> Cannot be called from an
849     *   {@link android.accessibilityservice.AccessibilityService}.
850     *   This class is made immutable before being delivered to an AccessibilityService.
851     * </p>
852     *
853     * @param checkable True if the node is checkable.
854     *
855     * @throws IllegalStateException If called from an AccessibilityService.
856     */
857    public void setCheckable(boolean checkable) {
858        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
859    }
860
861    /**
862     * Gets whether this node is checked.
863     *
864     * @return True if the node is checked.
865     */
866    public boolean isChecked() {
867        return getBooleanProperty(PROPERTY_CHECKED);
868    }
869
870    /**
871     * Sets whether this node is checked.
872     * <p>
873     *   <strong>Note:</strong> Cannot be called from an
874     *   {@link android.accessibilityservice.AccessibilityService}.
875     *   This class is made immutable before being delivered to an AccessibilityService.
876     * </p>
877     *
878     * @param checked True if the node is checked.
879     *
880     * @throws IllegalStateException If called from an AccessibilityService.
881     */
882    public void setChecked(boolean checked) {
883        setBooleanProperty(PROPERTY_CHECKED, checked);
884    }
885
886    /**
887     * Gets whether this node is focusable.
888     *
889     * @return True if the node is focusable.
890     */
891    public boolean isFocusable() {
892        return getBooleanProperty(PROPERTY_FOCUSABLE);
893    }
894
895    /**
896     * Sets whether this node is focusable.
897     * <p>
898     *   <strong>Note:</strong> Cannot be called from an
899     *   {@link android.accessibilityservice.AccessibilityService}.
900     *   This class is made immutable before being delivered to an AccessibilityService.
901     * </p>
902     *
903     * @param focusable True if the node is focusable.
904     *
905     * @throws IllegalStateException If called from an AccessibilityService.
906     */
907    public void setFocusable(boolean focusable) {
908        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
909    }
910
911    /**
912     * Gets whether this node is focused.
913     *
914     * @return True if the node is focused.
915     */
916    public boolean isFocused() {
917        return getBooleanProperty(PROPERTY_FOCUSED);
918    }
919
920    /**
921     * Sets whether this node is focused.
922     * <p>
923     *   <strong>Note:</strong> Cannot be called from an
924     *   {@link android.accessibilityservice.AccessibilityService}.
925     *   This class is made immutable before being delivered to an AccessibilityService.
926     * </p>
927     *
928     * @param focused True if the node is focused.
929     *
930     * @throws IllegalStateException If called from an AccessibilityService.
931     */
932    public void setFocused(boolean focused) {
933        setBooleanProperty(PROPERTY_FOCUSED, focused);
934    }
935
936    /**
937     * Sets whether this node is visible to the user.
938     *
939     * @return Whether the node is visible to the user.
940     */
941    public boolean isVisibleToUser() {
942        return getBooleanProperty(PROPERTY_VISIBLE_TO_USER);
943    }
944
945    /**
946     * Sets whether this node is visible to the user.
947     * <p>
948     *   <strong>Note:</strong> Cannot be called from an
949     *   {@link android.accessibilityservice.AccessibilityService}.
950     *   This class is made immutable before being delivered to an AccessibilityService.
951     * </p>
952     *
953     * @param visibleToUser Whether the node is visible to the user.
954     *
955     * @throws IllegalStateException If called from an AccessibilityService.
956     */
957    public void setVisibleToUser(boolean visibleToUser) {
958        setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser);
959    }
960
961    /**
962     * Gets whether this node is accessibility focused.
963     *
964     * @return True if the node is accessibility focused.
965     */
966    public boolean isAccessibilityFocused() {
967        return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
968    }
969
970    /**
971     * Sets whether this node is accessibility focused.
972     * <p>
973     *   <strong>Note:</strong> Cannot be called from an
974     *   {@link android.accessibilityservice.AccessibilityService}.
975     *   This class is made immutable before being delivered to an AccessibilityService.
976     * </p>
977     *
978     * @param focused True if the node is accessibility focused.
979     *
980     * @throws IllegalStateException If called from an AccessibilityService.
981     */
982    public void setAccessibilityFocused(boolean focused) {
983        setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
984    }
985
986    /**
987     * Gets whether this node is selected.
988     *
989     * @return True if the node is selected.
990     */
991    public boolean isSelected() {
992        return getBooleanProperty(PROPERTY_SELECTED);
993    }
994
995    /**
996     * Sets whether this node is selected.
997     * <p>
998     *   <strong>Note:</strong> Cannot be called from an
999     *   {@link android.accessibilityservice.AccessibilityService}.
1000     *   This class is made immutable before being delivered to an AccessibilityService.
1001     * </p>
1002     *
1003     * @param selected True if the node is selected.
1004     *
1005     * @throws IllegalStateException If called from an AccessibilityService.
1006     */
1007    public void setSelected(boolean selected) {
1008        setBooleanProperty(PROPERTY_SELECTED, selected);
1009    }
1010
1011    /**
1012     * Gets whether this node is clickable.
1013     *
1014     * @return True if the node is clickable.
1015     */
1016    public boolean isClickable() {
1017        return getBooleanProperty(PROPERTY_CLICKABLE);
1018    }
1019
1020    /**
1021     * Sets whether this node is clickable.
1022     * <p>
1023     *   <strong>Note:</strong> Cannot be called from an
1024     *   {@link android.accessibilityservice.AccessibilityService}.
1025     *   This class is made immutable before being delivered to an AccessibilityService.
1026     * </p>
1027     *
1028     * @param clickable True if the node is clickable.
1029     *
1030     * @throws IllegalStateException If called from an AccessibilityService.
1031     */
1032    public void setClickable(boolean clickable) {
1033        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
1034    }
1035
1036    /**
1037     * Gets whether this node is long clickable.
1038     *
1039     * @return True if the node is long clickable.
1040     */
1041    public boolean isLongClickable() {
1042        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
1043    }
1044
1045    /**
1046     * Sets whether this node is long clickable.
1047     * <p>
1048     *   <strong>Note:</strong> Cannot be called from an
1049     *   {@link android.accessibilityservice.AccessibilityService}.
1050     *   This class is made immutable before being delivered to an AccessibilityService.
1051     * </p>
1052     *
1053     * @param longClickable True if the node is long clickable.
1054     *
1055     * @throws IllegalStateException If called from an AccessibilityService.
1056     */
1057    public void setLongClickable(boolean longClickable) {
1058        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
1059    }
1060
1061    /**
1062     * Gets whether this node is enabled.
1063     *
1064     * @return True if the node is enabled.
1065     */
1066    public boolean isEnabled() {
1067        return getBooleanProperty(PROPERTY_ENABLED);
1068    }
1069
1070    /**
1071     * Sets whether this node is enabled.
1072     * <p>
1073     *   <strong>Note:</strong> Cannot be called from an
1074     *   {@link android.accessibilityservice.AccessibilityService}.
1075     *   This class is made immutable before being delivered to an AccessibilityService.
1076     * </p>
1077     *
1078     * @param enabled True if the node is enabled.
1079     *
1080     * @throws IllegalStateException If called from an AccessibilityService.
1081     */
1082    public void setEnabled(boolean enabled) {
1083        setBooleanProperty(PROPERTY_ENABLED, enabled);
1084    }
1085
1086    /**
1087     * Gets whether this node is a password.
1088     *
1089     * @return True if the node is a password.
1090     */
1091    public boolean isPassword() {
1092        return getBooleanProperty(PROPERTY_PASSWORD);
1093    }
1094
1095    /**
1096     * Sets whether this node is a password.
1097     * <p>
1098     *   <strong>Note:</strong> Cannot be called from an
1099     *   {@link android.accessibilityservice.AccessibilityService}.
1100     *   This class is made immutable before being delivered to an AccessibilityService.
1101     * </p>
1102     *
1103     * @param password True if the node is a password.
1104     *
1105     * @throws IllegalStateException If called from an AccessibilityService.
1106     */
1107    public void setPassword(boolean password) {
1108        setBooleanProperty(PROPERTY_PASSWORD, password);
1109    }
1110
1111    /**
1112     * Gets if the node is scrollable.
1113     *
1114     * @return True if the node is scrollable, false otherwise.
1115     */
1116    public boolean isScrollable() {
1117        return getBooleanProperty(PROPERTY_SCROLLABLE);
1118    }
1119
1120    /**
1121     * Sets if the node is scrollable.
1122     * <p>
1123     *   <strong>Note:</strong> Cannot be called from an
1124     *   {@link android.accessibilityservice.AccessibilityService}.
1125     *   This class is made immutable before being delivered to an AccessibilityService.
1126     * </p>
1127     *
1128     * @param scrollable True if the node is scrollable, false otherwise.
1129     *
1130     * @throws IllegalStateException If called from an AccessibilityService.
1131     */
1132    public void setScrollable(boolean scrollable) {
1133        enforceNotSealed();
1134        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
1135    }
1136
1137    /**
1138     * Gets the package this node comes from.
1139     *
1140     * @return The package name.
1141     */
1142    public CharSequence getPackageName() {
1143        return mPackageName;
1144    }
1145
1146    /**
1147     * Sets the package this node comes from.
1148     * <p>
1149     *   <strong>Note:</strong> Cannot be called from an
1150     *   {@link android.accessibilityservice.AccessibilityService}.
1151     *   This class is made immutable before being delivered to an AccessibilityService.
1152     * </p>
1153     *
1154     * @param packageName The package name.
1155     *
1156     * @throws IllegalStateException If called from an AccessibilityService.
1157     */
1158    public void setPackageName(CharSequence packageName) {
1159        enforceNotSealed();
1160        mPackageName = packageName;
1161    }
1162
1163    /**
1164     * Gets the class this node comes from.
1165     *
1166     * @return The class name.
1167     */
1168    public CharSequence getClassName() {
1169        return mClassName;
1170    }
1171
1172    /**
1173     * Sets the class this node comes from.
1174     * <p>
1175     *   <strong>Note:</strong> Cannot be called from an
1176     *   {@link android.accessibilityservice.AccessibilityService}.
1177     *   This class is made immutable before being delivered to an AccessibilityService.
1178     * </p>
1179     *
1180     * @param className The class name.
1181     *
1182     * @throws IllegalStateException If called from an AccessibilityService.
1183     */
1184    public void setClassName(CharSequence className) {
1185        enforceNotSealed();
1186        mClassName = className;
1187    }
1188
1189    /**
1190     * Gets the text of this node.
1191     *
1192     * @return The text.
1193     */
1194    public CharSequence getText() {
1195        return mText;
1196    }
1197
1198    /**
1199     * Sets the text of this node.
1200     * <p>
1201     *   <strong>Note:</strong> Cannot be called from an
1202     *   {@link android.accessibilityservice.AccessibilityService}.
1203     *   This class is made immutable before being delivered to an AccessibilityService.
1204     * </p>
1205     *
1206     * @param text The text.
1207     *
1208     * @throws IllegalStateException If called from an AccessibilityService.
1209     */
1210    public void setText(CharSequence text) {
1211        enforceNotSealed();
1212        mText = text;
1213    }
1214
1215    /**
1216     * Gets the content description of this node.
1217     *
1218     * @return The content description.
1219     */
1220    public CharSequence getContentDescription() {
1221        return mContentDescription;
1222    }
1223
1224    /**
1225     * Sets the content description of this node.
1226     * <p>
1227     *   <strong>Note:</strong> Cannot be called from an
1228     *   {@link android.accessibilityservice.AccessibilityService}.
1229     *   This class is made immutable before being delivered to an AccessibilityService.
1230     * </p>
1231     *
1232     * @param contentDescription The content description.
1233     *
1234     * @throws IllegalStateException If called from an AccessibilityService.
1235     */
1236    public void setContentDescription(CharSequence contentDescription) {
1237        enforceNotSealed();
1238        mContentDescription = contentDescription;
1239    }
1240
1241    /**
1242     * Gets the value of a boolean property.
1243     *
1244     * @param property The property.
1245     * @return The value.
1246     */
1247    private boolean getBooleanProperty(int property) {
1248        return (mBooleanProperties & property) != 0;
1249    }
1250
1251    /**
1252     * Sets a boolean property.
1253     *
1254     * @param property The property.
1255     * @param value The value.
1256     *
1257     * @throws IllegalStateException If called from an AccessibilityService.
1258     */
1259    private void setBooleanProperty(int property, boolean value) {
1260        enforceNotSealed();
1261        if (value) {
1262            mBooleanProperties |= property;
1263        } else {
1264            mBooleanProperties &= ~property;
1265        }
1266    }
1267
1268    /**
1269     * Sets the unique id of the IAccessibilityServiceConnection over which
1270     * this instance can send requests to the system.
1271     *
1272     * @param connectionId The connection id.
1273     *
1274     * @hide
1275     */
1276    public void setConnectionId(int connectionId) {
1277        enforceNotSealed();
1278        mConnectionId = connectionId;
1279    }
1280
1281    /**
1282     * {@inheritDoc}
1283     */
1284    public int describeContents() {
1285        return 0;
1286    }
1287
1288    /**
1289     * Gets the id of the source node.
1290     *
1291     * @return The id.
1292     *
1293     * @hide
1294     */
1295    public long getSourceNodeId() {
1296        return mSourceNodeId;
1297    }
1298
1299    /**
1300     * Sets if this instance is sealed.
1301     *
1302     * @param sealed Whether is sealed.
1303     *
1304     * @hide
1305     */
1306    public void setSealed(boolean sealed) {
1307        mSealed = sealed;
1308    }
1309
1310    /**
1311     * Gets if this instance is sealed.
1312     *
1313     * @return Whether is sealed.
1314     *
1315     * @hide
1316     */
1317    public boolean isSealed() {
1318        return mSealed;
1319    }
1320
1321    /**
1322     * Enforces that this instance is sealed.
1323     *
1324     * @throws IllegalStateException If this instance is not sealed.
1325     *
1326     * @hide
1327     */
1328    protected void enforceSealed() {
1329        if (!isSealed()) {
1330            throw new IllegalStateException("Cannot perform this "
1331                    + "action on a not sealed instance.");
1332        }
1333    }
1334
1335    private void enforceValidFocusDirection(int direction) {
1336        switch (direction) {
1337            case View.FOCUS_DOWN:
1338            case View.FOCUS_UP:
1339            case View.FOCUS_LEFT:
1340            case View.FOCUS_RIGHT:
1341            case View.FOCUS_FORWARD:
1342            case View.FOCUS_BACKWARD:
1343            case View.ACCESSIBILITY_FOCUS_DOWN:
1344            case View.ACCESSIBILITY_FOCUS_UP:
1345            case View.ACCESSIBILITY_FOCUS_LEFT:
1346            case View.ACCESSIBILITY_FOCUS_RIGHT:
1347            case View.ACCESSIBILITY_FOCUS_FORWARD:
1348            case View.ACCESSIBILITY_FOCUS_BACKWARD:
1349                return;
1350            default:
1351                throw new IllegalArgumentException("Unknown direction: " + direction);
1352        }
1353    }
1354
1355    private void enforceValidFocusType(int focusType) {
1356        switch (focusType) {
1357            case FOCUS_INPUT:
1358            case FOCUS_ACCESSIBILITY:
1359                return;
1360            default:
1361                throw new IllegalArgumentException("Unknown focus type: " + focusType);
1362        }
1363    }
1364
1365    /**
1366     * Enforces that this instance is not sealed.
1367     *
1368     * @throws IllegalStateException If this instance is sealed.
1369     *
1370     * @hide
1371     */
1372    protected void enforceNotSealed() {
1373        if (isSealed()) {
1374            throw new IllegalStateException("Cannot perform this "
1375                    + "action on a sealed instance.");
1376        }
1377    }
1378
1379    /**
1380     * Returns a cached instance if such is available otherwise a new one
1381     * and sets the source.
1382     *
1383     * @param source The source view.
1384     * @return An instance.
1385     *
1386     * @see #setSource(View)
1387     */
1388    public static AccessibilityNodeInfo obtain(View source) {
1389        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1390        info.setSource(source);
1391        return info;
1392    }
1393
1394    /**
1395     * Returns a cached instance if such is available otherwise a new one
1396     * and sets the source.
1397     *
1398     * @param root The root of the virtual subtree.
1399     * @param virtualDescendantId The id of the virtual descendant.
1400     * @return An instance.
1401     *
1402     * @see #setSource(View, int)
1403     */
1404    public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
1405        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1406        info.setSource(root, virtualDescendantId);
1407        return info;
1408    }
1409
1410    /**
1411     * Returns a cached instance if such is available otherwise a new one.
1412     *
1413     * @return An instance.
1414     */
1415    public static AccessibilityNodeInfo obtain() {
1416        synchronized (sPoolLock) {
1417            if (sPool != null) {
1418                AccessibilityNodeInfo info = sPool;
1419                sPool = sPool.mNext;
1420                sPoolSize--;
1421                info.mNext = null;
1422                info.mIsInPool = false;
1423                return info;
1424            }
1425            return new AccessibilityNodeInfo();
1426        }
1427    }
1428
1429    /**
1430     * Returns a cached instance if such is available or a new one is
1431     * create. The returned instance is initialized from the given
1432     * <code>info</code>.
1433     *
1434     * @param info The other info.
1435     * @return An instance.
1436     */
1437    public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
1438        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
1439        infoClone.init(info);
1440        return infoClone;
1441    }
1442
1443    /**
1444     * Return an instance back to be reused.
1445     * <p>
1446     * <strong>Note:</strong> You must not touch the object after calling this function.
1447     *
1448     * @throws IllegalStateException If the info is already recycled.
1449     */
1450    public void recycle() {
1451        if (mIsInPool) {
1452            throw new IllegalStateException("Info already recycled!");
1453        }
1454        clear();
1455        synchronized (sPoolLock) {
1456            if (sPoolSize <= MAX_POOL_SIZE) {
1457                mNext = sPool;
1458                sPool = this;
1459                mIsInPool = true;
1460                sPoolSize++;
1461            }
1462        }
1463    }
1464
1465    /**
1466     * {@inheritDoc}
1467     * <p>
1468     *   <strong>Note:</strong> After the instance is written to a parcel it
1469     *      is recycled. You must not touch the object after calling this function.
1470     * </p>
1471     */
1472    public void writeToParcel(Parcel parcel, int flags) {
1473        parcel.writeInt(isSealed() ? 1 : 0);
1474        parcel.writeLong(mSourceNodeId);
1475        parcel.writeInt(mWindowId);
1476        parcel.writeLong(mParentNodeId);
1477        parcel.writeInt(mConnectionId);
1478
1479        SparseLongArray childIds = mChildNodeIds;
1480        final int childIdsSize = childIds.size();
1481        parcel.writeInt(childIdsSize);
1482        for (int i = 0; i < childIdsSize; i++) {
1483            parcel.writeLong(childIds.valueAt(i));
1484        }
1485
1486        parcel.writeInt(mBoundsInParent.top);
1487        parcel.writeInt(mBoundsInParent.bottom);
1488        parcel.writeInt(mBoundsInParent.left);
1489        parcel.writeInt(mBoundsInParent.right);
1490
1491        parcel.writeInt(mBoundsInScreen.top);
1492        parcel.writeInt(mBoundsInScreen.bottom);
1493        parcel.writeInt(mBoundsInScreen.left);
1494        parcel.writeInt(mBoundsInScreen.right);
1495
1496        parcel.writeInt(mActions);
1497
1498        parcel.writeInt(mMovementGranularities);
1499
1500        parcel.writeInt(mBooleanProperties);
1501
1502        parcel.writeCharSequence(mPackageName);
1503        parcel.writeCharSequence(mClassName);
1504        parcel.writeCharSequence(mText);
1505        parcel.writeCharSequence(mContentDescription);
1506
1507        // Since instances of this class are fetched via synchronous i.e. blocking
1508        // calls in IPCs we always recycle as soon as the instance is marshaled.
1509        recycle();
1510    }
1511
1512    /**
1513     * Initializes this instance from another one.
1514     *
1515     * @param other The other instance.
1516     */
1517    @SuppressWarnings("unchecked")
1518    private void init(AccessibilityNodeInfo other) {
1519        mSealed = other.mSealed;
1520        mSourceNodeId = other.mSourceNodeId;
1521        mParentNodeId = other.mParentNodeId;
1522        mWindowId = other.mWindowId;
1523        mConnectionId = other.mConnectionId;
1524        mBoundsInParent.set(other.mBoundsInParent);
1525        mBoundsInScreen.set(other.mBoundsInScreen);
1526        mPackageName = other.mPackageName;
1527        mClassName = other.mClassName;
1528        mText = other.mText;
1529        mContentDescription = other.mContentDescription;
1530        mActions= other.mActions;
1531        mBooleanProperties = other.mBooleanProperties;
1532        mMovementGranularities = other.mMovementGranularities;
1533        final int otherChildIdCount = other.mChildNodeIds.size();
1534        for (int i = 0; i < otherChildIdCount; i++) {
1535            mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
1536        }
1537    }
1538
1539    /**
1540     * Creates a new instance from a {@link Parcel}.
1541     *
1542     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
1543     */
1544    private void initFromParcel(Parcel parcel) {
1545        mSealed = (parcel.readInt()  == 1);
1546        mSourceNodeId = parcel.readLong();
1547        mWindowId = parcel.readInt();
1548        mParentNodeId = parcel.readLong();
1549        mConnectionId = parcel.readInt();
1550
1551        SparseLongArray childIds = mChildNodeIds;
1552        final int childrenSize = parcel.readInt();
1553        for (int i = 0; i < childrenSize; i++) {
1554            final long childId = parcel.readLong();
1555            childIds.put(i, childId);
1556        }
1557
1558        mBoundsInParent.top = parcel.readInt();
1559        mBoundsInParent.bottom = parcel.readInt();
1560        mBoundsInParent.left = parcel.readInt();
1561        mBoundsInParent.right = parcel.readInt();
1562
1563        mBoundsInScreen.top = parcel.readInt();
1564        mBoundsInScreen.bottom = parcel.readInt();
1565        mBoundsInScreen.left = parcel.readInt();
1566        mBoundsInScreen.right = parcel.readInt();
1567
1568        mActions = parcel.readInt();
1569
1570        mMovementGranularities = parcel.readInt();
1571
1572        mBooleanProperties = parcel.readInt();
1573
1574        mPackageName = parcel.readCharSequence();
1575        mClassName = parcel.readCharSequence();
1576        mText = parcel.readCharSequence();
1577        mContentDescription = parcel.readCharSequence();
1578    }
1579
1580    /**
1581     * Clears the state of this instance.
1582     */
1583    private void clear() {
1584        mSealed = false;
1585        mSourceNodeId = ROOT_NODE_ID;
1586        mParentNodeId = ROOT_NODE_ID;
1587        mWindowId = UNDEFINED;
1588        mConnectionId = UNDEFINED;
1589        mMovementGranularities = 0;
1590        mChildNodeIds.clear();
1591        mBoundsInParent.set(0, 0, 0, 0);
1592        mBoundsInScreen.set(0, 0, 0, 0);
1593        mBooleanProperties = 0;
1594        mPackageName = null;
1595        mClassName = null;
1596        mText = null;
1597        mContentDescription = null;
1598        mActions = 0;
1599    }
1600
1601    /**
1602     * Gets the human readable action symbolic name.
1603     *
1604     * @param action The action.
1605     * @return The symbolic name.
1606     */
1607    private static String getActionSymbolicName(int action) {
1608        switch (action) {
1609            case ACTION_FOCUS:
1610                return "ACTION_FOCUS";
1611            case ACTION_CLEAR_FOCUS:
1612                return "ACTION_CLEAR_FOCUS";
1613            case ACTION_SELECT:
1614                return "ACTION_SELECT";
1615            case ACTION_CLEAR_SELECTION:
1616                return "ACTION_CLEAR_SELECTION";
1617            case ACTION_CLICK:
1618                return "ACTION_CLICK";
1619            case ACTION_LONG_CLICK:
1620                return "ACTION_LONG_CLICK";
1621            case ACTION_ACCESSIBILITY_FOCUS:
1622                return "ACTION_ACCESSIBILITY_FOCUS";
1623            case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
1624                return "ACTION_CLEAR_ACCESSIBILITY_FOCUS";
1625            case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
1626                return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY";
1627            case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
1628                return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY";
1629            case ACTION_NEXT_HTML_ELEMENT:
1630                return "ACTION_NEXT_HTML_ELEMENT";
1631            case ACTION_PREVIOUS_HTML_ELEMENT:
1632                return "ACTION_PREVIOUS_HTML_ELEMENT";
1633            case ACTION_SCROLL_FORWARD:
1634                return "ACTION_SCROLL_FORWARD";
1635            case ACTION_SCROLL_BACKWARD:
1636                return "ACTION_SCROLL_BACKWARD";
1637            default:
1638                throw new IllegalArgumentException("Unknown action: " + action);
1639        }
1640    }
1641
1642    /**
1643     * Gets the human readable movement granularity symbolic name.
1644     *
1645     * @param granularity The granularity.
1646     * @return The symbolic name.
1647     */
1648    private static String getMovementGranularitySymbolicName(int granularity) {
1649        switch (granularity) {
1650            case MOVEMENT_GRANULARITY_CHARACTER:
1651                return "MOVEMENT_GRANULARITY_CHARACTER";
1652            case MOVEMENT_GRANULARITY_WORD:
1653                return "MOVEMENT_GRANULARITY_WORD";
1654            case MOVEMENT_GRANULARITY_LINE:
1655                return "MOVEMENT_GRANULARITY_LINE";
1656            case MOVEMENT_GRANULARITY_PARAGRAPH:
1657                return "MOVEMENT_GRANULARITY_PARAGRAPH";
1658            case MOVEMENT_GRANULARITY_PAGE:
1659                return "MOVEMENT_GRANULARITY_PAGE";
1660            default:
1661                throw new IllegalArgumentException("Unknown movement granularity: " + granularity);
1662        }
1663    }
1664
1665    private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
1666        return (mWindowId != UNDEFINED
1667                && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
1668                && mConnectionId != UNDEFINED);
1669    }
1670
1671    @Override
1672    public boolean equals(Object object) {
1673        if (this == object) {
1674            return true;
1675        }
1676        if (object == null) {
1677            return false;
1678        }
1679        if (getClass() != object.getClass()) {
1680            return false;
1681        }
1682        AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
1683        if (mSourceNodeId != other.mSourceNodeId) {
1684            return false;
1685        }
1686        if (mWindowId != other.mWindowId) {
1687            return false;
1688        }
1689        return true;
1690    }
1691
1692    @Override
1693    public int hashCode() {
1694        final int prime = 31;
1695        int result = 1;
1696        result = prime * result + getAccessibilityViewId(mSourceNodeId);
1697        result = prime * result + getVirtualDescendantId(mSourceNodeId);
1698        result = prime * result + mWindowId;
1699        return result;
1700    }
1701
1702    @Override
1703    public String toString() {
1704        StringBuilder builder = new StringBuilder();
1705        builder.append(super.toString());
1706
1707        if (DEBUG) {
1708            builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
1709            builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
1710            builder.append("; mParentNodeId: " + mParentNodeId);
1711
1712            int granularities = mMovementGranularities;
1713            builder.append("; MovementGranularities: [");
1714            while (granularities != 0) {
1715                final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
1716                granularities &= ~granularity;
1717                builder.append(getMovementGranularitySymbolicName(granularity));
1718                if (granularities != 0) {
1719                    builder.append(", ");
1720                }
1721            }
1722            builder.append("]");
1723
1724            SparseLongArray childIds = mChildNodeIds;
1725            builder.append("; childAccessibilityIds: [");
1726            for (int i = 0, count = childIds.size(); i < count; i++) {
1727                builder.append(childIds.valueAt(i));
1728                if (i < count - 1) {
1729                    builder.append(", ");
1730                }
1731            }
1732            builder.append("]");
1733        }
1734
1735        builder.append("; boundsInParent: " + mBoundsInParent);
1736        builder.append("; boundsInScreen: " + mBoundsInScreen);
1737
1738        builder.append("; packageName: ").append(mPackageName);
1739        builder.append("; className: ").append(mClassName);
1740        builder.append("; text: ").append(mText);
1741        builder.append("; contentDescription: ").append(mContentDescription);
1742
1743        builder.append("; checkable: ").append(isCheckable());
1744        builder.append("; checked: ").append(isChecked());
1745        builder.append("; focusable: ").append(isFocusable());
1746        builder.append("; focused: ").append(isFocused());
1747        builder.append("; selected: ").append(isSelected());
1748        builder.append("; clickable: ").append(isClickable());
1749        builder.append("; longClickable: ").append(isLongClickable());
1750        builder.append("; enabled: ").append(isEnabled());
1751        builder.append("; password: ").append(isPassword());
1752        builder.append("; scrollable: " + isScrollable());
1753
1754        builder.append("; [");
1755        for (int actionBits = mActions; actionBits != 0;) {
1756            final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
1757            actionBits &= ~action;
1758            builder.append(getActionSymbolicName(action));
1759            if (actionBits != 0) {
1760                builder.append(", ");
1761            }
1762        }
1763        builder.append("]");
1764
1765        return builder.toString();
1766    }
1767
1768    /**
1769     * @see Parcelable.Creator
1770     */
1771    public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
1772            new Parcelable.Creator<AccessibilityNodeInfo>() {
1773        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
1774            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1775            info.initFromParcel(parcel);
1776            return info;
1777        }
1778
1779        public AccessibilityNodeInfo[] newArray(int size) {
1780            return new AccessibilityNodeInfo[size];
1781        }
1782    };
1783}
1784