• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.xtremelabs.robolectric.shadows;
2 
3 import static com.xtremelabs.robolectric.Robolectric.shadowOf;
4 import static com.xtremelabs.robolectric.Robolectric.Reflection.newInstanceOf;
5 
6 import android.content.Context;
7 import android.content.res.Resources;
8 import android.graphics.Bitmap;
9 import android.graphics.Point;
10 import android.graphics.drawable.ColorDrawable;
11 import android.graphics.drawable.Drawable;
12 import android.util.AttributeSet;
13 import android.view.KeyEvent;
14 import android.view.MotionEvent;
15 import android.view.View;
16 import android.view.View.MeasureSpec;
17 import android.view.ViewGroup;
18 import android.view.ViewParent;
19 import android.view.ViewTreeObserver;
20 import android.view.animation.Animation;
21 
22 import com.xtremelabs.robolectric.Robolectric;
23 import com.xtremelabs.robolectric.internal.Implementation;
24 import com.xtremelabs.robolectric.internal.Implements;
25 import com.xtremelabs.robolectric.internal.RealObject;
26 
27 import java.io.PrintStream;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.HashMap;
31 import java.util.Map;
32 
33 /**
34  * Shadow implementation of {@code View} that simulates the behavior of this
35  * class.
36  * <p/>
37  * Supports listeners, focusability (but not focus order), resource loading,
38  * visibility, onclick, tags, and tracks the size and shape of the view.
39  */
40 @SuppressWarnings({"UnusedDeclaration"})
41 @Implements(View.class)
42 public class ShadowView {
43     @RealObject
44     protected View realView;
45 
46     private int id;
47     ShadowView parent;
48     protected Context context;
49     private boolean selected;
50     private View.OnClickListener onClickListener;
51     private View.OnLongClickListener onLongClickListener;
52     private Object tag;
53     private boolean enabled = true;
54     private int visibility = View.VISIBLE;
55     private boolean filterTouchesWhenObscured = false;
56     int left;
57     int top;
58     int right;
59     int bottom;
60     private int paddingLeft;
61     private int paddingTop;
62     private int paddingRight;
63     private int paddingBottom;
64     private ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(0, 0);
65     private final Map<Integer, Object> tags = new HashMap<Integer, Object>();
66     private boolean clickable;
67     protected boolean focusable;
68     boolean focusableInTouchMode;
69     private int backgroundResourceId = -1;
70     private int backgroundColor;
71     protected View.OnKeyListener onKeyListener;
72     private boolean isFocused;
73     private View.OnFocusChangeListener onFocusChangeListener;
74     private boolean wasInvalidated;
75     private View.OnTouchListener onTouchListener;
76     protected AttributeSet attributeSet;
77     private boolean drawingCacheEnabled;
78     public Point scrollToCoordinates;
79     private boolean didRequestLayout;
80     private Drawable background;
81     private Animation animation;
82     private ViewTreeObserver viewTreeObserver;
83     private MotionEvent lastTouchEvent;
84     private int nextFocusDownId = View.NO_ID;
85     private CharSequence contentDescription = null;
86     private int measuredWidth = 0;
87     private int measuredHeight = 0;
88 
__constructor__(Context context)89     public void __constructor__(Context context) {
90         __constructor__(context, null);
91     }
92 
__constructor__(Context context, AttributeSet attributeSet)93     public void __constructor__(Context context, AttributeSet attributeSet) {
94         __constructor__(context, attributeSet, 0);
95     }
96 
__constructor__(Context context, AttributeSet attributeSet, int defStyle)97     public void __constructor__(Context context, AttributeSet attributeSet, int defStyle) {
98         this.context = context;
99         this.attributeSet = attributeSet;
100 
101         if (attributeSet != null) {
102             applyAttributes();
103         }
104     }
105 
applyAttributes()106     public void applyAttributes() {
107         applyIdAttribute();
108         applyVisibilityAttribute();
109         applyFilterTouchesWhenObscuredAttribute();
110         applyEnabledAttribute();
111         applyBackgroundAttribute();
112         applyTagAttribute();
113         applyOnClickAttribute();
114         applyContentDescriptionAttribute();
115     }
116 
117     @Implementation
setId(int id)118     public void setId(int id) {
119         this.id = id;
120     }
121 
122     @Implementation
setClickable(boolean clickable)123     public void setClickable(boolean clickable) {
124         this.clickable = clickable;
125     }
126 
127     /**
128      * Also sets focusable in touch mode to false if {@code focusable} is false, which is the Android behavior.
129      *
130      * @param focusable the new status of the {@code View}'s focusability
131      */
132     @Implementation
setFocusable(boolean focusable)133     public void setFocusable(boolean focusable) {
134         this.focusable = focusable;
135         if (!focusable) {
136             setFocusableInTouchMode(false);
137         }
138     }
139 
140     @Implementation
isFocusableInTouchMode()141     public final boolean isFocusableInTouchMode() {
142         return focusableInTouchMode;
143     }
144 
145     /**
146      * Also sets focusable to true if {@code focusableInTouchMode} is true, which is the Android behavior.
147      *
148      * @param focusableInTouchMode the new status of the {@code View}'s touch mode focusability
149      */
150     @Implementation
setFocusableInTouchMode(boolean focusableInTouchMode)151     public void setFocusableInTouchMode(boolean focusableInTouchMode) {
152         this.focusableInTouchMode = focusableInTouchMode;
153         if (focusableInTouchMode) {
154             setFocusable(true);
155         }
156     }
157 
158     @Implementation(i18nSafe = false)
setContentDescription(CharSequence contentDescription)159     public void setContentDescription(CharSequence contentDescription) {
160         this.contentDescription = contentDescription;
161     }
162 
163     @Implementation
isFocusable()164     public boolean isFocusable() {
165         return focusable;
166     }
167 
168     @Implementation
getId()169     public int getId() {
170         return id;
171     }
172 
173     @Implementation
getContentDescription()174     public CharSequence getContentDescription() {
175         return contentDescription;
176     }
177 
178     /**
179      * Simulates the inflating of the requested resource.
180      *
181      * @param context  the context from which to obtain a layout inflater
182      * @param resource the ID of the resource to inflate
183      * @param root     the {@code ViewGroup} to add the inflated {@code View} to
184      * @return the inflated View
185      */
186     @Implementation
inflate(Context context, int resource, ViewGroup root)187     public static View inflate(Context context, int resource, ViewGroup root) {
188         return ShadowLayoutInflater.from(context).inflate(resource, root);
189     }
190 
191     /**
192      * Finds this {@code View} if it's ID is passed in, returns {@code null} otherwise
193      *
194      * @param id the id of the {@code View} to find
195      * @return the {@code View}, if found, {@code null} otherwise
196      */
197     @Implementation
findViewById(int id)198     public View findViewById(int id) {
199         if (id == this.id) {
200             return realView;
201         }
202 
203         return null;
204     }
205 
206     @Implementation
findViewWithTag(Object obj)207     public View findViewWithTag(Object obj) {
208         if (obj.equals(realView.getTag())) {
209             return realView;
210         }
211 
212         return null;
213     }
214 
215     @Implementation
getRootView()216     public View getRootView() {
217         ShadowView root = this;
218         while (root.parent != null) {
219             root = root.parent;
220         }
221         return root.realView;
222     }
223 
224     @Implementation
getLayoutParams()225     public ViewGroup.LayoutParams getLayoutParams() {
226         return layoutParams;
227     }
228 
229     @Implementation
setLayoutParams(ViewGroup.LayoutParams params)230     public void setLayoutParams(ViewGroup.LayoutParams params) {
231         layoutParams = params;
232     }
233 
234     @Implementation
getParent()235     public final ViewParent getParent() {
236         return parent == null ? null : (ViewParent) parent.realView;
237     }
238 
239     @Implementation
getContext()240     public final Context getContext() {
241         return context;
242     }
243 
244     @Implementation
getResources()245     public Resources getResources() {
246         return context.getResources();
247     }
248 
249     @Implementation
setBackgroundResource(int backgroundResourceId)250     public void setBackgroundResource(int backgroundResourceId) {
251         this.backgroundResourceId = backgroundResourceId;
252         setBackgroundDrawable(getResources().getDrawable(backgroundResourceId));
253     }
254 
255     /**
256      * Non-Android accessor.
257      *
258      * @return the resource ID of this views background
259      */
getBackgroundResourceId()260     public int getBackgroundResourceId() {
261         return backgroundResourceId;
262     }
263 
264     @Implementation
setBackgroundColor(int color)265     public void setBackgroundColor(int color) {
266         backgroundColor = color;
267         setBackgroundDrawable(new ColorDrawable(getResources().getColor(color)));
268     }
269 
270     /**
271      * Non-Android accessor.
272      *
273      * @return the resource color ID of this views background
274      */
getBackgroundColor()275     public int getBackgroundColor() {
276         return backgroundColor;
277     }
278 
279     @Implementation
setBackgroundDrawable(Drawable d)280     public void setBackgroundDrawable(Drawable d) {
281         this.background = d;
282     }
283 
284     @Implementation
getBackground()285     public Drawable getBackground() {
286         return background;
287     }
288 
289     @Implementation
getVisibility()290     public int getVisibility() {
291         return visibility;
292     }
293 
294     @Implementation
setVisibility(int visibility)295     public void setVisibility(int visibility) {
296         this.visibility = visibility;
297     }
298 
299     @Implementation
getFilterTouchesWhenObscured()300     public boolean getFilterTouchesWhenObscured() {
301         return filterTouchesWhenObscured;
302     }
303 
304     @Implementation
setFilterTouchesWhenObscured(boolean enabled)305     public void setFilterTouchesWhenObscured(boolean enabled) {
306         this.filterTouchesWhenObscured = enabled;
307     }
308 
309     @Implementation
setSelected(boolean selected)310     public void setSelected(boolean selected) {
311         this.selected = selected;
312     }
313 
314     @Implementation
isSelected()315     public boolean isSelected() {
316         return this.selected;
317     }
318 
319     @Implementation
isEnabled()320     public boolean isEnabled() {
321         return this.enabled;
322     }
323 
324     @Implementation
setEnabled(boolean enabled)325     public void setEnabled(boolean enabled) {
326         this.enabled = enabled;
327     }
328 
329     @Implementation
setOnClickListener(View.OnClickListener onClickListener)330     public void setOnClickListener(View.OnClickListener onClickListener) {
331         this.onClickListener = onClickListener;
332     }
333 
334     @Implementation
performClick()335     public boolean performClick() {
336         if (onClickListener != null) {
337             onClickListener.onClick(realView);
338             return true;
339         } else {
340             return false;
341         }
342     }
343 
344     @Implementation
setOnLongClickListener(View.OnLongClickListener onLongClickListener)345     public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
346         this.onLongClickListener = onLongClickListener;
347     }
348 
349     @Implementation
performLongClick()350     public boolean performLongClick() {
351         if (onLongClickListener != null) {
352             onLongClickListener.onLongClick(realView);
353             return true;
354         } else {
355             return false;
356         }
357     }
358 
359     @Implementation
setOnKeyListener(View.OnKeyListener onKeyListener)360     public void setOnKeyListener(View.OnKeyListener onKeyListener) {
361         this.onKeyListener = onKeyListener;
362     }
363 
364     @Implementation
getTag()365     public Object getTag() {
366         return this.tag;
367     }
368 
369     @Implementation
setTag(Object tag)370     public void setTag(Object tag) {
371         this.tag = tag;
372     }
373 
374     @Implementation
getHeight()375     public final int getHeight() {
376         return bottom - top;
377     }
378 
379     @Implementation
getWidth()380     public final int getWidth() {
381         return right - left;
382     }
383 
384     @Implementation
getMeasuredWidth()385     public final int getMeasuredWidth() {
386         return measuredWidth;
387     }
388 
389     @Implementation
getMeasuredHeight()390     public final int getMeasuredHeight() {
391         return measuredHeight;
392     }
393 
394     @Implementation
setMeasuredDimension(int measuredWidth, int measuredHeight)395     public final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
396     	this.measuredWidth = measuredWidth;
397     	this.measuredHeight = measuredHeight;
398     }
399 
400     @Implementation
onMeasure(int widthMeasureSpec, int heightMeasureSpec)401     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
402     	setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
403     			MeasureSpec.getSize(heightMeasureSpec));
404     }
405 
406     @Implementation
measure(int widthMeasureSpec, int heightMeasureSpec)407     public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
408     	// We really want to invoke the onMeasure method of the real view,
409     	// as the real View likely contains an implementation of onMeasure
410     	// worthy of test, rather the default shadow implementation.
411     	// But Android declares onMeasure as protected.
412     	try {
413     		Method onMeasureMethod = realView.getClass().getDeclaredMethod("onMeasure", Integer.TYPE, Integer.TYPE );
414     		onMeasureMethod.setAccessible(true);
415     		onMeasureMethod.invoke( realView, widthMeasureSpec, heightMeasureSpec );
416     	} catch ( NoSuchMethodException e ) {
417     		// use default shadow implementation
418     		onMeasure(widthMeasureSpec, heightMeasureSpec);
419     	} catch ( IllegalAccessException e ) {
420     		throw new RuntimeException(e);
421     	} catch ( InvocationTargetException e ) {
422     		throw new RuntimeException(e);
423     	}
424     }
425 
426     @Implementation
layout(int l, int t, int r, int b)427     public final void layout(int l, int t, int r, int b) {
428         left = l;
429         top = t;
430         right = r;
431         bottom = b;
432 // todo:       realView.onLayout();
433     }
434 
435     @Implementation
setPadding(int left, int top, int right, int bottom)436     public void setPadding(int left, int top, int right, int bottom) {
437         paddingLeft = left;
438         paddingTop = top;
439         paddingRight = right;
440         paddingBottom = bottom;
441     }
442 
443     @Implementation
getPaddingTop()444     public int getPaddingTop() {
445         return paddingTop;
446     }
447 
448     @Implementation
getPaddingLeft()449     public int getPaddingLeft() {
450         return paddingLeft;
451     }
452 
453     @Implementation
getPaddingRight()454     public int getPaddingRight() {
455         return paddingRight;
456     }
457 
458     @Implementation
getPaddingBottom()459     public int getPaddingBottom() {
460         return paddingBottom;
461     }
462 
463     @Implementation
getTag(int key)464     public Object getTag(int key) {
465         return tags.get(key);
466     }
467 
468     @Implementation
setTag(int key, Object value)469     public void setTag(int key, Object value) {
470         tags.put(key, value);
471     }
472 
473     @Implementation
requestLayout()474     public void requestLayout() {
475         didRequestLayout = true;
476     }
477 
didRequestLayout()478     public boolean didRequestLayout() {
479         return didRequestLayout;
480     }
481 
482     @Implementation
requestFocus()483     public final boolean requestFocus() {
484         return requestFocus(View.FOCUS_DOWN);
485     }
486 
487     @Implementation
requestFocus(int direction)488     public final boolean requestFocus(int direction) {
489         setViewFocus(true);
490         return true;
491     }
492 
setViewFocus(boolean hasFocus)493     public void setViewFocus(boolean hasFocus) {
494         this.isFocused = hasFocus;
495 
496         try {
497             Class rectClass = Class.forName("android.graphics.Rect");
498             Method method = View.class.getDeclaredMethod("onFocusChanged", Boolean.TYPE, Integer.TYPE,
499                 rectClass);
500             method.setAccessible(true);
501             method.invoke(realView, this.isFocused, 0, null);
502         } catch (IllegalAccessException e) {
503             throw new RuntimeException(e);
504         } catch (InvocationTargetException e) {
505             throw new RuntimeException(e);
506         } catch (NoSuchMethodException e) {
507             throw new RuntimeException(e);
508         } catch (ClassNotFoundException e) {
509             throw new RuntimeException(e);
510         }
511 
512         if (onFocusChangeListener != null) {
513             onFocusChangeListener.onFocusChange(realView, hasFocus);
514         }
515     }
516 
517     @Implementation
getNextFocusDownId()518     public int getNextFocusDownId() {
519         return nextFocusDownId;
520     }
521 
522     @Implementation
setNextFocusDownId(int nextFocusDownId)523     public void setNextFocusDownId(int nextFocusDownId) {
524         this.nextFocusDownId = nextFocusDownId;
525     }
526 
527     @Implementation
isFocused()528     public boolean isFocused() {
529         return isFocused;
530     }
531 
532     @Implementation
hasFocus()533     public boolean hasFocus() {
534         return isFocused;
535     }
536 
537     @Implementation
clearFocus()538     public void clearFocus() {
539         setViewFocus(false);
540     }
541 
542     @Implementation
findFocus()543     public View findFocus() {
544         return hasFocus() ? realView : null;
545     }
546 
547     @Implementation
setOnFocusChangeListener(View.OnFocusChangeListener listener)548     public void setOnFocusChangeListener(View.OnFocusChangeListener listener) {
549         onFocusChangeListener = listener;
550     }
551 
552     @Implementation
getOnFocusChangeListener()553     public View.OnFocusChangeListener getOnFocusChangeListener() {
554         return onFocusChangeListener;
555     }
556 
557     @Implementation
invalidate()558     public void invalidate() {
559         wasInvalidated = true;
560     }
561 
562     @Implementation
onTouchEvent(MotionEvent event)563     public boolean onTouchEvent(MotionEvent event) {
564         lastTouchEvent = event;
565         return false;
566     }
567 
568     @Implementation
setOnTouchListener(View.OnTouchListener onTouchListener)569     public void setOnTouchListener(View.OnTouchListener onTouchListener) {
570         this.onTouchListener = onTouchListener;
571     }
572 
573     @Implementation
dispatchTouchEvent(MotionEvent event)574     public boolean dispatchTouchEvent(MotionEvent event) {
575         if (onTouchListener != null && onTouchListener.onTouch(realView, event)) {
576             return true;
577         }
578         return realView.onTouchEvent(event);
579     }
580 
getLastTouchEvent()581     public MotionEvent getLastTouchEvent() {
582         return lastTouchEvent;
583     }
584 
585     @Implementation
dispatchKeyEvent(KeyEvent event)586     public boolean dispatchKeyEvent(KeyEvent event) {
587         if (onKeyListener != null) {
588             return onKeyListener.onKey(realView, event.getKeyCode(), event);
589         }
590         return false;
591     }
592 
593     /**
594      * Returns a string representation of this {@code View}. Unless overridden, it will be an empty string.
595      * <p/>
596      * Robolectric extension.
597      */
innerText()598     public String innerText() {
599         return "";
600     }
601 
602     /**
603      * Dumps the status of this {@code View} to {@code System.out}
604      */
dump()605     public void dump() {
606         dump(System.out, 0);
607     }
608 
609     /**
610      * Dumps the status of this {@code View} to {@code System.out} at the given indentation level
611      */
dump(PrintStream out, int indent)612     public void dump(PrintStream out, int indent) {
613         dumpFirstPart(out, indent);
614         out.println("/>");
615     }
616 
dumpFirstPart(PrintStream out, int indent)617     protected void dumpFirstPart(PrintStream out, int indent) {
618         dumpIndent(out, indent);
619 
620         out.print("<" + realView.getClass().getSimpleName());
621         if (id > 0) {
622             out.print(" id=\"" + shadowOf(context).getResourceLoader().getNameForId(id) + "\"");
623         }
624     }
625 
dumpIndent(PrintStream out, int indent)626     protected void dumpIndent(PrintStream out, int indent) {
627         for (int i = 0; i < indent; i++) out.print(" ");
628     }
629 
630     /**
631      * @return left side of the view
632      */
633     @Implementation
getLeft()634     public int getLeft() {
635         return left;
636     }
637 
638     /**
639      * @return top coordinate of the view
640      */
641     @Implementation
getTop()642     public int getTop() {
643         return top;
644     }
645 
646     /**
647      * @return right side of the view
648      */
649     @Implementation
getRight()650     public int getRight() {
651         return right;
652     }
653 
654     /**
655      * @return bottom coordinate of the view
656      */
657     @Implementation
getBottom()658     public int getBottom() {
659         return bottom;
660     }
661 
662     /**
663      * @return whether the view is clickable
664      */
665     @Implementation
isClickable()666     public boolean isClickable() {
667         return clickable;
668     }
669 
670     /**
671      * Non-Android accessor.
672      *
673      * @return whether or not {@link #invalidate()} has been called
674      */
wasInvalidated()675     public boolean wasInvalidated() {
676         return wasInvalidated;
677     }
678 
679     /**
680      * Clears the wasInvalidated flag
681      */
clearWasInvalidated()682     public void clearWasInvalidated() {
683         wasInvalidated = false;
684     }
685 
686     @Implementation
setLeft(int left)687     public void setLeft(int left) {
688         this.left = left;
689     }
690 
691     @Implementation
setTop(int top)692     public void setTop(int top) {
693         this.top = top;
694     }
695 
696     @Implementation
setRight(int right)697     public void setRight(int right) {
698         this.right = right;
699     }
700 
701     @Implementation
setBottom(int bottom)702     public void setBottom(int bottom) {
703         this.bottom = bottom;
704     }
705 
706     /**
707      * Non-Android accessor.
708      */
setPaddingLeft(int paddingLeft)709     public void setPaddingLeft(int paddingLeft) {
710         this.paddingLeft = paddingLeft;
711     }
712 
713     /**
714      * Non-Android accessor.
715      */
setPaddingTop(int paddingTop)716     public void setPaddingTop(int paddingTop) {
717         this.paddingTop = paddingTop;
718     }
719 
720     /**
721      * Non-Android accessor.
722      */
setPaddingRight(int paddingRight)723     public void setPaddingRight(int paddingRight) {
724         this.paddingRight = paddingRight;
725     }
726 
727     /**
728      * Non-Android accessor.
729      */
setPaddingBottom(int paddingBottom)730     public void setPaddingBottom(int paddingBottom) {
731         this.paddingBottom = paddingBottom;
732     }
733 
734     /**
735      * Non-Android accessor.
736      */
setFocused(boolean focused)737     public void setFocused(boolean focused) {
738         isFocused = focused;
739     }
740 
741     /**
742      * Non-Android accessor.
743      *
744      * @return true if this object and all of its ancestors are {@code View.VISIBLE}, returns false if this or
745      *         any ancestor is not {@code View.VISIBLE}
746      */
derivedIsVisible()747     public boolean derivedIsVisible() {
748         View parent = realView;
749         while (parent != null) {
750             if (parent.getVisibility() != View.VISIBLE) {
751                 return false;
752             }
753             parent = (View) parent.getParent();
754         }
755         return true;
756     }
757 
758     /**
759      * Utility method for clicking on views exposing testing scenarios that are not possible when using the actual app.
760      *
761      * @throws RuntimeException if the view is disabled or if the view or any of its parents are not visible.
762      */
checkedPerformClick()763     public boolean checkedPerformClick() {
764         if (!derivedIsVisible()) {
765             throw new RuntimeException("View is not visible and cannot be clicked");
766         }
767         if (!realView.isEnabled()) {
768             throw new RuntimeException("View is not enabled and cannot be clicked");
769         }
770 
771         return realView.performClick();
772     }
773 
applyFocus()774     public void applyFocus() {
775         if (noParentHasFocus(realView)) {
776             Boolean focusRequested = attributeSet.getAttributeBooleanValue("android", "focus", false);
777             if (focusRequested || realView.isFocusableInTouchMode()) {
778                 realView.requestFocus();
779             }
780         }
781     }
782 
applyIdAttribute()783     private void applyIdAttribute() {
784         Integer id = attributeSet.getAttributeResourceValue("android", "id", 0);
785         if (getId() == 0) {
786             setId(id);
787         }
788     }
789 
applyTagAttribute()790     private void applyTagAttribute() {
791         Object tag = attributeSet.getAttributeValue("android", "tag");
792         if (tag != null) {
793             setTag(tag);
794         }
795     }
796 
applyVisibilityAttribute()797     private void applyVisibilityAttribute() {
798         String visibility = attributeSet.getAttributeValue("android", "visibility");
799         if (visibility != null) {
800             if (visibility.equals("gone")) {
801                 setVisibility(View.GONE);
802             } else if (visibility.equals("invisible")) {
803                 setVisibility(View.INVISIBLE);
804             }
805         }
806     }
807 
applyFilterTouchesWhenObscuredAttribute()808     private void applyFilterTouchesWhenObscuredAttribute() {
809         setFilterTouchesWhenObscured(attributeSet.getAttributeBooleanValue(
810                 "android", "filterTouchesWhenObscured", false));
811     }
812 
applyEnabledAttribute()813     private void applyEnabledAttribute() {
814         setEnabled(attributeSet.getAttributeBooleanValue("android", "enabled", true));
815     }
816 
applyBackgroundAttribute()817     private void applyBackgroundAttribute() {
818         String source = attributeSet.getAttributeValue("android", "background");
819         if (source != null) {
820             if (source.startsWith("@drawable/")) {
821                 setBackgroundResource(attributeSet.getAttributeResourceValue("android", "background", 0));
822             }
823         }
824     }
825 
applyOnClickAttribute()826     private void applyOnClickAttribute() {
827         final String handlerName = attributeSet.getAttributeValue("android",
828                 "onClick");
829         if (handlerName == null) {
830             return;
831         }
832 
833         /* good part of following code has been directly copied from original
834          * android source */
835         setOnClickListener(new View.OnClickListener() {
836             @Override
837             public void onClick(View v) {
838                 Method mHandler;
839                 try {
840                     mHandler = getContext().getClass().getMethod(handlerName,
841                             View.class);
842                 } catch (NoSuchMethodException e) {
843                     int id = getId();
844                     String idText = id == View.NO_ID ? "" : " with id '"
845                             + shadowOf(context).getResourceLoader()
846                             .getNameForId(id) + "'";
847                     throw new IllegalStateException("Could not find a method " +
848                             handlerName + "(View) in the activity "
849                             + getContext().getClass() + " for onClick handler"
850                             + " on view " + realView.getClass() + idText, e);
851                 }
852 
853                 try {
854                     mHandler.invoke(getContext(), realView);
855                 } catch (IllegalAccessException e) {
856                     throw new IllegalStateException("Could not execute non "
857                             + "public method of the activity", e);
858                 } catch (InvocationTargetException e) {
859                     throw new IllegalStateException("Could not execute "
860                             + "method of the activity", e);
861                 }
862             }
863         });
864     }
865 
applyContentDescriptionAttribute()866     private void applyContentDescriptionAttribute() {
867         String contentDescription = attributeSet.getAttributeValue("android", "contentDescription");
868         if (contentDescription != null) {
869             if (contentDescription.startsWith("@string/")) {
870                 int resId = attributeSet.getAttributeResourceValue("android", "contentDescription", 0);
871                 contentDescription = context.getResources().getString(resId);
872             }
873             setContentDescription(contentDescription);
874         }
875     }
876 
noParentHasFocus(View view)877     private boolean noParentHasFocus(View view) {
878         while (view != null) {
879             if (view.hasFocus()) return false;
880             view = (View) view.getParent();
881         }
882         return true;
883     }
884 
885     /**
886      * Non-android accessor.  Returns touch listener, if set.
887      *
888      * @return
889      */
getOnTouchListener()890     public View.OnTouchListener getOnTouchListener() {
891         return onTouchListener;
892     }
893 
894     /**
895      * Non-android accessor.  Returns click listener, if set.
896      *
897      * @return
898      */
getOnClickListener()899     public View.OnClickListener getOnClickListener() {
900         return onClickListener;
901     }
902 
903     @Implementation
setDrawingCacheEnabled(boolean drawingCacheEnabled)904     public void setDrawingCacheEnabled(boolean drawingCacheEnabled) {
905         this.drawingCacheEnabled = drawingCacheEnabled;
906     }
907 
908     @Implementation
isDrawingCacheEnabled()909     public boolean isDrawingCacheEnabled() {
910         return drawingCacheEnabled;
911     }
912 
913     @Implementation
getDrawingCache()914     public Bitmap getDrawingCache() {
915         return Robolectric.newInstanceOf(Bitmap.class);
916     }
917 
918     @Implementation
post(Runnable action)919     public void post(Runnable action) {
920         Robolectric.getUiThreadScheduler().post(action);
921     }
922 
923     @Implementation
postDelayed(Runnable action, long delayMills)924     public void postDelayed(Runnable action, long delayMills) {
925         Robolectric.getUiThreadScheduler().postDelayed(action, delayMills);
926     }
927 
928     @Implementation
postInvalidateDelayed(long delayMilliseconds)929     public void postInvalidateDelayed(long delayMilliseconds) {
930         Robolectric.getUiThreadScheduler().postDelayed(new Runnable() {
931             @Override
932             public void run() {
933                 realView.invalidate();
934             }
935         }, delayMilliseconds);
936     }
937 
938     @Implementation
getAnimation()939     public Animation getAnimation() {
940         return animation;
941     }
942 
943     @Implementation
setAnimation(Animation anim)944     public void setAnimation(Animation anim) {
945         animation = anim;
946     }
947 
948     @Implementation
startAnimation(Animation anim)949     public void startAnimation(Animation anim) {
950         setAnimation(anim);
951         animation.start();
952     }
953 
954     @Implementation
clearAnimation()955     public void clearAnimation() {
956         if (animation != null) {
957             animation.cancel();
958             animation = null;
959         }
960     }
961 
962     @Implementation
scrollTo(int x, int y)963     public void scrollTo(int x, int y) {
964         this.scrollToCoordinates = new Point(x, y);
965     }
966 
967     @Implementation
getScrollX()968     public int getScrollX() {
969         return scrollToCoordinates != null ? scrollToCoordinates.x : 0;
970     }
971 
972     @Implementation
getScrollY()973     public int getScrollY() {
974         return scrollToCoordinates != null ? scrollToCoordinates.y : 0;
975     }
976 
977     @Implementation
getViewTreeObserver()978     public ViewTreeObserver getViewTreeObserver() {
979         if (viewTreeObserver == null) {
980             viewTreeObserver = newInstanceOf(ViewTreeObserver.class);
981         }
982         return viewTreeObserver;
983     }
984 
985     @Implementation
onAnimationEnd()986     public void onAnimationEnd() {
987     }
988 
989     /*
990      * Non-Android accessor.
991      */
finishedAnimation()992     public void finishedAnimation() {
993         try {
994             Method onAnimationEnd = realView.getClass().getDeclaredMethod("onAnimationEnd", new Class[0]);
995             onAnimationEnd.setAccessible(true);
996             onAnimationEnd.invoke(realView);
997         } catch (Exception e) {
998             throw new RuntimeException(e);
999         }
1000     }
1001 }
1002