Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      4 
      5 import android.graphics.Rect;
      6 import android.view.accessibility.AccessibilityNodeInfo;
      7 import android.view.accessibility.AccessibilityWindowInfo;
      8 import java.util.ArrayList;
      9 import java.util.HashMap;
     10 import java.util.List;
     11 import java.util.Map;
     12 import org.robolectric.annotation.Implementation;
     13 import org.robolectric.annotation.Implements;
     14 import org.robolectric.annotation.RealObject;
     15 import org.robolectric.shadow.api.Shadow;
     16 import org.robolectric.util.ReflectionHelpers;
     17 
     18 /**
     19  * Shadow of {@link android.view.accessibility.AccessibilityWindowInfo} that allows a test to set
     20  * properties that are locked in the original class.
     21  */
     22 @Implements(value = AccessibilityWindowInfo.class, minSdk = LOLLIPOP)
     23 public class ShadowAccessibilityWindowInfo {
     24 
     25   private static final Map<StrictEqualityWindowWrapper, StackTraceElement[]> obtainedInstances =
     26       new HashMap<>();
     27 
     28   private List<AccessibilityWindowInfo> children = null;
     29 
     30   private AccessibilityWindowInfo parent = null;
     31 
     32   private AccessibilityNodeInfo rootNode = null;
     33 
     34   private Rect boundsInScreen = new Rect();
     35 
     36   private int type = AccessibilityWindowInfo.TYPE_APPLICATION;
     37 
     38   private int layer = 0;
     39 
     40   private int id = 0;
     41 
     42   private boolean isAccessibilityFocused = false;
     43 
     44   private boolean isActive = false;
     45 
     46   private boolean isFocused = false;
     47 
     48   @RealObject
     49   private AccessibilityWindowInfo mRealAccessibilityWindowInfo;
     50 
     51   @Implementation
     52   public void __constructor__() {}
     53 
     54   @Implementation
     55   public static AccessibilityWindowInfo obtain() {
     56     final AccessibilityWindowInfo obtainedInstance =
     57         ReflectionHelpers.callConstructor(AccessibilityWindowInfo.class);
     58     StrictEqualityWindowWrapper wrapper = new StrictEqualityWindowWrapper(obtainedInstance);
     59     obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
     60     return obtainedInstance;
     61   }
     62 
     63   @Implementation
     64   public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo window) {
     65     final ShadowAccessibilityWindowInfo shadowInfo = Shadow.extract(window);
     66     final AccessibilityWindowInfo obtainedInstance = shadowInfo.getClone();
     67     StrictEqualityWindowWrapper wrapper = new StrictEqualityWindowWrapper(obtainedInstance);
     68     obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
     69     return obtainedInstance;
     70   }
     71 
     72   private AccessibilityWindowInfo getClone() {
     73     final AccessibilityWindowInfo newInfo =
     74         ReflectionHelpers.callConstructor(AccessibilityWindowInfo.class);
     75     final ShadowAccessibilityWindowInfo newShadow = Shadow.extract(newInfo);
     76 
     77     newShadow.boundsInScreen = new Rect(boundsInScreen);
     78     newShadow.parent = parent;
     79     newShadow.rootNode = rootNode;
     80     newShadow.type = type;
     81     newShadow.layer = layer;
     82     newShadow.id = id;
     83     newShadow.isAccessibilityFocused = isAccessibilityFocused;
     84     newShadow.isActive = isActive;
     85     newShadow.isFocused = isFocused;
     86 
     87     return newInfo;
     88   }
     89 
     90   /**
     91    * Clear list of obtained instance objects. {@code areThereUnrecycledWindows} will always
     92    * return false if called immediately afterwards.
     93    */
     94   public static void resetObtainedInstances() {
     95     obtainedInstances.clear();
     96   }
     97 
     98   /**
     99    * Check for leaked objects that were {@code obtain}ed but never
    100    * {@code recycle}d.
    101    *
    102    * @param printUnrecycledWindowsToSystemErr - if true, stack traces of calls
    103    *        to {@code obtain} that lack matching calls to {@code recycle} are
    104    *        dumped to System.err.
    105    * @return {@code true} if there are unrecycled windows
    106    */
    107   public static boolean areThereUnrecycledWindows(boolean printUnrecycledWindowsToSystemErr) {
    108     if (printUnrecycledWindowsToSystemErr) {
    109       for (final StrictEqualityWindowWrapper wrapper : obtainedInstances.keySet()) {
    110         final ShadowAccessibilityWindowInfo shadow = Shadow.extract(wrapper.mInfo);
    111 
    112         System.err.println(String.format(
    113             "Leaked type = %d, id = %d. Stack trace:", shadow.getType(), shadow.getId()));
    114         for (final StackTraceElement stackTraceElement : obtainedInstances.get(wrapper)) {
    115           System.err.println(stackTraceElement.toString());
    116         }
    117       }
    118     }
    119 
    120     return (obtainedInstances.size() != 0);
    121   }
    122 
    123   @Override
    124   @Implementation
    125   @SuppressWarnings("ReferenceEquality")
    126   public boolean equals(Object object) {
    127     if (!(object instanceof AccessibilityWindowInfo)) {
    128       return false;
    129     }
    130 
    131     final AccessibilityWindowInfo window = (AccessibilityWindowInfo) object;
    132     final ShadowAccessibilityWindowInfo otherShadow = Shadow.extract(window);
    133 
    134     boolean areEqual = (type == otherShadow.getType());
    135     areEqual &= (parent == otherShadow.getParent());
    136     areEqual &= (rootNode == otherShadow.getRoot());
    137     areEqual &= (layer == otherShadow.getLayer());
    138     areEqual &= (id == otherShadow.getId());
    139     areEqual &= (isAccessibilityFocused == otherShadow.isAccessibilityFocused());
    140     areEqual &= (isActive == otherShadow.isActive());
    141     areEqual &= (isFocused == otherShadow.isFocused());
    142     Rect anotherBounds = new Rect();
    143     otherShadow.getBoundsInScreen(anotherBounds);
    144     areEqual &= (boundsInScreen.equals(anotherBounds));
    145     return areEqual;
    146   }
    147 
    148   @Override
    149   @Implementation
    150   public int hashCode() {
    151     // This is 0 for a reason. If you change it, you will break the obtained instances map in
    152     // a manner that is remarkably difficult to debug. Having a dynamic hash code keeps this
    153     // object from being located in the map if it was mutated after being obtained.
    154     return 0;
    155   }
    156 
    157   @Implementation
    158   public int getType() {
    159     return type;
    160   }
    161 
    162   @Implementation
    163   public int getChildCount() {
    164     if (children == null) {
    165       return 0;
    166     }
    167 
    168     return children.size();
    169   }
    170 
    171   @Implementation
    172   public AccessibilityWindowInfo getChild(int index) {
    173     if (children == null) {
    174       return null;
    175     }
    176 
    177     return children.get(index);
    178   }
    179 
    180   @Implementation
    181   public AccessibilityWindowInfo getParent() {
    182     return parent;
    183   }
    184 
    185   @Implementation
    186   public AccessibilityNodeInfo getRoot() {
    187     return (rootNode == null) ? null : AccessibilityNodeInfo.obtain(rootNode);
    188   }
    189 
    190   @Implementation
    191   public boolean isActive() {
    192     return isActive;
    193   }
    194 
    195   @Implementation
    196   public int getId() {
    197     return id;
    198   }
    199 
    200   @Implementation
    201   public void getBoundsInScreen(Rect outBounds) {
    202     if (boundsInScreen == null) {
    203       outBounds.setEmpty();
    204     } else {
    205       outBounds.set(boundsInScreen);
    206     }
    207   }
    208 
    209   @Implementation
    210   public int getLayer() {
    211     return layer;
    212   }
    213 
    214   @Implementation
    215   public boolean isFocused() {
    216     return isFocused;
    217   }
    218 
    219   @Implementation
    220   public boolean isAccessibilityFocused() {
    221     return isAccessibilityFocused;
    222   }
    223 
    224   @Implementation
    225   public void recycle() {
    226     // This shadow does not track recycling of windows.
    227   }
    228 
    229   public void setRoot(AccessibilityNodeInfo root) {
    230     rootNode = root;
    231   }
    232 
    233   public void setType(int value) {
    234     type = value;
    235   }
    236 
    237   public void setBoundsInScreen(Rect bounds) {
    238     boundsInScreen.set(bounds);
    239   }
    240 
    241   public void setAccessibilityFocused(boolean value) {
    242     isAccessibilityFocused = value;
    243   }
    244 
    245   public void setActive(boolean value) {
    246     isActive = value;
    247   }
    248 
    249   public void setId(int value) {
    250     id = value;
    251   }
    252 
    253   public void setLayer(int value) {
    254     layer = value;
    255   }
    256 
    257   public void setFocused(boolean focused) {
    258     isFocused = focused;
    259   }
    260 
    261   public void addChild(AccessibilityWindowInfo child) {
    262     if (children == null) {
    263       children = new ArrayList<>();
    264     }
    265 
    266     children.add(child);
    267     ((ShadowAccessibilityWindowInfo) Shadow.extract(child)).parent =
    268         mRealAccessibilityWindowInfo;
    269   }
    270 
    271   /**
    272    * Private class to keep different windows referring to the same window straight
    273    * in the mObtainedInstances map.
    274    */
    275   private static class StrictEqualityWindowWrapper {
    276     public final AccessibilityWindowInfo mInfo;
    277 
    278     public StrictEqualityWindowWrapper(AccessibilityWindowInfo info) {
    279       mInfo = info;
    280     }
    281 
    282     @Override
    283     @SuppressWarnings("ReferenceEquality")
    284     public boolean equals(Object object) {
    285       if (object == null) {
    286         return false;
    287       }
    288 
    289       final StrictEqualityWindowWrapper wrapper = (StrictEqualityWindowWrapper) object;
    290       return mInfo == wrapper.mInfo;
    291     }
    292 
    293     @Override
    294     public int hashCode() {
    295       return mInfo.hashCode();
    296     }
    297   }
    298 }