AccessibilityNodeInfo.java revision 9210ccbdc3629cead65a822d729e1783a773118c
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.os.RemoteException;
24import android.text.TextUtils;
25import android.util.SparseArray;
26import android.util.SparseIntArray;
27import android.view.View;
28
29/**
30 * This class represents a node of the screen content. From the point of
31 * view of an accessibility service the screen content is presented as tree
32 * of accessibility nodes.
33 *
34 * TODO(svertoslavganov): Update the documentation, add sample, and describe
35 *                        the security policy.
36 */
37public class AccessibilityNodeInfo implements Parcelable {
38
39    private static final boolean DEBUG = false;
40
41    // Actions.
42
43    /**
44     * Action that focuses the node.
45     */
46    public static final int ACTION_FOCUS =  0x00000001;
47
48    /**
49     * Action that unfocuses the node.
50     */
51    public static final int ACTION_CLEAR_FOCUS =  0x00000002;
52
53    /**
54     * Action that selects the node.
55     */
56    public static final int ACTION_SELECT =  0x00000004;
57
58    /**
59     * Action that unselects the node.
60     */
61    public static final int ACTION_CLEAR_SELECTION =  0x00000008;
62
63    // Boolean attributes.
64
65    private static final int PROPERTY_CHECKABLE = 0x00000001;
66
67    private static final int PROPERTY_CHECKED = 0x00000002;
68
69    private static final int PROPERTY_FOCUSABLE = 0x00000004;
70
71    private static final int PROPERTY_FOCUSED = 0x00000008;
72
73    private static final int PROPERTY_SELECTED = 0x00000010;
74
75    private static final int PROPERTY_CLICKABLE = 0x00000020;
76
77    private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
78
79    private static final int PROPERTY_ENABLED = 0x00000080;
80
81    private static final int PROPERTY_PASSWORD = 0x00000100;
82
83    // Readable representations - lazily initialized.
84    private static SparseArray<String> sActionSymbolicNames;
85
86    // Housekeeping.
87    private static final int MAX_POOL_SIZE = 50;
88    private static final Object sPoolLock = new Object();
89    private static AccessibilityNodeInfo sPool;
90    private static int sPoolSize;
91    private AccessibilityNodeInfo mNext;
92    private boolean mIsInPool;
93    private boolean mSealed;
94
95    // Data.
96    private int mAccessibilityViewId = View.NO_ID;
97    private int mAccessibilityWindowId = View.NO_ID;
98    private int mParentAccessibilityViewId = View.NO_ID;
99    private int mBooleanProperties;
100    private final Rect mBounds = new Rect();
101
102    private CharSequence mPackageName;
103    private CharSequence mClassName;
104    private CharSequence mText;
105    private CharSequence mContentDescription;
106
107    private final SparseIntArray mChildAccessibilityIds = new SparseIntArray();
108    private int mActions;
109
110    private IAccessibilityServiceConnection mConnection;
111
112    /**
113     * Hide constructor from clients.
114     */
115    private AccessibilityNodeInfo() {
116        /* do nothing */
117    }
118
119    /**
120     * Sets the source.
121     *
122     * @param source The info source.
123     */
124    public void setSource(View source) {
125        enforceNotSealed();
126        mAccessibilityViewId = source.getAccessibilityViewId();
127        mAccessibilityWindowId = source.getAccessibilityWindowId();
128    }
129
130    /**
131     * Gets the id of the window from which the info comes from.
132     *
133     * @return The window id.
134     */
135    public int getAccessibilityWindowId() {
136        return mAccessibilityWindowId;
137    }
138
139    /**
140     * Gets the number of children.
141     *
142     * @return The child count.
143     */
144    public int getChildCount() {
145        return mChildAccessibilityIds.size();
146    }
147
148    /**
149     * Get the child at given index.
150     * <p>
151     *   <strong>
152     *     It is a client responsibility to recycle the received info by
153     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
154     *     of multiple instances.
155     *   </strong>
156     * </p>
157     * @param index The child index.
158     * @return The child node.
159     *
160     * @throws IllegalStateException If called outside of an AccessibilityService.
161     *
162     */
163    public AccessibilityNodeInfo getChild(int index) {
164        enforceSealed();
165        final int childAccessibilityViewId = mChildAccessibilityIds.get(index);
166        try {
167            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
168                    childAccessibilityViewId);
169        } catch (RemoteException e) {
170             return null;
171        }
172    }
173
174    /**
175     * Adds a child.
176     * <p>
177     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
178     *   This class is made immutable before being delivered to an AccessibilityService.
179     * </p>
180     * @param child The child.
181     *
182     * @throws IllegalStateException If called from an AccessibilityService.
183     */
184    public void addChild(View child) {
185        enforceNotSealed();
186        final int childAccessibilityViewId = child.getAccessibilityViewId();
187        final int index = mChildAccessibilityIds.size();
188        mChildAccessibilityIds.put(index, childAccessibilityViewId);
189    }
190
191    /**
192     * Gets the actions that can be performed on the node.
193     *
194     * @return The bit mask of with actions.
195     *
196     * @see AccessibilityNodeInfo#ACTION_FOCUS
197     * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
198     * @see AccessibilityNodeInfo#ACTION_SELECT
199     * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
200     */
201    public int getActions() {
202        return mActions;
203    }
204
205    /**
206     * Adds an action that can be performed on the node.
207     * <p>
208     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
209     *   This class is made immutable before being delivered to an AccessibilityService.
210     * </p>
211     * @param action The action.
212     *
213     * @throws IllegalStateException If called from an AccessibilityService.
214     */
215    public void addAction(int action) {
216        enforceNotSealed();
217        mActions |= action;
218    }
219
220    /**
221     * Performs an action on the node.
222     * <p>
223     *   Note: An action can be performed only if the request is made
224     *   from an {@link android.accessibilityservice.AccessibilityService}.
225     * </p>
226     * @param action The action to perform.
227     * @return True if the action was performed.
228     *
229     * @throws IllegalStateException If called outside of an AccessibilityService.
230     */
231    public boolean performAction(int action) {
232        enforceSealed();
233        try {
234            return mConnection.performAccessibilityAction(mAccessibilityWindowId,
235                    mAccessibilityViewId, action);
236        } catch (RemoteException e) {
237            return false;
238        }
239    }
240
241    /**
242     * Gets the unique id identifying this node's parent.
243     * <p>
244     *   <strong>
245     *     It is a client responsibility to recycle the received info by
246     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
247     *     of multiple instances.
248     *   </strong>
249     * </p>
250     * @return The node's patent id.
251     */
252    public AccessibilityNodeInfo getParent() {
253        enforceSealed();
254        try {
255            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
256                    mParentAccessibilityViewId);
257        } catch (RemoteException e) {
258            return null;
259        }
260    }
261
262    /**
263     * Sets the parent.
264     * <p>
265     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
266     *   This class is made immutable before being delivered to an AccessibilityService.
267     * </p>
268     * @param parent The parent.
269     *
270     * @throws IllegalStateException If called from an AccessibilityService.
271     */
272    public void setParent(View parent) {
273        enforceNotSealed();
274        mParentAccessibilityViewId = parent.getAccessibilityViewId();
275    }
276
277    /**
278     * Gets the node bounds in parent coordinates.
279     *
280     * @param outBounds The output node bounds.
281     */
282    public void getBounds(Rect outBounds) {
283        outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
284    }
285
286    /**
287     * Sets the node bounds in parent coordinates.
288     * <p>
289     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
290     *   This class is made immutable before being delivered to an AccessibilityService.
291     * </p>
292     * @param bounds The node bounds.
293     *
294     * @throws IllegalStateException If called from an AccessibilityService.
295     */
296    public void setBounds(Rect bounds) {
297        enforceNotSealed();
298        mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
299    }
300
301    /**
302     * Gets whether this node is checkable.
303     *
304     * @return True if the node is checkable.
305     */
306    public boolean isCheckable() {
307        return getBooleanProperty(PROPERTY_CHECKABLE);
308    }
309
310    /**
311     * Sets whether this node is checkable.
312     * <p>
313     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
314     *   This class is made immutable before being delivered to an AccessibilityService.
315     * </p>
316     * @param checkable True if the node is checkable.
317     *
318     * @throws IllegalStateException If called from an AccessibilityService.
319     */
320    public void setCheckable(boolean checkable) {
321        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
322    }
323
324    /**
325     * Gets whether this node is checked.
326     *
327     * @return True if the node is checked.
328     */
329    public boolean isChecked() {
330        return getBooleanProperty(PROPERTY_CHECKED);
331    }
332
333    /**
334     * Sets whether this node is checked.
335     * <p>
336     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
337     *   This class is made immutable before being delivered to an AccessibilityService.
338     * </p>
339     * @param checked True if the node is checked.
340     *
341     * @throws IllegalStateException If called from an AccessibilityService.
342     */
343    public void setChecked(boolean checked) {
344        setBooleanProperty(PROPERTY_CHECKED, checked);
345    }
346
347    /**
348     * Gets whether this node is focusable.
349     *
350     * @return True if the node is focusable.
351     */
352    public boolean isFocusable() {
353        return getBooleanProperty(PROPERTY_FOCUSABLE);
354    }
355
356    /**
357     * Sets whether this node is focusable.
358     * <p>
359     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
360     *   This class is made immutable before being delivered to an AccessibilityService.
361     * </p>
362     * @param focusable True if the node is focusable.
363     *
364     * @throws IllegalStateException If called from an AccessibilityService.
365     */
366    public void setFocusable(boolean focusable) {
367        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
368    }
369
370    /**
371     * Gets whether this node is focused.
372     *
373     * @return True if the node is focused.
374     */
375    public boolean isFocused() {
376        return getBooleanProperty(PROPERTY_FOCUSED);
377    }
378
379    /**
380     * Sets whether this node is focused.
381     * <p>
382     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
383     *   This class is made immutable before being delivered to an AccessibilityService.
384     * </p>
385     * @param focused True if the node is focused.
386     *
387     * @throws IllegalStateException If called from an AccessibilityService.
388     */
389    public void setFocused(boolean focused) {
390        setBooleanProperty(PROPERTY_FOCUSED, focused);
391    }
392
393    /**
394     * Gets whether this node is selected.
395     *
396     * @return True if the node is selected.
397     */
398    public boolean isSelected() {
399        return getBooleanProperty(PROPERTY_SELECTED);
400    }
401
402    /**
403     * Sets whether this node is selected.
404     * <p>
405     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
406     *   This class is made immutable before being delivered to an AccessibilityService.
407     * </p>
408     * @param selected True if the node is selected.
409     *
410     * @throws IllegalStateException If called from an AccessibilityService.
411     */
412    public void setSelected(boolean selected) {
413        setBooleanProperty(PROPERTY_SELECTED, selected);
414    }
415
416    /**
417     * Gets whether this node is clickable.
418     *
419     * @return True if the node is clickable.
420     */
421    public boolean isClickable() {
422        return getBooleanProperty(PROPERTY_CLICKABLE);
423    }
424
425    /**
426     * Sets whether this node is clickable.
427     * <p>
428     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
429     *   This class is made immutable before being delivered to an AccessibilityService.
430     * </p>
431     * @param clickable True if the node is clickable.
432     *
433     * @throws IllegalStateException If called from an AccessibilityService.
434     */
435    public void setClickable(boolean clickable) {
436        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
437    }
438
439    /**
440     * Gets whether this node is long clickable.
441     *
442     * @return True if the node is long clickable.
443     */
444    public boolean isLongClickable() {
445        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
446    }
447
448    /**
449     * Sets whether this node is long clickable.
450     * <p>
451     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
452     *   This class is made immutable before being delivered to an AccessibilityService.
453     * </p>
454     * @param longClickable True if the node is long clickable.
455     *
456     * @throws IllegalStateException If called from an AccessibilityService.
457     */
458    public void setLongClickable(boolean longClickable) {
459        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
460    }
461
462    /**
463     * Gets whether this node is enabled.
464     *
465     * @return True if the node is enabled.
466     */
467    public boolean isEnabled() {
468        return getBooleanProperty(PROPERTY_ENABLED);
469    }
470
471    /**
472     * Sets whether this node is enabled.
473     * <p>
474     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
475     *   This class is made immutable before being delivered to an AccessibilityService.
476     * </p>
477     * @param enabled True if the node is enabled.
478     *
479     * @throws IllegalStateException If called from an AccessibilityService.
480     */
481    public void setEnabled(boolean enabled) {
482        setBooleanProperty(PROPERTY_ENABLED, enabled);
483    }
484
485    /**
486     * Gets whether this node is a password.
487     *
488     * @return True if the node is a password.
489     */
490    public boolean isPassword() {
491        return getBooleanProperty(PROPERTY_PASSWORD);
492    }
493
494    /**
495     * Sets whether this node is a password.
496     * <p>
497     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
498     *   This class is made immutable before being delivered to an AccessibilityService.
499     * </p>
500     * @param password True if the node is a password.
501     *
502     * @throws IllegalStateException If called from an AccessibilityService.
503     */
504    public void setPassword(boolean password) {
505        setBooleanProperty(PROPERTY_PASSWORD, password);
506    }
507
508    /**
509     * Gets the package this node comes from.
510     *
511     * @return The package name.
512     */
513    public CharSequence getPackageName() {
514        return mPackageName;
515    }
516
517    /**
518     * Sets the package this node comes from.
519     * <p>
520     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
521     *   This class is made immutable before being delivered to an AccessibilityService.
522     * </p>
523     * @param packageName The package name.
524     *
525     * @throws IllegalStateException If called from an AccessibilityService.
526     */
527    public void setPackageName(CharSequence packageName) {
528        enforceNotSealed();
529        mPackageName = packageName;
530    }
531
532    /**
533     * Gets the class this node comes from.
534     *
535     * @return The class name.
536     */
537    public CharSequence getClassName() {
538        return mClassName;
539    }
540
541    /**
542     * Sets the class this node comes from.
543     * <p>
544     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
545     *   This class is made immutable before being delivered to an AccessibilityService.
546     * </p>
547     * @param className The class name.
548     *
549     * @throws IllegalStateException If called from an AccessibilityService.
550     */
551    public void setClassName(CharSequence className) {
552        enforceNotSealed();
553        mClassName = className;
554    }
555
556    /**
557     * Gets the text of this node.
558     *
559     * @return The text.
560     */
561    public CharSequence getText() {
562        return mText;
563    }
564
565    /**
566     * Sets the text of this node.
567     * <p>
568     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
569     *   This class is made immutable before being delivered to an AccessibilityService.
570     * </p>
571     * @param text The text.
572     *
573     * @throws IllegalStateException If called from an AccessibilityService.
574     */
575    public void setText(CharSequence text) {
576        enforceNotSealed();
577        mText = text;
578    }
579
580    /**
581     * Gets the content description of this node.
582     *
583     * @return The content description.
584     */
585    public CharSequence getContentDescription() {
586        return mContentDescription;
587    }
588
589    /**
590     * Sets the content description of this node.
591     * <p>
592     *   Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}.
593     *   This class is made immutable before being delivered to an AccessibilityService.
594     * </p>
595     * @param contentDescription The content description.
596     *
597     * @throws IllegalStateException If called from an AccessibilityService.
598     */
599    public void setContentDescription(CharSequence contentDescription) {
600        enforceNotSealed();
601        mContentDescription = contentDescription;
602    }
603
604    /**
605     * Gets the value of a boolean property.
606     *
607     * @param property The property.
608     * @return The value.
609     */
610    private boolean getBooleanProperty(int property) {
611        return (mBooleanProperties & property) != 0;
612    }
613
614    /**
615     * Sets a boolean property.
616     *
617     * @param property The property.
618     * @param value The value.
619     *
620     * @throws IllegalStateException If called from an AccessibilityService.
621     */
622    private void setBooleanProperty(int property, boolean value) {
623        enforceNotSealed();
624        if (value) {
625            mBooleanProperties |= property;
626        } else {
627            mBooleanProperties &= ~property;
628        }
629    }
630
631    /**
632     * Sets the connection for interacting with the system.
633     *
634     * @param connection The client token.
635     *
636     * @hide
637     */
638    public final void setConnection(IAccessibilityServiceConnection connection) {
639        mConnection = connection;
640    }
641
642    /**
643     * {@inheritDoc}
644     */
645    public int describeContents() {
646        return 0;
647    }
648
649    /**
650     * Sets if this instance is sealed.
651     *
652     * @param sealed Whether is sealed.
653     *
654     * @hide
655     */
656    public void setSealed(boolean sealed) {
657        mSealed = sealed;
658    }
659
660    /**
661     * Gets if this instance is sealed.
662     *
663     * @return Whether is sealed.
664     *
665     * @hide
666     */
667    public boolean isSealed() {
668        return mSealed;
669    }
670
671    /**
672     * Enforces that this instance is sealed.
673     *
674     * @throws IllegalStateException If this instance is not sealed.
675     *
676     * @hide
677     */
678    protected void enforceSealed() {
679        if (!isSealed()) {
680            throw new IllegalStateException("Cannot perform this "
681                    + "action on a not sealed instance.");
682        }
683    }
684
685    /**
686     * Enforces that this instance is not sealed.
687     *
688     * @throws IllegalStateException If this instance is sealed.
689     *
690     * @hide
691     */
692    protected void enforceNotSealed() {
693        if (isSealed()) {
694            throw new IllegalStateException("Cannot perform this "
695                    + "action on an sealed instance.");
696        }
697    }
698
699    /**
700     * Returns a cached instance if such is available otherwise a new one
701     * and sets the source.
702     *
703     * @return An instance.
704     *
705     * @see #setSource(View)
706     */
707    public static AccessibilityNodeInfo obtain(View source) {
708        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
709        info.setSource(source);
710        return info;
711    }
712
713    /**
714     * Returns a cached instance if such is available otherwise a new one.
715     *
716     * @return An instance.
717     */
718    public static AccessibilityNodeInfo obtain() {
719        synchronized (sPoolLock) {
720            if (sPool != null) {
721                AccessibilityNodeInfo info = sPool;
722                sPool = sPool.mNext;
723                sPoolSize--;
724                info.mNext = null;
725                info.mIsInPool = false;
726                return info;
727            }
728            return new AccessibilityNodeInfo();
729        }
730    }
731
732    /**
733     * Return an instance back to be reused.
734     * <p>
735     * <b>Note: You must not touch the object after calling this function.</b>
736     *
737     * @throws IllegalStateException If the info is already recycled.
738     */
739    public void recycle() {
740        if (mIsInPool) {
741            throw new IllegalStateException("Info already recycled!");
742        }
743        clear();
744        synchronized (sPoolLock) {
745            if (sPoolSize <= MAX_POOL_SIZE) {
746                mNext = sPool;
747                sPool = this;
748                mIsInPool = true;
749                sPoolSize++;
750            }
751        }
752    }
753
754    /**
755     * {@inheritDoc}
756     * <p>
757     *   <b>Note: After the instance is written to a parcel it is recycled.
758     *      You must not touch the object after calling this function.</b>
759     * </p>
760     */
761    public void writeToParcel(Parcel parcel, int flags) {
762        if (mConnection == null) {
763            parcel.writeInt(0);
764        } else {
765            parcel.writeInt(1);
766            parcel.writeStrongBinder(mConnection.asBinder());
767        }
768        parcel.writeInt(isSealed() ? 1 : 0);
769        parcel.writeInt(mAccessibilityViewId);
770        parcel.writeInt(mAccessibilityWindowId);
771        parcel.writeInt(mParentAccessibilityViewId);
772
773        SparseIntArray childIds = mChildAccessibilityIds;
774        final int childIdsSize = childIds.size();
775        parcel.writeInt(childIdsSize);
776        for (int i = 0; i < childIdsSize; i++) {
777            parcel.writeInt(childIds.valueAt(i));
778        }
779
780        parcel.writeInt(mBounds.top);
781        parcel.writeInt(mBounds.bottom);
782        parcel.writeInt(mBounds.left);
783        parcel.writeInt(mBounds.right);
784
785        parcel.writeInt(mActions);
786
787        parcel.writeInt(mBooleanProperties);
788
789        TextUtils.writeToParcel(mPackageName, parcel, flags);
790        TextUtils.writeToParcel(mClassName, parcel, flags);
791        TextUtils.writeToParcel(mText, parcel, flags);
792        TextUtils.writeToParcel(mContentDescription, parcel, flags);
793
794        // Since instances of this class are fetched via synchronous i.e. blocking
795        // calls in IPCs and we always recycle as soon as the instance is marshaled.
796        recycle();
797    }
798
799    /**
800     * Creates a new instance from a {@link Parcel}.
801     *
802     * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
803     */
804    private void initFromParcel(Parcel parcel) {
805        if (parcel.readInt() == 1) {
806            mConnection = IAccessibilityServiceConnection.Stub.asInterface(
807                    parcel.readStrongBinder());
808        }
809        mSealed = (parcel.readInt()  == 1);
810        mAccessibilityViewId = parcel.readInt();
811        mAccessibilityWindowId = parcel.readInt();
812        mParentAccessibilityViewId = parcel.readInt();
813
814        SparseIntArray childIds = mChildAccessibilityIds;
815        final int childrenSize = parcel.readInt();
816        for (int i = 0; i < childrenSize; i++) {
817            final int childId = parcel.readInt();
818            childIds.put(i, childId);
819        }
820
821        mBounds.top = parcel.readInt();
822        mBounds.bottom = parcel.readInt();
823        mBounds.left = parcel.readInt();
824        mBounds.right = parcel.readInt();
825
826        mActions = parcel.readInt();
827
828        mBooleanProperties = parcel.readInt();
829
830        mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
831        mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
832        mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
833        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
834    }
835
836    /**
837     * Clears the state of this instance.
838     */
839    private void clear() {
840        mSealed = false;
841        mConnection = null;
842        mAccessibilityViewId = View.NO_ID;
843        mParentAccessibilityViewId = View.NO_ID;
844        mChildAccessibilityIds.clear();
845        mBounds.set(0, 0, 0, 0);
846        mBooleanProperties = 0;
847        mPackageName = null;
848        mClassName = null;
849        mText = null;
850        mContentDescription = null;
851        mActions = 0;
852    }
853
854    /**
855     * Gets the human readable action symbolic name.
856     *
857     * @param action The action.
858     * @return The symbolic name.
859     */
860    private static String getActionSymbolicName(int action) {
861        SparseArray<String> actionSymbolicNames = sActionSymbolicNames;
862        if (actionSymbolicNames == null) {
863            actionSymbolicNames = sActionSymbolicNames = new SparseArray<String>();
864            actionSymbolicNames.put(ACTION_FOCUS, "ACTION_FOCUS");
865            actionSymbolicNames.put(ACTION_CLEAR_FOCUS, "ACTION_UNFOCUS");
866            actionSymbolicNames.put(ACTION_SELECT, "ACTION_SELECT");
867            actionSymbolicNames.put(ACTION_CLEAR_SELECTION, "ACTION_UNSELECT");
868        }
869        return actionSymbolicNames.get(action);
870    }
871
872    @Override
873    public int hashCode() {
874        final int prime = 31;
875        int result = 1;
876        result = prime * result + mAccessibilityViewId;
877        result = prime * result + mAccessibilityWindowId;
878        return result;
879    }
880
881    @Override
882    public String toString() {
883        StringBuilder builder = new StringBuilder();
884        builder.append(super.toString());
885
886        if (DEBUG) {
887            builder.append("; accessibilityId: " + mAccessibilityViewId);
888            builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId);
889            SparseIntArray childIds = mChildAccessibilityIds;
890            builder.append("; childAccessibilityIds: [");
891            for (int i = 0, count = childIds.size(); i < count; i++) {
892                builder.append(childIds.valueAt(i));
893                if (i < count - 1) {
894                    builder.append(", ");
895                }
896           }
897           builder.append("]");
898        }
899
900        builder.append("; bounds: " + mBounds);
901
902        builder.append("; packageName: ").append(mPackageName);
903        builder.append("; className: ").append(mClassName);
904        builder.append("; text: ").append(mText);
905        builder.append("; contentDescription: ").append(mContentDescription);
906
907        builder.append("; checkable: ").append(isCheckable());
908        builder.append("; checked: ").append(isChecked());
909        builder.append("; focusable: ").append(isFocusable());
910        builder.append("; focused: ").append(isFocused());
911        builder.append("; selected: ").append(isSelected());
912        builder.append("; clickable: ").append(isClickable());
913        builder.append("; longClickable: ").append(isLongClickable());
914        builder.append("; enabled: ").append(isEnabled());
915        builder.append("; password: ").append(isPassword());
916
917        builder.append("; [");
918
919        for (int actionBits = mActions; actionBits != 0;) {
920            final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
921            actionBits &= ~action;
922            builder.append(getActionSymbolicName(action));
923            if (actionBits != 0) {
924                builder.append(", ");
925            }
926        }
927
928        builder.append("]");
929
930        return builder.toString();
931    }
932
933    /**
934     * @see Parcelable.Creator
935     */
936    public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
937            new Parcelable.Creator<AccessibilityNodeInfo>() {
938        public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
939            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
940            info.initFromParcel(parcel);
941            return info;
942        }
943
944        public AccessibilityNodeInfo[] newArray(int size) {
945            return new AccessibilityNodeInfo[size];
946        }
947    };
948}
949