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