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