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