• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.N;
4 import static android.os.Build.VERSION_CODES.O;
5 import static android.os.Build.VERSION_CODES.Q;
6 import static android.os.Build.VERSION_CODES.R;
7 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
8 import static org.robolectric.util.ReflectionHelpers.getField;
9 import static org.robolectric.util.reflector.Reflector.reflector;
10 
11 import android.annotation.SuppressLint;
12 import android.content.Context;
13 import android.graphics.Canvas;
14 import android.graphics.Paint;
15 import android.graphics.Point;
16 import android.graphics.Rect;
17 import android.graphics.RectF;
18 import android.graphics.drawable.Drawable;
19 import android.os.Build;
20 import android.os.Looper;
21 import android.os.RemoteException;
22 import android.os.SystemClock;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 import android.view.Choreographer;
26 import android.view.IWindowFocusObserver;
27 import android.view.IWindowId;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewGroup.LayoutParams;
31 import android.view.ViewParent;
32 import android.view.WindowId;
33 import android.view.WindowManager;
34 import android.view.animation.Animation;
35 import android.view.animation.Transformation;
36 import com.google.common.annotations.Beta;
37 import com.google.common.collect.ImmutableList;
38 import java.io.PrintStream;
39 import java.util.ArrayList;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43 import java.util.concurrent.CopyOnWriteArrayList;
44 import org.robolectric.RuntimeEnvironment;
45 import org.robolectric.annotation.GraphicsMode;
46 import org.robolectric.annotation.GraphicsMode.Mode;
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.config.ConfigurationRegistry;
54 import org.robolectric.shadow.api.Shadow;
55 import org.robolectric.shadows.ShadowViewRootImpl.ViewRootImplReflector;
56 import org.robolectric.util.TimeUtils;
57 import org.robolectric.util.reflector.Accessor;
58 import org.robolectric.util.reflector.Direct;
59 import org.robolectric.util.reflector.ForType;
60 
61 @Implements(View.class)
62 @SuppressLint("NewApi")
63 public class ShadowView {
64 
65   @RealObject protected View realView;
66   @ReflectorObject protected _View_ viewReflector;
67   private static final List<View.OnClickListener> globalClickListeners =
68       new CopyOnWriteArrayList<>();
69   private static final List<View.OnLongClickListener> globalLongClickListeners =
70       new CopyOnWriteArrayList<>();
71   private View.OnClickListener onClickListener;
72   private View.OnLongClickListener onLongClickListener;
73   private View.OnFocusChangeListener onFocusChangeListener;
74   private View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener;
75   private final HashSet<View.OnAttachStateChangeListener> onAttachStateChangeListeners =
76       new HashSet<>();
77   private final HashSet<View.OnLayoutChangeListener> onLayoutChangeListeners = new HashSet<>();
78   private boolean wasInvalidated;
79   private View.OnTouchListener onTouchListener;
80   protected AttributeSet attributeSet;
81   public Point scrollToCoordinates = new Point();
82   private boolean didRequestLayout;
83   private MotionEvent lastTouchEvent;
84   private int hapticFeedbackPerformed = -1;
85   private boolean onLayoutWasCalled;
86   private View.OnCreateContextMenuListener onCreateContextMenuListener;
87   private Rect globalVisibleRect;
88   private int layerType;
89   private final ArrayList<Animation> animations = new ArrayList<>();
90   private AnimationRunner animationRunner;
91 
92   /**
93    * Calls {@code performClick()} on a {@code View} after ensuring that it and its ancestors are
94    * visible and that it is enabled.
95    *
96    * @param view the view to click on
97    * @return true if {@code View.OnClickListener}s were found and fired, false otherwise.
98    * @throws RuntimeException if the preconditions are not met.
99    * @deprecated Please use Espresso for view interactions
100    */
101   @Deprecated
clickOn(View view)102   public static boolean clickOn(View view) {
103     ShadowView shadowView = Shadow.extract(view);
104     return shadowView.checkedPerformClick();
105   }
106 
107   /**
108    * Returns a textual representation of the appearance of the object.
109    *
110    * @param view the view to visualize
111    * @return Textual representation of the appearance of the object.
112    */
visualize(View view)113   public static String visualize(View view) {
114     Canvas canvas = new Canvas();
115     view.draw(canvas);
116     if (!useRealGraphics()) {
117       ShadowCanvas shadowCanvas = Shadow.extract(canvas);
118       return shadowCanvas.getDescription();
119     } else {
120       return "";
121     }
122   }
123 
124   /**
125    * Emits an xml-like representation of the view to System.out.
126    *
127    * @param view the view to dump.
128    * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
129    */
130   @SuppressWarnings("UnusedDeclaration")
131   @Deprecated
dump(View view)132   public static void dump(View view) {
133     ShadowView shadowView = Shadow.extract(view);
134     shadowView.dump();
135   }
136 
137   /**
138    * Returns the text contained within this view.
139    *
140    * @param view the view to scan for text
141    * @return Text contained within this view.
142    */
143   @SuppressWarnings("UnusedDeclaration")
innerText(View view)144   public static String innerText(View view) {
145     ShadowView shadowView = Shadow.extract(view);
146     return shadowView.innerText();
147   }
148 
getLocationInSurfaceCompat(View view)149   static int[] getLocationInSurfaceCompat(View view) {
150     int[] locationInSurface = new int[2];
151     if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
152       view.getLocationInSurface(locationInSurface);
153     } else {
154       view.getLocationInWindow(locationInSurface);
155       Rect surfaceInsets =
156           reflector(ViewRootImplReflector.class, view.getViewRootImpl())
157               .getWindowAttributes()
158               .surfaceInsets;
159       locationInSurface[0] += surfaceInsets.left;
160       locationInSurface[1] += surfaceInsets.top;
161     }
162     return locationInSurface;
163   }
164 
165   /* Note: maxSdk is R because capturing `attributeSet` is not needed any more after R. */
166   @Implementation(maxSdk = R)
__constructor__( Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes)167   protected void __constructor__(
168       Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
169     this.attributeSet = attributeSet;
170     reflector(_View_.class, realView)
171         .__constructor__(context, attributeSet, defStyleAttr, 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.LEGACY) {
528       ShadowApplication.getInstance().getForegroundThreadScheduler().post(action);
529       return true;
530     } else {
531       return reflector(_View_.class, realView).post(action);
532     }
533   }
534 
535   @Implementation
postDelayed(Runnable action, long delayMills)536   protected boolean postDelayed(Runnable action, long delayMills) {
537     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
538       ShadowApplication.getInstance()
539           .getForegroundThreadScheduler()
540           .postDelayed(action, delayMills);
541       return true;
542     } else {
543       return reflector(_View_.class, realView).postDelayed(action, delayMills);
544     }
545   }
546 
547   @Implementation
postInvalidateDelayed(long delayMilliseconds)548   protected void postInvalidateDelayed(long delayMilliseconds) {
549     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
550       ShadowApplication.getInstance()
551           .getForegroundThreadScheduler()
552           .postDelayed(
553               new Runnable() {
554                 @Override
555                 public void run() {
556                   realView.invalidate();
557                 }
558               },
559               delayMilliseconds);
560     } else {
561       reflector(_View_.class, realView).postInvalidateDelayed(delayMilliseconds);
562     }
563   }
564 
565   @Implementation
removeCallbacks(Runnable callback)566   protected boolean removeCallbacks(Runnable callback) {
567     if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
568       ShadowLegacyLooper shadowLooper = Shadow.extract(Looper.getMainLooper());
569       shadowLooper.getScheduler().remove(callback);
570       return true;
571     } else {
572       return reflector(_View_.class, realView).removeCallbacks(callback);
573     }
574   }
575 
576   @Implementation
scrollTo(int x, int y)577   protected void scrollTo(int x, int y) {
578     if (useRealScrolling()) {
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 (useRealScrolling()) {
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 (useRealScrolling()) {
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 (useRealScrolling()) {
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 (useRealScrolling()) {
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 (useRealScrolling()) {
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
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     @Direct
__constructor__(Context context, AttributeSet attributeSet, int defStyle)931     void __constructor__(Context context, AttributeSet attributeSet, int defStyle);
932 
933     @Direct
__constructor__( Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes)934     void __constructor__(
935         Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes);
936   }
937 
callOnAttachedToWindow()938   public void callOnAttachedToWindow() {
939     reflector(_View_.class, realView).onAttachedToWindow();
940   }
941 
callOnDetachedFromWindow()942   public void callOnDetachedFromWindow() {
943     reflector(_View_.class, realView).onDetachedFromWindow();
944   }
945 
946   @Implementation
getWindowId()947   protected WindowId getWindowId() {
948     return WindowIdHelper.getWindowId(this);
949   }
950 
951   @Implementation
performHapticFeedback(int hapticFeedbackType)952   protected boolean performHapticFeedback(int hapticFeedbackType) {
953     hapticFeedbackPerformed = hapticFeedbackType;
954     return true;
955   }
956 
957   @Implementation
getGlobalVisibleRect(Rect rect, Point globalOffset)958   protected boolean getGlobalVisibleRect(Rect rect, Point globalOffset) {
959     if (globalVisibleRect == null) {
960       return reflector(_View_.class, realView).getGlobalVisibleRect(rect, globalOffset);
961     }
962 
963     if (!globalVisibleRect.isEmpty()) {
964       rect.set(globalVisibleRect);
965       if (globalOffset != null) {
966         rect.offset(-globalOffset.x, -globalOffset.y);
967       }
968       return true;
969     }
970     rect.setEmpty();
971     return false;
972   }
973 
setGlobalVisibleRect(Rect rect)974   public void setGlobalVisibleRect(Rect rect) {
975     if (rect != null) {
976       globalVisibleRect = new Rect();
977       globalVisibleRect.set(rect);
978     } else {
979       globalVisibleRect = null;
980     }
981   }
982 
lastHapticFeedbackPerformed()983   public int lastHapticFeedbackPerformed() {
984     return hapticFeedbackPerformed;
985   }
986 
setMyParent(ViewParent viewParent)987   public void setMyParent(ViewParent viewParent) {
988     reflector(_View_.class, realView).assignParent(viewParent);
989   }
990 
991   @Implementation
getWindowVisibleDisplayFrame(Rect outRect)992   protected void getWindowVisibleDisplayFrame(Rect outRect) {
993     // TODO: figure out how to simulate this logic instead
994     // if (mAttachInfo != null) {
995     //   mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
996 
997     ShadowDisplay.getDefaultDisplay().getRectSize(outRect);
998   }
999 
1000   @Implementation(minSdk = N)
getWindowDisplayFrame(Rect outRect)1001   protected void getWindowDisplayFrame(Rect outRect) {
1002     // TODO: figure out how to simulate this logic instead
1003     // if (mAttachInfo != null) {
1004     //   mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
1005 
1006     ShadowDisplay.getDefaultDisplay().getRectSize(outRect);
1007   }
1008 
1009   /**
1010    * Returns the layout resource id this view was inflated from. Backwards compatible version of
1011    * {@link View#getSourceLayoutResId()}, passes through to the underlying implementation on API
1012    * levels where it is supported.
1013    */
1014   @Implementation(minSdk = Q)
getSourceLayoutResId()1015   public int getSourceLayoutResId() {
1016     if (RuntimeEnvironment.getApiLevel() >= Q) {
1017       return reflector(_View_.class, realView).getSourceLayoutResId();
1018     } else {
1019       return ShadowResources.getAttributeSetSourceResId(attributeSet);
1020     }
1021   }
1022 
1023   public static class WindowIdHelper {
getWindowId(ShadowView shadowView)1024     public static WindowId getWindowId(ShadowView shadowView) {
1025       if (shadowView.isAttachedToWindow()) {
1026         Object attachInfo = shadowView.getAttachInfo();
1027         if (getField(attachInfo, "mWindowId") == null) {
1028           IWindowId iWindowId = new MyIWindowIdStub();
1029           reflector(_AttachInfo_.class, attachInfo).setWindowId(new WindowId(iWindowId));
1030           reflector(_AttachInfo_.class, attachInfo).setIWindowId(iWindowId);
1031         }
1032       }
1033 
1034       return reflector(_View_.class, shadowView.realView).getWindowId();
1035     }
1036 
1037     private static class MyIWindowIdStub extends IWindowId.Stub {
1038       @Override
registerFocusObserver(IWindowFocusObserver iWindowFocusObserver)1039       public void registerFocusObserver(IWindowFocusObserver iWindowFocusObserver)
1040           throws RemoteException {}
1041 
1042       @Override
unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver)1043       public void unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver)
1044           throws RemoteException {}
1045 
1046       @Override
isFocused()1047       public boolean isFocused() throws RemoteException {
1048         return true;
1049       }
1050     }
1051   }
1052 
1053   /** Reflector interface for android.view.View.AttachInfo's internals. */
1054   @ForType(className = "android.view.View$AttachInfo")
1055   interface _AttachInfo_ {
1056 
1057     @Accessor("mIWindowId")
setIWindowId(IWindowId iWindowId)1058     void setIWindowId(IWindowId iWindowId);
1059 
1060     @Accessor("mWindowId")
setWindowId(WindowId windowId)1061     void setWindowId(WindowId windowId);
1062   }
1063 
1064   /**
1065    * Internal API to determine if native graphics is enabled.
1066    *
1067    * <p>This is currently public because it has to be accessed from multiple packages, but it is not
1068    * recommended to depend on this API.
1069    */
1070   @Beta
useRealGraphics()1071   public static boolean useRealGraphics() {
1072     GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class);
1073     return graphicsMode == Mode.NATIVE && RuntimeEnvironment.getApiLevel() >= O;
1074   }
1075 
1076   /**
1077    * Currently the default View scrolling implementation is broken and low-fidelity. For instance,
1078    * even if a View has no children, Robolectric will still happily set the scroll position of a
1079    * View. Long-term we want to eliminate this broken behavior, but in the mean time the real
1080    * scrolling behavior is enabled when native graphics are enabled, or when a system property is
1081    * set.
1082    */
useRealScrolling()1083   static boolean useRealScrolling() {
1084     return useRealGraphics()
1085         || Boolean.parseBoolean(System.getProperty("robolectric.useRealScrolling", "true"));
1086   }
1087 }
1088