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