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