Home | History | Annotate | Download | only in shadows
      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 
     89     public void __constructor__(Context context) {
     90         __constructor__(context, null);
     91     }
     92 
     93     public void __constructor__(Context context, AttributeSet attributeSet) {
     94         __constructor__(context, attributeSet, 0);
     95     }
     96 
     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 
    106     public void applyAttributes() {
    107         applyIdAttribute();
    108         applyVisibilityAttribute();
    109         applyFilterTouchesWhenObscuredAttribute();
    110         applyEnabledAttribute();
    111         applyBackgroundAttribute();
    112         applyTagAttribute();
    113         applyOnClickAttribute();
    114         applyContentDescriptionAttribute();
    115     }
    116 
    117     @Implementation
    118     public void setId(int id) {
    119         this.id = id;
    120     }
    121 
    122     @Implementation
    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
    133     public void setFocusable(boolean focusable) {
    134         this.focusable = focusable;
    135         if (!focusable) {
    136             setFocusableInTouchMode(false);
    137         }
    138     }
    139 
    140     @Implementation
    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
    151     public void setFocusableInTouchMode(boolean focusableInTouchMode) {
    152         this.focusableInTouchMode = focusableInTouchMode;
    153         if (focusableInTouchMode) {
    154             setFocusable(true);
    155         }
    156     }
    157 
    158     @Implementation(i18nSafe = false)
    159     public void setContentDescription(CharSequence contentDescription) {
    160         this.contentDescription = contentDescription;
    161     }
    162 
    163     @Implementation
    164     public boolean isFocusable() {
    165         return focusable;
    166     }
    167 
    168     @Implementation
    169     public int getId() {
    170         return id;
    171     }
    172 
    173     @Implementation
    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
    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
    198     public View findViewById(int id) {
    199         if (id == this.id) {
    200             return realView;
    201         }
    202 
    203         return null;
    204     }
    205 
    206     @Implementation
    207     public View findViewWithTag(Object obj) {
    208         if (obj.equals(realView.getTag())) {
    209             return realView;
    210         }
    211 
    212         return null;
    213     }
    214 
    215     @Implementation
    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
    225     public ViewGroup.LayoutParams getLayoutParams() {
    226         return layoutParams;
    227     }
    228 
    229     @Implementation
    230     public void setLayoutParams(ViewGroup.LayoutParams params) {
    231         layoutParams = params;
    232     }
    233 
    234     @Implementation
    235     public final ViewParent getParent() {
    236         return parent == null ? null : (ViewParent) parent.realView;
    237     }
    238 
    239     @Implementation
    240     public final Context getContext() {
    241         return context;
    242     }
    243 
    244     @Implementation
    245     public Resources getResources() {
    246         return context.getResources();
    247     }
    248 
    249     @Implementation
    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      */
    260     public int getBackgroundResourceId() {
    261         return backgroundResourceId;
    262     }
    263 
    264     @Implementation
    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      */
    275     public int getBackgroundColor() {
    276         return backgroundColor;
    277     }
    278 
    279     @Implementation
    280     public void setBackgroundDrawable(Drawable d) {
    281         this.background = d;
    282     }
    283 
    284     @Implementation
    285     public Drawable getBackground() {
    286         return background;
    287     }
    288 
    289     @Implementation
    290     public int getVisibility() {
    291         return visibility;
    292     }
    293 
    294     @Implementation
    295     public void setVisibility(int visibility) {
    296         this.visibility = visibility;
    297     }
    298 
    299     @Implementation
    300     public boolean getFilterTouchesWhenObscured() {
    301         return filterTouchesWhenObscured;
    302     }
    303 
    304     @Implementation
    305     public void setFilterTouchesWhenObscured(boolean enabled) {
    306         this.filterTouchesWhenObscured = enabled;
    307     }
    308 
    309     @Implementation
    310     public void setSelected(boolean selected) {
    311         this.selected = selected;
    312     }
    313 
    314     @Implementation
    315     public boolean isSelected() {
    316         return this.selected;
    317     }
    318 
    319     @Implementation
    320     public boolean isEnabled() {
    321         return this.enabled;
    322     }
    323 
    324     @Implementation
    325     public void setEnabled(boolean enabled) {
    326         this.enabled = enabled;
    327     }
    328 
    329     @Implementation
    330     public void setOnClickListener(View.OnClickListener onClickListener) {
    331         this.onClickListener = onClickListener;
    332     }
    333 
    334     @Implementation
    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
    345     public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
    346         this.onLongClickListener = onLongClickListener;
    347     }
    348 
    349     @Implementation
    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
    360     public void setOnKeyListener(View.OnKeyListener onKeyListener) {
    361         this.onKeyListener = onKeyListener;
    362     }
    363 
    364     @Implementation
    365     public Object getTag() {
    366         return this.tag;
    367     }
    368 
    369     @Implementation
    370     public void setTag(Object tag) {
    371         this.tag = tag;
    372     }
    373 
    374     @Implementation
    375     public final int getHeight() {
    376         return bottom - top;
    377     }
    378 
    379     @Implementation
    380     public final int getWidth() {
    381         return right - left;
    382     }
    383 
    384     @Implementation
    385     public final int getMeasuredWidth() {
    386         return measuredWidth;
    387     }
    388 
    389     @Implementation
    390     public final int getMeasuredHeight() {
    391         return measuredHeight;
    392     }
    393 
    394     @Implementation
    395     public final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    396     	this.measuredWidth = measuredWidth;
    397     	this.measuredHeight = measuredHeight;
    398     }
    399 
    400     @Implementation
    401     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    402     	setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    403     			MeasureSpec.getSize(heightMeasureSpec));
    404     }
    405 
    406     @Implementation
    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
    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
    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
    444     public int getPaddingTop() {
    445         return paddingTop;
    446     }
    447 
    448     @Implementation
    449     public int getPaddingLeft() {
    450         return paddingLeft;
    451     }
    452 
    453     @Implementation
    454     public int getPaddingRight() {
    455         return paddingRight;
    456     }
    457 
    458     @Implementation
    459     public int getPaddingBottom() {
    460         return paddingBottom;
    461     }
    462 
    463     @Implementation
    464     public Object getTag(int key) {
    465         return tags.get(key);
    466     }
    467 
    468     @Implementation
    469     public void setTag(int key, Object value) {
    470         tags.put(key, value);
    471     }
    472 
    473     @Implementation
    474     public void requestLayout() {
    475         didRequestLayout = true;
    476     }
    477 
    478     public boolean didRequestLayout() {
    479         return didRequestLayout;
    480     }
    481 
    482     @Implementation
    483     public final boolean requestFocus() {
    484         return requestFocus(View.FOCUS_DOWN);
    485     }
    486 
    487     @Implementation
    488     public final boolean requestFocus(int direction) {
    489         setViewFocus(true);
    490         return true;
    491     }
    492 
    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
    518     public int getNextFocusDownId() {
    519         return nextFocusDownId;
    520     }
    521 
    522     @Implementation
    523     public void setNextFocusDownId(int nextFocusDownId) {
    524         this.nextFocusDownId = nextFocusDownId;
    525     }
    526 
    527     @Implementation
    528     public boolean isFocused() {
    529         return isFocused;
    530     }
    531 
    532     @Implementation
    533     public boolean hasFocus() {
    534         return isFocused;
    535     }
    536 
    537     @Implementation
    538     public void clearFocus() {
    539         setViewFocus(false);
    540     }
    541 
    542     @Implementation
    543     public View findFocus() {
    544         return hasFocus() ? realView : null;
    545     }
    546 
    547     @Implementation
    548     public void setOnFocusChangeListener(View.OnFocusChangeListener listener) {
    549         onFocusChangeListener = listener;
    550     }
    551 
    552     @Implementation
    553     public View.OnFocusChangeListener getOnFocusChangeListener() {
    554         return onFocusChangeListener;
    555     }
    556 
    557     @Implementation
    558     public void invalidate() {
    559         wasInvalidated = true;
    560     }
    561 
    562     @Implementation
    563     public boolean onTouchEvent(MotionEvent event) {
    564         lastTouchEvent = event;
    565         return false;
    566     }
    567 
    568     @Implementation
    569     public void setOnTouchListener(View.OnTouchListener onTouchListener) {
    570         this.onTouchListener = onTouchListener;
    571     }
    572 
    573     @Implementation
    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 
    581     public MotionEvent getLastTouchEvent() {
    582         return lastTouchEvent;
    583     }
    584 
    585     @Implementation
    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      */
    598     public String innerText() {
    599         return "";
    600     }
    601 
    602     /**
    603      * Dumps the status of this {@code View} to {@code System.out}
    604      */
    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      */
    612     public void dump(PrintStream out, int indent) {
    613         dumpFirstPart(out, indent);
    614         out.println("/>");
    615     }
    616 
    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 
    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
    634     public int getLeft() {
    635         return left;
    636     }
    637 
    638     /**
    639      * @return top coordinate of the view
    640      */
    641     @Implementation
    642     public int getTop() {
    643         return top;
    644     }
    645 
    646     /**
    647      * @return right side of the view
    648      */
    649     @Implementation
    650     public int getRight() {
    651         return right;
    652     }
    653 
    654     /**
    655      * @return bottom coordinate of the view
    656      */
    657     @Implementation
    658     public int getBottom() {
    659         return bottom;
    660     }
    661 
    662     /**
    663      * @return whether the view is clickable
    664      */
    665     @Implementation
    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      */
    675     public boolean wasInvalidated() {
    676         return wasInvalidated;
    677     }
    678 
    679     /**
    680      * Clears the wasInvalidated flag
    681      */
    682     public void clearWasInvalidated() {
    683         wasInvalidated = false;
    684     }
    685 
    686     @Implementation
    687     public void setLeft(int left) {
    688         this.left = left;
    689     }
    690 
    691     @Implementation
    692     public void setTop(int top) {
    693         this.top = top;
    694     }
    695 
    696     @Implementation
    697     public void setRight(int right) {
    698         this.right = right;
    699     }
    700 
    701     @Implementation
    702     public void setBottom(int bottom) {
    703         this.bottom = bottom;
    704     }
    705 
    706     /**
    707      * Non-Android accessor.
    708      */
    709     public void setPaddingLeft(int paddingLeft) {
    710         this.paddingLeft = paddingLeft;
    711     }
    712 
    713     /**
    714      * Non-Android accessor.
    715      */
    716     public void setPaddingTop(int paddingTop) {
    717         this.paddingTop = paddingTop;
    718     }
    719 
    720     /**
    721      * Non-Android accessor.
    722      */
    723     public void setPaddingRight(int paddingRight) {
    724         this.paddingRight = paddingRight;
    725     }
    726 
    727     /**
    728      * Non-Android accessor.
    729      */
    730     public void setPaddingBottom(int paddingBottom) {
    731         this.paddingBottom = paddingBottom;
    732     }
    733 
    734     /**
    735      * Non-Android accessor.
    736      */
    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      */
    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      */
    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 
    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 
    783     private void applyIdAttribute() {
    784         Integer id = attributeSet.getAttributeResourceValue("android", "id", 0);
    785         if (getId() == 0) {
    786             setId(id);
    787         }
    788     }
    789 
    790     private void applyTagAttribute() {
    791         Object tag = attributeSet.getAttributeValue("android", "tag");
    792         if (tag != null) {
    793             setTag(tag);
    794         }
    795     }
    796 
    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 
    808     private void applyFilterTouchesWhenObscuredAttribute() {
    809         setFilterTouchesWhenObscured(attributeSet.getAttributeBooleanValue(
    810                 "android", "filterTouchesWhenObscured", false));
    811     }
    812 
    813     private void applyEnabledAttribute() {
    814         setEnabled(attributeSet.getAttributeBooleanValue("android", "enabled", true));
    815     }
    816 
    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 
    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 
    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 
    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      */
    890     public View.OnTouchListener getOnTouchListener() {
    891         return onTouchListener;
    892     }
    893 
    894     /**
    895      * Non-android accessor.  Returns click listener, if set.
    896      *
    897      * @return
    898      */
    899     public View.OnClickListener getOnClickListener() {
    900         return onClickListener;
    901     }
    902 
    903     @Implementation
    904     public void setDrawingCacheEnabled(boolean drawingCacheEnabled) {
    905         this.drawingCacheEnabled = drawingCacheEnabled;
    906     }
    907 
    908     @Implementation
    909     public boolean isDrawingCacheEnabled() {
    910         return drawingCacheEnabled;
    911     }
    912 
    913     @Implementation
    914     public Bitmap getDrawingCache() {
    915         return Robolectric.newInstanceOf(Bitmap.class);
    916     }
    917 
    918     @Implementation
    919     public void post(Runnable action) {
    920         Robolectric.getUiThreadScheduler().post(action);
    921     }
    922 
    923     @Implementation
    924     public void postDelayed(Runnable action, long delayMills) {
    925         Robolectric.getUiThreadScheduler().postDelayed(action, delayMills);
    926     }
    927 
    928     @Implementation
    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
    939     public Animation getAnimation() {
    940         return animation;
    941     }
    942 
    943     @Implementation
    944     public void setAnimation(Animation anim) {
    945         animation = anim;
    946     }
    947 
    948     @Implementation
    949     public void startAnimation(Animation anim) {
    950         setAnimation(anim);
    951         animation.start();
    952     }
    953 
    954     @Implementation
    955     public void clearAnimation() {
    956         if (animation != null) {
    957             animation.cancel();
    958             animation = null;
    959         }
    960     }
    961 
    962     @Implementation
    963     public void scrollTo(int x, int y) {
    964         this.scrollToCoordinates = new Point(x, y);
    965     }
    966 
    967     @Implementation
    968     public int getScrollX() {
    969         return scrollToCoordinates != null ? scrollToCoordinates.x : 0;
    970     }
    971 
    972     @Implementation
    973     public int getScrollY() {
    974         return scrollToCoordinates != null ? scrollToCoordinates.y : 0;
    975     }
    976 
    977     @Implementation
    978     public ViewTreeObserver getViewTreeObserver() {
    979         if (viewTreeObserver == null) {
    980             viewTreeObserver = newInstanceOf(ViewTreeObserver.class);
    981         }
    982         return viewTreeObserver;
    983     }
    984 
    985     @Implementation
    986     public void onAnimationEnd() {
    987     }
    988 
    989     /*
    990      * Non-Android accessor.
    991      */
    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