• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
4 import static android.os.Build.VERSION_CODES.KITKAT;
5 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
6 import static android.os.Build.VERSION_CODES.N;
7 import static android.os.Build.VERSION_CODES.O;
8 import static android.os.Build.VERSION_CODES.Q;
9 import static android.os.Build.VERSION_CODES.R;
10 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
11 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
12 import static org.robolectric.util.ReflectionHelpers.getField;
13 import static org.robolectric.util.reflector.Reflector.reflector;
14 
15 import android.annotation.SuppressLint;
16 import android.content.Context;
17 import android.graphics.Canvas;
18 import android.graphics.Paint;
19 import android.graphics.Point;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.graphics.drawable.Drawable;
23 import android.os.Looper;
24 import android.os.RemoteException;
25 import android.os.SystemClock;
26 import android.text.TextUtils;
27 import android.util.AttributeSet;
28 import android.view.Choreographer;
29 import android.view.IWindowFocusObserver;
30 import android.view.IWindowId;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewGroup.LayoutParams;
34 import android.view.ViewParent;
35 import android.view.WindowId;
36 import android.view.WindowManager;
37 import android.view.animation.Animation;
38 import android.view.animation.Transformation;
39 import com.google.common.collect.ImmutableList;
40 import java.io.PrintStream;
41 import java.util.ArrayList;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Set;
45 import java.util.concurrent.CopyOnWriteArrayList;
46 import org.robolectric.RuntimeEnvironment;
47 import org.robolectric.annotation.Implementation;
48 import org.robolectric.annotation.Implements;
49 import org.robolectric.annotation.LooperMode;
50 import org.robolectric.annotation.RealObject;
51 import org.robolectric.annotation.ReflectorObject;
52 import org.robolectric.annotation.Resetter;
53 import org.robolectric.shadow.api.Shadow;
54 import org.robolectric.util.ReflectionHelpers.ClassParameter;
55 import org.robolectric.util.TimeUtils;
56 import org.robolectric.util.reflector.Accessor;
57 import org.robolectric.util.reflector.Direct;
58 import org.robolectric.util.reflector.ForType;
59 
60 @Implements(View.class)
61 @SuppressLint("NewApi")
62 public class ShadowView {
63 
64   @RealObject protected View realView;
65   @ReflectorObject protected _View_ viewReflector;
66   private static final List<View.OnClickListener> globalClickListeners =
67       new CopyOnWriteArrayList<>();
68   private static final List<View.OnLongClickListener> globalLongClickListeners =
69       new CopyOnWriteArrayList<>();
70   private View.OnClickListener onClickListener;
71   private View.OnLongClickListener onLongClickListener;
72   private View.OnFocusChangeListener onFocusChangeListener;
73   private View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener;
74   private final HashSet<View.OnAttachStateChangeListener> onAttachStateChangeListeners =
75       new HashSet<>();
76   private final HashSet<View.OnLayoutChangeListener> onLayoutChangeListeners = new HashSet<>();
77   private boolean wasInvalidated;
78   private View.OnTouchListener onTouchListener;
79   protected AttributeSet attributeSet;
80   public Point scrollToCoordinates = new Point();
81   private boolean didRequestLayout;
82   private MotionEvent lastTouchEvent;
83   private int hapticFeedbackPerformed = -1;
84   private boolean onLayoutWasCalled;
85   private View.OnCreateContextMenuListener onCreateContextMenuListener;
86   private Rect globalVisibleRect;
87   private int layerType;
88   private final ArrayList<Animation> animations = new ArrayList<>();
89   private AnimationRunner animationRunner;
90 
91   /**
92    * Calls {@code performClick()} on a {@code View} after ensuring that it and its ancestors are
93    * visible and that it is enabled.
94    *
95    * @param view the view to click on
96    * @return true if {@code View.OnClickListener}s were found and fired, false otherwise.
97    * @throws RuntimeException if the preconditions are not met.
98    * @deprecated Please use Espresso for view interactions
99    */
100   @Deprecated
clickOn(View view)101   public static boolean clickOn(View view) {
102     ShadowView shadowView = Shadow.extract(view);
103     return shadowView.checkedPerformClick();
104   }
105 
106   /**
107    * Returns a textual representation of the appearance of the object.
108    *
109    * @param view the view to visualize
110    * @return Textual representation of the appearance of the object.
111    */
visualize(View view)112   public static String visualize(View view) {
113     Canvas canvas = new Canvas();
114     view.draw(canvas);
115     if (!useRealGraphics()) {
116       ShadowCanvas shadowCanvas = Shadow.extract(canvas);
117       return shadowCanvas.getDescription();
118     } else {
119       return "";
120     }
121   }
122 
123   /**
124    * Emits an xml-like representation of the view to System.out.
125    *
126    * @param view the view to dump.
127    * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
128    */
129   @SuppressWarnings("UnusedDeclaration")
130   @Deprecated
dump(View view)131   public static void dump(View view) {
132     ShadowView shadowView = Shadow.extract(view);
133     shadowView.dump();
134   }
135 
136   /**
137    * Returns the text contained within this view.
138    *
139    * @param view the view to scan for text
140    * @return Text contained within this view.
141    */
142   @SuppressWarnings("UnusedDeclaration")
innerText(View view)143   public static String innerText(View view) {
144     ShadowView shadowView = Shadow.extract(view);
145     return shadowView.innerText();
146   }
147 
148   // Only override up to kitkat, while this version exists after kitkat it just calls through to the
149   // __constructor__(Context, AttributeSet, int, int) variant below.
150   @Implementation(maxSdk = KITKAT)
__constructor__(Context context, AttributeSet attributeSet, int defStyle)151   protected void __constructor__(Context context, AttributeSet attributeSet, int defStyle) {
152     this.attributeSet = attributeSet;
153     invokeConstructor(
154         View.class,
155         realView,
156         ClassParameter.from(Context.class, context),
157         ClassParameter.from(AttributeSet.class, attributeSet),
158         ClassParameter.from(int.class, defStyle));
159   }
160 
161   @Implementation(minSdk = KITKAT_WATCH)
__constructor__( Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes)162   protected void __constructor__(
163       Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
164     this.attributeSet = attributeSet;
165     invokeConstructor(
166         View.class,
167         realView,
168         ClassParameter.from(Context.class, context),
169         ClassParameter.from(AttributeSet.class, attributeSet),
170         ClassParameter.from(int.class, defStyleAttr),
171         ClassParameter.from(int.class, defStyleRes));
172   }
173 
174   @Implementation
setLayerType(int layerType, Paint paint)175   protected void setLayerType(int layerType, Paint paint) {
176     this.layerType = layerType;
177     reflector(_View_.class, realView).setLayerType(layerType, paint);
178   }
179 
180   @Implementation
setOnFocusChangeListener(View.OnFocusChangeListener l)181   protected void setOnFocusChangeListener(View.OnFocusChangeListener l) {
182     onFocusChangeListener = l;
183     reflector(_View_.class, realView).setOnFocusChangeListener(l);
184   }
185 
186   @Implementation
setOnClickListener(View.OnClickListener onClickListener)187   protected void setOnClickListener(View.OnClickListener onClickListener) {
188     this.onClickListener = onClickListener;
189     reflector(_View_.class, realView).setOnClickListener(onClickListener);
190   }
191 
192   @Implementation
setOnLongClickListener(View.OnLongClickListener onLongClickListener)193   protected void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
194     this.onLongClickListener = onLongClickListener;
195     reflector(_View_.class, realView).setOnLongClickListener(onLongClickListener);
196   }
197 
198   @Implementation
setOnSystemUiVisibilityChangeListener( View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener)199   protected void setOnSystemUiVisibilityChangeListener(
200       View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener) {
201     this.onSystemUiVisibilityChangeListener = onSystemUiVisibilityChangeListener;
202     reflector(_View_.class, realView)
203         .setOnSystemUiVisibilityChangeListener(onSystemUiVisibilityChangeListener);
204   }
205 
206   @Implementation
setOnCreateContextMenuListener( View.OnCreateContextMenuListener onCreateContextMenuListener)207   protected void setOnCreateContextMenuListener(
208       View.OnCreateContextMenuListener onCreateContextMenuListener) {
209     this.onCreateContextMenuListener = onCreateContextMenuListener;
210     reflector(_View_.class, realView).setOnCreateContextMenuListener(onCreateContextMenuListener);
211   }
212 
213   @Implementation
addOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)214   protected void addOnAttachStateChangeListener(
215       View.OnAttachStateChangeListener onAttachStateChangeListener) {
216     onAttachStateChangeListeners.add(onAttachStateChangeListener);
217     reflector(_View_.class, realView).addOnAttachStateChangeListener(onAttachStateChangeListener);
218   }
219 
220   @Implementation
removeOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)221   protected void removeOnAttachStateChangeListener(
222       View.OnAttachStateChangeListener onAttachStateChangeListener) {
223     onAttachStateChangeListeners.remove(onAttachStateChangeListener);
224     reflector(_View_.class, realView)
225         .removeOnAttachStateChangeListener(onAttachStateChangeListener);
226   }
227 
228   @Implementation
addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)229   protected void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) {
230     onLayoutChangeListeners.add(onLayoutChangeListener);
231     reflector(_View_.class, realView).addOnLayoutChangeListener(onLayoutChangeListener);
232   }
233 
234   @Implementation
removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)235   protected void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) {
236     onLayoutChangeListeners.remove(onLayoutChangeListener);
237     reflector(_View_.class, realView).removeOnLayoutChangeListener(onLayoutChangeListener);
238   }
239 
240   @Implementation
draw(Canvas canvas)241   protected void draw(Canvas canvas) {
242     Drawable background = realView.getBackground();
243     if (background != null && !useRealGraphics()) {
244       Object shadowCanvas = Shadow.extract(canvas);
245       // Check that Canvas is not a Mockito mock
246       if (shadowCanvas instanceof ShadowCanvas) {
247         ((ShadowCanvas) shadowCanvas).appendDescription("background:");
248       }
249     }
250     reflector(_View_.class, realView).draw(canvas);
251   }
252 
253   @Implementation
onLayout(boolean changed, int left, int top, int right, int bottom)254   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
255     onLayoutWasCalled = true;
256     reflector(_View_.class, realView).onLayout(changed, left, top, right, bottom);
257   }
258 
onLayoutWasCalled()259   public boolean onLayoutWasCalled() {
260     return onLayoutWasCalled;
261   }
262 
263   @Implementation
requestLayout()264   protected void requestLayout() {
265     didRequestLayout = true;
266     reflector(_View_.class, realView).requestLayout();
267   }
268 
269   @Implementation
performClick()270   protected boolean performClick() {
271     for (View.OnClickListener listener : globalClickListeners) {
272       listener.onClick(realView);
273     }
274     return reflector(_View_.class, realView).performClick();
275   }
276 
277   /**
278    * Registers an {@link View.OnClickListener} to the {@link ShadowView}.
279    *
280    * @param listener The {@link View.OnClickListener} to be registered.
281    */
addGlobalPerformClickListener(View.OnClickListener listener)282   public static void addGlobalPerformClickListener(View.OnClickListener listener) {
283     ShadowView.globalClickListeners.add(listener);
284   }
285 
286   /**
287    * Removes an {@link View.OnClickListener} from the {@link ShadowView}.
288    *
289    * @param listener The {@link View.OnClickListener} to be removed.
290    */
removeGlobalPerformClickListener(View.OnClickListener listener)291   public static void removeGlobalPerformClickListener(View.OnClickListener listener) {
292     ShadowView.globalClickListeners.remove(listener);
293   }
294 
295   @Implementation
performLongClick()296   protected boolean performLongClick() {
297     for (View.OnLongClickListener listener : globalLongClickListeners) {
298       listener.onLongClick(realView);
299     }
300     return reflector(_View_.class, realView).performLongClick();
301   }
302 
303   /**
304    * Registers an {@link View.OnLongClickListener} to the {@link ShadowView}.
305    *
306    * @param listener The {@link View.OnLongClickListener} to be registered.
307    */
addGlobalPerformLongClickListener(View.OnLongClickListener listener)308   public static void addGlobalPerformLongClickListener(View.OnLongClickListener listener) {
309     ShadowView.globalLongClickListeners.add(listener);
310   }
311 
312   /**
313    * Removes an {@link View.OnLongClickListener} from the {@link ShadowView}.
314    *
315    * @param listener The {@link View.OnLongClickListener} to be removed.
316    */
removeGlobalPerformLongClickListener(View.OnLongClickListener listener)317   public static void removeGlobalPerformLongClickListener(View.OnLongClickListener listener) {
318     ShadowView.globalLongClickListeners.remove(listener);
319   }
320 
321   @Resetter
reset()322   public static void reset() {
323     ShadowView.globalClickListeners.clear();
324     ShadowView.globalLongClickListeners.clear();
325   }
326 
didRequestLayout()327   public boolean didRequestLayout() {
328     return didRequestLayout;
329   }
330 
setDidRequestLayout(boolean didRequestLayout)331   public void setDidRequestLayout(boolean didRequestLayout) {
332     this.didRequestLayout = didRequestLayout;
333   }
334 
setViewFocus(boolean hasFocus)335   public void setViewFocus(boolean hasFocus) {
336     if (onFocusChangeListener != null) {
337       onFocusChangeListener.onFocusChange(realView, hasFocus);
338     }
339   }
340 
341   @Implementation
invalidate()342   protected void invalidate() {
343     wasInvalidated = true;
344     reflector(_View_.class, realView).invalidate();
345   }
346 
347   @Implementation
onTouchEvent(MotionEvent event)348   protected boolean onTouchEvent(MotionEvent event) {
349     lastTouchEvent = event;
350     return reflector(_View_.class, realView).onTouchEvent(event);
351   }
352 
353   @Implementation
setOnTouchListener(View.OnTouchListener onTouchListener)354   protected void setOnTouchListener(View.OnTouchListener onTouchListener) {
355     this.onTouchListener = onTouchListener;
356     reflector(_View_.class, realView).setOnTouchListener(onTouchListener);
357   }
358 
getLastTouchEvent()359   public MotionEvent getLastTouchEvent() {
360     return lastTouchEvent;
361   }
362 
363   /**
364    * Returns a string representation of this {@code View}. Unless overridden, it will be an empty
365    * string.
366    *
367    * <p>Robolectric extension.
368    *
369    * @return String representation of this view.
370    */
innerText()371   public String innerText() {
372     return "";
373   }
374 
375   /**
376    * Dumps the status of this {@code View} to {@code System.out}
377    *
378    * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
379    */
380   @Deprecated
dump()381   public void dump() {
382     dump(System.out, 0);
383   }
384 
385   /**
386    * Dumps the status of this {@code View} to {@code System.out} at the given indentation level
387    *
388    * @param out Output stream.
389    * @param indent Indentation level.
390    * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
391    */
392   @Deprecated
dump(PrintStream out, int indent)393   public void dump(PrintStream out, int indent) {
394     dumpFirstPart(out, indent);
395     out.println("/>");
396   }
397 
398   @Deprecated
dumpFirstPart(PrintStream out, int indent)399   protected void dumpFirstPart(PrintStream out, int indent) {
400     dumpIndent(out, indent);
401 
402     out.print("<" + realView.getClass().getSimpleName());
403     dumpAttributes(out);
404   }
405 
406   @Deprecated
dumpAttributes(PrintStream out)407   protected void dumpAttributes(PrintStream out) {
408     if (realView.getId() > 0) {
409       dumpAttribute(
410           out, "id", realView.getContext().getResources().getResourceName(realView.getId()));
411     }
412 
413     switch (realView.getVisibility()) {
414       case View.VISIBLE:
415         break;
416       case View.INVISIBLE:
417         dumpAttribute(out, "visibility", "INVISIBLE");
418         break;
419       case View.GONE:
420         dumpAttribute(out, "visibility", "GONE");
421         break;
422     }
423   }
424 
425   @Deprecated
dumpAttribute(PrintStream out, String name, String value)426   protected void dumpAttribute(PrintStream out, String name, String value) {
427     out.print(" " + name + "=\"" + (value == null ? null : TextUtils.htmlEncode(value)) + "\"");
428   }
429 
430   @Deprecated
dumpIndent(PrintStream out, int indent)431   protected void dumpIndent(PrintStream out, int indent) {
432     for (int i = 0; i < indent; i++) out.print(" ");
433   }
434 
435   /**
436    * @return whether or not {@link #invalidate()} has been called
437    */
wasInvalidated()438   public boolean wasInvalidated() {
439     return wasInvalidated;
440   }
441 
442   /** Clears the wasInvalidated flag */
clearWasInvalidated()443   public void clearWasInvalidated() {
444     wasInvalidated = false;
445   }
446 
447   /**
448    * Utility method for clicking on views exposing testing scenarios that are not possible when
449    * using the actual app.
450    *
451    * <p>If running with LooperMode PAUSED will also idle the main Looper.
452    *
453    * @throws RuntimeException if the view is disabled or if the view or any of its parents are not
454    *     visible.
455    * @return Return value of the underlying click operation.
456    * @deprecated - Please use Espresso for View interactions.
457    */
458   @Deprecated
checkedPerformClick()459   public boolean checkedPerformClick() {
460     if (!realView.isShown()) {
461       throw new RuntimeException("View is not visible and cannot be clicked");
462     }
463     if (!realView.isEnabled()) {
464       throw new RuntimeException("View is not enabled and cannot be clicked");
465     }
466     boolean res = realView.performClick();
467     shadowMainLooper().idleIfPaused();
468     return res;
469   }
470 
471   /**
472    * @return Touch listener, if set.
473    */
getOnTouchListener()474   public View.OnTouchListener getOnTouchListener() {
475     return onTouchListener;
476   }
477 
478   /**
479    * @return Returns click listener, if set.
480    */
getOnClickListener()481   public View.OnClickListener getOnClickListener() {
482     return onClickListener;
483   }
484 
485   /**
486    * @return Returns long click listener, if set.
487    */
488   @Implementation(minSdk = R)
getOnLongClickListener()489   public View.OnLongClickListener getOnLongClickListener() {
490     if (RuntimeEnvironment.getApiLevel() >= R) {
491       return reflector(_View_.class, realView).getOnLongClickListener();
492     } else {
493       return onLongClickListener;
494     }
495   }
496 
497   /**
498    * @return Returns system ui visibility change listener.
499    */
getOnSystemUiVisibilityChangeListener()500   public View.OnSystemUiVisibilityChangeListener getOnSystemUiVisibilityChangeListener() {
501     return onSystemUiVisibilityChangeListener;
502   }
503 
504   /**
505    * @return Returns create ContextMenu listener, if set.
506    */
getOnCreateContextMenuListener()507   public View.OnCreateContextMenuListener getOnCreateContextMenuListener() {
508     return onCreateContextMenuListener;
509   }
510 
511   /**
512    * @return Returns the attached listeners, or the empty set if none are present.
513    */
getOnAttachStateChangeListeners()514   public Set<View.OnAttachStateChangeListener> getOnAttachStateChangeListeners() {
515     return onAttachStateChangeListeners;
516   }
517 
518   /**
519    * @return Returns the layout change listeners, or the empty set if none are present.
520    */
getOnLayoutChangeListeners()521   public Set<View.OnLayoutChangeListener> getOnLayoutChangeListeners() {
522     return onLayoutChangeListeners;
523   }
524 
525   @Implementation
post(Runnable action)526   protected boolean post(Runnable action) {
527     if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
528       return reflector(_View_.class, realView).post(action);
529     } else {
530       ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
531       return true;
532     }
533   }
534 
535   @Implementation
postDelayed(Runnable action, long delayMills)536   protected boolean postDelayed(Runnable action, long delayMills) {
537     if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
538       return reflector(_View_.class, realView).postDelayed(action, delayMills);
539     } else {
540       ShadowApplication.getInstance()
541           .getForegroundThreadScheduler()
542           .postDelayed(action, delayMills);
543       return true;
544     }
545   }
546 
547   @Implementation
postInvalidateDelayed(long delayMilliseconds)548   protected void postInvalidateDelayed(long delayMilliseconds) {
549     if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
550       reflector(_View_.class, realView).postInvalidateDelayed(delayMilliseconds);
551     } else {
552       ShadowApplication.getInstance()
553           .getForegroundThreadScheduler()
554           .postDelayed(
555               new Runnable() {
556                 @Override
557                 public void run() {
558                   realView.invalidate();
559                 }
560               },
561               delayMilliseconds);
562     }
563   }
564 
565   @Implementation
removeCallbacks(Runnable callback)566   protected boolean removeCallbacks(Runnable callback) {
567     if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
568       return reflector(_View_.class, realView).removeCallbacks(callback);
569     } else {
570       ShadowLegacyLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
571       shadowLooper.getScheduler().remove(callback);
572       return true;
573     }
574   }
575 
576   @Implementation
scrollTo(int x, int y)577   protected void scrollTo(int x, int y) {
578     if (useRealGraphics()) {
579       reflector(_View_.class, realView).scrollTo(x, y);
580     } else {
581       reflector(_View_.class, realView)
582           .onScrollChanged(x, y, scrollToCoordinates.x, scrollToCoordinates.y);
583       scrollToCoordinates = new Point(x, y);
584       reflector(_View_.class, realView).setMemberScrollX(x);
585       reflector(_View_.class, realView).setMemberScrollY(y);
586     }
587   }
588 
589   @Implementation
scrollBy(int x, int y)590   protected void scrollBy(int x, int y) {
591     if (useRealGraphics()) {
592       reflector(_View_.class, realView).scrollBy(x, y);
593     } else {
594       scrollTo(getScrollX() + x, getScrollY() + y);
595     }
596   }
597 
598   @Implementation
getScrollX()599   protected int getScrollX() {
600     if (useRealGraphics()) {
601       return reflector(_View_.class, realView).getScrollX();
602     } else {
603       return scrollToCoordinates != null ? scrollToCoordinates.x : 0;
604     }
605   }
606 
607   @Implementation
getScrollY()608   protected int getScrollY() {
609     if (useRealGraphics()) {
610       return reflector(_View_.class, realView).getScrollY();
611     } else {
612       return scrollToCoordinates != null ? scrollToCoordinates.y : 0;
613     }
614   }
615 
616   @Implementation
setScrollX(int scrollX)617   protected void setScrollX(int scrollX) {
618     if (useRealGraphics()) {
619       reflector(_View_.class, realView).setScrollX(scrollX);
620     } else {
621       scrollTo(scrollX, scrollToCoordinates.y);
622     }
623   }
624 
625   @Implementation
setScrollY(int scrollY)626   protected void setScrollY(int scrollY) {
627     if (useRealGraphics()) {
628       reflector(_View_.class, realView).setScrollY(scrollY);
629     } else {
630       scrollTo(scrollToCoordinates.x, scrollY);
631     }
632   }
633 
634   @Implementation
getLocationOnScreen(int[] outLocation)635   protected void getLocationOnScreen(int[] outLocation) {
636     reflector(_View_.class, realView).getLocationOnScreen(outLocation);
637     int[] windowLocation = getWindowLocation();
638     outLocation[0] += windowLocation[0];
639     outLocation[1] += windowLocation[1];
640   }
641 
642   @Implementation(minSdk = O)
mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent)643   protected void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) {
644     reflector(_View_.class, realView).mapRectFromViewToScreenCoords(rect, clipToParent);
645     int[] windowLocation = getWindowLocation();
646     rect.offset(windowLocation[0], windowLocation[1]);
647   }
648 
649   // TODO(paulsowden): Should configure the correct frame on the ViewRootImpl instead and remove
650   //  this.
getWindowLocation()651   private int[] getWindowLocation() {
652     int[] location = new int[2];
653     LayoutParams rootParams = realView.getRootView().getLayoutParams();
654     if (rootParams instanceof WindowManager.LayoutParams) {
655       location[0] = ((WindowManager.LayoutParams) rootParams).x;
656       location[1] = ((WindowManager.LayoutParams) rootParams).y;
657     }
658     return location;
659   }
660 
661   @Implementation
getLayerType()662   protected int getLayerType() {
663     return this.layerType;
664   }
665 
666   /** Returns a list of all animations that have been set on this view. */
getAnimations()667   public ImmutableList<Animation> getAnimations() {
668     return ImmutableList.copyOf(animations);
669   }
670 
671   /** Resets the list returned by {@link #getAnimations()} to an empty list. */
clearAnimations()672   public void clearAnimations() {
673     animations.clear();
674   }
675 
676   @Implementation
setAnimation(final Animation animation)677   protected void setAnimation(final Animation animation) {
678     reflector(_View_.class, realView).setAnimation(animation);
679 
680     if (animation != null) {
681       animations.add(animation);
682       if (animationRunner != null) {
683         animationRunner.cancel();
684       }
685       animationRunner = new AnimationRunner(animation);
686       animationRunner.start();
687     }
688   }
689 
690   @Implementation
clearAnimation()691   protected void clearAnimation() {
692     reflector(_View_.class, realView).clearAnimation();
693 
694     if (animationRunner != null) {
695       animationRunner.cancel();
696       animationRunner = null;
697     }
698   }
699 
700   @Implementation
initialAwakenScrollBars()701   protected boolean initialAwakenScrollBars() {
702     // Temporarily allow disabling initial awaken of scroll bars to aid in migration of tests to
703     // default to window's being marked visible, this will be removed once migration is complete.
704     if (Boolean.getBoolean("robolectric.disableInitialAwakenScrollBars")) {
705       return false;
706     } else {
707       return viewReflector.initialAwakenScrollBars();
708     }
709   }
710 
711   private class AnimationRunner implements Runnable {
712     private final Animation animation;
713     private final Transformation transformation = new Transformation();
714     private long startTime;
715     private long elapsedTime;
716     private boolean canceled;
717 
AnimationRunner(Animation animation)718     AnimationRunner(Animation animation) {
719       this.animation = animation;
720     }
721 
start()722     private void start() {
723       startTime = animation.getStartTime();
724       long startOffset = animation.getStartOffset();
725       long startDelay =
726           startTime == Animation.START_ON_FIRST_FRAME
727               ? startOffset
728               : (startTime + startOffset) - SystemClock.uptimeMillis();
729       Choreographer.getInstance()
730           .postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, this, null, startDelay);
731     }
732 
step()733     private boolean step() {
734       long animationTime =
735           animation.getStartTime() == Animation.START_ON_FIRST_FRAME
736               ? SystemClock.uptimeMillis()
737               : (animation.getStartTime() + animation.getStartOffset() + elapsedTime);
738       // Note in real android the parent is non-nullable, retain legacy robolectric behavior which
739       // allows detached views to animate.
740       if (!animation.isInitialized() && realView.getParent() != null) {
741         View parent = (View) realView.getParent();
742         animation.initialize(
743             realView.getWidth(), realView.getHeight(), parent.getWidth(), parent.getHeight());
744       }
745       boolean next = animation.getTransformation(animationTime, transformation);
746       // Note in real view implementation it doesn't check the animation equality before clearing,
747       // but in the real implementation the animation listeners are posted so it doesn't race with
748       // chained animations.
749       if (realView.getAnimation() == animation && !next) {
750         if (!animation.getFillAfter()) {
751           realView.clearAnimation();
752         }
753       }
754       // We can't handle infinitely repeating animations in the current scheduling model, so abort
755       // after one iteration.
756       return next
757           && (animation.getRepeatCount() != Animation.INFINITE
758               || elapsedTime < animation.getDuration());
759     }
760 
761     @Override
run()762     public void run() {
763       // Abort if start time has been messed with, as this simulation is only designed to handle
764       // standard situations.
765       if (!canceled && animation.getStartTime() == startTime && step()) {
766         // Start time updates for repeating animations and if START_ON_FIRST_FRAME.
767         startTime = animation.getStartTime();
768         elapsedTime +=
769             ShadowLooper.looperMode().equals(LooperMode.Mode.LEGACY)
770                 ? ShadowChoreographer.getFrameInterval() / TimeUtils.NANOS_PER_MS
771                 : ShadowChoreographer.getFrameDelay().toMillis();
772         Choreographer.getInstance().postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
773       } else if (animationRunner == this) {
774         animationRunner = null;
775       }
776     }
777 
cancel()778     public void cancel() {
779       this.canceled = true;
780       Choreographer.getInstance()
781           .removeCallbacks(Choreographer.CALLBACK_ANIMATION, animationRunner, null);
782     }
783   }
784 
785   @Implementation(minSdk = KITKAT)
isAttachedToWindow()786   protected boolean isAttachedToWindow() {
787     return getAttachInfo() != null;
788   }
789 
getAttachInfo()790   private Object getAttachInfo() {
791     return reflector(_View_.class, realView).getAttachInfo();
792   }
793 
794   /** Reflector interface for {@link View}'s internals. */
795   @ForType(View.class)
796   private interface _View_ {
797 
798     @Direct
draw(Canvas canvas)799     void draw(Canvas canvas);
800 
801     @Direct
onLayout(boolean changed, int left, int top, int right, int bottom)802     void onLayout(boolean changed, int left, int top, int right, int bottom);
803 
assignParent(ViewParent viewParent)804     void assignParent(ViewParent viewParent);
805 
806     @Direct
setOnFocusChangeListener(View.OnFocusChangeListener l)807     void setOnFocusChangeListener(View.OnFocusChangeListener l);
808 
809     @Direct
setLayerType(int layerType, Paint paint)810     void setLayerType(int layerType, Paint paint);
811 
812     @Direct
setOnClickListener(View.OnClickListener onClickListener)813     void setOnClickListener(View.OnClickListener onClickListener);
814 
815     @Direct
setOnLongClickListener(View.OnLongClickListener onLongClickListener)816     void setOnLongClickListener(View.OnLongClickListener onLongClickListener);
817 
818     @Direct
getOnLongClickListener()819     View.OnLongClickListener getOnLongClickListener();
820 
821     @Direct
setOnSystemUiVisibilityChangeListener( View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener)822     void setOnSystemUiVisibilityChangeListener(
823         View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener);
824 
825     @Direct
setOnCreateContextMenuListener( View.OnCreateContextMenuListener onCreateContextMenuListener)826     void setOnCreateContextMenuListener(
827         View.OnCreateContextMenuListener onCreateContextMenuListener);
828 
829     @Direct
addOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)830     void addOnAttachStateChangeListener(
831         View.OnAttachStateChangeListener onAttachStateChangeListener);
832 
833     @Direct
removeOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)834     void removeOnAttachStateChangeListener(
835         View.OnAttachStateChangeListener onAttachStateChangeListener);
836 
837     @Direct
addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)838     void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener);
839 
840     @Direct
removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)841     void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener);
842 
843     @Direct
requestLayout()844     void requestLayout();
845 
846     @Direct
performClick()847     boolean performClick();
848 
849     @Direct
performLongClick()850     boolean performLongClick();
851 
852     @Direct
invalidate()853     void invalidate();
854 
855     @Direct
onTouchEvent(MotionEvent event)856     boolean onTouchEvent(MotionEvent event);
857 
858     @Direct
setOnTouchListener(View.OnTouchListener onTouchListener)859     void setOnTouchListener(View.OnTouchListener onTouchListener);
860 
861     @Direct
post(Runnable action)862     boolean post(Runnable action);
863 
864     @Direct
postDelayed(Runnable action, long delayMills)865     boolean postDelayed(Runnable action, long delayMills);
866 
867     @Direct
postInvalidateDelayed(long delayMilliseconds)868     void postInvalidateDelayed(long delayMilliseconds);
869 
870     @Direct
removeCallbacks(Runnable callback)871     boolean removeCallbacks(Runnable callback);
872 
873     @Direct
setAnimation(final Animation animation)874     void setAnimation(final Animation animation);
875 
876     @Direct
clearAnimation()877     void clearAnimation();
878 
879     @Direct
getGlobalVisibleRect(Rect rect, Point globalOffset)880     boolean getGlobalVisibleRect(Rect rect, Point globalOffset);
881 
882     @Direct
getWindowId()883     WindowId getWindowId();
884 
885     @Accessor("mAttachInfo")
getAttachInfo()886     Object getAttachInfo();
887 
onAttachedToWindow()888     void onAttachedToWindow();
889 
onDetachedFromWindow()890     void onDetachedFromWindow();
891 
onScrollChanged(int l, int t, int oldl, int oldt)892     void onScrollChanged(int l, int t, int oldl, int oldt);
893 
894     @Direct
getLocationOnScreen(int[] outLocation)895     void getLocationOnScreen(int[] outLocation);
896 
897     @Direct
mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent)898     void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent);
899 
900     @Direct
getSourceLayoutResId()901     int getSourceLayoutResId();
902 
903     @Direct
initialAwakenScrollBars()904     boolean initialAwakenScrollBars();
905 
906     @Accessor("mScrollX")
setMemberScrollX(int value)907     void setMemberScrollX(int value);
908 
909     @Accessor("mScrollY")
setMemberScrollY(int value)910     void setMemberScrollY(int value);
911 
912     @Direct
scrollTo(int x, int y)913     void scrollTo(int x, int y);
914 
915     @Direct
scrollBy(int x, int y)916     void scrollBy(int x, int y);
917 
918     @Direct
getScrollX()919     int getScrollX();
920 
921     @Direct
getScrollY()922     int getScrollY();
923 
924     @Direct
setScrollX(int value)925     void setScrollX(int value);
926 
927     @Direct
setScrollY(int value)928     void setScrollY(int value);
929   }
930 
callOnAttachedToWindow()931   public void callOnAttachedToWindow() {
932     reflector(_View_.class, realView).onAttachedToWindow();
933   }
934 
callOnDetachedFromWindow()935   public void callOnDetachedFromWindow() {
936     reflector(_View_.class, realView).onDetachedFromWindow();
937   }
938 
939   @Implementation(minSdk = JELLY_BEAN_MR2)
getWindowId()940   protected WindowId getWindowId() {
941     return WindowIdHelper.getWindowId(this);
942   }
943 
944   @Implementation
performHapticFeedback(int hapticFeedbackType)945   protected boolean performHapticFeedback(int hapticFeedbackType) {
946     hapticFeedbackPerformed = hapticFeedbackType;
947     return true;
948   }
949 
950   @Implementation
getGlobalVisibleRect(Rect rect, Point globalOffset)951   protected boolean getGlobalVisibleRect(Rect rect, Point globalOffset) {
952     if (globalVisibleRect == null) {
953       return reflector(_View_.class, realView).getGlobalVisibleRect(rect, globalOffset);
954     }
955 
956     if (!globalVisibleRect.isEmpty()) {
957       rect.set(globalVisibleRect);
958       if (globalOffset != null) {
959         rect.offset(-globalOffset.x, -globalOffset.y);
960       }
961       return true;
962     }
963     rect.setEmpty();
964     return false;
965   }
966 
setGlobalVisibleRect(Rect rect)967   public void setGlobalVisibleRect(Rect rect) {
968     if (rect != null) {
969       globalVisibleRect = new Rect();
970       globalVisibleRect.set(rect);
971     } else {
972       globalVisibleRect = null;
973     }
974   }
975 
lastHapticFeedbackPerformed()976   public int lastHapticFeedbackPerformed() {
977     return hapticFeedbackPerformed;
978   }
979 
setMyParent(ViewParent viewParent)980   public void setMyParent(ViewParent viewParent) {
981     reflector(_View_.class, realView).assignParent(viewParent);
982   }
983 
984   @Implementation
getWindowVisibleDisplayFrame(Rect outRect)985   protected void getWindowVisibleDisplayFrame(Rect outRect) {
986     // TODO: figure out how to simulate this logic instead
987     // if (mAttachInfo != null) {
988     //   mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
989 
990     ShadowDisplay.getDefaultDisplay().getRectSize(outRect);
991   }
992 
993   @Implementation(minSdk = N)
getWindowDisplayFrame(Rect outRect)994   protected void getWindowDisplayFrame(Rect outRect) {
995     // TODO: figure out how to simulate this logic instead
996     // if (mAttachInfo != null) {
997     //   mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
998 
999     ShadowDisplay.getDefaultDisplay().getRectSize(outRect);
1000   }
1001 
1002   /**
1003    * Returns the layout resource id this view was inflated from. Backwards compatible version of
1004    * {@link View#getSourceLayoutResId()}, passes through to the underlying implementation on API
1005    * levels where it is supported.
1006    */
1007   @Implementation(minSdk = Q)
getSourceLayoutResId()1008   public int getSourceLayoutResId() {
1009     if (RuntimeEnvironment.getApiLevel() >= Q) {
1010       return reflector(_View_.class, realView).getSourceLayoutResId();
1011     } else {
1012       return ShadowResources.getAttributeSetSourceResId(attributeSet);
1013     }
1014   }
1015 
1016   public static class WindowIdHelper {
getWindowId(ShadowView shadowView)1017     public static WindowId getWindowId(ShadowView shadowView) {
1018       if (shadowView.isAttachedToWindow()) {
1019         Object attachInfo = shadowView.getAttachInfo();
1020         if (getField(attachInfo, "mWindowId") == null) {
1021           IWindowId iWindowId = new MyIWindowIdStub();
1022           reflector(_AttachInfo_.class, attachInfo).setWindowId(new WindowId(iWindowId));
1023           reflector(_AttachInfo_.class, attachInfo).setIWindowId(iWindowId);
1024         }
1025       }
1026 
1027       return reflector(_View_.class, shadowView.realView).getWindowId();
1028     }
1029 
1030     private static class MyIWindowIdStub extends IWindowId.Stub {
1031       @Override
registerFocusObserver(IWindowFocusObserver iWindowFocusObserver)1032       public void registerFocusObserver(IWindowFocusObserver iWindowFocusObserver)
1033           throws RemoteException {}
1034 
1035       @Override
unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver)1036       public void unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver)
1037           throws RemoteException {}
1038 
1039       @Override
isFocused()1040       public boolean isFocused() throws RemoteException {
1041         return true;
1042       }
1043     }
1044   }
1045 
1046   /** Reflector interface for android.view.View.AttachInfo's internals. */
1047   @ForType(className = "android.view.View$AttachInfo")
1048   interface _AttachInfo_ {
1049 
1050     @Accessor("mIWindowId")
setIWindowId(IWindowId iWindowId)1051     void setIWindowId(IWindowId iWindowId);
1052 
1053     @Accessor("mWindowId")
setWindowId(WindowId windowId)1054     void setWindowId(WindowId windowId);
1055   }
1056 
useRealGraphics()1057   static boolean useRealGraphics() {
1058     return Boolean.getBoolean("robolectric.nativeruntime.enableGraphics");
1059   }
1060 }
1061