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