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