• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.xtremelabs.robolectric.shadows;
2 
3 import android.content.Context;
4 import android.graphics.Typeface;
5 import android.graphics.drawable.Drawable;
6 import android.text.Layout;
7 import android.text.SpannableStringBuilder;
8 import android.text.TextPaint;
9 import android.text.TextWatcher;
10 import android.text.method.MovementMethod;
11 import android.text.method.TransformationMethod;
12 import android.text.style.URLSpan;
13 import android.text.util.Linkify;
14 import android.view.KeyEvent;
15 import android.view.MotionEvent;
16 import android.view.inputmethod.EditorInfo;
17 import android.widget.TextView;
18 import com.xtremelabs.robolectric.internal.Implementation;
19 import com.xtremelabs.robolectric.internal.Implements;
20 
21 import java.util.ArrayList;
22 import java.util.List;
23 
24 import static android.view.View.VISIBLE;
25 import static com.xtremelabs.robolectric.Robolectric.shadowOf;
26 import static com.xtremelabs.robolectric.Robolectric.shadowOf_;
27 
28 @SuppressWarnings({"UnusedDeclaration"})
29 @Implements(TextView.class)
30 public class ShadowTextView extends ShadowView {
31     private CharSequence text = "";
32     private CompoundDrawables compoundDrawablesImpl = new CompoundDrawables(0, 0, 0, 0);
33     private Integer textColorHexValue;
34     private Integer hintColorHexValue;
35     private float textSize = 14.0f;
36     private boolean autoLinkPhoneNumbers;
37     private int autoLinkMask;
38     private CharSequence hintText;
39     private CharSequence errorText;
40     private int compoundDrawablePadding;
41     private MovementMethod movementMethod;
42     private boolean linksClickable;
43     private int gravity;
44     private TextView.OnEditorActionListener onEditorActionListener;
45     private int imeOptions = EditorInfo.IME_NULL;
46     private int textAppearanceId;
47     private TransformationMethod transformationMethod;
48     private int inputType;
49     protected int selectionStart = 0;
50     protected int selectionEnd = 0;
51     private Typeface typeface;
52 
53     private List<TextWatcher> watchers = new ArrayList<TextWatcher>();
54     private List<Integer> previousKeyCodes = new ArrayList<Integer>();
55     private List<KeyEvent> previousKeyEvents = new ArrayList<KeyEvent>();
56     private Layout layout;
57 
58     @Override
applyAttributes()59     public void applyAttributes() {
60         super.applyAttributes();
61         applyTextAttribute();
62         applyTextColorAttribute();
63         applyHintAttribute();
64         applyHintColorAttribute();
65         applyCompoundDrawablesWithIntrinsicBoundsAttributes();
66     }
67 
68     @Implementation(i18nSafe = false)
setText(CharSequence text)69     public void setText(CharSequence text) {
70         if (text == null) {
71             text = "";
72         }
73 
74         sendBeforeTextChanged(text);
75 
76         CharSequence oldValue = this.text;
77         this.text = text;
78 
79         sendOnTextChanged(oldValue);
80         sendAfterTextChanged();
81     }
82 
83     @Implementation
append(CharSequence text)84     public final void append(CharSequence text) {
85         boolean isSelectStartAtEnd = selectionStart == this.text.length();
86         boolean isSelectEndAtEnd = selectionEnd == this.text.length();
87         CharSequence oldValue = this.text;
88         StringBuffer sb = new StringBuffer(this.text);
89         sb.append(text);
90 
91         sendBeforeTextChanged(sb.toString());
92         this.text = sb.toString();
93 
94         if (isSelectStartAtEnd) {
95             selectionStart = this.text.length();
96         }
97         if (isSelectEndAtEnd) {
98             selectionEnd = this.text.length();
99         }
100 
101         sendOnTextChanged(oldValue);
102         sendAfterTextChanged();
103     }
104 
105     @Implementation
setText(int textResourceId)106     public void setText(int textResourceId) {
107         sendBeforeTextChanged(text);
108 
109         CharSequence oldValue = this.text;
110         this.text = getResources().getText(textResourceId);
111 
112         sendOnTextChanged(oldValue);
113         sendAfterTextChanged();
114     }
115 
sendAfterTextChanged()116     private void sendAfterTextChanged() {
117         for (TextWatcher watcher : watchers) {
118             watcher.afterTextChanged(new SpannableStringBuilder(getText()));
119         }
120     }
121 
sendOnTextChanged(CharSequence oldValue)122     private void sendOnTextChanged(CharSequence oldValue) {
123         for (TextWatcher watcher : watchers) {
124             watcher.onTextChanged(text, 0, oldValue.length(), text.length());
125         }
126     }
127 
sendBeforeTextChanged(CharSequence newValue)128     private void sendBeforeTextChanged(CharSequence newValue) {
129         for (TextWatcher watcher : watchers) {
130             watcher.beforeTextChanged(this.text, 0, this.text.length(), newValue.length());
131         }
132     }
133 
134     @Implementation
getText()135     public CharSequence getText() {
136         return text;
137     }
138 
139     @Implementation
getTypeface()140     public Typeface getTypeface() {
141         return typeface;
142     }
143 
144     @Implementation
setTypeface(Typeface typeface)145     public void setTypeface(Typeface typeface) {
146         this.typeface = typeface;
147     }
148 
149     @Implementation
length()150     public int length() {
151         return text.length();
152     }
153 
154     @Implementation
setTextColor(int color)155     public void setTextColor(int color) {
156         textColorHexValue = color;
157     }
158 
159     @Implementation
setTextSize(float size)160     public void setTextSize(float size) {
161         textSize = size;
162     }
163 
164     @Implementation
setTextAppearance(Context context, int resid)165     public void setTextAppearance(Context context, int resid) {
166         textAppearanceId = resid;
167     }
168 
169     @Implementation
setInputType(int type)170     public void setInputType(int type) {
171         this.inputType = type;
172     }
173 
174     @Implementation
getInputType()175     public int getInputType() {
176         return this.inputType;
177     }
178 
179     @Implementation
setHint(int resId)180     public final void setHint(int resId) {
181         this.hintText = getResources().getText(resId);
182     }
183 
184     @Implementation(i18nSafe = false)
setHint(CharSequence hintText)185     public final void setHint(CharSequence hintText) {
186         this.hintText = hintText;
187     }
188 
189     @Implementation
getHint()190     public CharSequence getHint() {
191         return hintText;
192     }
193 
194     @Implementation
setHintTextColor(int color)195     public final void setHintTextColor(int color) {
196         hintColorHexValue = color;
197     }
198 
199     @Implementation
getLinksClickable()200     public final boolean getLinksClickable() {
201         return linksClickable;
202     }
203 
204     @Implementation
setLinksClickable(boolean whether)205     public final void setLinksClickable(boolean whether) {
206         linksClickable = whether;
207     }
208 
209     @Implementation
getMovementMethod()210     public final MovementMethod getMovementMethod() {
211         return movementMethod;
212     }
213 
214     @Implementation
setMovementMethod(MovementMethod movement)215     public final void setMovementMethod(MovementMethod movement) {
216         movementMethod = movement;
217     }
218 
219     @Implementation
getUrls()220     public URLSpan[] getUrls() {
221         String[] words = text.toString().split("\\s+");
222         List<URLSpan> urlSpans = new ArrayList<URLSpan>();
223         for (String word : words) {
224             if (word.startsWith("http://")) {
225                 urlSpans.add(new URLSpan(word));
226             }
227         }
228         return urlSpans.toArray(new URLSpan[urlSpans.size()]);
229     }
230 
231     @Implementation
setAutoLinkMask(int mask)232     public final void setAutoLinkMask(int mask) {
233         autoLinkMask = mask;
234 
235         autoLinkPhoneNumbers = (mask & Linkify.PHONE_NUMBERS) != 0;
236     }
237 
238     @Implementation
setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)239     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
240         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
241     }
242 
243     @Implementation
setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)244     public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
245                                                         Drawable right, Drawable bottom) {
246         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
247     }
248 
249     @Implementation
setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)250     public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
251         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
252     }
253 
254     @Implementation
getCompoundDrawables()255     public Drawable[] getCompoundDrawables() {
256         if (compoundDrawablesImpl == null) {
257             return new Drawable[]{null, null, null, null};
258         }
259         return new Drawable[]{
260                 compoundDrawablesImpl.leftDrawable,
261                 compoundDrawablesImpl.topDrawable,
262                 compoundDrawablesImpl.rightDrawable,
263                 compoundDrawablesImpl.bottomDrawable
264         };
265     }
266 
267     @Implementation
setCompoundDrawablePadding(int compoundDrawablePadding)268     public void setCompoundDrawablePadding(int compoundDrawablePadding) {
269         this.compoundDrawablePadding = compoundDrawablePadding;
270     }
271 
272     @Implementation
getCompoundDrawablePadding()273     public int getCompoundDrawablePadding() {
274         return compoundDrawablePadding;
275     }
276 
277     @Implementation
onKeyDown(int keyCode, KeyEvent event)278     public boolean onKeyDown(int keyCode, KeyEvent event) {
279         previousKeyCodes.add(keyCode);
280         previousKeyEvents.add(event);
281         if (onKeyListener != null) {
282             return onKeyListener.onKey(realView, keyCode, event);
283         } else {
284             return false;
285         }
286     }
287 
288     @Implementation
onKeyUp(int keyCode, KeyEvent event)289     public boolean onKeyUp(int keyCode, KeyEvent event) {
290         previousKeyCodes.add(keyCode);
291         previousKeyEvents.add(event);
292         if (onKeyListener != null) {
293             return onKeyListener.onKey(realView, keyCode, event);
294         } else {
295             return false;
296         }
297     }
298 
getPreviousKeyCode(int index)299     public int getPreviousKeyCode(int index) {
300         return previousKeyCodes.get(index);
301     }
302 
getPreviousKeyEvent(int index)303     public KeyEvent getPreviousKeyEvent(int index) {
304         return previousKeyEvents.get(index);
305     }
306 
307     @Implementation
getGravity()308     public int getGravity() {
309         return gravity;
310     }
311 
312     @Implementation
setGravity(int gravity)313     public void setGravity(int gravity) {
314         this.gravity = gravity;
315     }
316 
317 
318     @Implementation
getImeOptions()319     public int getImeOptions() {
320         return imeOptions;
321     }
322 
323     @Implementation
setImeOptions(int imeOptions)324     public void setImeOptions(int imeOptions) {
325         this.imeOptions = imeOptions;
326     }
327 
328     /**
329      * Returns the text string of this {@code TextView}.
330      * <p/>
331      * Robolectric extension.
332      */
333     @Override
innerText()334     public String innerText() {
335         return (text == null || getVisibility() != VISIBLE) ? "" : text.toString();
336     }
337 
338     @Implementation
setError(CharSequence error)339     public void setError(CharSequence error) {
340       errorText = error;
341     }
342 
343     @Implementation
getError()344     public CharSequence getError() {
345       return errorText;
346     }
347 
348     @Override
349     @Implementation
equals(Object o)350     public boolean equals(Object o) {
351         return super.equals(shadowOf_(o));
352     }
353 
354     @Override
355     @Implementation
hashCode()356     public int hashCode() {
357         return super.hashCode();
358     }
359 
getCompoundDrawablesImpl()360     public CompoundDrawables getCompoundDrawablesImpl() {
361         return compoundDrawablesImpl;
362     }
363 
setCompoundDrawablesImpl(CompoundDrawables compoundDrawablesImpl)364     void setCompoundDrawablesImpl(CompoundDrawables compoundDrawablesImpl) {
365         this.compoundDrawablesImpl = compoundDrawablesImpl;
366     }
367 
getTextColorHexValue()368     public Integer getTextColorHexValue() {
369         return textColorHexValue;
370     }
371 
getTextAppearanceId()372     public int getTextAppearanceId() {
373         return textAppearanceId;
374     }
375 
getHintColorHexValue()376     public Integer getHintColorHexValue() {
377         return hintColorHexValue;
378     }
379 
380     @Implementation
getTextSize()381     public float getTextSize() {
382         return textSize;
383     }
384 
isAutoLinkPhoneNumbers()385     public boolean isAutoLinkPhoneNumbers() {
386         return autoLinkPhoneNumbers;
387     }
388 
applyTextAttribute()389     private void applyTextAttribute() {
390         String text = attributeSet.getAttributeValue("android", "text");
391         if (text != null) {
392             if (text.startsWith("@string/")) {
393                 int textResId = attributeSet.getAttributeResourceValue("android", "text", 0);
394                 text = context.getResources().getString(textResId);
395             }
396             setText(text);
397         }
398     }
399 
applyTextColorAttribute()400     private void applyTextColorAttribute() {
401         String colorValue = attributeSet.getAttributeValue("android", "textColor");
402         if (colorValue != null) {
403             if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) {
404                 int colorResId = attributeSet.getAttributeResourceValue("android", "textColor", 0);
405                 setTextColor(context.getResources().getColor(colorResId));
406             } else if (colorValue.startsWith("#")) {
407                 int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue();
408                 setTextColor(colorFromHex);
409             }
410         }
411     }
412 
applyHintAttribute()413     private void applyHintAttribute() {
414         String hint = attributeSet.getAttributeValue("android", "hint");
415         if (hint != null) {
416             if (hint.startsWith("@string/")) {
417                 int textResId = attributeSet.getAttributeResourceValue("android", "hint", 0);
418                 hint = context.getResources().getString(textResId);
419 
420             }
421             setHint(hint);
422         }
423     }
424 
applyHintColorAttribute()425     private void applyHintColorAttribute() {
426         String colorValue = attributeSet.getAttributeValue("android", "hintColor");
427         if (colorValue != null) {
428             if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) {
429                 int colorResId = attributeSet.getAttributeResourceValue("android", "hintColor", 0);
430                 setHintTextColor(context.getResources().getColor(colorResId));
431             } else if (colorValue.startsWith("#")) {
432                 int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue();
433                 setHintTextColor(colorFromHex);
434             }
435         }
436     }
437 
applyCompoundDrawablesWithIntrinsicBoundsAttributes()438     private void applyCompoundDrawablesWithIntrinsicBoundsAttributes() {
439         setCompoundDrawablesWithIntrinsicBounds(
440                 attributeSet.getAttributeResourceValue("android", "drawableLeft", 0),
441                 attributeSet.getAttributeResourceValue("android", "drawableTop", 0),
442                 attributeSet.getAttributeResourceValue("android", "drawableRight", 0),
443                 attributeSet.getAttributeResourceValue("android", "drawableBottom", 0));
444     }
445 
446     @Implementation
setOnEditorActionListener(android.widget.TextView.OnEditorActionListener onEditorActionListener)447     public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener onEditorActionListener) {
448         this.onEditorActionListener = onEditorActionListener;
449     }
450 
triggerEditorAction(int imeAction)451     public boolean triggerEditorAction(int imeAction) {
452         if (onEditorActionListener != null) {
453             return onEditorActionListener.onEditorAction((TextView) realView, imeAction, null);
454         }
455         return false;
456     }
457 
458     @Implementation
setTransformationMethod(TransformationMethod transformationMethod)459     public void setTransformationMethod(TransformationMethod transformationMethod) {
460         this.transformationMethod = transformationMethod;
461     }
462 
463     @Implementation
getTransformationMethod()464     public TransformationMethod getTransformationMethod() {
465         return transformationMethod;
466     }
467 
468     @Implementation
addTextChangedListener(TextWatcher watcher)469     public void addTextChangedListener(TextWatcher watcher) {
470         this.watchers.add(watcher);
471     }
472 
473     @Implementation
removeTextChangedListener(TextWatcher watcher)474     public void removeTextChangedListener(TextWatcher watcher) {
475         this.watchers.remove(watcher);
476     }
477 
478     @Implementation
getPaint()479     public TextPaint getPaint() {
480         return new TextPaint();
481     }
482 
483     @Implementation
getLayout()484     public Layout getLayout() {
485         return this.layout;
486     }
487 
setSelection(int index)488     public void setSelection(int index) {
489         setSelection(index, index);
490     }
491 
setSelection(int start, int end)492     public void setSelection(int start, int end) {
493         selectionStart = start;
494         selectionEnd = end;
495     }
496 
497     @Implementation
getSelectionStart()498     public int getSelectionStart() {
499         return selectionStart;
500     }
501 
502     @Implementation
getSelectionEnd()503     public int getSelectionEnd() {
504         return selectionEnd;
505     }
506 
507     @Implementation
onTouchEvent(MotionEvent event)508     public boolean onTouchEvent(MotionEvent event) {
509         boolean superResult = super.onTouchEvent(event);
510 
511         if (movementMethod != null) {
512             boolean handled = movementMethod.onTouchEvent(null, null, event);
513 
514             if (handled) {
515                 return true;
516             }
517         }
518 
519         return superResult;
520     }
521 
522     /**
523      * @return the list of currently registered watchers/listeners
524      */
getWatchers()525     public List<TextWatcher> getWatchers() {
526         return watchers;
527     }
528 
setLayout(Layout layout)529     public void setLayout(Layout layout) {
530         this.layout = layout;
531     }
532 
533     public static class CompoundDrawables {
534         public Drawable leftDrawable;
535         public Drawable topDrawable;
536         public Drawable rightDrawable;
537         public Drawable bottomDrawable;
538 
CompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)539         public CompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
540             leftDrawable = left;
541             topDrawable = top;
542             rightDrawable = right;
543             bottomDrawable = bottom;
544         }
545 
CompoundDrawables(int left, int top, int right, int bottom)546         public CompoundDrawables(int left, int top, int right, int bottom) {
547             leftDrawable = left != 0 ? ShadowDrawable.createFromResourceId(left) : null;
548             topDrawable = top != 0 ? ShadowDrawable.createFromResourceId(top) : null;
549             rightDrawable = right != 0 ? ShadowDrawable.createFromResourceId(right) : null;
550             bottomDrawable = bottom != 0 ? ShadowDrawable.createFromResourceId(bottom) : null;
551         }
552 
553         @Override
equals(Object o)554         public boolean equals(Object o) {
555             if (this == o) return true;
556             if (o == null || getClass() != o.getClass()) return false;
557 
558             CompoundDrawables that = (CompoundDrawables) o;
559 
560             if (getBottom() != that.getBottom()) return false;
561             if (getLeft() != that.getLeft()) return false;
562             if (getRight() != that.getRight()) return false;
563             if (getTop() != that.getTop()) return false;
564 
565             return true;
566         }
567 
568         @Override
hashCode()569         public int hashCode() {
570             int result = getLeft();
571             result = 31 * result + getTop();
572             result = 31 * result + getRight();
573             result = 31 * result + getBottom();
574             return result;
575         }
576 
577         @Override
toString()578         public String toString() {
579             return "CompoundDrawables{" +
580                     "left=" + getLeft() +
581                     ", top=" + getTop() +
582                     ", right=" + getRight() +
583                     ", bottom=" + getBottom() +
584                     '}';
585         }
586 
getLeft()587         public int getLeft() {
588             return shadowOf(leftDrawable).getLoadedFromResourceId();
589         }
590 
getTop()591         public int getTop() {
592             return shadowOf(topDrawable).getLoadedFromResourceId();
593         }
594 
getRight()595         public int getRight() {
596             return shadowOf(rightDrawable).getLoadedFromResourceId();
597         }
598 
getBottom()599         public int getBottom() {
600             return shadowOf(bottomDrawable).getLoadedFromResourceId();
601         }
602     }
603 }
604