AccessibilityNodeInfo.java revision f76a50ce8fdc6aea22cabc77b2977a1a15a79630
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.Parcel;
21import android.os.Parcelable;
22import android.text.TextUtils;
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 *
47 * @see android.accessibilityservice.AccessibilityService
48 * @see AccessibilityEvent
49 * @see AccessibilityManager
50 */
51public class AccessibilityNodeInfo implements Parcelable {
52
53    private static final boolean DEBUG = false;
54
55    /** @hide */
56    public static final int UNDEFINED = -1;
57
58    /** @hide */
59    public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED);
60
61    /** @hide */
62    public static final int ACTIVE_WINDOW_ID = UNDEFINED;
63
64    /** @hide */
65    public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
66
67    /** @hide */
68    public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
69
70    /** @hide */
71    public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000003;
72
73    // Actions.
74
75    /**
76     * Action that focuses the node.
77     */
78    public static final int ACTION_FOCUS =  0x00000001;
79
80    /**
81     * Action that unfocuses the node.
82     */
83    public static final int ACTION_CLEAR_FOCUS =  0x00000002;
84
85    /**
86     * Action that selects the node.
87     */
88    public static final int ACTION_SELECT =  0x00000004;
89
90    /**
91     * Action that unselects the node.
92     */
93    public static final int ACTION_CLEAR_SELECTION =  0x00000008;
94
95    // Boolean attributes.
96
97    private static final int PROPERTY_CHECKABLE = 0x00000001;
98
99    private static final int PROPERTY_CHECKED = 0x00000002;
100
101    private static final int PROPERTY_FOCUSABLE = 0x00000004;
102
103    private static final int PROPERTY_FOCUSED = 0x00000008;
104
105    private static final int PROPERTY_SELECTED = 0x00000010;
106
107    private static final int PROPERTY_CLICKABLE = 0x00000020;
108
109    private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
110
111    private static final int PROPERTY_ENABLED = 0x00000080;
112
113    private static final int PROPERTY_PASSWORD = 0x00000100;
114
115    private static final int PROPERTY_SCROLLABLE = 0x00000200;
116
117    /**
118     * Bits that provide the id of a virtual descendant of a view.
119     */
120    private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
121
122    /**
123     * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
124     * virtual descendant of a view. Such a descendant does not exist in the view
125     * hierarchy and is only reported via the accessibility APIs.
126     */
127    private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
128
129    /**
130     * Gets the accessibility view id which identifies a View in the view three.
131     *
132     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
133     * @return The accessibility view id part of the node id.
134     *
135     * @hide
136     */
137    public static int getAccessibilityViewId(long accessibilityNodeId) {
138        return (int) accessibilityNodeId;
139    }
140
141    /**
142     * Gets the virtual descendant id which identifies an imaginary view in a
143     * containing View.
144     *
145     * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
146     * @return The virtual view id part of the node id.
147     *
148     * @hide
149     */
150    public static int getVirtualDescendantId(long accessibilityNodeId) {
151        return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
152                >> VIRTUAL_DESCENDANT_ID_SHIFT);
153    }
154
155    /**
156     * Makes a node id by shifting the <code>virtualDescendantId</code>
157     * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
158     * the bitwise or with the <code>accessibilityViewId</code>.
159     *
160     * @param accessibilityViewId A View accessibility id.
161     * @param virtualDescendantId A virtual descendant id.
162     * @return The node id.
163     *
164     * @hide
165     */
166    public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
167        return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
168    }
169
170    // Housekeeping.
171    private static final int MAX_POOL_SIZE = 50;
172    private static final Object sPoolLock = new Object();
173    private static AccessibilityNodeInfo sPool;
174    private static int sPoolSize;
175    private AccessibilityNodeInfo mNext;
176    private boolean mIsInPool;
177    private boolean mSealed;
178
179    // Data.
180    private int mWindowId = UNDEFINED;
181    private long mSourceNodeId = ROOT_NODE_ID;
182    private long mParentNodeId = ROOT_NODE_ID;
183
184    private int mBooleanProperties;
185    private final Rect mBoundsInParent = new Rect();
186    private final Rect mBoundsInScreen = new Rect();
187
188    private CharSequence mPackageName;
189    private CharSequence mClassName;
190    private CharSequence mText;
191    private CharSequence mContentDescription;
192
193    private SparseLongArray mChildNodeIds = new SparseLongArray();
194    private int mActions;
195
196    private int mConnectionId = UNDEFINED;
197
198    /**
199     * Hide constructor from clients.
200     */
201    private AccessibilityNodeInfo() {
202        /* do nothing */
203    }
204
205    /**
206     * Sets the source.
207     * <p>
208     *   <strong>Note:</strong> Cannot be called from an
209     *   {@link android.accessibilityservice.AccessibilityService}.
210     *   This class is made immutable before being delivered to an AccessibilityService.
211     * </p>
212     *
213     * @param source The info source.
214     */
215    public void setSource(View source) {
216        setSource(source, UNDEFINED);
217    }
218
219    /**
220     * Sets the source to be a virtual descendant of the given <code>root</code>.
221     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
222     * is set as the source.
223     * <p>
224     * A virtual descendant is an imaginary View that is reported as a part of the view
225     * hierarchy for accessibility purposes. This enables custom views that draw complex
226     * content to report themselves as a tree of virtual views, thus conveying their
227     * logical structure.
228     * </p>
229     * <p>
230     *   <strong>Note:</strong> Cannot be called from an
231     *   {@link android.accessibilityservice.AccessibilityService}.
232     *   This class is made immutable before being delivered to an AccessibilityService.
233     * </p>
234     *
235     * @param root The root of the virtual subtree.
236     * @param virtualDescendantId The id of the virtual descendant.
237     */
238    public void setSource(View root, int virtualDescendantId) {
239        enforceNotSealed();
240        mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
241        final int rootAccessibilityViewId =
242            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
243        mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
244    }
245
246    /**
247     * Gets the id of the window from which the info comes from.
248     *
249     * @return The window id.
250     */
251    public int getWindowId() {
252        return mWindowId;
253    }
254
255    /**
256     * @return The ids of the children.
257     *
258     * @hide
259     */
260    public SparseLongArray getChildNodeIds() {
261        return mChildNodeIds;
262    }
263
264    /**
265     * Gets the number of children.
266     *
267     * @return The child count.
268     */
269    public int getChildCount() {
270        return mChildNodeIds.size();
271    }
272
273    /**
274     * Get the child at given index.
275     * <p>
276     *   <strong>Note:</strong> It is a client responsibility to recycle the
277     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
278     *     to avoid creating of multiple instances.
279     * </p>
280     *
281     * @param index The child index.
282     * @return The child node.
283     *
284     * @throws IllegalStateException If called outside of an AccessibilityService.
285     *
286     */
287    public AccessibilityNodeInfo getChild(int index) {
288        enforceSealed();
289        if (!canPerformRequestOverConnection(mSourceNodeId)) {
290            return null;
291        }
292        final long childId = mChildNodeIds.get(index);
293        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
294        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
295                childId, FLAG_PREFETCH_DESCENDANTS);
296    }
297
298    /**
299     * Adds a child.
300     * <p>
301     * <strong>Note:</strong> Cannot be called from an
302     * {@link android.accessibilityservice.AccessibilityService}.
303     * This class is made immutable before being delivered to an AccessibilityService.
304     * </p>
305     *
306     * @param child The child.
307     *
308     * @throws IllegalStateException If called from an AccessibilityService.
309     */
310    public void addChild(View child) {
311        addChild(child, UNDEFINED);
312    }
313
314    /**
315     * Adds a virtual child which is a descendant of the given <code>root</code>.
316     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
317     * is added as a child.
318     * <p>
319     * A virtual descendant is an imaginary View that is reported as a part of the view
320     * hierarchy for accessibility purposes. This enables custom views that draw complex
321     * content to report them selves as a tree of virtual views, thus conveying their
322     * logical structure.
323     * </p>
324     *
325     * @param root The root of the virtual subtree.
326     * @param virtualDescendantId The id of the virtual child.
327     */
328    public void addChild(View root, int virtualDescendantId) {
329        enforceNotSealed();
330        final int index = mChildNodeIds.size();
331        final int rootAccessibilityViewId =
332            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
333        final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
334        mChildNodeIds.put(index, childNodeId);
335    }
336
337    /**
338     * Gets the actions that can be performed on the node.
339     *
340     * @return The bit mask of with actions.
341     *
342     * @see AccessibilityNodeInfo#ACTION_FOCUS
343     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
344     * @see AccessibilityNodeInfo#ACTION_SELECT
345     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
346     */
347    public int getActions() {
348        return mActions;
349    }
350
351    /**
352     * Adds an action that can be performed on the node.
353     * <p>
354     *   <strong>Note:</strong> Cannot be called from an
355     *   {@link android.accessibilityservice.AccessibilityService}.
356     *   This class is made immutable before being delivered to an AccessibilityService.
357     * </p>
358     *
359     * @param action The action.
360     *
361     * @throws IllegalStateException If called from an AccessibilityService.
362     */
363    public void addAction(int action) {
364        enforceNotSealed();
365        mActions |= action;
366    }
367
368    /**
369     * Performs an action on the node.
370     * <p>
371     *   <strong>Note:</strong> An action can be performed only if the request is made
372     *   from an {@link android.accessibilityservice.AccessibilityService}.
373     * </p>
374     *
375     * @param action The action to perform.
376     * @return True if the action was performed.
377     *
378     * @throws IllegalStateException If called outside of an AccessibilityService.
379     */
380    public boolean performAction(int action) {
381        enforceSealed();
382        if (!canPerformRequestOverConnection(mSourceNodeId)) {
383            return false;
384        }
385        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
386        return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, action);
387    }
388
389    /**
390     * Finds {@link AccessibilityNodeInfo}s by text. The match is case
391     * insensitive containment. The search is relative to this info i.e.
392     * this info is the root of the traversed tree.
393     *
394     * <p>
395     *   <strong>Note:</strong> It is a client responsibility to recycle the
396     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
397     *     to avoid creating of multiple instances.
398     * </p>
399     *
400     * @param text The searched text.
401     * @return A list of node info.
402     */
403    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
404        enforceSealed();
405        if (!canPerformRequestOverConnection(mSourceNodeId)) {
406            return Collections.emptyList();
407        }
408        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
409        return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
410                text);
411    }
412
413    /**
414     * Gets the parent.
415     * <p>
416     *   <strong>Note:</strong> It is a client responsibility to recycle the
417     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
418     *     to avoid creating of multiple instances.
419     * </p>
420     *
421     * @return The parent.
422     */
423    public AccessibilityNodeInfo getParent() {
424        enforceSealed();
425        if (!canPerformRequestOverConnection(mParentNodeId)) {
426            return null;
427        }
428        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
429        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
430                mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
431    }
432
433    /**
434     * @return The parent node id.
435     *
436     * @hide
437     */
438    public long getParentNodeId() {
439        return mParentNodeId;
440    }
441
442    /**
443     * Sets the parent.
444     * <p>
445     *   <strong>Note:</strong> Cannot be called from an
446     *   {@link android.accessibilityservice.AccessibilityService}.
447     *   This class is made immutable before being delivered to an AccessibilityService.
448     * </p>
449     *
450     * @param parent The parent.
451     *
452     * @throws IllegalStateException If called from an AccessibilityService.
453     */
454    public void setParent(View parent) {
455        setParent(parent, UNDEFINED);
456    }
457
458    /**
459     * Sets the parent to be a virtual descendant of the given <code>root</code>.
460     * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
461     * is set as the parent.
462     * <p>
463     * A virtual descendant is an imaginary View that is reported as a part of the view
464     * hierarchy for accessibility purposes. This enables custom views that draw complex
465     * content to report them selves as a tree of virtual views, thus conveying their
466     * logical structure.
467     * </p>
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 root The root of the virtual subtree.
475     * @param virtualDescendantId The id of the virtual descendant.
476     */
477    public void setParent(View root, int virtualDescendantId) {
478        enforceNotSealed();
479        final int rootAccessibilityViewId =
480            (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
481        mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
482    }
483
484    /**
485     * Gets the node bounds in parent coordinates.
486     *
487     * @param outBounds The output node bounds.
488     */
489    public void getBoundsInParent(Rect outBounds) {
490        outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
491                mBoundsInParent.right, mBoundsInParent.bottom);
492    }
493
494    /**
495     * Sets the node bounds in parent coordinates.
496     * <p>
497     *   <strong>Note:</strong> Cannot be called from an
498     *   {@link android.accessibilityservice.AccessibilityService}.
499     *   This class is made immutable before being delivered to an AccessibilityService.
500     * </p>
501     *
502     * @param bounds The node bounds.
503     *
504     * @throws IllegalStateException If called from an AccessibilityService.
505     */
506    public void setBoundsInParent(Rect bounds) {
507        enforceNotSealed();
508        mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
509    }
510
511    /**
512     * Gets the node bounds in screen coordinates.
513     *
514     * @param outBounds The output node bounds.
515     */
516    public void getBoundsInScreen(Rect outBounds) {
517        outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
518                mBoundsInScreen.right, mBoundsInScreen.bottom);
519    }
520
521    /**
522     * Sets the node bounds in screen coordinates.
523     * <p>
524     *   <strong>Note:</strong> Cannot be called from an
525     *   {@link android.accessibilityservice.AccessibilityService}.
526     *   This class is made immutable before being delivered to an AccessibilityService.
527     * </p>
528     *
529     * @param bounds The node bounds.
530     *
531     * @throws IllegalStateException If called from an AccessibilityService.
532     */
533    public void setBoundsInScreen(Rect bounds) {
534        enforceNotSealed();
535        mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
536    }
537
538    /**
539     * Gets whether this node is checkable.
540     *
541     * @return True if the node is checkable.
542     */
543    public boolean isCheckable() {
544        return getBooleanProperty(PROPERTY_CHECKABLE);
545    }
546
547    /**
548     * Sets whether this node is checkable.
549     * <p>
550     *   <strong>Note:</strong> Cannot be called from an
551     *   {@link android.accessibilityservice.AccessibilityService}.
552     *   This class is made immutable before being delivered to an AccessibilityService.
553     * </p>
554     *
555     * @param checkable True if the node is checkable.
556     *
557     * @throws IllegalStateException If called from an AccessibilityService.
558     */
559    public void setCheckable(boolean checkable) {
560        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
561    }
562
563    /**
564     * Gets whether this node is checked.
565     *
566     * @return True if the node is checked.
567     */
568    public boolean isChecked() {
569        return getBooleanProperty(PROPERTY_CHECKED);
570    }
571
572    /**
573     * Sets whether this node is checked.
574     * <p>
575     *   <strong>Note:</strong> Cannot be called from an
576     *   {@link android.accessibilityservice.AccessibilityService}.
577     *   This class is made immutable before being delivered to an AccessibilityService.
578     * </p>
579     *
580     * @param checked True if the node is checked.
581     *
582     * @throws IllegalStateException If called from an AccessibilityService.
583     */
584    public void setChecked(boolean checked) {
585        setBooleanProperty(PROPERTY_CHECKED, checked);
586    }
587
588    /**
589     * Gets whether this node is focusable.
590     *
591     * @return True if the node is focusable.
592     */
593    public boolean isFocusable() {
594        return getBooleanProperty(PROPERTY_FOCUSABLE);
595    }
596
597    /**
598     * Sets whether this node is focusable.
599     * <p>
600     *   <strong>Note:</strong> Cannot be called from an
601     *   {@link android.accessibilityservice.AccessibilityService}.
602     *   This class is made immutable before being delivered to an AccessibilityService.
603     * </p>
604     *
605     * @param focusable True if the node is focusable.
606     *
607     * @throws IllegalStateException If called from an AccessibilityService.
608     */
609    public void setFocusable(boolean focusable) {
610        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
611    }
612
613    /**
614     * Gets whether this node is focused.
615     *
616     * @return True if the node is focused.
617     */
618    public boolean isFocused() {
619        return getBooleanProperty(PROPERTY_FOCUSED);
620    }
621
622    /**
623     * Sets whether this node is focused.
624     * <p>
625     *   <strong>Note:</strong> Cannot be called from an
626     *   {@link android.accessibilityservice.AccessibilityService}.
627     *   This class is made immutable before being delivered to an AccessibilityService.
628     * </p>
629     *
630     * @param focused True if the node is focused.
631     *
632     * @throws IllegalStateException If called from an AccessibilityService.
633     */
634    public void setFocused(boolean focused) {
635        setBooleanProperty(PROPERTY_FOCUSED, focused);
636    }
637
638    /**
639     * Gets whether this node is selected.
640     *
641     * @return True if the node is selected.
642     */
643    public boolean isSelected() {
644        return getBooleanProperty(PROPERTY_SELECTED);
645    }
646
647    /**
648     * Sets whether this node is selected.
649     * <p>
650     *   <strong>Note:</strong> Cannot be called from an
651     *   {@link android.accessibilityservice.AccessibilityService}.
652     *   This class is made immutable before being delivered to an AccessibilityService.
653     * </p>
654     *
655     * @param selected True if the node is selected.
656     *
657     * @throws IllegalStateException If called from an AccessibilityService.
658     */
659    public void setSelected(boolean selected) {
660        setBooleanProperty(PROPERTY_SELECTED, selected);
661    }
662
663    /**
664     * Gets whether this node is clickable.
665     *
666     * @return True if the node is clickable.
667     */
668    public boolean isClickable() {
669        return getBooleanProperty(PROPERTY_CLICKABLE);
670    }
671
672    /**
673     * Sets whether this node is clickable.
674     * <p>
675     *   <strong>Note:</strong> Cannot be called from an
676     *   {@link android.accessibilityservice.AccessibilityService}.
677     *   This class is made immutable before being delivered to an AccessibilityService.
678     * </p>
679     *
680     * @param clickable True if the node is clickable.
681     *
682     * @throws IllegalStateException If called from an AccessibilityService.
683     */
684    public void setClickable(boolean clickable) {
685        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
686    }
687
688    /**
689     * Gets whether this node is long clickable.
690     *
691     * @return True if the node is long clickable.
692     */
693    public boolean isLongClickable() {
694        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
695    }
696
697    /**
698     * Sets whether this node is long clickable.
699     * <p>
700     *   <strong>Note:</strong> Cannot be called from an
701     *   {@link android.accessibilityservice.AccessibilityService}.
702     *   This class is made immutable before being delivered to an AccessibilityService.
703     * </p>
704     *
705     * @param longClickable True if the node is long clickable.
706     *
707     * @throws IllegalStateException If called from an AccessibilityService.
708     */
709    public void setLongClickable(boolean longClickable) {
710        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
711    }
712
713    /**
714     * Gets whether this node is enabled.
715     *
716     * @return True if the node is enabled.
717     */
718    public boolean isEnabled() {
719        return getBooleanProperty(PROPERTY_ENABLED);
720    }
721
722    /**
723     * Sets whether this node is enabled.
724     * <p>
725     *   <strong>Note:</strong> Cannot be called from an
726     *   {@link android.accessibilityservice.AccessibilityService}.
727     *   This class is made immutable before being delivered to an AccessibilityService.
728     * </p>
729     *
730     * @param enabled True if the node is enabled.
731     *
732     * @throws IllegalStateException If called from an AccessibilityService.
733     */
734    public void setEnabled(boolean enabled) {
735        setBooleanProperty(PROPERTY_ENABLED, enabled);
736    }
737
738    /**
739     * Gets whether this node is a password.
740     *
741     * @return True if the node is a password.
742     */
743    public boolean isPassword() {
744        return getBooleanProperty(PROPERTY_PASSWORD);
745    }
746
747    /**
748     * Sets whether this node is a password.
749     * <p>
750     *   <strong>Note:</strong> Cannot be called from an
751     *   {@link android.accessibilityservice.AccessibilityService}.
752     *   This class is made immutable before being delivered to an AccessibilityService.
753     * </p>
754     *
755     * @param password True if the node is a password.
756     *
757     * @throws IllegalStateException If called from an AccessibilityService.
758     */
759    public void setPassword(boolean password) {
760        setBooleanProperty(PROPERTY_PASSWORD, password);
761    }
762
763    /**
764     * Gets if the node is scrollable.
765     *
766     * @return True if the node is scrollable, false otherwise.
767     */
768    public boolean isScrollable() {
769        return getBooleanProperty(PROPERTY_SCROLLABLE);
770    }
771
772    /**
773     * Sets if the node is scrollable.
774     * <p>
775     *   <strong>Note:</strong> Cannot be called from an
776     *   {@link android.accessibilityservice.AccessibilityService}.
777     *   This class is made immutable before being delivered to an AccessibilityService.
778     * </p>
779     *
780     * @param scrollable True if the node is scrollable, false otherwise.
781     *
782     * @throws IllegalStateException If called from an AccessibilityService.
783     */
784    public void setScrollable(boolean scrollable) {
785        enforceNotSealed();
786        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
787    }
788
789    /**
790     * Gets the package this node comes from.
791     *
792     * @return The package name.
793     */
794    public CharSequence getPackageName() {
795        return mPackageName;
796    }
797
798    /**
799     * Sets the package this node comes from.
800     * <p>
801     *   <strong>Note:</strong> Cannot be called from an
802     *   {@link android.accessibilityservice.AccessibilityService}.
803     *   This class is made immutable before being delivered to an AccessibilityService.
804     * </p>
805     *
806     * @param packageName The package name.
807     *
808     * @throws IllegalStateException If called from an AccessibilityService.
809     */
810    public void setPackageName(CharSequence packageName) {
811        enforceNotSealed();
812        mPackageName = packageName;
813    }
814
815    /**
816     * Gets the class this node comes from.
817     *
818     * @return The class name.
819     */
820    public CharSequence getClassName() {
821        return mClassName;
822    }
823
824    /**
825     * Sets the class this node comes from.
826     * <p>
827     *   <strong>Note:</strong> Cannot be called from an
828     *   {@link android.accessibilityservice.AccessibilityService}.
829     *   This class is made immutable before being delivered to an AccessibilityService.
830     * </p>
831     *
832     * @param className The class name.
833     *
834     * @throws IllegalStateException If called from an AccessibilityService.
835     */
836    public void setClassName(CharSequence className) {
837        enforceNotSealed();
838        mClassName = className;
839    }
840
841    /**
842     * Gets the text of this node.
843     *
844     * @return The text.
845     */
846    public CharSequence getText() {
847        return mText;
848    }
849
850    /**
851     * Sets the text of this node.
852     * <p>
853     *   <strong>Note:</strong> Cannot be called from an
854     *   {@link android.accessibilityservice.AccessibilityService}.
855     *   This class is made immutable before being delivered to an AccessibilityService.
856     * </p>
857     *
858     * @param text The text.
859     *
860     * @throws IllegalStateException If called from an AccessibilityService.
861     */
862    public void setText(CharSequence text) {
863        enforceNotSealed();
864        mText = text;
865    }
866
867    /**
868     * Gets the content description of this node.
869     *
870     * @return The content description.
871     */
872    public CharSequence getContentDescription() {
873        return mContentDescription;
874    }
875
876    /**
877     * Sets the content description of this node.
878     * <p>
879     *   <strong>Note:</strong> Cannot be called from an
880     *   {@link android.accessibilityservice.AccessibilityService}.
881     *   This class is made immutable before being delivered to an AccessibilityService.
882     * </p>
883     *
884     * @param contentDescription The content description.
885     *
886     * @throws IllegalStateException If called from an AccessibilityService.
887     */
888    public void setContentDescription(CharSequence contentDescription) {
889        enforceNotSealed();
890        mContentDescription = contentDescription;
891    }
892
893    /**
894     * Gets the value of a boolean property.
895     *
896     * @param property The property.
897     * @return The value.
898     */
899    private boolean getBooleanProperty(int property) {
900        return (mBooleanProperties & property) != 0;
901    }
902
903    /**
904     * Sets a boolean property.
905     *
906     * @param property The property.
907     * @param value The value.
908     *
909     * @throws IllegalStateException If called from an AccessibilityService.
910     */
911    private void setBooleanProperty(int property, boolean value) {
912        enforceNotSealed();
913        if (value) {
914            mBooleanProperties |= property;
915        } else {
916            mBooleanProperties &= ~property;
917        }
918    }
919
920    /**
921     * Sets the unique id of the IAccessibilityServiceConnection over which
922     * this instance can send requests to the system.
923     *
924     * @param connectionId The connection id.
925     *
926     * @hide
927     */
928    public void setConnectionId(int connectionId) {
929        enforceNotSealed();
930        mConnectionId = connectionId;
931    }
932
933    /**
934     * {@inheritDoc}
935     */
936    public int describeContents() {
937        return 0;
938    }
939
940    /**
941     * Gets the id of the source node.
942     *
943     * @return The id.
944     *
945     * @hide
946     */
947    public long getSourceNodeId() {
948        return mSourceNodeId;
949    }
950
951    /**
952     * Sets if this instance is sealed.
953     *
954     * @param sealed Whether is sealed.
955     *
956     * @hide
957     */
958    public void setSealed(boolean sealed) {
959        mSealed = sealed;
960    }
961
962    /**
963     * Gets if this instance is sealed.
964     *
965     * @return Whether is sealed.
966     *
967     * @hide
968     */
969    public boolean isSealed() {
970        return mSealed;
971    }
972
973    /**
974     * Enforces that this instance is sealed.
975     *
976     * @throws IllegalStateException If this instance is not sealed.
977     *
978     * @hide
979     */
980    protected void enforceSealed() {
981        if (!isSealed()) {
982            throw new IllegalStateException("Cannot perform this "
983                    + "action on a not sealed instance.");
984        }
985    }
986
987    /**
988     * Enforces that this instance is not sealed.
989     *
990     * @throws IllegalStateException If this instance is sealed.
991     *
992     * @hide
993     */
994    protected void enforceNotSealed() {
995        if (isSealed()) {
996            throw new IllegalStateException("Cannot perform this "
997                    + "action on a sealed instance.");
998        }
999    }
1000
1001    /**
1002     * Returns a cached instance if such is available otherwise a new one
1003     * and sets the source.
1004     *
1005     * @param source The source view.
1006     * @return An instance.
1007     *
1008     * @see #setSource(View)
1009     */
1010    public static AccessibilityNodeInfo obtain(View source) {
1011        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1012        info.setSource(source);
1013        return info;
1014    }
1015
1016    /**
1017     * Returns a cached instance if such is available otherwise a new one
1018     * and sets the source.
1019     *
1020     * @param root The root of the virtual subtree.
1021     * @param virtualDescendantId The id of the virtual descendant.
1022     * @return An instance.
1023     *
1024     * @see #setSource(View, int)
1025     */
1026    public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
1027        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1028        info.setSource(root, virtualDescendantId);
1029        return info;
1030    }
1031
1032    /**
1033     * Returns a cached instance if such is available otherwise a new one.
1034     *
1035     * @return An instance.
1036     */
1037    public static AccessibilityNodeInfo obtain() {
1038        synchronized (sPoolLock) {
1039            if (sPool != null) {
1040                AccessibilityNodeInfo info = sPool;
1041                sPool = sPool.mNext;
1042                sPoolSize--;
1043                info.mNext = null;
1044                info.mIsInPool = false;
1045                return info;
1046            }
1047            return new AccessibilityNodeInfo();
1048        }
1049    }
1050
1051    /**
1052     * Returns a cached instance if such is available or a new one is
1053     * create. The returned instance is initialized from the given
1054     * <code>info</code>.
1055     *
1056     * @param info The other info.
1057     * @return An instance.
1058     */
1059    public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
1060        AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
1061        infoClone.init(info);
1062        return infoClone;
1063    }
1064
1065    /**
1066     * Return an instance back to be reused.
1067     * <p>
1068     * <strong>Note:</strong> You must not touch the object after calling this function.
1069     *
1070     * @throws IllegalStateException If the info is already recycled.
1071     */
1072    public void recycle() {
1073        if (mIsInPool) {
1074            throw new IllegalStateException("Info already recycled!");
1075        }
1076        clear();
1077        synchronized (sPoolLock) {
1078            if (sPoolSize <= MAX_POOL_SIZE) {
1079                mNext = sPool;
1080                sPool = this;
1081                mIsInPool = true;
1082                sPoolSize++;
1083            }
1084        }
1085    }
1086
1087    /**
1088     * {@inheritDoc}
1089     * <p>
1090     *   <strong>Note:</strong> After the instance is written to a parcel it
1091     *      is recycled. You must not touch the object after calling this function.
1092     * </p>
1093     */
1094    public void writeToParcel(Parcel parcel, int flags) {
1095        parcel.writeInt(isSealed() ? 1 : 0);
1096        parcel.writeLong(mSourceNodeId);
1097        parcel.writeInt(mWindowId);
1098        parcel.writeLong(mParentNodeId);
1099        parcel.writeInt(mConnectionId);
1100
1101        SparseLongArray childIds = mChildNodeIds;
1102        final int childIdsSize = childIds.size();
1103        parcel.writeInt(childIdsSize);
1104        for (int i = 0; i < childIdsSize; i++) {
1105            parcel.writeLong(childIds.valueAt(i));
1106        }
1107
1108        parcel.writeInt(mBoundsInParent.top);
1109        parcel.writeInt(mBoundsInParent.bottom);
1110        parcel.writeInt(mBoundsInParent.left);
1111        parcel.writeInt(mBoundsInParent.right);
1112
1113        parcel.writeInt(mBoundsInScreen.top);
1114        parcel.writeInt(mBoundsInScreen.bottom);
1115        parcel.writeInt(mBoundsInScreen.left);
1116        parcel.writeInt(mBoundsInScreen.right);
1117
1118        parcel.writeInt(mActions);
1119
1120        parcel.writeInt(mBooleanProperties);
1121
1122        TextUtils.writeToParcel(mPackageName, parcel, flags);
1123        TextUtils.writeToParcel(mClassName, parcel, flags);
1124        TextUtils.writeToParcel(mText, parcel, flags);
1125        TextUtils.writeToParcel(mContentDescription, parcel, flags);
1126
1127        // Since instances of this class are fetched via synchronous i.e. blocking
1128        // calls in IPCs we always recycle as soon as the instance is marshaled.
1129        recycle();
1130    }
1131
1132    /**
1133     * Initializes this instance from another one.
1134     *
1135     * @param other The other instance.
1136     */
1137    private void init(AccessibilityNodeInfo other) {
1138        mSealed = other.mSealed;
1139        mSourceNodeId = other.mSourceNodeId;
1140        mParentNodeId = other.mParentNodeId;
1141        mWindowId = other.mWindowId;
1142        mConnectionId = other.mConnectionId;
1143        mBoundsInParent.set(other.mBoundsInParent);
1144        mBoundsInScreen.set(other.mBoundsInScreen);
1145        mPackageName = other.mPackageName;
1146        mClassName = other.mClassName;
1147        mText = other.mText;
1148        mContentDescription = other.mContentDescription;
1149        mActions= other.mActions;
1150        mBooleanProperties = other.mBooleanProperties;
1151        mChildNodeIds = other.mChildNodeIds.clone();
1152    }
1153
1154    /**
1155     * Creates a new instance from a {@link Parcel}.
1156     *
1157     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
1158     */
1159    private void initFromParcel(Parcel parcel) {
1160        mSealed = (parcel.readInt()  == 1);
1161        mSourceNodeId = parcel.readLong();
1162        mWindowId = parcel.readInt();
1163        mParentNodeId = parcel.readLong();
1164        mConnectionId = parcel.readInt();
1165
1166        SparseLongArray childIds = mChildNodeIds;
1167        final int childrenSize = parcel.readInt();
1168        for (int i = 0; i < childrenSize; i++) {
1169            final long childId = parcel.readLong();
1170            childIds.put(i, childId);
1171        }
1172
1173        mBoundsInParent.top = parcel.readInt();
1174        mBoundsInParent.bottom = parcel.readInt();
1175        mBoundsInParent.left = parcel.readInt();
1176        mBoundsInParent.right = parcel.readInt();
1177
1178        mBoundsInScreen.top = parcel.readInt();
1179        mBoundsInScreen.bottom = parcel.readInt();
1180        mBoundsInScreen.left = parcel.readInt();
1181        mBoundsInScreen.right = parcel.readInt();
1182
1183        mActions = parcel.readInt();
1184
1185        mBooleanProperties = parcel.readInt();
1186
1187        mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1188        mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1189        mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1190        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1191    }
1192
1193    /**
1194     * Clears the state of this instance.
1195     */
1196    private void clear() {
1197        mSealed = false;
1198        mSourceNodeId = ROOT_NODE_ID;
1199        mParentNodeId = ROOT_NODE_ID;
1200        mWindowId = UNDEFINED;
1201        mConnectionId = UNDEFINED;
1202        mChildNodeIds.clear();
1203        mBoundsInParent.set(0, 0, 0, 0);
1204        mBoundsInScreen.set(0, 0, 0, 0);
1205        mBooleanProperties = 0;
1206        mPackageName = null;
1207        mClassName = null;
1208        mText = null;
1209        mContentDescription = null;
1210        mActions = 0;
1211    }
1212
1213    /**
1214     * Gets the human readable action symbolic name.
1215     *
1216     * @param action The action.
1217     * @return The symbolic name.
1218     */
1219    private static String getActionSymbolicName(int action) {
1220        switch (action) {
1221            case ACTION_FOCUS:
1222                return "ACTION_FOCUS";
1223            case ACTION_CLEAR_FOCUS:
1224                return "ACTION_CLEAR_FOCUS";
1225            case ACTION_SELECT:
1226                return "ACTION_SELECT";
1227            case ACTION_CLEAR_SELECTION:
1228                return "ACTION_CLEAR_SELECTION";
1229            default:
1230                throw new IllegalArgumentException("Unknown action: " + action);
1231        }
1232    }
1233
1234    private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
1235        return (mWindowId != UNDEFINED
1236                && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
1237                && mConnectionId != UNDEFINED);
1238    }
1239
1240    @Override
1241    public boolean equals(Object object) {
1242        if (this == object) {
1243            return true;
1244        }
1245        if (object == null) {
1246            return false;
1247        }
1248        if (getClass() != object.getClass()) {
1249            return false;
1250        }
1251        AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
1252        if (mSourceNodeId != other.mSourceNodeId) {
1253            return false;
1254        }
1255        if (mWindowId != other.mWindowId) {
1256            return false;
1257        }
1258        return true;
1259    }
1260
1261    @Override
1262    public int hashCode() {
1263        final int prime = 31;
1264        int result = 1;
1265        result = prime * result + getAccessibilityViewId(mSourceNodeId);
1266        result = prime * result + getVirtualDescendantId(mSourceNodeId);
1267        result = prime * result + mWindowId;
1268        return result;
1269    }
1270
1271    @Override
1272    public String toString() {
1273        StringBuilder builder = new StringBuilder();
1274        builder.append(super.toString());
1275
1276        if (DEBUG) {
1277            builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
1278            builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
1279            builder.append("; mParentNodeId: " + mParentNodeId);
1280            SparseLongArray childIds = mChildNodeIds;
1281            builder.append("; childAccessibilityIds: [");
1282            for (int i = 0, count = childIds.size(); i < count; i++) {
1283                builder.append(childIds.valueAt(i));
1284                if (i < count - 1) {
1285                    builder.append(", ");
1286                }
1287           }
1288           builder.append("]");
1289        }
1290
1291        builder.append("; boundsInParent: " + mBoundsInParent);
1292        builder.append("; boundsInScreen: " + mBoundsInScreen);
1293
1294        builder.append("; packageName: ").append(mPackageName);
1295        builder.append("; className: ").append(mClassName);
1296        builder.append("; text: ").append(mText);
1297        builder.append("; contentDescription: ").append(mContentDescription);
1298
1299        builder.append("; checkable: ").append(isCheckable());
1300        builder.append("; checked: ").append(isChecked());
1301        builder.append("; focusable: ").append(isFocusable());
1302        builder.append("; focused: ").append(isFocused());
1303        builder.append("; selected: ").append(isSelected());
1304        builder.append("; clickable: ").append(isClickable());
1305        builder.append("; longClickable: ").append(isLongClickable());
1306        builder.append("; enabled: ").append(isEnabled());
1307        builder.append("; password: ").append(isPassword());
1308        builder.append("; scrollable: " + isScrollable());
1309
1310        builder.append("; [");
1311
1312        for (int actionBits = mActions; actionBits != 0;) {
1313            final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
1314            actionBits &= ~action;
1315            builder.append(getActionSymbolicName(action));
1316            if (actionBits != 0) {
1317                builder.append(", ");
1318            }
1319        }
1320
1321        builder.append("]");
1322
1323        return builder.toString();
1324    }
1325
1326    /**
1327     * @see Parcelable.Creator
1328     */
1329    public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
1330            new Parcelable.Creator<AccessibilityNodeInfo>() {
1331        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
1332            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1333            info.initFromParcel(parcel);
1334            return info;
1335        }
1336
1337        public AccessibilityNodeInfo[] newArray(int size) {
1338            return new AccessibilityNodeInfo[size];
1339        }
1340    };
1341}
1342