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