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