Home | History | Annotate | Download | only in shadows
      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
    187   protected void __constructor__() {
    188     ReflectionHelpers.setStaticField(AccessibilityNodeInfo.class, "CREATOR", ShadowAccessibilityNodeInfo.CREATOR);
    189   }
    190 
    191   @Implementation
    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
    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
    238   protected static AccessibilityNodeInfo obtain() {
    239     return obtain(new View(RuntimeEnvironment.application.getApplicationContext()));
    240   }
    241 
    242   @Implementation
    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    */
    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
    278   public static void resetObtainedInstances() {
    279     obtainedInstances.clear();
    280     orderedInstances.clear();
    281   }
    282 
    283   @Implementation
    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
    321   protected int getChildCount() {
    322     if (children == null) {
    323       return 0;
    324     }
    325 
    326     return children.size();
    327   }
    328 
    329   @Implementation
    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
    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)
    353   protected boolean refresh() {
    354       return refreshReturnValue;
    355   }
    356 
    357   public void setRefreshReturnValue(boolean refreshReturnValue) {
    358     this.refreshReturnValue = refreshReturnValue;
    359   }
    360 
    361   @Implementation
    362   protected boolean isClickable() {
    363     return ((propertyFlags & CLICKABLE_MASK) != 0);
    364   }
    365 
    366   @Implementation
    367   protected boolean isLongClickable() {
    368     return ((propertyFlags & LONGCLICKABLE_MASK) != 0);
    369   }
    370 
    371   @Implementation
    372   protected boolean isFocusable() {
    373     return ((propertyFlags & FOCUSABLE_MASK) != 0);
    374   }
    375 
    376   @Implementation
    377   protected boolean isFocused() {
    378     return ((propertyFlags & FOCUSED_MASK) != 0);
    379   }
    380 
    381   @Implementation
    382   protected boolean isVisibleToUser() {
    383     return ((propertyFlags & VISIBLE_TO_USER_MASK) != 0);
    384   }
    385 
    386   @Implementation
    387   protected boolean isScrollable() {
    388     return ((propertyFlags & SCROLLABLE_MASK) != 0);
    389   }
    390 
    391   public boolean isPasteable() {
    392     return ((propertyFlags & PASTEABLE_MASK) != 0);
    393   }
    394 
    395   @Implementation(minSdk = JELLY_BEAN_MR2)
    396   protected boolean isEditable() {
    397     return ((propertyFlags & EDITABLE_MASK) != 0);
    398   }
    399 
    400   public boolean isTextSelectionSetable() {
    401     return ((propertyFlags & TEXT_SELECTION_SETABLE_MASK) != 0);
    402   }
    403 
    404   @Implementation
    405   protected boolean isCheckable() {
    406     return ((propertyFlags & CHECKABLE_MASK) != 0);
    407   }
    408 
    409   @Implementation
    410   protected void setCheckable(boolean checkable) {
    411     propertyFlags = (propertyFlags & ~CHECKABLE_MASK) |
    412         (checkable ? CHECKABLE_MASK : 0);
    413   }
    414 
    415   @Implementation
    416   protected void setChecked(boolean checked) {
    417     propertyFlags = (propertyFlags & ~CHECKED_MASK) |
    418         (checked ? CHECKED_MASK : 0);
    419   }
    420 
    421   @Implementation
    422   protected boolean isChecked() {
    423     return ((propertyFlags & CHECKED_MASK) != 0);
    424   }
    425 
    426   @Implementation
    427   protected void setEnabled(boolean enabled) {
    428     propertyFlags = (propertyFlags & ~ENABLED_MASK) |
    429         (enabled ? ENABLED_MASK : 0);
    430   }
    431 
    432   @Implementation
    433   protected boolean isEnabled() {
    434     return ((propertyFlags & ENABLED_MASK) != 0);
    435   }
    436 
    437   @Implementation
    438   protected void setPassword(boolean password) {
    439     propertyFlags = (propertyFlags & ~PASSWORD_MASK) |
    440         (password ? PASSWORD_MASK : 0);
    441   }
    442 
    443   @Implementation
    444   protected boolean isPassword() {
    445     return ((propertyFlags & PASSWORD_MASK) != 0);
    446   }
    447 
    448   @Implementation
    449   protected void setSelected(boolean selected) {
    450     propertyFlags = (propertyFlags & ~SELECTED_MASK) |
    451         (selected ? SELECTED_MASK : 0);
    452   }
    453 
    454   @Implementation
    455   protected boolean isSelected() {
    456     return ((propertyFlags & SELECTED_MASK) != 0);
    457   }
    458 
    459   @Implementation
    460   protected void setAccessibilityFocused(boolean focused) {
    461     propertyFlags = (propertyFlags & ~A11YFOCUSED_MASK) |
    462         (focused ? A11YFOCUSED_MASK : 0);
    463   }
    464 
    465   @Implementation
    466   protected boolean isAccessibilityFocused() {
    467     return ((propertyFlags & A11YFOCUSED_MASK) != 0);
    468   }
    469 
    470   @Implementation(minSdk = LOLLIPOP)
    471   protected void setMultiLine(boolean multiLine) {
    472     propertyFlags = (propertyFlags & ~MULTILINE_MASK) |
    473         (multiLine ? MULTILINE_MASK : 0);
    474   }
    475 
    476   @Implementation(minSdk = LOLLIPOP)
    477   protected boolean isMultiLine() {
    478     return ((propertyFlags & MULTILINE_MASK) != 0);
    479   }
    480 
    481   @Implementation(minSdk = LOLLIPOP)
    482   protected void setContentInvalid(boolean contentInvalid) {
    483     propertyFlags = (propertyFlags & ~CONTENT_INVALID_MASK) |
    484         (contentInvalid ? CONTENT_INVALID_MASK : 0);
    485   }
    486 
    487   @Implementation(minSdk = LOLLIPOP)
    488   protected boolean isContentInvalid() {
    489     return ((propertyFlags & CONTENT_INVALID_MASK) != 0);
    490   }
    491 
    492   @Implementation(minSdk = LOLLIPOP)
    493   protected void setDismissable(boolean dismissable) {
    494     propertyFlags = (propertyFlags & ~DISMISSABLE_MASK) |
    495         (dismissable ? DISMISSABLE_MASK : 0);
    496   }
    497 
    498   @Implementation(minSdk = LOLLIPOP)
    499   protected boolean isDismissable() {
    500     return ((propertyFlags & DISMISSABLE_MASK) != 0);
    501   }
    502 
    503   @Implementation(minSdk = LOLLIPOP)
    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)
    510   protected boolean canOpenPopup() {
    511     return ((propertyFlags & CAN_OPEN_POPUP_MASK) != 0);
    512   }
    513 
    514   public void setTextSelectionSetable(boolean isTextSelectionSetable) {
    515     propertyFlags = (propertyFlags & ~TEXT_SELECTION_SETABLE_MASK) |
    516         (isTextSelectionSetable ? TEXT_SELECTION_SETABLE_MASK : 0);
    517   }
    518 
    519   @Implementation
    520   protected void setClickable(boolean isClickable) {
    521     propertyFlags = (propertyFlags & ~CLICKABLE_MASK) | (isClickable ? CLICKABLE_MASK : 0);
    522   }
    523 
    524   @Implementation
    525   protected void setLongClickable(boolean isLongClickable) {
    526     propertyFlags =
    527         (propertyFlags & ~LONGCLICKABLE_MASK) | (isLongClickable ? LONGCLICKABLE_MASK : 0);
    528   }
    529 
    530   @Implementation
    531   protected void setFocusable(boolean isFocusable) {
    532     propertyFlags = (propertyFlags & ~FOCUSABLE_MASK) | (isFocusable ? FOCUSABLE_MASK : 0);
    533   }
    534 
    535   @Implementation
    536   protected void setFocused(boolean isFocused) {
    537     propertyFlags = (propertyFlags & ~FOCUSED_MASK) | (isFocused ? FOCUSED_MASK : 0);
    538   }
    539 
    540   @Implementation
    541   protected void setScrollable(boolean isScrollable) {
    542     propertyFlags = (propertyFlags & ~SCROLLABLE_MASK) | (isScrollable ? SCROLLABLE_MASK : 0);
    543   }
    544 
    545   public void setPasteable(boolean isPasteable) {
    546     propertyFlags = (propertyFlags & ~PASTEABLE_MASK) | (isPasteable ? PASTEABLE_MASK : 0);
    547   }
    548 
    549   @Implementation(minSdk = JELLY_BEAN_MR2)
    550   protected void setEditable(boolean isEditable) {
    551     propertyFlags = (propertyFlags & ~EDITABLE_MASK) | (isEditable ? EDITABLE_MASK : 0);
    552   }
    553 
    554   @Implementation
    555   protected void setVisibleToUser(boolean isVisibleToUser) {
    556     propertyFlags =
    557         (propertyFlags & ~VISIBLE_TO_USER_MASK) | (isVisibleToUser ? VISIBLE_TO_USER_MASK : 0);
    558   }
    559 
    560   @Implementation
    561   protected void setContentDescription(CharSequence description) {
    562     contentDescription = description;
    563   }
    564 
    565   @Implementation
    566   protected CharSequence getContentDescription() {
    567     return contentDescription;
    568   }
    569 
    570   @Implementation
    571   protected void setClassName(CharSequence name) {
    572     className = name;
    573   }
    574 
    575   @Implementation
    576   protected CharSequence getClassName() {
    577     return className;
    578   }
    579 
    580   @Implementation
    581   protected void setText(CharSequence t) {
    582     text = t;
    583   }
    584 
    585   @Implementation
    586   protected CharSequence getText() {
    587     return text;
    588   }
    589 
    590   @Implementation(minSdk = JELLY_BEAN_MR2)
    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)
    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)
    612   protected int getTextSelectionEnd() {
    613       return textSelectionEnd;
    614   }
    615 
    616   @Implementation(minSdk = JELLY_BEAN_MR2)
    617   protected AccessibilityNodeInfo getLabelFor() {
    618     if (labelFor == null) {
    619       return null;
    620     }
    621 
    622     return obtain(labelFor);
    623   }
    624 
    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)
    634   protected AccessibilityNodeInfo getLabeledBy() {
    635     if (labeledBy == null) {
    636       return null;
    637     }
    638 
    639     return obtain(labeledBy);
    640   }
    641 
    642   public void setLabeledBy(AccessibilityNodeInfo info) {
    643     if (labeledBy != null) {
    644       labeledBy.recycle();
    645     }
    646 
    647     labeledBy = obtain(info);
    648   }
    649 
    650   @Implementation
    651   protected int getMovementGranularities() {
    652     return movementGranularities;
    653   }
    654 
    655   @Implementation
    656   protected void setMovementGranularities(int movementGranularities) {
    657     this.movementGranularities = movementGranularities;
    658   }
    659 
    660   @Implementation
    661   protected CharSequence getPackageName() {
    662     return packageName;
    663   }
    664 
    665   @Implementation
    666   protected void setPackageName(CharSequence packageName) {
    667     this.packageName = packageName;
    668   }
    669 
    670   @Implementation(minSdk = JELLY_BEAN_MR2)
    671   protected String getViewIdResourceName() {
    672     return viewIdResourceName;
    673   }
    674 
    675   @Implementation(minSdk = JELLY_BEAN_MR2)
    676   protected void setViewIdResourceName(String viewIdResourceName) {
    677     this.viewIdResourceName = viewIdResourceName;
    678   }
    679 
    680   @Implementation(minSdk = KITKAT)
    681   protected CollectionInfo getCollectionInfo() {
    682     return collectionInfo;
    683   }
    684 
    685   @Implementation(minSdk = KITKAT)
    686   protected void setCollectionInfo(CollectionInfo collectionInfo) {
    687     this.collectionInfo = collectionInfo;
    688   }
    689 
    690   @Implementation(minSdk = KITKAT)
    691   protected CollectionItemInfo getCollectionItemInfo() {
    692     return collectionItemInfo;
    693   }
    694 
    695   @Implementation(minSdk = KITKAT)
    696   protected void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
    697     this.collectionItemInfo = collectionItemInfo;
    698   }
    699 
    700   @Implementation(minSdk = KITKAT)
    701   protected int getInputType() {
    702     return inputType;
    703   }
    704 
    705   @Implementation(minSdk = KITKAT)
    706   protected void setInputType(int inputType) {
    707     this.inputType = inputType;
    708   }
    709 
    710   @Implementation(minSdk = KITKAT)
    711   protected int getLiveRegion() {
    712     return liveRegion;
    713   }
    714 
    715   @Implementation(minSdk = KITKAT)
    716   protected void setLiveRegion(int liveRegion) {
    717     this.liveRegion = liveRegion;
    718   }
    719 
    720   @Implementation(minSdk = KITKAT)
    721   protected RangeInfo getRangeInfo() {
    722     return rangeInfo;
    723   }
    724 
    725   @Implementation(minSdk = KITKAT)
    726   protected void setRangeInfo(RangeInfo rangeInfo) {
    727     this.rangeInfo = rangeInfo;
    728   }
    729 
    730   @Implementation(minSdk = LOLLIPOP)
    731   protected int getMaxTextLength() {
    732     return maxTextLength;
    733   }
    734 
    735   @Implementation(minSdk = LOLLIPOP)
    736   protected void setMaxTextLength(int maxTextLength) {
    737     this.maxTextLength = maxTextLength;
    738   }
    739 
    740   @Implementation(minSdk = LOLLIPOP)
    741   protected CharSequence getError() {
    742     return error;
    743   }
    744 
    745   @Implementation(minSdk = LOLLIPOP)
    746   protected void setError(CharSequence error) {
    747     this.error = error;
    748   }
    749 
    750   @Implementation(minSdk = LOLLIPOP_MR1)
    751   protected AccessibilityNodeInfo getTraversalAfter() {
    752     if (traversalAfter == null) {
    753       return null;
    754     }
    755 
    756     return obtain(traversalAfter);
    757   }
    758 
    759   @Implementation(minSdk = LOLLIPOP_MR1)
    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    */
    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)
    786   protected AccessibilityNodeInfo getTraversalBefore() {
    787     if (traversalBefore == null) {
    788       return null;
    789     }
    790 
    791     return obtain(traversalBefore);
    792   }
    793 
    794   @Implementation(minSdk = LOLLIPOP_MR1)
    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    */
    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
    821   protected void setSource(View source) {
    822     this.view = source;
    823   }
    824 
    825   @Implementation
    826   protected void setSource(View root, int virtualDescendantId) {
    827     this.view = root;
    828   }
    829 
    830   @Implementation
    831   protected void getBoundsInScreen(Rect outBounds) {
    832     if (boundsInScreen == null) {
    833       boundsInScreen = new Rect();
    834     }
    835     outBounds.set(boundsInScreen);
    836   }
    837 
    838   @Implementation
    839   protected void getBoundsInParent(Rect outBounds) {
    840     if (boundsInParent == null) {
    841       boundsInParent = new Rect();
    842     }
    843     outBounds.set(boundsInParent);
    844   }
    845 
    846   @Implementation
    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
    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
    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)
    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)
    897   protected void removeAction(int action) {
    898     AccessibilityAction convertedAction = getActionFromIdFromFrameWork(action);
    899     removeAction(convertedAction);
    900   }
    901 
    902   @Implementation(minSdk = LOLLIPOP)
    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
    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)
    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)
    950   protected void setDrawingOrder(int drawingOrder) {
    951     this.drawingOrder = drawingOrder;
    952   }
    953 
    954   @Implementation(minSdk = LOLLIPOP)
    955   protected AccessibilityWindowInfo getWindow() {
    956     return accessibilityWindowInfo;
    957   }
    958 
    959   /** Returns the id of the window from which the info comes. */
    960   @Implementation
    961   protected int getWindowId() {
    962     return (accessibilityWindowInfo == null) ? -1 : accessibilityWindowInfo.getId();
    963   }
    964 
    965   public void setAccessibilityWindowInfo(AccessibilityWindowInfo info) {
    966     accessibilityWindowInfo = info;
    967   }
    968 
    969   @Implementation(minSdk = LOLLIPOP)
    970   protected List<AccessibilityAction> getActionList() {
    971     if (actionsArray == null) {
    972       return Collections.emptyList();
    973     }
    974 
    975     return actionsArray;
    976   }
    977 
    978   @Implementation
    979   protected boolean performAction(int action) {
    980     return performAction(action, null);
    981   }
    982 
    983   @Implementation
    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
    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
   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    */
   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
   1043   protected void addChild(View child) {
   1044     AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(child);
   1045     addChild(node);
   1046   }
   1047 
   1048   @Implementation
   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    */
   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    */
   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    */
   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 
   1164     public StrictEqualityNodeWrapper(AccessibilityNodeInfo info) {
   1165       mInfo = info;
   1166     }
   1167 
   1168     @Override
   1169     @SuppressWarnings("ReferenceEquality")
   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
   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
   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
   1203     protected int getId() {
   1204       return id;
   1205     }
   1206 
   1207     @Implementation
   1208     protected CharSequence getLabel() {
   1209       return label;
   1210     }
   1211 
   1212     @Override
   1213     @Implementation
   1214     @SuppressWarnings("EqualsHashCode")
   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
   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
   1240   protected int describeContents() {
   1241     return 0;
   1242   }
   1243 
   1244   @Implementation
   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 
   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 
   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 
   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    */
   1277   public void setOnPerformActionListener(OnPerformActionListener listener) {
   1278     actionListener = listener;
   1279   }
   1280 
   1281   public interface OnPerformActionListener {
   1282     boolean onPerformAccessibilityAction(int action, Bundle arguments);
   1283   }
   1284 }
   1285