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