• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
5 import static android.os.Build.VERSION_CODES.KITKAT;
6 import static android.os.Build.VERSION_CODES.LOLLIPOP;
7 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
8 import static android.os.Build.VERSION_CODES.N;
9 import static org.robolectric.RuntimeEnvironment.getApiLevel;
10 
11 import android.graphics.Rect;
12 import android.os.Bundle;
13 import android.os.Parcel;
14 import android.os.Parcelable;
15 import android.util.Pair;
16 import android.util.SparseArray;
17 import android.view.View;
18 import android.view.accessibility.AccessibilityNodeInfo;
19 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
20 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
21 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
22 import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
23 import android.view.accessibility.AccessibilityWindowInfo;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import org.robolectric.RuntimeEnvironment;
31 import org.robolectric.annotation.Implementation;
32 import org.robolectric.annotation.Implements;
33 import org.robolectric.annotation.RealObject;
34 import org.robolectric.annotation.Resetter;
35 import org.robolectric.shadow.api.Shadow;
36 import org.robolectric.util.ReflectionHelpers;
37 import org.robolectric.util.ReflectionHelpers.ClassParameter;
38 
39 /**
40  * Properties of {@link android.view.accessibility.AccessibilityNodeInfo} that are normally locked
41  * may be changed using test APIs.
42  *
43  * Calls to {@code obtain()} and {@code recycle()} are tracked to help spot bugs.
44  */
45 @Implements(AccessibilityNodeInfo.class)
46 public class ShadowAccessibilityNodeInfo {
47   // Map of obtained instances of the class along with stack traces of how they were obtained
48   private static final Map<StrictEqualityNodeWrapper, StackTraceElement[]> obtainedInstances =
49       new HashMap<>();
50 
51   private static final SparseArray<StrictEqualityNodeWrapper> orderedInstances =
52       new SparseArray<>();
53 
54   // Bitmasks for actions
55   public static final int UNDEFINED_SELECTION_INDEX = -1;
56 
57   public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
58       new Parcelable.Creator<AccessibilityNodeInfo>() {
59 
60     @Override
61     public AccessibilityNodeInfo createFromParcel(Parcel source) {
62       return obtain(orderedInstances.get(source.readInt()).mInfo);
63     }
64 
65     @Override
66     public AccessibilityNodeInfo[] newArray(int size) {
67       return new AccessibilityNodeInfo[size];
68     }};
69 
70   private static int sAllocationCount = 0;
71 
72   private static final int CLICKABLE_MASK = 0x00000001;
73 
74   private static final int LONGCLICKABLE_MASK = 0x00000002;
75 
76   private static final int FOCUSABLE_MASK = 0x00000004;
77 
78   private static final int FOCUSED_MASK = 0x00000008;
79 
80   private static final int VISIBLE_TO_USER_MASK = 0x00000010;
81 
82   private static final int SCROLLABLE_MASK = 0x00000020;
83 
84   private static final int PASTEABLE_MASK = 0x00000040;
85 
86   private static final int EDITABLE_MASK = 0x00000080;
87 
88   private static final int TEXT_SELECTION_SETABLE_MASK = 0x00000100;
89 
90   private static final int CHECKABLE_MASK = 0x00001000; //14
91 
92   private static final int CHECKED_MASK = 0x00002000; //14
93 
94   private static final int ENABLED_MASK = 0x00010000; //14
95 
96   private static final int PASSWORD_MASK = 0x00040000; //14
97 
98   private static final int SELECTED_MASK = 0x00080000; //14
99 
100   private static final int A11YFOCUSED_MASK = 0x00000800;  //16
101 
102   private static final int MULTILINE_MASK = 0x00020000; //19
103 
104   private static final int CONTENT_INVALID_MASK = 0x00004000; //19
105 
106   private static final int DISMISSABLE_MASK = 0x00008000; //19
107 
108   private static final int CAN_OPEN_POPUP_MASK = 0x00100000; //19
109 
110   /**
111    * Uniquely identifies the origin of the AccessibilityNodeInfo for equality
112    * testing. Two instances that come from the same node info should have the
113    * same ID.
114    */
115   private long mOriginNodeId;
116 
117   private List<AccessibilityNodeInfo> children;
118 
119   private Rect boundsInScreen = new Rect();
120 
121   private Rect boundsInParent = new Rect();
122 
123   private List<Pair<Integer, Bundle>> performedActionAndArgsList;
124 
125   // In API prior to 21, actions are stored in a flag, after 21 they are stored in array of
126   // AccessibilityAction so custom actions can be supported.
127   private ArrayList<AccessibilityAction> actionsArray;
128   private int actionsMask;
129   // Storage of flags
130 
131   private int propertyFlags;
132 
133   private AccessibilityNodeInfo parent;
134 
135   private AccessibilityNodeInfo labelFor;
136 
137   private AccessibilityNodeInfo labeledBy;
138 
139   private View view;
140 
141   private CharSequence contentDescription;
142 
143   private CharSequence text;
144 
145   private CharSequence className;
146 
147   private int textSelectionStart = UNDEFINED_SELECTION_INDEX;
148 
149   private int textSelectionEnd = UNDEFINED_SELECTION_INDEX;
150 
151   private boolean refreshReturnValue = true;
152 
153   private int movementGranularities; //16
154 
155   private CharSequence packageName; //14
156 
157   private String viewIdResourceName; //18
158 
159   private CollectionInfo collectionInfo; //19
160 
161   private CollectionItemInfo collectionItemInfo; //19
162 
163   private int inputType; //19
164 
165   private int liveRegion; //19
166 
167   private RangeInfo rangeInfo; //19
168 
169   private int maxTextLength; //21
170 
171   private CharSequence error; //21
172 
173   private AccessibilityWindowInfo accessibilityWindowInfo;
174 
175   private AccessibilityNodeInfo traversalAfter; //22
176 
177   private AccessibilityNodeInfo traversalBefore; //22
178 
179   private OnPerformActionListener actionListener;
180 
181   private int drawingOrder; // 24
182 
183   @RealObject
184   private AccessibilityNodeInfo realAccessibilityNodeInfo;
185 
186   @Implementation
__constructor__()187   protected void __constructor__() {
188     ReflectionHelpers.setStaticField(AccessibilityNodeInfo.class, "CREATOR", ShadowAccessibilityNodeInfo.CREATOR);
189   }
190 
191   @Implementation
obtain(AccessibilityNodeInfo info)192   protected static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
193     final ShadowAccessibilityNodeInfo shadowInfo = Shadow.extract(info);
194     final AccessibilityNodeInfo obtainedInstance = shadowInfo.getClone();
195 
196     sAllocationCount++;
197     if (shadowInfo.mOriginNodeId == 0) {
198       shadowInfo.mOriginNodeId = sAllocationCount;
199     }
200     StrictEqualityNodeWrapper wrapper = new StrictEqualityNodeWrapper(obtainedInstance);
201     obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
202     orderedInstances.put(sAllocationCount, wrapper);
203     return obtainedInstance;
204   }
205 
206   @Implementation
obtain(View view)207   protected static AccessibilityNodeInfo obtain(View view) {
208     // We explicitly avoid allocating the AccessibilityNodeInfo from the actual pool by using the
209     // private constructor. Not doing so affects test suites which use both shadow and
210     // non-shadow objects.
211     final AccessibilityNodeInfo obtainedInstance =
212         ReflectionHelpers.callConstructor(AccessibilityNodeInfo.class);
213     final ShadowAccessibilityNodeInfo shadowObtained = Shadow.extract(obtainedInstance);
214 
215     /*
216      * We keep a separate list of actions for each object newly obtained
217      * from a view, and perform a shallow copy during getClone. That way the
218      * list of actions performed contains all actions performed on the view
219      * by the tree of nodes initialized from it. Note that initializing two
220      * nodes with the same view will not merge the two lists, as so the list
221      * of performed actions will not contain all actions performed on the
222      * underlying view.
223      */
224     shadowObtained.performedActionAndArgsList = new ArrayList<>();
225 
226     shadowObtained.view = view;
227     sAllocationCount++;
228     if (shadowObtained.mOriginNodeId == 0) {
229       shadowObtained.mOriginNodeId = sAllocationCount;
230     }
231     StrictEqualityNodeWrapper wrapper = new StrictEqualityNodeWrapper(obtainedInstance);
232     obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
233     orderedInstances.put(sAllocationCount, wrapper);
234     return obtainedInstance;
235   }
236 
237   @Implementation
obtain()238   protected static AccessibilityNodeInfo obtain() {
239     return obtain(new View(RuntimeEnvironment.application.getApplicationContext()));
240   }
241 
242   @Implementation
obtain(View root, int virtualDescendantId)243   protected static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
244     AccessibilityNodeInfo node = obtain(root);
245     return node;
246   }
247 
248   /**
249    * Check for leaked objects that were {@code obtain}ed but never
250    * {@code recycle}d.
251    *
252    * @param printUnrecycledNodesToSystemErr - if true, stack traces of calls
253    *        to {@code obtain} that lack matching calls to {@code recycle} are
254    *        dumped to System.err.
255    * @return {@code true} if there are unrecycled nodes
256    */
areThereUnrecycledNodes(boolean printUnrecycledNodesToSystemErr)257   public static boolean areThereUnrecycledNodes(boolean printUnrecycledNodesToSystemErr) {
258     if (printUnrecycledNodesToSystemErr) {
259       for (final StrictEqualityNodeWrapper wrapper : obtainedInstances.keySet()) {
260         final ShadowAccessibilityNodeInfo shadow = Shadow.extract(wrapper.mInfo);
261 
262         System.err.println(String.format(
263             "Leaked contentDescription = %s. Stack trace:", shadow.getContentDescription()));
264         for (final StackTraceElement stackTraceElement : obtainedInstances.get(wrapper)) {
265           System.err.println(stackTraceElement.toString());
266         }
267       }
268     }
269 
270     return (obtainedInstances.size() != 0);
271   }
272 
273   /**
274    * Clear list of obtained instance objects. {@code areThereUnrecycledNodes}
275    * will always return false if called immediately afterwards.
276    */
277   @Resetter
resetObtainedInstances()278   public static void resetObtainedInstances() {
279     obtainedInstances.clear();
280     orderedInstances.clear();
281   }
282 
283   @Implementation
recycle()284   protected void recycle() {
285     final StrictEqualityNodeWrapper wrapper =
286         new StrictEqualityNodeWrapper(realAccessibilityNodeInfo);
287     if (!obtainedInstances.containsKey(wrapper)) {
288       throw new IllegalStateException();
289     }
290 
291     if (labelFor != null) {
292       labelFor.recycle();
293     }
294 
295     if (labeledBy != null) {
296       labeledBy.recycle();
297     }
298     if (getApiLevel() >= LOLLIPOP_MR1) {
299       if (traversalAfter != null) {
300         traversalAfter.recycle();
301       }
302 
303       if (traversalBefore != null) {
304         traversalBefore.recycle();
305       }
306     }
307 
308     obtainedInstances.remove(wrapper);
309     int keyOfWrapper = -1;
310     for (int i = 0; i < orderedInstances.size(); i++) {
311       int key = orderedInstances.keyAt(i);
312       if (orderedInstances.get(key).equals(wrapper)) {
313         keyOfWrapper = key;
314         break;
315       }
316     }
317     orderedInstances.remove(keyOfWrapper);
318   }
319 
320   @Implementation
getChildCount()321   protected int getChildCount() {
322     if (children == null) {
323       return 0;
324     }
325 
326     return children.size();
327   }
328 
329   @Implementation
getChild(int index)330   protected AccessibilityNodeInfo getChild(int index) {
331     if (children == null) {
332       return null;
333     }
334 
335     final AccessibilityNodeInfo child = children.get(index);
336     if (child == null) {
337       return null;
338     }
339 
340     return obtain(child);
341   }
342 
343   @Implementation
getParent()344   protected AccessibilityNodeInfo getParent() {
345     if (parent == null) {
346       return null;
347     }
348 
349     return obtain(parent);
350   }
351 
352   @Implementation(minSdk = JELLY_BEAN_MR2)
refresh()353   protected boolean refresh() {
354       return refreshReturnValue;
355   }
356 
setRefreshReturnValue(boolean refreshReturnValue)357   public void setRefreshReturnValue(boolean refreshReturnValue) {
358     this.refreshReturnValue = refreshReturnValue;
359   }
360 
361   @Implementation
isClickable()362   protected boolean isClickable() {
363     return ((propertyFlags & CLICKABLE_MASK) != 0);
364   }
365 
366   @Implementation
isLongClickable()367   protected boolean isLongClickable() {
368     return ((propertyFlags & LONGCLICKABLE_MASK) != 0);
369   }
370 
371   @Implementation
isFocusable()372   protected boolean isFocusable() {
373     return ((propertyFlags & FOCUSABLE_MASK) != 0);
374   }
375 
376   @Implementation
isFocused()377   protected boolean isFocused() {
378     return ((propertyFlags & FOCUSED_MASK) != 0);
379   }
380 
381   @Implementation
isVisibleToUser()382   protected boolean isVisibleToUser() {
383     return ((propertyFlags & VISIBLE_TO_USER_MASK) != 0);
384   }
385 
386   @Implementation
isScrollable()387   protected boolean isScrollable() {
388     return ((propertyFlags & SCROLLABLE_MASK) != 0);
389   }
390 
isPasteable()391   public boolean isPasteable() {
392     return ((propertyFlags & PASTEABLE_MASK) != 0);
393   }
394 
395   @Implementation(minSdk = JELLY_BEAN_MR2)
isEditable()396   protected boolean isEditable() {
397     return ((propertyFlags & EDITABLE_MASK) != 0);
398   }
399 
isTextSelectionSetable()400   public boolean isTextSelectionSetable() {
401     return ((propertyFlags & TEXT_SELECTION_SETABLE_MASK) != 0);
402   }
403 
404   @Implementation
isCheckable()405   protected boolean isCheckable() {
406     return ((propertyFlags & CHECKABLE_MASK) != 0);
407   }
408 
409   @Implementation
setCheckable(boolean checkable)410   protected void setCheckable(boolean checkable) {
411     propertyFlags = (propertyFlags & ~CHECKABLE_MASK) |
412         (checkable ? CHECKABLE_MASK : 0);
413   }
414 
415   @Implementation
setChecked(boolean checked)416   protected void setChecked(boolean checked) {
417     propertyFlags = (propertyFlags & ~CHECKED_MASK) |
418         (checked ? CHECKED_MASK : 0);
419   }
420 
421   @Implementation
isChecked()422   protected boolean isChecked() {
423     return ((propertyFlags & CHECKED_MASK) != 0);
424   }
425 
426   @Implementation
setEnabled(boolean enabled)427   protected void setEnabled(boolean enabled) {
428     propertyFlags = (propertyFlags & ~ENABLED_MASK) |
429         (enabled ? ENABLED_MASK : 0);
430   }
431 
432   @Implementation
isEnabled()433   protected boolean isEnabled() {
434     return ((propertyFlags & ENABLED_MASK) != 0);
435   }
436 
437   @Implementation
setPassword(boolean password)438   protected void setPassword(boolean password) {
439     propertyFlags = (propertyFlags & ~PASSWORD_MASK) |
440         (password ? PASSWORD_MASK : 0);
441   }
442 
443   @Implementation
isPassword()444   protected boolean isPassword() {
445     return ((propertyFlags & PASSWORD_MASK) != 0);
446   }
447 
448   @Implementation
setSelected(boolean selected)449   protected void setSelected(boolean selected) {
450     propertyFlags = (propertyFlags & ~SELECTED_MASK) |
451         (selected ? SELECTED_MASK : 0);
452   }
453 
454   @Implementation
isSelected()455   protected boolean isSelected() {
456     return ((propertyFlags & SELECTED_MASK) != 0);
457   }
458 
459   @Implementation
setAccessibilityFocused(boolean focused)460   protected void setAccessibilityFocused(boolean focused) {
461     propertyFlags = (propertyFlags & ~A11YFOCUSED_MASK) |
462         (focused ? A11YFOCUSED_MASK : 0);
463   }
464 
465   @Implementation
isAccessibilityFocused()466   protected boolean isAccessibilityFocused() {
467     return ((propertyFlags & A11YFOCUSED_MASK) != 0);
468   }
469 
470   @Implementation(minSdk = LOLLIPOP)
setMultiLine(boolean multiLine)471   protected void setMultiLine(boolean multiLine) {
472     propertyFlags = (propertyFlags & ~MULTILINE_MASK) |
473         (multiLine ? MULTILINE_MASK : 0);
474   }
475 
476   @Implementation(minSdk = LOLLIPOP)
isMultiLine()477   protected boolean isMultiLine() {
478     return ((propertyFlags & MULTILINE_MASK) != 0);
479   }
480 
481   @Implementation(minSdk = LOLLIPOP)
setContentInvalid(boolean contentInvalid)482   protected void setContentInvalid(boolean contentInvalid) {
483     propertyFlags = (propertyFlags & ~CONTENT_INVALID_MASK) |
484         (contentInvalid ? CONTENT_INVALID_MASK : 0);
485   }
486 
487   @Implementation(minSdk = LOLLIPOP)
isContentInvalid()488   protected boolean isContentInvalid() {
489     return ((propertyFlags & CONTENT_INVALID_MASK) != 0);
490   }
491 
492   @Implementation(minSdk = LOLLIPOP)
setDismissable(boolean dismissable)493   protected void setDismissable(boolean dismissable) {
494     propertyFlags = (propertyFlags & ~DISMISSABLE_MASK) |
495         (dismissable ? DISMISSABLE_MASK : 0);
496   }
497 
498   @Implementation(minSdk = LOLLIPOP)
isDismissable()499   protected boolean isDismissable() {
500     return ((propertyFlags & DISMISSABLE_MASK) != 0);
501   }
502 
503   @Implementation(minSdk = LOLLIPOP)
setCanOpenPopup(boolean opensPopup)504   protected void setCanOpenPopup(boolean opensPopup) {
505     propertyFlags = (propertyFlags & ~CAN_OPEN_POPUP_MASK) |
506         (opensPopup ? CAN_OPEN_POPUP_MASK : 0);
507   }
508 
509   @Implementation(minSdk = LOLLIPOP)
canOpenPopup()510   protected boolean canOpenPopup() {
511     return ((propertyFlags & CAN_OPEN_POPUP_MASK) != 0);
512   }
513 
setTextSelectionSetable(boolean isTextSelectionSetable)514   public void setTextSelectionSetable(boolean isTextSelectionSetable) {
515     propertyFlags = (propertyFlags & ~TEXT_SELECTION_SETABLE_MASK) |
516         (isTextSelectionSetable ? TEXT_SELECTION_SETABLE_MASK : 0);
517   }
518 
519   @Implementation
setClickable(boolean isClickable)520   protected void setClickable(boolean isClickable) {
521     propertyFlags = (propertyFlags & ~CLICKABLE_MASK) | (isClickable ? CLICKABLE_MASK : 0);
522   }
523 
524   @Implementation
setLongClickable(boolean isLongClickable)525   protected void setLongClickable(boolean isLongClickable) {
526     propertyFlags =
527         (propertyFlags & ~LONGCLICKABLE_MASK) | (isLongClickable ? LONGCLICKABLE_MASK : 0);
528   }
529 
530   @Implementation
setFocusable(boolean isFocusable)531   protected void setFocusable(boolean isFocusable) {
532     propertyFlags = (propertyFlags & ~FOCUSABLE_MASK) | (isFocusable ? FOCUSABLE_MASK : 0);
533   }
534 
535   @Implementation
setFocused(boolean isFocused)536   protected void setFocused(boolean isFocused) {
537     propertyFlags = (propertyFlags & ~FOCUSED_MASK) | (isFocused ? FOCUSED_MASK : 0);
538   }
539 
540   @Implementation
setScrollable(boolean isScrollable)541   protected void setScrollable(boolean isScrollable) {
542     propertyFlags = (propertyFlags & ~SCROLLABLE_MASK) | (isScrollable ? SCROLLABLE_MASK : 0);
543   }
544 
setPasteable(boolean isPasteable)545   public void setPasteable(boolean isPasteable) {
546     propertyFlags = (propertyFlags & ~PASTEABLE_MASK) | (isPasteable ? PASTEABLE_MASK : 0);
547   }
548 
549   @Implementation(minSdk = JELLY_BEAN_MR2)
setEditable(boolean isEditable)550   protected void setEditable(boolean isEditable) {
551     propertyFlags = (propertyFlags & ~EDITABLE_MASK) | (isEditable ? EDITABLE_MASK : 0);
552   }
553 
554   @Implementation
setVisibleToUser(boolean isVisibleToUser)555   protected void setVisibleToUser(boolean isVisibleToUser) {
556     propertyFlags =
557         (propertyFlags & ~VISIBLE_TO_USER_MASK) | (isVisibleToUser ? VISIBLE_TO_USER_MASK : 0);
558   }
559 
560   @Implementation
setContentDescription(CharSequence description)561   protected void setContentDescription(CharSequence description) {
562     contentDescription = description;
563   }
564 
565   @Implementation
getContentDescription()566   protected CharSequence getContentDescription() {
567     return contentDescription;
568   }
569 
570   @Implementation
setClassName(CharSequence name)571   protected void setClassName(CharSequence name) {
572     className = name;
573   }
574 
575   @Implementation
getClassName()576   protected CharSequence getClassName() {
577     return className;
578   }
579 
580   @Implementation
setText(CharSequence t)581   protected void setText(CharSequence t) {
582     text = t;
583   }
584 
585   @Implementation
getText()586   protected CharSequence getText() {
587     return text;
588   }
589 
590   @Implementation(minSdk = JELLY_BEAN_MR2)
setTextSelection(int start, int end)591   protected void setTextSelection(int start, int end) {
592       textSelectionStart = start;
593       textSelectionEnd = end;
594   }
595 
596   /**
597    * Gets the text selection start.
598    *
599    * @return The text selection start if there is selection or UNDEFINED_SELECTION_INDEX.
600    */
601   @Implementation(minSdk = JELLY_BEAN_MR2)
getTextSelectionStart()602   protected int getTextSelectionStart() {
603       return textSelectionStart;
604   }
605 
606   /**
607    * Gets the text selection end.
608    *
609    * @return The text selection end if there is selection or UNDEFINED_SELECTION_INDEX.
610    */
611   @Implementation(minSdk = JELLY_BEAN_MR2)
getTextSelectionEnd()612   protected int getTextSelectionEnd() {
613       return textSelectionEnd;
614   }
615 
616   @Implementation(minSdk = JELLY_BEAN_MR2)
getLabelFor()617   protected AccessibilityNodeInfo getLabelFor() {
618     if (labelFor == null) {
619       return null;
620     }
621 
622     return obtain(labelFor);
623   }
624 
setLabelFor(AccessibilityNodeInfo info)625   public void setLabelFor(AccessibilityNodeInfo info) {
626     if (labelFor != null) {
627       labelFor.recycle();
628     }
629 
630     labelFor = obtain(info);
631   }
632 
633   @Implementation(minSdk = JELLY_BEAN_MR1)
getLabeledBy()634   protected AccessibilityNodeInfo getLabeledBy() {
635     if (labeledBy == null) {
636       return null;
637     }
638 
639     return obtain(labeledBy);
640   }
641 
setLabeledBy(AccessibilityNodeInfo info)642   public void setLabeledBy(AccessibilityNodeInfo info) {
643     if (labeledBy != null) {
644       labeledBy.recycle();
645     }
646 
647     labeledBy = obtain(info);
648   }
649 
650   @Implementation
getMovementGranularities()651   protected int getMovementGranularities() {
652     return movementGranularities;
653   }
654 
655   @Implementation
setMovementGranularities(int movementGranularities)656   protected void setMovementGranularities(int movementGranularities) {
657     this.movementGranularities = movementGranularities;
658   }
659 
660   @Implementation
getPackageName()661   protected CharSequence getPackageName() {
662     return packageName;
663   }
664 
665   @Implementation
setPackageName(CharSequence packageName)666   protected void setPackageName(CharSequence packageName) {
667     this.packageName = packageName;
668   }
669 
670   @Implementation(minSdk = JELLY_BEAN_MR2)
getViewIdResourceName()671   protected String getViewIdResourceName() {
672     return viewIdResourceName;
673   }
674 
675   @Implementation(minSdk = JELLY_BEAN_MR2)
setViewIdResourceName(String viewIdResourceName)676   protected void setViewIdResourceName(String viewIdResourceName) {
677     this.viewIdResourceName = viewIdResourceName;
678   }
679 
680   @Implementation(minSdk = KITKAT)
getCollectionInfo()681   protected CollectionInfo getCollectionInfo() {
682     return collectionInfo;
683   }
684 
685   @Implementation(minSdk = KITKAT)
setCollectionInfo(CollectionInfo collectionInfo)686   protected void setCollectionInfo(CollectionInfo collectionInfo) {
687     this.collectionInfo = collectionInfo;
688   }
689 
690   @Implementation(minSdk = KITKAT)
getCollectionItemInfo()691   protected CollectionItemInfo getCollectionItemInfo() {
692     return collectionItemInfo;
693   }
694 
695   @Implementation(minSdk = KITKAT)
setCollectionItemInfo(CollectionItemInfo collectionItemInfo)696   protected void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
697     this.collectionItemInfo = collectionItemInfo;
698   }
699 
700   @Implementation(minSdk = KITKAT)
getInputType()701   protected int getInputType() {
702     return inputType;
703   }
704 
705   @Implementation(minSdk = KITKAT)
setInputType(int inputType)706   protected void setInputType(int inputType) {
707     this.inputType = inputType;
708   }
709 
710   @Implementation(minSdk = KITKAT)
getLiveRegion()711   protected int getLiveRegion() {
712     return liveRegion;
713   }
714 
715   @Implementation(minSdk = KITKAT)
setLiveRegion(int liveRegion)716   protected void setLiveRegion(int liveRegion) {
717     this.liveRegion = liveRegion;
718   }
719 
720   @Implementation(minSdk = KITKAT)
getRangeInfo()721   protected RangeInfo getRangeInfo() {
722     return rangeInfo;
723   }
724 
725   @Implementation(minSdk = KITKAT)
setRangeInfo(RangeInfo rangeInfo)726   protected void setRangeInfo(RangeInfo rangeInfo) {
727     this.rangeInfo = rangeInfo;
728   }
729 
730   @Implementation(minSdk = LOLLIPOP)
getMaxTextLength()731   protected int getMaxTextLength() {
732     return maxTextLength;
733   }
734 
735   @Implementation(minSdk = LOLLIPOP)
setMaxTextLength(int maxTextLength)736   protected void setMaxTextLength(int maxTextLength) {
737     this.maxTextLength = maxTextLength;
738   }
739 
740   @Implementation(minSdk = LOLLIPOP)
getError()741   protected CharSequence getError() {
742     return error;
743   }
744 
745   @Implementation(minSdk = LOLLIPOP)
setError(CharSequence error)746   protected void setError(CharSequence error) {
747     this.error = error;
748   }
749 
750   @Implementation(minSdk = LOLLIPOP_MR1)
getTraversalAfter()751   protected AccessibilityNodeInfo getTraversalAfter() {
752     if (traversalAfter == null) {
753       return null;
754     }
755 
756     return obtain(traversalAfter);
757   }
758 
759   @Implementation(minSdk = LOLLIPOP_MR1)
setTraversalAfter(View view, int virtualDescendantId)760   protected void setTraversalAfter(View view, int virtualDescendantId) {
761     if (this.traversalAfter != null) {
762       this.traversalAfter.recycle();
763     }
764 
765     this.traversalAfter = obtain(view);
766   }
767 
768   /**
769    * Sets the view whose node is visited after this one in accessibility traversal.
770    *
771    * This may be useful for configuring traversal order in tests before the corresponding
772    * views have been inflated.
773    *
774    * @param info The previous node.
775    * @see #getTraversalAfter()
776    */
setTraversalAfter(AccessibilityNodeInfo info)777   public void setTraversalAfter(AccessibilityNodeInfo info) {
778     if (this.traversalAfter != null) {
779       this.traversalAfter.recycle();
780     }
781 
782     this.traversalAfter = obtain(info);
783   }
784 
785   @Implementation(minSdk = LOLLIPOP_MR1)
getTraversalBefore()786   protected AccessibilityNodeInfo getTraversalBefore() {
787     if (traversalBefore == null) {
788       return null;
789     }
790 
791     return obtain(traversalBefore);
792   }
793 
794   @Implementation(minSdk = LOLLIPOP_MR1)
setTraversalBefore(View info, int virtualDescendantId)795   protected void setTraversalBefore(View info, int virtualDescendantId) {
796     if (this.traversalBefore != null) {
797       this.traversalBefore.recycle();
798     }
799 
800     this.traversalBefore = obtain(info);
801   }
802 
803   /**
804    * Sets the view before whose node this one should be visited during traversal.
805    *
806    * This may be useful for configuring traversal order in tests before the corresponding
807    * views have been inflated.
808    *
809    * @param info The view providing the preceding node.
810    * @see #getTraversalBefore()
811    */
setTraversalBefore(AccessibilityNodeInfo info)812   public void setTraversalBefore(AccessibilityNodeInfo info) {
813     if (this.traversalBefore != null) {
814       this.traversalBefore.recycle();
815     }
816 
817     this.traversalBefore = obtain(info);
818   }
819 
820   @Implementation
setSource(View source)821   protected void setSource(View source) {
822     this.view = source;
823   }
824 
825   @Implementation
setSource(View root, int virtualDescendantId)826   protected void setSource(View root, int virtualDescendantId) {
827     this.view = root;
828   }
829 
830   @Implementation
getBoundsInScreen(Rect outBounds)831   protected void getBoundsInScreen(Rect outBounds) {
832     if (boundsInScreen == null) {
833       boundsInScreen = new Rect();
834     }
835     outBounds.set(boundsInScreen);
836   }
837 
838   @Implementation
getBoundsInParent(Rect outBounds)839   protected void getBoundsInParent(Rect outBounds) {
840     if (boundsInParent == null) {
841       boundsInParent = new Rect();
842     }
843     outBounds.set(boundsInParent);
844   }
845 
846   @Implementation
setBoundsInScreen(Rect b)847   protected void setBoundsInScreen(Rect b) {
848     if (boundsInScreen == null) {
849       boundsInScreen = new Rect(b);
850     } else {
851       boundsInScreen.set(b);
852     }
853   }
854 
855   @Implementation
setBoundsInParent(Rect b)856   protected void setBoundsInParent(Rect b) {
857     if (boundsInParent == null) {
858       boundsInParent = new Rect(b);
859     } else {
860       boundsInParent.set(b);
861     }
862   }
863 
864   @Implementation
addAction(int action)865   protected void addAction(int action) {
866     if (getApiLevel() >= LOLLIPOP) {
867       if ((action & getActionTypeMaskFromFramework()) != 0) {
868         throw new IllegalArgumentException("Action is not a combination of the standard " +
869             "actions: " + action);
870       }
871       int remainingIds = action;
872       while (remainingIds > 0) {
873         final int id = 1 << Integer.numberOfTrailingZeros(remainingIds);
874         remainingIds &= ~id;
875         AccessibilityAction convertedAction = getActionFromIdFromFrameWork(id);
876         addAction(convertedAction);
877       }
878     } else {
879       actionsMask |= action;
880     }
881   }
882 
883   @Implementation(minSdk = LOLLIPOP)
addAction(AccessibilityAction action)884   protected void addAction(AccessibilityAction action) {
885     if (action == null) {
886       return;
887     }
888 
889     if (actionsArray == null) {
890       actionsArray = new ArrayList<>();
891     }
892     actionsArray.remove(action);
893     actionsArray.add(action);
894   }
895 
896   @Implementation(minSdk = LOLLIPOP)
removeAction(int action)897   protected void removeAction(int action) {
898     AccessibilityAction convertedAction = getActionFromIdFromFrameWork(action);
899     removeAction(convertedAction);
900   }
901 
902   @Implementation(minSdk = LOLLIPOP)
removeAction(AccessibilityAction action)903   protected boolean removeAction(AccessibilityAction action) {
904     if (action == null || actionsArray == null) {
905       return false;
906     }
907     return actionsArray.remove(action);
908   }
909 
910   /**
911    * Obtain flags for actions supported. Currently only supports {@link
912    * AccessibilityNodeInfo#ACTION_CLICK}, {@link AccessibilityNodeInfo#ACTION_LONG_CLICK}, {@link
913    * AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}, {@link AccessibilityNodeInfo#ACTION_PASTE},
914    * {@link AccessibilityNodeInfo#ACTION_FOCUS}, {@link AccessibilityNodeInfo#ACTION_SET_SELECTION},
915    * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} Returned value is derived from the
916    * getters.
917    *
918    * @return Action mask. 0 if no actions supported.
919    */
920   @Implementation
getActions()921   protected int getActions() {
922     if (getApiLevel() >= LOLLIPOP) {
923       int returnValue = 0;
924       if (actionsArray == null) {
925         return returnValue;
926       }
927 
928       // Custom actions are only returned by getActionsList
929       final int actionSize = actionsArray.size();
930       for (int i = 0; i < actionSize; i++) {
931         int actionId = actionsArray.get(i).getId();
932         if (actionId <= getLastLegacyActionFromFrameWork()) {
933           returnValue |= actionId;
934         }
935       }
936       return returnValue;
937     } else {
938       return actionsMask;
939     }
940   }
941 
942   /** Returns the drawing order of the view corresponding to this node. */
943   @Implementation(minSdk = N)
getDrawingOrder()944   protected int getDrawingOrder() {
945     return drawingOrder;
946   }
947 
948   /** Sets the drawing order of the view corresponding to this node. */
949   @Implementation(minSdk = N)
setDrawingOrder(int drawingOrder)950   protected void setDrawingOrder(int drawingOrder) {
951     this.drawingOrder = drawingOrder;
952   }
953 
954   @Implementation(minSdk = LOLLIPOP)
getWindow()955   protected AccessibilityWindowInfo getWindow() {
956     return accessibilityWindowInfo;
957   }
958 
959   /** Returns the id of the window from which the info comes. */
960   @Implementation
getWindowId()961   protected int getWindowId() {
962     return (accessibilityWindowInfo == null) ? -1 : accessibilityWindowInfo.getId();
963   }
964 
setAccessibilityWindowInfo(AccessibilityWindowInfo info)965   public void setAccessibilityWindowInfo(AccessibilityWindowInfo info) {
966     accessibilityWindowInfo = info;
967   }
968 
969   @Implementation(minSdk = LOLLIPOP)
getActionList()970   protected List<AccessibilityAction> getActionList() {
971     if (actionsArray == null) {
972       return Collections.emptyList();
973     }
974 
975     return actionsArray;
976   }
977 
978   @Implementation
performAction(int action)979   protected boolean performAction(int action) {
980     return performAction(action, null);
981   }
982 
983   @Implementation
performAction(int action, Bundle arguments)984   protected boolean performAction(int action, Bundle arguments) {
985     if (performedActionAndArgsList == null) {
986       performedActionAndArgsList = new ArrayList<>();
987     }
988 
989     performedActionAndArgsList.add(new Pair<>(action, arguments));
990     return actionListener == null || actionListener.onPerformAccessibilityAction(action, arguments);
991   }
992 
993   /**
994    * Equality check based on reference equality of the Views from which these instances were
995    * created, or the equality of their assigned IDs.
996    */
997   @Implementation
998   @Override
equals(Object object)999   public boolean equals(Object object) {
1000     if (!(object instanceof AccessibilityNodeInfo)) {
1001       return false;
1002     }
1003 
1004     final AccessibilityNodeInfo info = (AccessibilityNodeInfo) object;
1005     final ShadowAccessibilityNodeInfo otherShadow = Shadow.extract(info);
1006 
1007     if (this.view != null) {
1008       return this.view == otherShadow.view;
1009     }
1010     if (this.mOriginNodeId != 0) {
1011       return this.mOriginNodeId == otherShadow.mOriginNodeId;
1012     }
1013     throw new IllegalStateException("Node has neither an ID nor View");
1014   }
1015 
1016   @Implementation
1017   @Override
hashCode()1018   public int hashCode() {
1019     // This is 0 for a reason. If you change it, you will break the obtained
1020     // instances map in a manner that is remarkably difficult to debug.
1021     // Having a dynamic hash code keeps this object from being located
1022     // in the map if it was mutated after being obtained.
1023     return 0;
1024   }
1025 
1026   /**
1027    * Add a child node to this one. Also initializes the parent field of the
1028    * child.
1029    *
1030    * @param child The node to be added as a child.
1031    */
addChild(AccessibilityNodeInfo child)1032   public void addChild(AccessibilityNodeInfo child) {
1033     if (children == null) {
1034       children = new ArrayList<>();
1035     }
1036 
1037     children.add(child);
1038     ShadowAccessibilityNodeInfo shadowAccessibilityNodeInfo = Shadow.extract(child);
1039     shadowAccessibilityNodeInfo.parent = realAccessibilityNodeInfo;
1040   }
1041 
1042   @Implementation
addChild(View child)1043   protected void addChild(View child) {
1044     AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(child);
1045     addChild(node);
1046   }
1047 
1048   @Implementation
addChild(View root, int virtualDescendantId)1049   protected void addChild(View root, int virtualDescendantId) {
1050     AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(root, virtualDescendantId);
1051     addChild(node);
1052   }
1053 
1054   /**
1055    * @return The list of arguments for the various calls to performAction. Unmodifiable.
1056    */
getPerformedActions()1057   public List<Integer> getPerformedActions() {
1058     if (performedActionAndArgsList == null) {
1059       performedActionAndArgsList = new ArrayList<>();
1060     }
1061 
1062     // Here we take the actions out of the pairs and stick them into a separate LinkedList to return
1063     List<Integer> actionsOnly = new ArrayList<>();
1064     Iterator<Pair<Integer, Bundle>> iter = performedActionAndArgsList.iterator();
1065     while (iter.hasNext()) {
1066       actionsOnly.add(iter.next().first);
1067     }
1068 
1069     return Collections.unmodifiableList(actionsOnly);
1070   }
1071 
1072   /**
1073    * @return The list of arguments for the various calls to performAction. Unmodifiable.
1074    */
getPerformedActionsWithArgs()1075   public List<Pair<Integer, Bundle>> getPerformedActionsWithArgs() {
1076     if (performedActionAndArgsList == null) {
1077       performedActionAndArgsList = new ArrayList<>();
1078     }
1079     return Collections.unmodifiableList(performedActionAndArgsList);
1080   }
1081 
1082   /**
1083    * @return A shallow copy.
1084    */
getClone()1085   private AccessibilityNodeInfo getClone() {
1086     // We explicitly avoid allocating the AccessibilityNodeInfo from the actual pool by using
1087     // the private constructor. Not doing so affects test suites which use both shadow and
1088     // non-shadow objects.
1089     final AccessibilityNodeInfo newInfo =
1090         ReflectionHelpers.callConstructor(AccessibilityNodeInfo.class);
1091     final ShadowAccessibilityNodeInfo newShadow = Shadow.extract(newInfo);
1092 
1093     newShadow.mOriginNodeId = mOriginNodeId;
1094     newShadow.boundsInScreen = new Rect(boundsInScreen);
1095     newShadow.propertyFlags = propertyFlags;
1096     newShadow.contentDescription = contentDescription;
1097     newShadow.text = text;
1098     newShadow.performedActionAndArgsList = performedActionAndArgsList;
1099     newShadow.parent = parent;
1100     newShadow.className = className;
1101     newShadow.labelFor = (labelFor == null) ? null : obtain(labelFor);
1102     newShadow.labeledBy = (labeledBy == null) ? null : obtain(labeledBy);
1103     newShadow.view = view;
1104     newShadow.textSelectionStart = textSelectionStart;
1105     newShadow.textSelectionEnd = textSelectionEnd;
1106     newShadow.actionListener = actionListener;
1107     if (getApiLevel() >= LOLLIPOP) {
1108       if (actionsArray != null) {
1109         newShadow.actionsArray = new ArrayList<>();
1110         newShadow.actionsArray.addAll(actionsArray);
1111       } else {
1112         newShadow.actionsArray = null;
1113       }
1114     } else {
1115       newShadow.actionsMask = actionsMask;
1116     }
1117 
1118     if (children != null) {
1119       newShadow.children = new ArrayList<>();
1120       newShadow.children.addAll(children);
1121     } else {
1122       newShadow.children = null;
1123     }
1124 
1125     newShadow.refreshReturnValue = refreshReturnValue;
1126     newShadow.movementGranularities = movementGranularities;
1127     newShadow.packageName = packageName;
1128     if (getApiLevel() >= JELLY_BEAN_MR2) {
1129       newShadow.viewIdResourceName = viewIdResourceName;
1130     }
1131     if (getApiLevel() >= KITKAT) {
1132       newShadow.collectionInfo = collectionInfo;
1133       newShadow.collectionItemInfo = collectionItemInfo;
1134       newShadow.inputType = inputType;
1135       newShadow.liveRegion = liveRegion;
1136       newShadow.rangeInfo = rangeInfo;
1137     }
1138     if (getApiLevel() >= LOLLIPOP) {
1139       newShadow.maxTextLength = maxTextLength;
1140       newShadow.error = error;
1141     }
1142     if (getApiLevel() >= LOLLIPOP_MR1) {
1143       newShadow.traversalAfter = (traversalAfter == null) ? null : obtain(traversalAfter);
1144       newShadow.traversalBefore = (traversalBefore == null) ? null : obtain(traversalBefore);
1145     }
1146     if ((getApiLevel() >= LOLLIPOP) && (accessibilityWindowInfo != null)) {
1147       newShadow.accessibilityWindowInfo =
1148           ShadowAccessibilityWindowInfo.obtain(accessibilityWindowInfo);
1149     }
1150     if (getApiLevel() >= N) {
1151       newShadow.drawingOrder = drawingOrder;
1152     }
1153 
1154     return newInfo;
1155   }
1156 
1157   /**
1158    * Private class to keep different nodes referring to the same view straight
1159    * in the mObtainedInstances map.
1160    */
1161   private static class StrictEqualityNodeWrapper {
1162     public final AccessibilityNodeInfo mInfo;
1163 
StrictEqualityNodeWrapper(AccessibilityNodeInfo info)1164     public StrictEqualityNodeWrapper(AccessibilityNodeInfo info) {
1165       mInfo = info;
1166     }
1167 
1168     @Override
1169     @SuppressWarnings("ReferenceEquality")
equals(Object object)1170     public boolean equals(Object object) {
1171       if (object == null) {
1172         return false;
1173       }
1174 
1175       final StrictEqualityNodeWrapper wrapper = (StrictEqualityNodeWrapper) object;
1176       return mInfo == wrapper.mInfo;
1177     }
1178 
1179     @Override
hashCode()1180     public int hashCode() {
1181       return mInfo.hashCode();
1182     }
1183   }
1184 
1185   /**
1186    * Shadow of AccessibilityAction.
1187    */
1188   @Implements(value = AccessibilityNodeInfo.AccessibilityAction.class, minSdk = LOLLIPOP)
1189   public static final class ShadowAccessibilityAction {
1190     private int id;
1191     private CharSequence label;
1192 
1193     @Implementation
__constructor__(int id, CharSequence label)1194     protected void __constructor__(int id, CharSequence label) {
1195       if (((id & (int)ReflectionHelpers.getStaticField(AccessibilityNodeInfo.class, "ACTION_TYPE_MASK")) == 0) && Integer.bitCount(id) != 1) {
1196         throw new IllegalArgumentException("Invalid standard action id");
1197       }
1198       this.id = id;
1199       this.label = label;
1200     }
1201 
1202     @Implementation
getId()1203     protected int getId() {
1204       return id;
1205     }
1206 
1207     @Implementation
getLabel()1208     protected CharSequence getLabel() {
1209       return label;
1210     }
1211 
1212     @Override
1213     @Implementation
1214     @SuppressWarnings("EqualsHashCode")
equals(Object other)1215     public boolean equals(Object other) {
1216       if (other == null) {
1217         return false;
1218       }
1219 
1220       if (other == this) {
1221         return true;
1222       }
1223 
1224       if (other.getClass() != AccessibilityAction.class) {
1225         return false;
1226       }
1227 
1228       return id == ((AccessibilityAction) other).getId();
1229     }
1230 
1231     @Override
toString()1232     public String toString() {
1233       String actionSybolicName = ReflectionHelpers.callStaticMethod(
1234           AccessibilityNodeInfo.class, "getActionSymbolicName", ClassParameter.from(int.class, id));
1235       return "AccessibilityAction: " + actionSybolicName + " - " + label;
1236     }
1237   }
1238 
1239   @Implementation
describeContents()1240   protected int describeContents() {
1241     return 0;
1242   }
1243 
1244   @Implementation
writeToParcel(Parcel dest, int flags)1245   protected void writeToParcel(Parcel dest, int flags) {
1246     StrictEqualityNodeWrapper wrapper = new StrictEqualityNodeWrapper(realAccessibilityNodeInfo);
1247     int keyOfWrapper = -1;
1248     for (int i = 0; i < orderedInstances.size(); i++) {
1249       if (orderedInstances.valueAt(i).equals(wrapper)) {
1250         keyOfWrapper = orderedInstances.keyAt(i);
1251         break;
1252       }
1253     }
1254     dest.writeInt(keyOfWrapper);
1255   }
1256 
getActionTypeMaskFromFramework()1257   private static int getActionTypeMaskFromFramework() {
1258     // Get the mask to determine whether an int is a legit ID for an action, defined by Android
1259     return (int)ReflectionHelpers.getStaticField(AccessibilityNodeInfo.class, "ACTION_TYPE_MASK");
1260   }
1261 
getActionFromIdFromFrameWork(int id)1262   private static AccessibilityAction getActionFromIdFromFrameWork(int id) {
1263     // Convert an action ID to Android standard Accessibility Action defined by Android
1264     return ReflectionHelpers.callStaticMethod(
1265         AccessibilityNodeInfo.class, "getActionSingleton", ClassParameter.from(int.class, id));
1266   }
1267 
getLastLegacyActionFromFrameWork()1268   private static int getLastLegacyActionFromFrameWork() {
1269     return (int)ReflectionHelpers.getStaticField(AccessibilityNodeInfo.class, "LAST_LEGACY_STANDARD_ACTION");
1270   }
1271 
1272   /**
1273    * Configure the return result of an action if it is performed
1274    *
1275    * @param listener The listener.
1276    */
setOnPerformActionListener(OnPerformActionListener listener)1277   public void setOnPerformActionListener(OnPerformActionListener listener) {
1278     actionListener = listener;
1279   }
1280 
1281   public interface OnPerformActionListener {
onPerformAccessibilityAction(int action, Bundle arguments)1282     boolean onPerformAccessibilityAction(int action, Bundle arguments);
1283   }
1284 }
1285