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