AccessibilityNodeInfo.java revision aa780c110922148a6a4ba06734bb2b0bb8c98f93
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 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 from the node to go to the next entity in its content
126     * at a given granularity. For example, move to the next word, link, etc.
127     * <p>
128     * <strong>Arguments:</strong>
129     * <ul>
130     * <li>
131     * {@link #ACTION_ARGUMENT_GRANULARITY}
132     * </li>
133     * <li>
134     * </p>
135     * <p>
136     * <strong>Example:</strong>
137     * <code><pre><p>
138     *   // Assume the first granularity was presented to the user and she is
139     *   // making an explicit action to traverse the node at that granularity.
140     *   CharSequence granularity = info.getGranularity(0);
141     *   Bundle arguments = new Bundle();
142     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity);
143     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments);
144     * </code></pre></p>
145     * </li>
146     * </ul>
147     * </p>
148     * @see #setGranularities(CharSequence[])
149     * @see #getGranularities()
150     */
151    public static final int ACTION_NEXT_AT_GRANULARITY = 0x00000100;
152
153    /**
154     * Action that requests from the node to go to the previous entity in its content
155     * at a given granularity. For example, move to the next word, link, etc.
156     * <p>
157     * <strong>Arguments:</strong>
158     * <ul>
159     * <li>
160     *  {@link #ACTION_ARGUMENT_GRANULARITY}
161     * </li>
162     * <li>
163     * </p>
164     * <p>
165     * <strong>Example:</strong>
166     * <code><pre><p>
167     *   // Assume the first granularity was presented to the user and she is
168     *   // making an explicit action to traverse the node at that granularity.
169     *   CharSequence granularity = info.getGranularity(0);
170     *   Bundle arguments = new Bundle();
171     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity);
172     *   info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments);
173     * </code></pre></p>
174     * </li>
175     * </ul>
176     * </p>
177     * @see #setGranularities(CharSequence[])
178     * @see #getGranularities()
179     */
180    public static final int ACTION_PREVIOUS_AT_GRANULARITY = 0x00000200;
181
182    /**
183     * Argument for which content granularity to be used when traversing the node content.
184     * <p>
185     * <strong>Actions:</strong>
186     * <ul>
187     * <li>
188     * {@link #ACTION_PREVIOUS_AT_GRANULARITY}
189     * </li>
190     * <li>
191     * {@link #ACTION_PREVIOUS_AT_GRANULARITY}
192     * </li>
193     * </ul>
194     * </p>
195     */
196    public static final String ACTION_ARGUMENT_GRANULARITY = "ACTION_ARGUMENT_GRANULARITY";
197
198    /**
199     * The input focus.
200     */
201    public static final int FOCUS_INPUT = 1;
202
203    /**
204     * The accessibility focus.
205     */
206    public static final int FOCUS_ACCESSIBILITY = 2;
207
208    // Boolean attributes.
209
210    private static final int PROPERTY_CHECKABLE = 0x00000001;
211
212    private static final int PROPERTY_CHECKED = 0x00000002;
213
214    private static final int PROPERTY_FOCUSABLE = 0x00000004;
215
216    private static final int PROPERTY_FOCUSED = 0x00000008;
217
218    private static final int PROPERTY_SELECTED = 0x00000010;
219
220    private static final int PROPERTY_CLICKABLE = 0x00000020;
221
222    private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
223
224    private static final int PROPERTY_ENABLED = 0x00000080;
225
226    private static final int PROPERTY_PASSWORD = 0x00000100;
227
228    private static final int PROPERTY_SCROLLABLE = 0x00000200;
229
230    private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
231
232    /**
233     * Bits that provide the id of a virtual descendant of a view.
234     */
235    private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
236
237    /**
238     * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
239     * virtual descendant of a view. Such a descendant does not exist in the view
240     * hierarchy and is only reported via the accessibility APIs.
241     */
242    private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
243
244    /**
245     * Gets the accessibility view id which identifies a View in the view three.
246     *
247     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
248     * @return The accessibility view id part of the node id.
249     *
250     * @hide
251     */
252    public static int getAccessibilityViewId(long accessibilityNodeId) {
253        return (int) accessibilityNodeId;
254    }
255
256    /**
257     * Gets the virtual descendant id which identifies an imaginary view in a
258     * containing View.
259     *
260     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
261     * @return The virtual view id part of the node id.
262     *
263     * @hide
264     */
265    public static int getVirtualDescendantId(long accessibilityNodeId) {
266        return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
267                >> VIRTUAL_DESCENDANT_ID_SHIFT);
268    }
269
270    /**
271     * Makes a node id by shifting the <code>virtualDescendantId</code>
272     * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
273     * the bitwise or with the <code>accessibilityViewId</code>.
274     *
275     * @param accessibilityViewId A View accessibility id.
276     * @param virtualDescendantId A virtual descendant id.
277     * @return The node id.
278     *
279     * @hide
280     */
281    public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
282        return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
283    }
284
285    // Housekeeping.
286    private static final int MAX_POOL_SIZE = 50;
287    private static final Object sPoolLock = new Object();
288    private static AccessibilityNodeInfo sPool;
289    private static int sPoolSize;
290    private AccessibilityNodeInfo mNext;
291    private boolean mIsInPool;
292    private boolean mSealed;
293
294    // Data.
295    private int mWindowId = UNDEFINED;
296    private long mSourceNodeId = ROOT_NODE_ID;
297    private long mParentNodeId = ROOT_NODE_ID;
298
299    private int mBooleanProperties;
300    private final Rect mBoundsInParent = new Rect();
301    private final Rect mBoundsInScreen = new Rect();
302
303    private CharSequence mPackageName;
304    private CharSequence mClassName;
305    private CharSequence mText;
306    private CharSequence mContentDescription;
307
308    private final SparseLongArray mChildNodeIds = new SparseLongArray();
309    private int mActions;
310
311    private CharSequence[] mGranularities;
312
313    private int mConnectionId = UNDEFINED;
314
315    /**
316     * Hide constructor from clients.
317     */
318    private AccessibilityNodeInfo() {
319        /* do nothing */
320    }
321
322    /**
323     * Sets the source.
324     * <p>
325     *   <strong>Note:</strong> Cannot be called from an
326     *   {@link android.accessibilityservice.AccessibilityService}.
327     *   This class is made immutable before being delivered to an AccessibilityService.
328     * </p>
329     *
330     * @param source The info source.
331     */
332    public void setSource(View source) {
333        setSource(source, UNDEFINED);
334    }
335
336    /**
337     * Sets the source to be a virtual descendant of the given <code>root</code>.
338     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
339     * is set as the source.
340     * <p>
341     * A virtual descendant is an imaginary View that is reported as a part of the view
342     * hierarchy for accessibility purposes. This enables custom views that draw complex
343     * content to report themselves as a tree of virtual views, thus conveying their
344     * logical structure.
345     * </p>
346     * <p>
347     *   <strong>Note:</strong> Cannot be called from an
348     *   {@link android.accessibilityservice.AccessibilityService}.
349     *   This class is made immutable before being delivered to an AccessibilityService.
350     * </p>
351     *
352     * @param root The root of the virtual subtree.
353     * @param virtualDescendantId The id of the virtual descendant.
354     */
355    public void setSource(View root, int virtualDescendantId) {
356        enforceNotSealed();
357        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
358        final int rootAccessibilityViewId =
359            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
360        mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
361    }
362
363    /**
364     * Find the view that has the specified focus type. The search starts from
365     * the view represented by this node info.
366     *
367     * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
368     *         {@link #FOCUS_ACCESSIBILITY}.
369     * @return The node info of the focused view or null.
370     *
371     * @see #FOCUS_INPUT
372     * @see #FOCUS_ACCESSIBILITY
373     */
374    public AccessibilityNodeInfo findFocus(int focus) {
375        enforceSealed();
376        if (!canPerformRequestOverConnection(mSourceNodeId)) {
377            return null;
378        }
379        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
380                mSourceNodeId, focus);
381    }
382
383    /**
384     * Searches for the nearest view in the specified direction that can take
385     * the input focus.
386     *
387     * @param direction The direction. Can be one of:
388     *     {@link View#FOCUS_DOWN},
389     *     {@link View#FOCUS_UP},
390     *     {@link View#FOCUS_LEFT},
391     *     {@link View#FOCUS_RIGHT},
392     *     {@link View#FOCUS_FORWARD},
393     *     {@link View#FOCUS_BACKWARD},
394     *     {@link View#ACCESSIBILITY_FOCUS_IN},
395     *     {@link View#ACCESSIBILITY_FOCUS_OUT},
396     *     {@link View#ACCESSIBILITY_FOCUS_FORWARD},
397     *     {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
398     *     {@link View#ACCESSIBILITY_FOCUS_UP},
399     *     {@link View#ACCESSIBILITY_FOCUS_RIGHT},
400     *     {@link View#ACCESSIBILITY_FOCUS_DOWN},
401     *     {@link View#ACCESSIBILITY_FOCUS_LEFT}.
402     *
403     * @return The node info for the view that can take accessibility focus.
404     */
405    public AccessibilityNodeInfo focusSearch(int direction) {
406        enforceSealed();
407        if (!canPerformRequestOverConnection(mSourceNodeId)) {
408            return null;
409        }
410        return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
411                mSourceNodeId, direction);
412    }
413
414    /**
415     * Gets the id of the window from which the info comes from.
416     *
417     * @return The window id.
418     */
419    public int getWindowId() {
420        return mWindowId;
421    }
422
423    /**
424     * @return The ids of the children.
425     *
426     * @hide
427     */
428    public SparseLongArray getChildNodeIds() {
429        return mChildNodeIds;
430    }
431
432    /**
433     * Gets the number of children.
434     *
435     * @return The child count.
436     */
437    public int getChildCount() {
438        return mChildNodeIds.size();
439    }
440
441    /**
442     * Get the child at given index.
443     * <p>
444     *   <strong>Note:</strong> It is a client responsibility to recycle the
445     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
446     *     to avoid creating of multiple instances.
447     * </p>
448     *
449     * @param index The child index.
450     * @return The child node.
451     *
452     * @throws IllegalStateException If called outside of an AccessibilityService.
453     *
454     */
455    public AccessibilityNodeInfo getChild(int index) {
456        enforceSealed();
457        if (!canPerformRequestOverConnection(mSourceNodeId)) {
458            return null;
459        }
460        final long childId = mChildNodeIds.get(index);
461        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
462        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
463                childId, FLAG_PREFETCH_DESCENDANTS);
464    }
465
466    /**
467     * Adds a child.
468     * <p>
469     * <strong>Note:</strong> Cannot be called from an
470     * {@link android.accessibilityservice.AccessibilityService}.
471     * This class is made immutable before being delivered to an AccessibilityService.
472     * </p>
473     *
474     * @param child The child.
475     *
476     * @throws IllegalStateException If called from an AccessibilityService.
477     */
478    public void addChild(View child) {
479        addChild(child, UNDEFINED);
480    }
481
482    /**
483     * Adds a virtual child which is a descendant of the given <code>root</code>.
484     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
485     * is added as a child.
486     * <p>
487     * A virtual descendant is an imaginary View that is reported as a part of the view
488     * hierarchy for accessibility purposes. This enables custom views that draw complex
489     * content to report them selves as a tree of virtual views, thus conveying their
490     * logical structure.
491     * </p>
492     *
493     * @param root The root of the virtual subtree.
494     * @param virtualDescendantId The id of the virtual child.
495     */
496    public void addChild(View root, int virtualDescendantId) {
497        enforceNotSealed();
498        final int index = mChildNodeIds.size();
499        final int rootAccessibilityViewId =
500            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
501        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
502        mChildNodeIds.put(index, childNodeId);
503    }
504
505    /**
506     * Gets the actions that can be performed on the node.
507     *
508     * @return The bit mask of with actions.
509     *
510     * @see AccessibilityNodeInfo#ACTION_FOCUS
511     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
512     * @see AccessibilityNodeInfo#ACTION_SELECT
513     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
514     */
515    public int getActions() {
516        return mActions;
517    }
518
519    /**
520     * Adds an action that can be performed on the node.
521     * <p>
522     *   <strong>Note:</strong> Cannot be called from an
523     *   {@link android.accessibilityservice.AccessibilityService}.
524     *   This class is made immutable before being delivered to an AccessibilityService.
525     * </p>
526     *
527     * @param action The action.
528     *
529     * @throws IllegalStateException If called from an AccessibilityService.
530     */
531    public void addAction(int action) {
532        enforceNotSealed();
533        mActions |= action;
534    }
535
536    /**
537     * Sets the granularities for traversing the content of this node.
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 granularities The granularity names.
545     *
546     * @throws IllegalStateException If called from an AccessibilityService.
547     */
548    public void setGranularities(CharSequence[] granularities) {
549        enforceNotSealed();
550        mGranularities = granularities;
551    }
552
553    /**
554     * Gets the granularities for traversing the content of this node.
555     *
556     * @return The count.
557     */
558    public CharSequence[] getGranularities() {
559        return mGranularities;
560    }
561
562    /**
563     * Performs an action on the node.
564     * <p>
565     *   <strong>Note:</strong> An action can be performed only if the request is made
566     *   from an {@link android.accessibilityservice.AccessibilityService}.
567     * </p>
568     *
569     * @param action The action to perform.
570     * @return True if the action was performed.
571     *
572     * @throws IllegalStateException If called outside of an AccessibilityService.
573     */
574    public boolean performAction(int action) {
575        enforceSealed();
576        if (!canPerformRequestOverConnection(mSourceNodeId)) {
577            return false;
578        }
579        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
580        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
581                action, null);
582    }
583
584    /**
585     * Performs an action on the node.
586     * <p>
587     *   <strong>Note:</strong> An action can be performed only if the request is made
588     *   from an {@link android.accessibilityservice.AccessibilityService}.
589     * </p>
590     *
591     * @param action The action to perform.
592     * @param arguments A bundle with additional arguments.
593     * @return True if the action was performed.
594     *
595     * @throws IllegalStateException If called outside of an AccessibilityService.
596     */
597    public boolean performAction(int action, Bundle arguments) {
598        enforceSealed();
599        if (!canPerformRequestOverConnection(mSourceNodeId)) {
600            return false;
601        }
602        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
603        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
604                action, arguments);
605    }
606
607    /**
608     * Finds {@link AccessibilityNodeInfo}s by text. The match is case
609     * insensitive containment. The search is relative to this info i.e.
610     * this info is the root of the traversed tree.
611     *
612     * <p>
613     *   <strong>Note:</strong> It is a client responsibility to recycle the
614     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
615     *     to avoid creating of multiple instances.
616     * </p>
617     *
618     * @param text The searched text.
619     * @return A list of node info.
620     */
621    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
622        enforceSealed();
623        if (!canPerformRequestOverConnection(mSourceNodeId)) {
624            return Collections.emptyList();
625        }
626        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
627        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
628                text);
629    }
630
631    /**
632     * Gets the parent.
633     * <p>
634     *   <strong>Note:</strong> It is a client responsibility to recycle the
635     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
636     *     to avoid creating of multiple instances.
637     * </p>
638     *
639     * @return The parent.
640     */
641    public AccessibilityNodeInfo getParent() {
642        enforceSealed();
643        if (!canPerformRequestOverConnection(mParentNodeId)) {
644            return null;
645        }
646        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
647        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
648                mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
649    }
650
651    /**
652     * @return The parent node id.
653     *
654     * @hide
655     */
656    public long getParentNodeId() {
657        return mParentNodeId;
658    }
659
660    /**
661     * Sets the parent.
662     * <p>
663     *   <strong>Note:</strong> Cannot be called from an
664     *   {@link android.accessibilityservice.AccessibilityService}.
665     *   This class is made immutable before being delivered to an AccessibilityService.
666     * </p>
667     *
668     * @param parent The parent.
669     *
670     * @throws IllegalStateException If called from an AccessibilityService.
671     */
672    public void setParent(View parent) {
673        setParent(parent, UNDEFINED);
674    }
675
676    /**
677     * Sets the parent to be a virtual descendant of the given <code>root</code>.
678     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
679     * is set as the parent.
680     * <p>
681     * A virtual descendant is an imaginary View that is reported as a part of the view
682     * hierarchy for accessibility purposes. This enables custom views that draw complex
683     * content to report them selves as a tree of virtual views, thus conveying their
684     * logical structure.
685     * </p>
686     * <p>
687     *   <strong>Note:</strong> Cannot be called from an
688     *   {@link android.accessibilityservice.AccessibilityService}.
689     *   This class is made immutable before being delivered to an AccessibilityService.
690     * </p>
691     *
692     * @param root The root of the virtual subtree.
693     * @param virtualDescendantId The id of the virtual descendant.
694     */
695    public void setParent(View root, int virtualDescendantId) {
696        enforceNotSealed();
697        final int rootAccessibilityViewId =
698            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
699        mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
700    }
701
702    /**
703     * Gets the node bounds in parent coordinates.
704     *
705     * @param outBounds The output node bounds.
706     */
707    public void getBoundsInParent(Rect outBounds) {
708        outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
709                mBoundsInParent.right, mBoundsInParent.bottom);
710    }
711
712    /**
713     * Sets the node bounds in parent coordinates.
714     * <p>
715     *   <strong>Note:</strong> Cannot be called from an
716     *   {@link android.accessibilityservice.AccessibilityService}.
717     *   This class is made immutable before being delivered to an AccessibilityService.
718     * </p>
719     *
720     * @param bounds The node bounds.
721     *
722     * @throws IllegalStateException If called from an AccessibilityService.
723     */
724    public void setBoundsInParent(Rect bounds) {
725        enforceNotSealed();
726        mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
727    }
728
729    /**
730     * Gets the node bounds in screen coordinates.
731     *
732     * @param outBounds The output node bounds.
733     */
734    public void getBoundsInScreen(Rect outBounds) {
735        outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
736                mBoundsInScreen.right, mBoundsInScreen.bottom);
737    }
738
739    /**
740     * Sets the node bounds in screen coordinates.
741     * <p>
742     *   <strong>Note:</strong> Cannot be called from an
743     *   {@link android.accessibilityservice.AccessibilityService}.
744     *   This class is made immutable before being delivered to an AccessibilityService.
745     * </p>
746     *
747     * @param bounds The node bounds.
748     *
749     * @throws IllegalStateException If called from an AccessibilityService.
750     */
751    public void setBoundsInScreen(Rect bounds) {
752        enforceNotSealed();
753        mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
754    }
755
756    /**
757     * Gets whether this node is checkable.
758     *
759     * @return True if the node is checkable.
760     */
761    public boolean isCheckable() {
762        return getBooleanProperty(PROPERTY_CHECKABLE);
763    }
764
765    /**
766     * Sets whether this node is checkable.
767     * <p>
768     *   <strong>Note:</strong> Cannot be called from an
769     *   {@link android.accessibilityservice.AccessibilityService}.
770     *   This class is made immutable before being delivered to an AccessibilityService.
771     * </p>
772     *
773     * @param checkable True if the node is checkable.
774     *
775     * @throws IllegalStateException If called from an AccessibilityService.
776     */
777    public void setCheckable(boolean checkable) {
778        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
779    }
780
781    /**
782     * Gets whether this node is checked.
783     *
784     * @return True if the node is checked.
785     */
786    public boolean isChecked() {
787        return getBooleanProperty(PROPERTY_CHECKED);
788    }
789
790    /**
791     * Sets whether this node is checked.
792     * <p>
793     *   <strong>Note:</strong> Cannot be called from an
794     *   {@link android.accessibilityservice.AccessibilityService}.
795     *   This class is made immutable before being delivered to an AccessibilityService.
796     * </p>
797     *
798     * @param checked True if the node is checked.
799     *
800     * @throws IllegalStateException If called from an AccessibilityService.
801     */
802    public void setChecked(boolean checked) {
803        setBooleanProperty(PROPERTY_CHECKED, checked);
804    }
805
806    /**
807     * Gets whether this node is focusable.
808     *
809     * @return True if the node is focusable.
810     */
811    public boolean isFocusable() {
812        return getBooleanProperty(PROPERTY_FOCUSABLE);
813    }
814
815    /**
816     * Sets whether this node is focusable.
817     * <p>
818     *   <strong>Note:</strong> Cannot be called from an
819     *   {@link android.accessibilityservice.AccessibilityService}.
820     *   This class is made immutable before being delivered to an AccessibilityService.
821     * </p>
822     *
823     * @param focusable True if the node is focusable.
824     *
825     * @throws IllegalStateException If called from an AccessibilityService.
826     */
827    public void setFocusable(boolean focusable) {
828        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
829    }
830
831    /**
832     * Gets whether this node is focused.
833     *
834     * @return True if the node is focused.
835     */
836    public boolean isFocused() {
837        return getBooleanProperty(PROPERTY_FOCUSED);
838    }
839
840    /**
841     * Sets whether this node is focused.
842     * <p>
843     *   <strong>Note:</strong> Cannot be called from an
844     *   {@link android.accessibilityservice.AccessibilityService}.
845     *   This class is made immutable before being delivered to an AccessibilityService.
846     * </p>
847     *
848     * @param focused True if the node is focused.
849     *
850     * @throws IllegalStateException If called from an AccessibilityService.
851     */
852    public void setFocused(boolean focused) {
853        setBooleanProperty(PROPERTY_FOCUSED, focused);
854    }
855
856    /**
857     * Gets whether this node is accessibility focused.
858     *
859     * @return True if the node is accessibility focused.
860     */
861    public boolean isAccessibilityFocused() {
862        return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
863    }
864
865    /**
866     * Sets whether this node is accessibility focused.
867     * <p>
868     *   <strong>Note:</strong> Cannot be called from an
869     *   {@link android.accessibilityservice.AccessibilityService}.
870     *   This class is made immutable before being delivered to an AccessibilityService.
871     * </p>
872     *
873     * @param focused True if the node is accessibility focused.
874     *
875     * @throws IllegalStateException If called from an AccessibilityService.
876     */
877    public void setAccessibilityFocused(boolean focused) {
878        setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
879    }
880
881    /**
882     * Gets whether this node is selected.
883     *
884     * @return True if the node is selected.
885     */
886    public boolean isSelected() {
887        return getBooleanProperty(PROPERTY_SELECTED);
888    }
889
890    /**
891     * Sets whether this node is selected.
892     * <p>
893     *   <strong>Note:</strong> Cannot be called from an
894     *   {@link android.accessibilityservice.AccessibilityService}.
895     *   This class is made immutable before being delivered to an AccessibilityService.
896     * </p>
897     *
898     * @param selected True if the node is selected.
899     *
900     * @throws IllegalStateException If called from an AccessibilityService.
901     */
902    public void setSelected(boolean selected) {
903        setBooleanProperty(PROPERTY_SELECTED, selected);
904    }
905
906    /**
907     * Gets whether this node is clickable.
908     *
909     * @return True if the node is clickable.
910     */
911    public boolean isClickable() {
912        return getBooleanProperty(PROPERTY_CLICKABLE);
913    }
914
915    /**
916     * Sets whether this node is clickable.
917     * <p>
918     *   <strong>Note:</strong> Cannot be called from an
919     *   {@link android.accessibilityservice.AccessibilityService}.
920     *   This class is made immutable before being delivered to an AccessibilityService.
921     * </p>
922     *
923     * @param clickable True if the node is clickable.
924     *
925     * @throws IllegalStateException If called from an AccessibilityService.
926     */
927    public void setClickable(boolean clickable) {
928        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
929    }
930
931    /**
932     * Gets whether this node is long clickable.
933     *
934     * @return True if the node is long clickable.
935     */
936    public boolean isLongClickable() {
937        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
938    }
939
940    /**
941     * Sets whether this node is long clickable.
942     * <p>
943     *   <strong>Note:</strong> Cannot be called from an
944     *   {@link android.accessibilityservice.AccessibilityService}.
945     *   This class is made immutable before being delivered to an AccessibilityService.
946     * </p>
947     *
948     * @param longClickable True if the node is long clickable.
949     *
950     * @throws IllegalStateException If called from an AccessibilityService.
951     */
952    public void setLongClickable(boolean longClickable) {
953        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
954    }
955
956    /**
957     * Gets whether this node is enabled.
958     *
959     * @return True if the node is enabled.
960     */
961    public boolean isEnabled() {
962        return getBooleanProperty(PROPERTY_ENABLED);
963    }
964
965    /**
966     * Sets whether this node is enabled.
967     * <p>
968     *   <strong>Note:</strong> Cannot be called from an
969     *   {@link android.accessibilityservice.AccessibilityService}.
970     *   This class is made immutable before being delivered to an AccessibilityService.
971     * </p>
972     *
973     * @param enabled True if the node is enabled.
974     *
975     * @throws IllegalStateException If called from an AccessibilityService.
976     */
977    public void setEnabled(boolean enabled) {
978        setBooleanProperty(PROPERTY_ENABLED, enabled);
979    }
980
981    /**
982     * Gets whether this node is a password.
983     *
984     * @return True if the node is a password.
985     */
986    public boolean isPassword() {
987        return getBooleanProperty(PROPERTY_PASSWORD);
988    }
989
990    /**
991     * Sets whether this node is a password.
992     * <p>
993     *   <strong>Note:</strong> Cannot be called from an
994     *   {@link android.accessibilityservice.AccessibilityService}.
995     *   This class is made immutable before being delivered to an AccessibilityService.
996     * </p>
997     *
998     * @param password True if the node is a password.
999     *
1000     * @throws IllegalStateException If called from an AccessibilityService.
1001     */
1002    public void setPassword(boolean password) {
1003        setBooleanProperty(PROPERTY_PASSWORD, password);
1004    }
1005
1006    /**
1007     * Gets if the node is scrollable.
1008     *
1009     * @return True if the node is scrollable, false otherwise.
1010     */
1011    public boolean isScrollable() {
1012        return getBooleanProperty(PROPERTY_SCROLLABLE);
1013    }
1014
1015    /**
1016     * Sets if the node is scrollable.
1017     * <p>
1018     *   <strong>Note:</strong> Cannot be called from an
1019     *   {@link android.accessibilityservice.AccessibilityService}.
1020     *   This class is made immutable before being delivered to an AccessibilityService.
1021     * </p>
1022     *
1023     * @param scrollable True if the node is scrollable, false otherwise.
1024     *
1025     * @throws IllegalStateException If called from an AccessibilityService.
1026     */
1027    public void setScrollable(boolean scrollable) {
1028        enforceNotSealed();
1029        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
1030    }
1031
1032    /**
1033     * Gets the package this node comes from.
1034     *
1035     * @return The package name.
1036     */
1037    public CharSequence getPackageName() {
1038        return mPackageName;
1039    }
1040
1041    /**
1042     * Sets the package this node comes from.
1043     * <p>
1044     *   <strong>Note:</strong> Cannot be called from an
1045     *   {@link android.accessibilityservice.AccessibilityService}.
1046     *   This class is made immutable before being delivered to an AccessibilityService.
1047     * </p>
1048     *
1049     * @param packageName The package name.
1050     *
1051     * @throws IllegalStateException If called from an AccessibilityService.
1052     */
1053    public void setPackageName(CharSequence packageName) {
1054        enforceNotSealed();
1055        mPackageName = packageName;
1056    }
1057
1058    /**
1059     * Gets the class this node comes from.
1060     *
1061     * @return The class name.
1062     */
1063    public CharSequence getClassName() {
1064        return mClassName;
1065    }
1066
1067    /**
1068     * Sets the class this node comes from.
1069     * <p>
1070     *   <strong>Note:</strong> Cannot be called from an
1071     *   {@link android.accessibilityservice.AccessibilityService}.
1072     *   This class is made immutable before being delivered to an AccessibilityService.
1073     * </p>
1074     *
1075     * @param className The class name.
1076     *
1077     * @throws IllegalStateException If called from an AccessibilityService.
1078     */
1079    public void setClassName(CharSequence className) {
1080        enforceNotSealed();
1081        mClassName = className;
1082    }
1083
1084    /**
1085     * Gets the text of this node.
1086     *
1087     * @return The text.
1088     */
1089    public CharSequence getText() {
1090        return mText;
1091    }
1092
1093    /**
1094     * Sets the text of this node.
1095     * <p>
1096     *   <strong>Note:</strong> Cannot be called from an
1097     *   {@link android.accessibilityservice.AccessibilityService}.
1098     *   This class is made immutable before being delivered to an AccessibilityService.
1099     * </p>
1100     *
1101     * @param text The text.
1102     *
1103     * @throws IllegalStateException If called from an AccessibilityService.
1104     */
1105    public void setText(CharSequence text) {
1106        enforceNotSealed();
1107        mText = text;
1108    }
1109
1110    /**
1111     * Gets the content description of this node.
1112     *
1113     * @return The content description.
1114     */
1115    public CharSequence getContentDescription() {
1116        return mContentDescription;
1117    }
1118
1119    /**
1120     * Sets the content description of this node.
1121     * <p>
1122     *   <strong>Note:</strong> Cannot be called from an
1123     *   {@link android.accessibilityservice.AccessibilityService}.
1124     *   This class is made immutable before being delivered to an AccessibilityService.
1125     * </p>
1126     *
1127     * @param contentDescription The content description.
1128     *
1129     * @throws IllegalStateException If called from an AccessibilityService.
1130     */
1131    public void setContentDescription(CharSequence contentDescription) {
1132        enforceNotSealed();
1133        mContentDescription = contentDescription;
1134    }
1135
1136    /**
1137     * Gets the value of a boolean property.
1138     *
1139     * @param property The property.
1140     * @return The value.
1141     */
1142    private boolean getBooleanProperty(int property) {
1143        return (mBooleanProperties & property) != 0;
1144    }
1145
1146    /**
1147     * Sets a boolean property.
1148     *
1149     * @param property The property.
1150     * @param value The value.
1151     *
1152     * @throws IllegalStateException If called from an AccessibilityService.
1153     */
1154    private void setBooleanProperty(int property, boolean value) {
1155        enforceNotSealed();
1156        if (value) {
1157            mBooleanProperties |= property;
1158        } else {
1159            mBooleanProperties &= ~property;
1160        }
1161    }
1162
1163    /**
1164     * Sets the unique id of the IAccessibilityServiceConnection over which
1165     * this instance can send requests to the system.
1166     *
1167     * @param connectionId The connection id.
1168     *
1169     * @hide
1170     */
1171    public void setConnectionId(int connectionId) {
1172        enforceNotSealed();
1173        mConnectionId = connectionId;
1174    }
1175
1176    /**
1177     * {@inheritDoc}
1178     */
1179    public int describeContents() {
1180        return 0;
1181    }
1182
1183    /**
1184     * Gets the id of the source node.
1185     *
1186     * @return The id.
1187     *
1188     * @hide
1189     */
1190    public long getSourceNodeId() {
1191        return mSourceNodeId;
1192    }
1193
1194    /**
1195     * Sets if this instance is sealed.
1196     *
1197     * @param sealed Whether is sealed.
1198     *
1199     * @hide
1200     */
1201    public void setSealed(boolean sealed) {
1202        mSealed = sealed;
1203    }
1204
1205    /**
1206     * Gets if this instance is sealed.
1207     *
1208     * @return Whether is sealed.
1209     *
1210     * @hide
1211     */
1212    public boolean isSealed() {
1213        return mSealed;
1214    }
1215
1216    /**
1217     * Enforces that this instance is sealed.
1218     *
1219     * @throws IllegalStateException If this instance is not sealed.
1220     *
1221     * @hide
1222     */
1223    protected void enforceSealed() {
1224        if (!isSealed()) {
1225            throw new IllegalStateException("Cannot perform this "
1226                    + "action on a not sealed instance.");
1227        }
1228    }
1229
1230    /**
1231     * Enforces that this instance is not sealed.
1232     *
1233     * @throws IllegalStateException If this instance is sealed.
1234     *
1235     * @hide
1236     */
1237    protected void enforceNotSealed() {
1238        if (isSealed()) {
1239            throw new IllegalStateException("Cannot perform this "
1240                    + "action on a sealed instance.");
1241        }
1242    }
1243
1244    /**
1245     * Returns a cached instance if such is available otherwise a new one
1246     * and sets the source.
1247     *
1248     * @param source The source view.
1249     * @return An instance.
1250     *
1251     * @see #setSource(View)
1252     */
1253    public static AccessibilityNodeInfo obtain(View source) {
1254        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1255        info.setSource(source);
1256        return info;
1257    }
1258
1259    /**
1260     * Returns a cached instance if such is available otherwise a new one
1261     * and sets the source.
1262     *
1263     * @param root The root of the virtual subtree.
1264     * @param virtualDescendantId The id of the virtual descendant.
1265     * @return An instance.
1266     *
1267     * @see #setSource(View, int)
1268     */
1269    public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
1270        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1271        info.setSource(root, virtualDescendantId);
1272        return info;
1273    }
1274
1275    /**
1276     * Returns a cached instance if such is available otherwise a new one.
1277     *
1278     * @return An instance.
1279     */
1280    public static AccessibilityNodeInfo obtain() {
1281        synchronized (sPoolLock) {
1282            if (sPool != null) {
1283                AccessibilityNodeInfo info = sPool;
1284                sPool = sPool.mNext;
1285                sPoolSize--;
1286                info.mNext = null;
1287                info.mIsInPool = false;
1288                return info;
1289            }
1290            return new AccessibilityNodeInfo();
1291        }
1292    }
1293
1294    /**
1295     * Returns a cached instance if such is available or a new one is
1296     * create. The returned instance is initialized from the given
1297     * <code>info</code>.
1298     *
1299     * @param info The other info.
1300     * @return An instance.
1301     */
1302    public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
1303        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
1304        infoClone.init(info);
1305        return infoClone;
1306    }
1307
1308    /**
1309     * Return an instance back to be reused.
1310     * <p>
1311     * <strong>Note:</strong> You must not touch the object after calling this function.
1312     *
1313     * @throws IllegalStateException If the info is already recycled.
1314     */
1315    public void recycle() {
1316        if (mIsInPool) {
1317            throw new IllegalStateException("Info already recycled!");
1318        }
1319        clear();
1320        synchronized (sPoolLock) {
1321            if (sPoolSize <= MAX_POOL_SIZE) {
1322                mNext = sPool;
1323                sPool = this;
1324                mIsInPool = true;
1325                sPoolSize++;
1326            }
1327        }
1328    }
1329
1330    /**
1331     * {@inheritDoc}
1332     * <p>
1333     *   <strong>Note:</strong> After the instance is written to a parcel it
1334     *      is recycled. You must not touch the object after calling this function.
1335     * </p>
1336     */
1337    public void writeToParcel(Parcel parcel, int flags) {
1338        parcel.writeInt(isSealed() ? 1 : 0);
1339        parcel.writeLong(mSourceNodeId);
1340        parcel.writeInt(mWindowId);
1341        parcel.writeLong(mParentNodeId);
1342        parcel.writeInt(mConnectionId);
1343
1344        parcel.writeCharSequenceArray(mGranularities);
1345
1346        SparseLongArray childIds = mChildNodeIds;
1347        final int childIdsSize = childIds.size();
1348        parcel.writeInt(childIdsSize);
1349        for (int i = 0; i < childIdsSize; i++) {
1350            parcel.writeLong(childIds.valueAt(i));
1351        }
1352
1353        parcel.writeInt(mBoundsInParent.top);
1354        parcel.writeInt(mBoundsInParent.bottom);
1355        parcel.writeInt(mBoundsInParent.left);
1356        parcel.writeInt(mBoundsInParent.right);
1357
1358        parcel.writeInt(mBoundsInScreen.top);
1359        parcel.writeInt(mBoundsInScreen.bottom);
1360        parcel.writeInt(mBoundsInScreen.left);
1361        parcel.writeInt(mBoundsInScreen.right);
1362
1363        parcel.writeInt(mActions);
1364
1365        parcel.writeInt(mBooleanProperties);
1366
1367        parcel.writeCharSequence(mPackageName);
1368        parcel.writeCharSequence(mClassName);
1369        parcel.writeCharSequence(mText);
1370        parcel.writeCharSequence(mContentDescription);
1371
1372        // Since instances of this class are fetched via synchronous i.e. blocking
1373        // calls in IPCs we always recycle as soon as the instance is marshaled.
1374        recycle();
1375    }
1376
1377    /**
1378     * Initializes this instance from another one.
1379     *
1380     * @param other The other instance.
1381     */
1382    @SuppressWarnings("unchecked")
1383    private void init(AccessibilityNodeInfo other) {
1384        mSealed = other.mSealed;
1385        mSourceNodeId = other.mSourceNodeId;
1386        mParentNodeId = other.mParentNodeId;
1387        mWindowId = other.mWindowId;
1388        mConnectionId = other.mConnectionId;
1389        mBoundsInParent.set(other.mBoundsInParent);
1390        mBoundsInScreen.set(other.mBoundsInScreen);
1391        mPackageName = other.mPackageName;
1392        mClassName = other.mClassName;
1393        mText = other.mText;
1394        mContentDescription = other.mContentDescription;
1395        mActions= other.mActions;
1396        mBooleanProperties = other.mBooleanProperties;
1397        mGranularities = other.mGranularities.clone();
1398        final int otherChildIdCount = other.mChildNodeIds.size();
1399        for (int i = 0; i < otherChildIdCount; i++) {
1400            mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
1401        }
1402    }
1403
1404    /**
1405     * Creates a new instance from a {@link Parcel}.
1406     *
1407     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
1408     */
1409    private void initFromParcel(Parcel parcel) {
1410        mSealed = (parcel.readInt()  == 1);
1411        mSourceNodeId = parcel.readLong();
1412        mWindowId = parcel.readInt();
1413        mParentNodeId = parcel.readLong();
1414        mConnectionId = parcel.readInt();
1415
1416        mGranularities = parcel.readCharSequenceArray();
1417
1418        SparseLongArray childIds = mChildNodeIds;
1419        final int childrenSize = parcel.readInt();
1420        for (int i = 0; i < childrenSize; i++) {
1421            final long childId = parcel.readLong();
1422            childIds.put(i, childId);
1423        }
1424
1425        mBoundsInParent.top = parcel.readInt();
1426        mBoundsInParent.bottom = parcel.readInt();
1427        mBoundsInParent.left = parcel.readInt();
1428        mBoundsInParent.right = parcel.readInt();
1429
1430        mBoundsInScreen.top = parcel.readInt();
1431        mBoundsInScreen.bottom = parcel.readInt();
1432        mBoundsInScreen.left = parcel.readInt();
1433        mBoundsInScreen.right = parcel.readInt();
1434
1435        mActions = parcel.readInt();
1436
1437        mBooleanProperties = parcel.readInt();
1438
1439        mPackageName = parcel.readCharSequence();
1440        mClassName = parcel.readCharSequence();
1441        mText = parcel.readCharSequence();
1442        mContentDescription = parcel.readCharSequence();
1443    }
1444
1445    /**
1446     * Clears the state of this instance.
1447     */
1448    private void clear() {
1449        mSealed = false;
1450        mSourceNodeId = ROOT_NODE_ID;
1451        mParentNodeId = ROOT_NODE_ID;
1452        mWindowId = UNDEFINED;
1453        mConnectionId = UNDEFINED;
1454        mGranularities = null;
1455        mChildNodeIds.clear();
1456        mBoundsInParent.set(0, 0, 0, 0);
1457        mBoundsInScreen.set(0, 0, 0, 0);
1458        mBooleanProperties = 0;
1459        mPackageName = null;
1460        mClassName = null;
1461        mText = null;
1462        mContentDescription = null;
1463        mActions = 0;
1464    }
1465
1466    /**
1467     * Gets the human readable action symbolic name.
1468     *
1469     * @param action The action.
1470     * @return The symbolic name.
1471     */
1472    private static String getActionSymbolicName(int action) {
1473        switch (action) {
1474            case ACTION_FOCUS:
1475                return "ACTION_FOCUS";
1476            case ACTION_CLEAR_FOCUS:
1477                return "ACTION_CLEAR_FOCUS";
1478            case ACTION_SELECT:
1479                return "ACTION_SELECT";
1480            case ACTION_CLEAR_SELECTION:
1481                return "ACTION_CLEAR_SELECTION";
1482            default:
1483                throw new IllegalArgumentException("Unknown action: " + action);
1484        }
1485    }
1486
1487    private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
1488        return (mWindowId != UNDEFINED
1489                && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
1490                && mConnectionId != UNDEFINED);
1491    }
1492
1493    @Override
1494    public boolean equals(Object object) {
1495        if (this == object) {
1496            return true;
1497        }
1498        if (object == null) {
1499            return false;
1500        }
1501        if (getClass() != object.getClass()) {
1502            return false;
1503        }
1504        AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
1505        if (mSourceNodeId != other.mSourceNodeId) {
1506            return false;
1507        }
1508        if (mWindowId != other.mWindowId) {
1509            return false;
1510        }
1511        return true;
1512    }
1513
1514    @Override
1515    public int hashCode() {
1516        final int prime = 31;
1517        int result = 1;
1518        result = prime * result + getAccessibilityViewId(mSourceNodeId);
1519        result = prime * result + getVirtualDescendantId(mSourceNodeId);
1520        result = prime * result + mWindowId;
1521        return result;
1522    }
1523
1524    @Override
1525    public String toString() {
1526        StringBuilder builder = new StringBuilder();
1527        builder.append(super.toString());
1528
1529        if (DEBUG) {
1530            builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
1531            builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
1532            builder.append("; mParentNodeId: " + mParentNodeId);
1533
1534            CharSequence[] granularities = mGranularities;
1535            builder.append("; granularities: [");
1536            for (int i = 0, count = granularities.length; i < count; i++) {
1537                builder.append(granularities[i]);
1538                if (i < count - 1) {
1539                    builder.append(", ");
1540                }
1541            }
1542            builder.append("]");
1543
1544            SparseLongArray childIds = mChildNodeIds;
1545            builder.append("; childAccessibilityIds: [");
1546            for (int i = 0, count = childIds.size(); i < count; i++) {
1547                builder.append(childIds.valueAt(i));
1548                if (i < count - 1) {
1549                    builder.append(", ");
1550                }
1551            }
1552            builder.append("]");
1553        }
1554
1555        builder.append("; boundsInParent: " + mBoundsInParent);
1556        builder.append("; boundsInScreen: " + mBoundsInScreen);
1557
1558        builder.append("; packageName: ").append(mPackageName);
1559        builder.append("; className: ").append(mClassName);
1560        builder.append("; text: ").append(mText);
1561        builder.append("; contentDescription: ").append(mContentDescription);
1562
1563        builder.append("; checkable: ").append(isCheckable());
1564        builder.append("; checked: ").append(isChecked());
1565        builder.append("; focusable: ").append(isFocusable());
1566        builder.append("; focused: ").append(isFocused());
1567        builder.append("; selected: ").append(isSelected());
1568        builder.append("; clickable: ").append(isClickable());
1569        builder.append("; longClickable: ").append(isLongClickable());
1570        builder.append("; enabled: ").append(isEnabled());
1571        builder.append("; password: ").append(isPassword());
1572        builder.append("; scrollable: " + isScrollable());
1573
1574        builder.append("; [");
1575
1576        for (int actionBits = mActions; actionBits != 0;) {
1577            final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
1578            actionBits &= ~action;
1579            builder.append(getActionSymbolicName(action));
1580            if (actionBits != 0) {
1581                builder.append(", ");
1582            }
1583        }
1584
1585        builder.append("]");
1586
1587        return builder.toString();
1588    }
1589
1590    /**
1591     * @see Parcelable.Creator
1592     */
1593    public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
1594            new Parcelable.Creator<AccessibilityNodeInfo>() {
1595        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
1596            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1597            info.initFromParcel(parcel);
1598            return info;
1599        }
1600
1601        public AccessibilityNodeInfo[] newArray(int size) {
1602            return new AccessibilityNodeInfo[size];
1603        }
1604    };
1605}
1606