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