TextView.java revision f1e484acb594a726fb57ad0ae4cfe902c7f35858
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.content.res.XmlResourceParser; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.Path; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.Parcel; 34import android.os.Parcelable; 35import android.os.SystemClock; 36import android.os.Message; 37import android.text.BoringLayout; 38import android.text.DynamicLayout; 39import android.text.Editable; 40import android.text.GetChars; 41import android.text.GraphicsOperations; 42import android.text.ClipboardManager; 43import android.text.InputFilter; 44import android.text.Layout; 45import android.text.Selection; 46import android.text.SpanWatcher; 47import android.text.Spannable; 48import android.text.Spanned; 49import android.text.SpannedString; 50import android.text.SpannableString; 51import android.text.StaticLayout; 52import android.text.TextPaint; 53import android.text.TextUtils; 54import android.text.TextWatcher; 55import android.text.method.DateKeyListener; 56import android.text.method.DateTimeKeyListener; 57import android.text.method.DialerKeyListener; 58import android.text.method.DigitsKeyListener; 59import android.text.method.KeyListener; 60import android.text.method.LinkMovementMethod; 61import android.text.method.MetaKeyKeyListener; 62import android.text.method.MovementMethod; 63import android.text.method.TimeKeyListener; 64 65import android.text.method.PasswordTransformationMethod; 66import android.text.method.SingleLineTransformationMethod; 67import android.text.method.TextKeyListener; 68import android.text.method.TransformationMethod; 69import android.text.style.ParagraphStyle; 70import android.text.style.URLSpan; 71import android.text.style.UpdateAppearance; 72import android.text.style.UpdateLayout; 73import android.text.util.Linkify; 74import android.util.AttributeSet; 75import android.util.Log; 76import android.util.FloatMath; 77import android.util.TypedValue; 78import android.view.ContextMenu; 79import android.view.Gravity; 80import android.view.KeyEvent; 81import android.view.LayoutInflater; 82import android.view.MenuItem; 83import android.view.MotionEvent; 84import android.view.View; 85import android.view.ViewDebug; 86import android.view.ViewTreeObserver; 87import android.view.ViewGroup.LayoutParams; 88import android.view.animation.AnimationUtils; 89import android.view.inputmethod.CompletionInfo; 90import android.view.inputmethod.ExtractedText; 91import android.view.inputmethod.ExtractedTextRequest; 92import android.view.inputmethod.InputConnection; 93import android.view.inputmethod.InputMethodManager; 94import android.view.inputmethod.EditorInfo; 95import android.widget.RemoteViews.RemoteView; 96 97import java.io.IOException; 98import java.lang.ref.WeakReference; 99import java.util.ArrayList; 100 101import com.android.internal.util.FastMath; 102import com.android.internal.widget.EditableInputConnection; 103 104import org.xmlpull.v1.XmlPullParserException; 105 106/** 107 * Displays text to the user and optionally allows them to edit it. A TextView 108 * is a complete text editor, however the basic class is configured to not 109 * allow editing; see {@link EditText} for a subclass that configures the text 110 * view for editing. 111 * 112 * <p> 113 * <b>XML attributes</b> 114 * <p> 115 * See {@link android.R.styleable#TextView TextView Attributes}, 116 * {@link android.R.styleable#View View Attributes} 117 * 118 * @attr ref android.R.styleable#TextView_text 119 * @attr ref android.R.styleable#TextView_bufferType 120 * @attr ref android.R.styleable#TextView_hint 121 * @attr ref android.R.styleable#TextView_textColor 122 * @attr ref android.R.styleable#TextView_textColorHighlight 123 * @attr ref android.R.styleable#TextView_textColorHint 124 * @attr ref android.R.styleable#TextView_textSize 125 * @attr ref android.R.styleable#TextView_textScaleX 126 * @attr ref android.R.styleable#TextView_typeface 127 * @attr ref android.R.styleable#TextView_textStyle 128 * @attr ref android.R.styleable#TextView_cursorVisible 129 * @attr ref android.R.styleable#TextView_maxLines 130 * @attr ref android.R.styleable#TextView_maxHeight 131 * @attr ref android.R.styleable#TextView_lines 132 * @attr ref android.R.styleable#TextView_height 133 * @attr ref android.R.styleable#TextView_minLines 134 * @attr ref android.R.styleable#TextView_minHeight 135 * @attr ref android.R.styleable#TextView_maxEms 136 * @attr ref android.R.styleable#TextView_maxWidth 137 * @attr ref android.R.styleable#TextView_ems 138 * @attr ref android.R.styleable#TextView_width 139 * @attr ref android.R.styleable#TextView_minEms 140 * @attr ref android.R.styleable#TextView_minWidth 141 * @attr ref android.R.styleable#TextView_gravity 142 * @attr ref android.R.styleable#TextView_scrollHorizontally 143 * @attr ref android.R.styleable#TextView_password 144 * @attr ref android.R.styleable#TextView_singleLine 145 * @attr ref android.R.styleable#TextView_selectAllOnFocus 146 * @attr ref android.R.styleable#TextView_includeFontPadding 147 * @attr ref android.R.styleable#TextView_maxLength 148 * @attr ref android.R.styleable#TextView_shadowColor 149 * @attr ref android.R.styleable#TextView_shadowDx 150 * @attr ref android.R.styleable#TextView_shadowDy 151 * @attr ref android.R.styleable#TextView_shadowRadius 152 * @attr ref android.R.styleable#TextView_autoLink 153 * @attr ref android.R.styleable#TextView_linksClickable 154 * @attr ref android.R.styleable#TextView_numeric 155 * @attr ref android.R.styleable#TextView_digits 156 * @attr ref android.R.styleable#TextView_phoneNumber 157 * @attr ref android.R.styleable#TextView_inputMethod 158 * @attr ref android.R.styleable#TextView_capitalize 159 * @attr ref android.R.styleable#TextView_autoText 160 * @attr ref android.R.styleable#TextView_editable 161 * @attr ref android.R.styleable#TextView_drawableTop 162 * @attr ref android.R.styleable#TextView_drawableBottom 163 * @attr ref android.R.styleable#TextView_drawableRight 164 * @attr ref android.R.styleable#TextView_drawableLeft 165 * @attr ref android.R.styleable#TextView_lineSpacingExtra 166 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 167 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 168 */ 169@RemoteView 170public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 171 private static int PRIORITY = 100; 172 173 private ColorStateList mTextColor; 174 private int mCurTextColor; 175 private ColorStateList mHintTextColor; 176 private ColorStateList mLinkTextColor; 177 private int mCurHintTextColor; 178 private boolean mFreezesText; 179 private boolean mFrozenWithFocus; 180 181 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 182 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 183 184 private float mShadowRadius, mShadowDx, mShadowDy; 185 186 private static final int PREDRAW_NOT_REGISTERED = 0; 187 private static final int PREDRAW_PENDING = 1; 188 private static final int PREDRAW_DONE = 2; 189 private int mPreDrawState = PREDRAW_NOT_REGISTERED; 190 191 private TextUtils.TruncateAt mEllipsize = null; 192 193 // Enum for the "typeface" XML parameter. 194 // TODO: How can we get this from the XML instead of hardcoding it here? 195 private static final int SANS = 1; 196 private static final int SERIF = 2; 197 private static final int MONOSPACE = 3; 198 199 // Bitfield for the "numeric" XML parameter. 200 // TODO: How can we get this from the XML instead of hardcoding it here? 201 private static final int SIGNED = 2; 202 private static final int DECIMAL = 4; 203 204 class Drawables { 205 final Rect mCompoundRect = new Rect(); 206 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight; 207 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight; 208 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight; 209 int mDrawablePadding; 210 }; 211 private Drawables mDrawables; 212 213 private CharSequence mError; 214 private boolean mErrorWasChanged; 215 private PopupWindow mPopup; 216 217 private CharWrapper mCharWrapper = null; 218 219 private boolean mSelectionMoved = false; 220 221 private Marquee mMarquee; 222 private boolean mRestartMarquee; 223 224 private int mMarqueeRepeatLimit = 3; 225 226 class InputContentType { 227 String privateContentType; 228 Bundle extras; 229 } 230 InputContentType mInputContentType; 231 232 class InputMethodState { 233 Rect mCursorRectInWindow = new Rect(); 234 RectF mTmpRectF = new RectF(); 235 float[] mTmpOffset = new float[2]; 236 ExtractedTextRequest mExtracting; 237 final ExtractedText mTmpExtracted = new ExtractedText(); 238 boolean mBatchEditing; 239 } 240 InputMethodState mInputMethodState; 241 242 /* 243 * Kick-start the font cache for the zygote process (to pay the cost of 244 * initializing freetype for our default font only once). 245 */ 246 static { 247 Paint p = new Paint(); 248 p.setAntiAlias(true); 249 // We don't care about the result, just the side-effect of measuring. 250 p.measureText("H"); 251 } 252 253 public TextView(Context context) { 254 this(context, null); 255 } 256 257 public TextView(Context context, 258 AttributeSet attrs) { 259 this(context, attrs, com.android.internal.R.attr.textViewStyle); 260 } 261 262 public TextView(Context context, 263 AttributeSet attrs, 264 int defStyle) { 265 super(context, attrs, defStyle); 266 mText = ""; 267 268 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 269 // If we get the paint from the skin, we should set it to left, since 270 // the layout always wants it to be left. 271 // mTextPaint.setTextAlign(Paint.Align.LEFT); 272 273 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 274 275 mMovement = getDefaultMovementMethod(); 276 mTransformation = null; 277 278 TypedArray a = 279 context.obtainStyledAttributes( 280 attrs, com.android.internal.R.styleable.TextView, defStyle, 0); 281 282 int textColorHighlight = 0; 283 ColorStateList textColor = null; 284 ColorStateList textColorHint = null; 285 ColorStateList textColorLink = null; 286 int textSize = 15; 287 int typefaceIndex = -1; 288 int styleIndex = -1; 289 290 /* 291 * Look the appearance up without checking first if it exists because 292 * almost every TextView has one and it greatly simplifies the logic 293 * to be able to parse the appearance first and then let specific tags 294 * for this View override it. 295 */ 296 TypedArray appearance = null; 297 int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1); 298 if (ap != -1) { 299 appearance = context.obtainStyledAttributes(ap, 300 com.android.internal.R.styleable. 301 TextAppearance); 302 } 303 if (appearance != null) { 304 int n = appearance.getIndexCount(); 305 for (int i = 0; i < n; i++) { 306 int attr = appearance.getIndex(i); 307 308 switch (attr) { 309 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 310 textColorHighlight = appearance.getColor(attr, textColorHighlight); 311 break; 312 313 case com.android.internal.R.styleable.TextAppearance_textColor: 314 textColor = appearance.getColorStateList(attr); 315 break; 316 317 case com.android.internal.R.styleable.TextAppearance_textColorHint: 318 textColorHint = appearance.getColorStateList(attr); 319 break; 320 321 case com.android.internal.R.styleable.TextAppearance_textColorLink: 322 textColorLink = appearance.getColorStateList(attr); 323 break; 324 325 case com.android.internal.R.styleable.TextAppearance_textSize: 326 textSize = appearance.getDimensionPixelSize(attr, textSize); 327 break; 328 329 case com.android.internal.R.styleable.TextAppearance_typeface: 330 typefaceIndex = appearance.getInt(attr, -1); 331 break; 332 333 case com.android.internal.R.styleable.TextAppearance_textStyle: 334 styleIndex = appearance.getInt(attr, -1); 335 break; 336 } 337 } 338 339 appearance.recycle(); 340 } 341 342 boolean editable = getDefaultEditable(); 343 CharSequence inputMethod = null; 344 int numeric = 0; 345 CharSequence digits = null; 346 boolean phone = false; 347 boolean autotext = false; 348 int autocap = -1; 349 int buffertype = 0; 350 boolean selectallonfocus = false; 351 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 352 drawableBottom = null; 353 int drawablePadding = 0; 354 int ellipsize = -1; 355 boolean singleLine = false; 356 int maxlength = -1; 357 CharSequence text = ""; 358 int shadowcolor = 0; 359 float dx = 0, dy = 0, r = 0; 360 boolean password = false; 361 int contentType = EditorInfo.TYPE_NULL; 362 363 int n = a.getIndexCount(); 364 for (int i = 0; i < n; i++) { 365 int attr = a.getIndex(i); 366 367 switch (attr) { 368 case com.android.internal.R.styleable.TextView_editable: 369 editable = a.getBoolean(attr, editable); 370 break; 371 372 case com.android.internal.R.styleable.TextView_inputMethod: 373 inputMethod = a.getText(attr); 374 break; 375 376 case com.android.internal.R.styleable.TextView_numeric: 377 numeric = a.getInt(attr, numeric); 378 break; 379 380 case com.android.internal.R.styleable.TextView_digits: 381 digits = a.getText(attr); 382 break; 383 384 case com.android.internal.R.styleable.TextView_phoneNumber: 385 phone = a.getBoolean(attr, phone); 386 break; 387 388 case com.android.internal.R.styleable.TextView_autoText: 389 autotext = a.getBoolean(attr, autotext); 390 break; 391 392 case com.android.internal.R.styleable.TextView_capitalize: 393 autocap = a.getInt(attr, autocap); 394 break; 395 396 case com.android.internal.R.styleable.TextView_bufferType: 397 buffertype = a.getInt(attr, buffertype); 398 break; 399 400 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 401 selectallonfocus = a.getBoolean(attr, selectallonfocus); 402 break; 403 404 case com.android.internal.R.styleable.TextView_autoLink: 405 mAutoLinkMask = a.getInt(attr, 0); 406 break; 407 408 case com.android.internal.R.styleable.TextView_linksClickable: 409 mLinksClickable = a.getBoolean(attr, true); 410 break; 411 412 case com.android.internal.R.styleable.TextView_drawableLeft: 413 drawableLeft = a.getDrawable(attr); 414 break; 415 416 case com.android.internal.R.styleable.TextView_drawableTop: 417 drawableTop = a.getDrawable(attr); 418 break; 419 420 case com.android.internal.R.styleable.TextView_drawableRight: 421 drawableRight = a.getDrawable(attr); 422 break; 423 424 case com.android.internal.R.styleable.TextView_drawableBottom: 425 drawableBottom = a.getDrawable(attr); 426 break; 427 428 case com.android.internal.R.styleable.TextView_drawablePadding: 429 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 430 break; 431 432 case com.android.internal.R.styleable.TextView_maxLines: 433 setMaxLines(a.getInt(attr, -1)); 434 break; 435 436 case com.android.internal.R.styleable.TextView_maxHeight: 437 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 438 break; 439 440 case com.android.internal.R.styleable.TextView_lines: 441 setLines(a.getInt(attr, -1)); 442 break; 443 444 case com.android.internal.R.styleable.TextView_height: 445 setHeight(a.getDimensionPixelSize(attr, -1)); 446 break; 447 448 case com.android.internal.R.styleable.TextView_minLines: 449 setMinLines(a.getInt(attr, -1)); 450 break; 451 452 case com.android.internal.R.styleable.TextView_minHeight: 453 setMinHeight(a.getDimensionPixelSize(attr, -1)); 454 break; 455 456 case com.android.internal.R.styleable.TextView_maxEms: 457 setMaxEms(a.getInt(attr, -1)); 458 break; 459 460 case com.android.internal.R.styleable.TextView_maxWidth: 461 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 462 break; 463 464 case com.android.internal.R.styleable.TextView_ems: 465 setEms(a.getInt(attr, -1)); 466 break; 467 468 case com.android.internal.R.styleable.TextView_width: 469 setWidth(a.getDimensionPixelSize(attr, -1)); 470 break; 471 472 case com.android.internal.R.styleable.TextView_minEms: 473 setMinEms(a.getInt(attr, -1)); 474 break; 475 476 case com.android.internal.R.styleable.TextView_minWidth: 477 setMinWidth(a.getDimensionPixelSize(attr, -1)); 478 break; 479 480 case com.android.internal.R.styleable.TextView_gravity: 481 setGravity(a.getInt(attr, -1)); 482 break; 483 484 case com.android.internal.R.styleable.TextView_hint: 485 setHint(a.getText(attr)); 486 break; 487 488 case com.android.internal.R.styleable.TextView_text: 489 text = a.getText(attr); 490 break; 491 492 case com.android.internal.R.styleable.TextView_scrollHorizontally: 493 if (a.getBoolean(attr, false)) { 494 setHorizontallyScrolling(true); 495 } 496 break; 497 498 case com.android.internal.R.styleable.TextView_singleLine: 499 singleLine = a.getBoolean(attr, singleLine); 500 break; 501 502 case com.android.internal.R.styleable.TextView_ellipsize: 503 ellipsize = a.getInt(attr, ellipsize); 504 break; 505 506 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 507 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 508 break; 509 510 case com.android.internal.R.styleable.TextView_includeFontPadding: 511 if (!a.getBoolean(attr, true)) { 512 setIncludeFontPadding(false); 513 } 514 break; 515 516 case com.android.internal.R.styleable.TextView_cursorVisible: 517 if (!a.getBoolean(attr, true)) { 518 setCursorVisible(false); 519 } 520 break; 521 522 case com.android.internal.R.styleable.TextView_maxLength: 523 maxlength = a.getInt(attr, -1); 524 break; 525 526 case com.android.internal.R.styleable.TextView_textScaleX: 527 setTextScaleX(a.getFloat(attr, 1.0f)); 528 break; 529 530 case com.android.internal.R.styleable.TextView_freezesText: 531 mFreezesText = a.getBoolean(attr, false); 532 break; 533 534 case com.android.internal.R.styleable.TextView_shadowColor: 535 shadowcolor = a.getInt(attr, 0); 536 break; 537 538 case com.android.internal.R.styleable.TextView_shadowDx: 539 dx = a.getFloat(attr, 0); 540 break; 541 542 case com.android.internal.R.styleable.TextView_shadowDy: 543 dy = a.getFloat(attr, 0); 544 break; 545 546 case com.android.internal.R.styleable.TextView_shadowRadius: 547 r = a.getFloat(attr, 0); 548 break; 549 550 case com.android.internal.R.styleable.TextView_enabled: 551 setEnabled(a.getBoolean(attr, isEnabled())); 552 break; 553 554 case com.android.internal.R.styleable.TextView_textColorHighlight: 555 textColorHighlight = a.getColor(attr, textColorHighlight); 556 break; 557 558 case com.android.internal.R.styleable.TextView_textColor: 559 textColor = a.getColorStateList(attr); 560 break; 561 562 case com.android.internal.R.styleable.TextView_textColorHint: 563 textColorHint = a.getColorStateList(attr); 564 break; 565 566 case com.android.internal.R.styleable.TextView_textColorLink: 567 textColorLink = a.getColorStateList(attr); 568 break; 569 570 case com.android.internal.R.styleable.TextView_textSize: 571 textSize = a.getDimensionPixelSize(attr, textSize); 572 break; 573 574 case com.android.internal.R.styleable.TextView_typeface: 575 typefaceIndex = a.getInt(attr, typefaceIndex); 576 break; 577 578 case com.android.internal.R.styleable.TextView_textStyle: 579 styleIndex = a.getInt(attr, styleIndex); 580 break; 581 582 case com.android.internal.R.styleable.TextView_password: 583 password = a.getBoolean(attr, password); 584 break; 585 586 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 587 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 588 break; 589 590 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 591 mSpacingMult = a.getFloat(attr, mSpacingMult); 592 break; 593 594 case com.android.internal.R.styleable.TextView_inputType: 595 contentType = a.getInt(attr, mInputType); 596 break; 597 598 case com.android.internal.R.styleable.TextView_editorPrivateContentType: 599 setPrivateContentType(a.getString(attr)); 600 break; 601 602 case com.android.internal.R.styleable.TextView_editorExtras: 603 try { 604 setInputExtras(a.getResourceId(attr, 0)); 605 } catch (XmlPullParserException e) { 606 Log.w("TextView", "Failure reading input extras", e); 607 } catch (IOException e) { 608 Log.w("TextView", "Failure reading input extras", e); 609 } 610 break; 611 } 612 } 613 a.recycle(); 614 615 BufferType bufferType = BufferType.EDITABLE; 616 617 if ((contentType&(EditorInfo.TYPE_MASK_CLASS 618 |EditorInfo.TYPE_MASK_VARIATION)) 619 == (EditorInfo.TYPE_CLASS_TEXT 620 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 621 password = true; 622 } 623 624 if (inputMethod != null) { 625 Class c; 626 627 try { 628 c = Class.forName(inputMethod.toString()); 629 } catch (ClassNotFoundException ex) { 630 throw new RuntimeException(ex); 631 } 632 633 try { 634 mInput = (KeyListener) c.newInstance(); 635 } catch (InstantiationException ex) { 636 throw new RuntimeException(ex); 637 } catch (IllegalAccessException ex) { 638 throw new RuntimeException(ex); 639 } 640 try { 641 mInputType = contentType != EditorInfo.TYPE_NULL 642 ? contentType 643 : mInput.getInputType(); 644 } catch (IncompatibleClassChangeError e) { 645 mInputType = EditorInfo.TYPE_CLASS_TEXT; 646 } 647 } else if (digits != null) { 648 mInput = DigitsKeyListener.getInstance(digits.toString()); 649 mInputType = contentType; 650 } else if (contentType != EditorInfo.TYPE_NULL) { 651 setInputType(contentType, true); 652 singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS 653 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) != 654 (EditorInfo.TYPE_CLASS_TEXT 655 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 656 } else if (phone) { 657 mInput = DialerKeyListener.getInstance(); 658 contentType = EditorInfo.TYPE_CLASS_PHONE; 659 } else if (numeric != 0) { 660 mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, 661 (numeric & DECIMAL) != 0); 662 contentType = EditorInfo.TYPE_CLASS_NUMBER; 663 if ((numeric & SIGNED) != 0) { 664 contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; 665 } 666 if ((numeric & DECIMAL) != 0) { 667 contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; 668 } 669 mInputType = contentType; 670 } else if (autotext || autocap != -1) { 671 TextKeyListener.Capitalize cap; 672 673 contentType = EditorInfo.TYPE_CLASS_TEXT; 674 if (!singleLine) { 675 contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 676 } 677 678 switch (autocap) { 679 case 1: 680 cap = TextKeyListener.Capitalize.SENTENCES; 681 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 682 break; 683 684 case 2: 685 cap = TextKeyListener.Capitalize.WORDS; 686 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 687 break; 688 689 case 3: 690 cap = TextKeyListener.Capitalize.CHARACTERS; 691 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 692 break; 693 694 default: 695 cap = TextKeyListener.Capitalize.NONE; 696 break; 697 } 698 699 mInput = TextKeyListener.getInstance(autotext, cap); 700 mInputType = contentType; 701 } else if (editable) { 702 mInput = TextKeyListener.getInstance(); 703 mInputType = EditorInfo.TYPE_CLASS_TEXT; 704 } else { 705 mInput = null; 706 707 switch (buffertype) { 708 case 0: 709 bufferType = BufferType.NORMAL; 710 break; 711 case 1: 712 bufferType = BufferType.SPANNABLE; 713 break; 714 case 2: 715 bufferType = BufferType.EDITABLE; 716 break; 717 } 718 } 719 720 if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS) 721 == EditorInfo.TYPE_CLASS_TEXT) { 722 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 723 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 724 } 725 726 if (selectallonfocus) { 727 mSelectAllOnFocus = true; 728 729 if (bufferType == BufferType.NORMAL) 730 bufferType = BufferType.SPANNABLE; 731 } 732 733 setCompoundDrawablesWithIntrinsicBounds( 734 drawableLeft, drawableTop, drawableRight, drawableBottom); 735 setCompoundDrawablePadding(drawablePadding); 736 737 if (singleLine) { 738 setSingleLine(); 739 740 if (mInput == null && ellipsize < 0) { 741 ellipsize = 3; // END 742 } 743 } 744 745 switch (ellipsize) { 746 case 1: 747 setEllipsize(TextUtils.TruncateAt.START); 748 break; 749 case 2: 750 setEllipsize(TextUtils.TruncateAt.MIDDLE); 751 break; 752 case 3: 753 setEllipsize(TextUtils.TruncateAt.END); 754 break; 755 case 4: 756 setHorizontalFadingEdgeEnabled(true); 757 setEllipsize(TextUtils.TruncateAt.MARQUEE); 758 break; 759 } 760 761 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 762 setHintTextColor(textColorHint); 763 setLinkTextColor(textColorLink); 764 if (textColorHighlight != 0) { 765 setHighlightColor(textColorHighlight); 766 } 767 setRawTextSize(textSize); 768 769 if (password) { 770 setTransformationMethod(PasswordTransformationMethod.getInstance()); 771 typefaceIndex = MONOSPACE; 772 } 773 774 setTypefaceByIndex(typefaceIndex, styleIndex); 775 776 if (shadowcolor != 0) { 777 setShadowLayer(r, dx, dy, shadowcolor); 778 } 779 780 if (maxlength >= 0) { 781 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 782 } else { 783 setFilters(NO_FILTERS); 784 } 785 786 setText(text, bufferType); 787 788 /* 789 * Views are not normally focusable unless specified to be. 790 * However, TextViews that have input or movement methods *are* 791 * focusable by default. 792 */ 793 a = context.obtainStyledAttributes(attrs, 794 com.android.internal.R.styleable.View, 795 defStyle, 0); 796 797 boolean focusable = mMovement != null || mInput != null; 798 boolean clickable = focusable; 799 boolean longClickable = focusable; 800 801 n = a.getIndexCount(); 802 for (int i = 0; i < n; i++) { 803 int attr = a.getIndex(i); 804 805 switch (attr) { 806 case com.android.internal.R.styleable.View_focusable: 807 focusable = a.getBoolean(attr, focusable); 808 break; 809 810 case com.android.internal.R.styleable.View_clickable: 811 clickable = a.getBoolean(attr, clickable); 812 break; 813 814 case com.android.internal.R.styleable.View_longClickable: 815 longClickable = a.getBoolean(attr, longClickable); 816 break; 817 } 818 } 819 a.recycle(); 820 821 setFocusable(focusable); 822 setClickable(clickable); 823 setLongClickable(longClickable); 824 } 825 826 private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { 827 Typeface tf = null; 828 switch (typefaceIndex) { 829 case SANS: 830 tf = Typeface.SANS_SERIF; 831 break; 832 833 case SERIF: 834 tf = Typeface.SERIF; 835 break; 836 837 case MONOSPACE: 838 tf = Typeface.MONOSPACE; 839 break; 840 } 841 842 setTypeface(tf, styleIndex); 843 } 844 845 /** 846 * Sets the typeface and style in which the text should be displayed, 847 * and turns on the fake bold and italic bits in the Paint if the 848 * Typeface that you provided does not have all the bits in the 849 * style that you specified. 850 * 851 * @attr ref android.R.styleable#TextView_typeface 852 * @attr ref android.R.styleable#TextView_textStyle 853 */ 854 public void setTypeface(Typeface tf, int style) { 855 if (style > 0) { 856 if (tf == null) { 857 tf = Typeface.defaultFromStyle(style); 858 } else { 859 tf = Typeface.create(tf, style); 860 } 861 862 setTypeface(tf); 863 // now compute what (if any) algorithmic styling is needed 864 int typefaceStyle = tf != null ? tf.getStyle() : 0; 865 int need = style & ~typefaceStyle; 866 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 867 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 868 } else { 869 mTextPaint.setFakeBoldText(false); 870 mTextPaint.setTextSkewX(0); 871 setTypeface(tf); 872 } 873 } 874 875 /** 876 * Subclasses override this to specify that they have a KeyListener 877 * by default even if not specifically called for in the XML options. 878 */ 879 protected boolean getDefaultEditable() { 880 return false; 881 } 882 883 /** 884 * Subclasses override this to specify a default movement method. 885 */ 886 protected MovementMethod getDefaultMovementMethod() { 887 return null; 888 } 889 890 /** 891 * Return the text the TextView is displaying. If setText() was called with 892 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast 893 * the return value from this method to Spannable or Editable, respectively. 894 * 895 * Note: The content of the return value should not be modified. If you want 896 * a modifiable one, you should make your own copy first. 897 */ 898 @ViewDebug.CapturedViewProperty 899 public CharSequence getText() { 900 return mText; 901 } 902 903 /** 904 * Returns the length, in characters, of the text managed by this TextView 905 */ 906 public int length() { 907 return mText.length(); 908 } 909 910 /** 911 * Return the text the TextView is displaying as an Editable object. If 912 * the text is not editable, null is returned. 913 * 914 * @see #getText 915 */ 916 public Editable getEditableText() { 917 return (mText instanceof Editable) ? (Editable)mText : null; 918 } 919 920 /** 921 * @return the height of one standard line in pixels. Note that markup 922 * within the text can cause individual lines to be taller or shorter 923 * than this height, and the layout may contain additional first- 924 * or last-line padding. 925 */ 926 public int getLineHeight() { 927 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult 928 + mSpacingAdd); 929 } 930 931 /** 932 * @return the Layout that is currently being used to display the text. 933 * This can be null if the text or width has recently changes. 934 */ 935 public final Layout getLayout() { 936 return mLayout; 937 } 938 939 /** 940 * @return the current key listener for this TextView. 941 * This will frequently be null for non-EditText TextViews. 942 */ 943 public final KeyListener getKeyListener() { 944 return mInput; 945 } 946 947 /** 948 * Sets the key listener to be used with this TextView. This can be null 949 * to disallow user input. Note that this method has significant and 950 * subtle interactions with soft keyboards and other input method: 951 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 952 * for important details. Calling this method will replace the current 953 * content type of the text view with the content type returned by the 954 * key listener. 955 * <p> 956 * Be warned that if you want a TextView with a key listener or movement 957 * method not to be focusable, or if you want a TextView without a 958 * key listener or movement method to be focusable, you must call 959 * {@link #setFocusable} again after calling this to get the focusability 960 * back the way you want it. 961 * 962 * @attr ref android.R.styleable#TextView_numeric 963 * @attr ref android.R.styleable#TextView_digits 964 * @attr ref android.R.styleable#TextView_phoneNumber 965 * @attr ref android.R.styleable#TextView_inputMethod 966 * @attr ref android.R.styleable#TextView_capitalize 967 * @attr ref android.R.styleable#TextView_autoText 968 */ 969 public void setKeyListener(KeyListener input) { 970 setKeyListenerOnly(input); 971 fixFocusableAndClickableSettings(); 972 973 if (input != null) { 974 try { 975 mInputType = mInput.getInputType(); 976 } catch (IncompatibleClassChangeError e) { 977 mInputType = EditorInfo.TYPE_CLASS_TEXT; 978 } 979 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 980 == EditorInfo.TYPE_CLASS_TEXT) { 981 if (mSingleLine) { 982 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 983 } else { 984 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 985 } 986 } 987 } else { 988 mInputType = EditorInfo.TYPE_NULL; 989 } 990 991 InputMethodManager imm = InputMethodManager.peekInstance(); 992 if (imm != null) imm.restartInput(this); 993 } 994 995 private void setKeyListenerOnly(KeyListener input) { 996 mInput = input; 997 if (mInput != null && !(mText instanceof Editable)) 998 setText(mText); 999 1000 setFilters((Editable) mText, mFilters); 1001 } 1002 1003 /** 1004 * @return the movement method being used for this TextView. 1005 * This will frequently be null for non-EditText TextViews. 1006 */ 1007 public final MovementMethod getMovementMethod() { 1008 return mMovement; 1009 } 1010 1011 /** 1012 * Sets the movement method (arrow key handler) to be used for 1013 * this TextView. This can be null to disallow using the arrow keys 1014 * to move the cursor or scroll the view. 1015 * <p> 1016 * Be warned that if you want a TextView with a key listener or movement 1017 * method not to be focusable, or if you want a TextView without a 1018 * key listener or movement method to be focusable, you must call 1019 * {@link #setFocusable} again after calling this to get the focusability 1020 * back the way you want it. 1021 */ 1022 public final void setMovementMethod(MovementMethod movement) { 1023 mMovement = movement; 1024 1025 if (mMovement != null && !(mText instanceof Spannable)) 1026 setText(mText); 1027 1028 fixFocusableAndClickableSettings(); 1029 } 1030 1031 private void fixFocusableAndClickableSettings() { 1032 if ((mMovement != null) || mInput != null) { 1033 setFocusable(true); 1034 setClickable(true); 1035 setLongClickable(true); 1036 } else { 1037 setFocusable(false); 1038 setClickable(false); 1039 setLongClickable(false); 1040 } 1041 } 1042 1043 /** 1044 * @return the current transformation method for this TextView. 1045 * This will frequently be null except for single-line and password 1046 * fields. 1047 */ 1048 public final TransformationMethod getTransformationMethod() { 1049 return mTransformation; 1050 } 1051 1052 /** 1053 * Sets the transformation that is applied to the text that this 1054 * TextView is displaying. 1055 * 1056 * @attr ref android.R.styleable#TextView_password 1057 * @attr ref android.R.styleable#TextView_singleLine 1058 */ 1059 public final void setTransformationMethod(TransformationMethod method) { 1060 if (mTransformation != null) { 1061 if (mText instanceof Spannable) { 1062 ((Spannable) mText).removeSpan(mTransformation); 1063 } 1064 } 1065 1066 mTransformation = method; 1067 1068 setText(mText); 1069 } 1070 1071 /** 1072 * Returns the top padding of the view, plus space for the top 1073 * Drawable if any. 1074 */ 1075 public int getCompoundPaddingTop() { 1076 final Drawables dr = mDrawables; 1077 if (dr == null || dr.mDrawableTop == null) { 1078 return mPaddingTop; 1079 } else { 1080 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 1081 } 1082 } 1083 1084 /** 1085 * Returns the bottom padding of the view, plus space for the bottom 1086 * Drawable if any. 1087 */ 1088 public int getCompoundPaddingBottom() { 1089 final Drawables dr = mDrawables; 1090 if (dr == null || dr.mDrawableBottom == null) { 1091 return mPaddingBottom; 1092 } else { 1093 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 1094 } 1095 } 1096 1097 /** 1098 * Returns the left padding of the view, plus space for the left 1099 * Drawable if any. 1100 */ 1101 public int getCompoundPaddingLeft() { 1102 final Drawables dr = mDrawables; 1103 if (dr == null || dr.mDrawableLeft == null) { 1104 return mPaddingLeft; 1105 } else { 1106 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 1107 } 1108 } 1109 1110 /** 1111 * Returns the right padding of the view, plus space for the right 1112 * Drawable if any. 1113 */ 1114 public int getCompoundPaddingRight() { 1115 final Drawables dr = mDrawables; 1116 if (dr == null || dr.mDrawableRight == null) { 1117 return mPaddingRight; 1118 } else { 1119 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 1120 } 1121 } 1122 1123 /** 1124 * Returns the extended top padding of the view, including both the 1125 * top Drawable if any and any extra space to keep more than maxLines 1126 * of text from showing. It is only valid to call this after measuring. 1127 */ 1128 public int getExtendedPaddingTop() { 1129 if (mMaxMode != LINES) { 1130 return getCompoundPaddingTop(); 1131 } 1132 1133 if (mLayout.getLineCount() <= mMaximum) { 1134 return getCompoundPaddingTop(); 1135 } 1136 1137 int top = getCompoundPaddingTop(); 1138 int bottom = getCompoundPaddingBottom(); 1139 int viewht = getHeight() - top - bottom; 1140 int layoutht = mLayout.getLineTop(mMaximum); 1141 1142 if (layoutht >= viewht) { 1143 return top; 1144 } 1145 1146 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1147 if (gravity == Gravity.TOP) { 1148 return top; 1149 } else if (gravity == Gravity.BOTTOM) { 1150 return top + viewht - layoutht; 1151 } else { // (gravity == Gravity.CENTER_VERTICAL) 1152 return top + (viewht - layoutht) / 2; 1153 } 1154 } 1155 1156 /** 1157 * Returns the extended bottom padding of the view, including both the 1158 * bottom Drawable if any and any extra space to keep more than maxLines 1159 * of text from showing. It is only valid to call this after measuring. 1160 */ 1161 public int getExtendedPaddingBottom() { 1162 if (mMaxMode != LINES) { 1163 return getCompoundPaddingBottom(); 1164 } 1165 1166 if (mLayout.getLineCount() <= mMaximum) { 1167 return getCompoundPaddingBottom(); 1168 } 1169 1170 int top = getCompoundPaddingTop(); 1171 int bottom = getCompoundPaddingBottom(); 1172 int viewht = getHeight() - top - bottom; 1173 int layoutht = mLayout.getLineTop(mMaximum); 1174 1175 if (layoutht >= viewht) { 1176 return bottom; 1177 } 1178 1179 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1180 if (gravity == Gravity.TOP) { 1181 return bottom + viewht - layoutht; 1182 } else if (gravity == Gravity.BOTTOM) { 1183 return bottom; 1184 } else { // (gravity == Gravity.CENTER_VERTICAL) 1185 return bottom + (viewht - layoutht) / 2; 1186 } 1187 } 1188 1189 /** 1190 * Returns the total left padding of the view, including the left 1191 * Drawable if any. 1192 */ 1193 public int getTotalPaddingLeft() { 1194 return getCompoundPaddingLeft(); 1195 } 1196 1197 /** 1198 * Returns the total right padding of the view, including the right 1199 * Drawable if any. 1200 */ 1201 public int getTotalPaddingRight() { 1202 return getCompoundPaddingRight(); 1203 } 1204 1205 /** 1206 * Returns the total top padding of the view, including the top 1207 * Drawable if any, the extra space to keep more than maxLines 1208 * from showing, and the vertical offset for gravity, if any. 1209 */ 1210 public int getTotalPaddingTop() { 1211 return getExtendedPaddingTop() + getVerticalOffset(true); 1212 } 1213 1214 /** 1215 * Returns the total bottom padding of the view, including the bottom 1216 * Drawable if any, the extra space to keep more than maxLines 1217 * from showing, and the vertical offset for gravity, if any. 1218 */ 1219 public int getTotalPaddingBottom() { 1220 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 1221 } 1222 1223 /** 1224 * Sets the Drawables (if any) to appear to the left of, above, 1225 * to the right of, and below the text. Use null if you do not 1226 * want a Drawable there. The Drawables must already have had 1227 * {@link Drawable#setBounds} called. 1228 * 1229 * @attr ref android.R.styleable#TextView_drawableLeft 1230 * @attr ref android.R.styleable#TextView_drawableTop 1231 * @attr ref android.R.styleable#TextView_drawableRight 1232 * @attr ref android.R.styleable#TextView_drawableBottom 1233 */ 1234 public void setCompoundDrawables(Drawable left, Drawable top, 1235 Drawable right, Drawable bottom) { 1236 Drawables dr = mDrawables; 1237 1238 final boolean drawables = left != null || top != null 1239 || right != null || bottom != null; 1240 1241 if (!drawables) { 1242 // Clearing drawables... can we free the data structure? 1243 if (dr != null) { 1244 if (dr.mDrawablePadding == 0) { 1245 mDrawables = null; 1246 } else { 1247 // We need to retain the last set padding, so just clear 1248 // out all of the fields in the existing structure. 1249 dr.mDrawableLeft = null; 1250 dr.mDrawableTop = null; 1251 dr.mDrawableRight = null; 1252 dr.mDrawableBottom = null; 1253 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1254 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1255 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1256 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1257 } 1258 } 1259 } else { 1260 if (dr == null) { 1261 mDrawables = dr = new Drawables(); 1262 } 1263 1264 dr.mDrawableLeft = left; 1265 dr.mDrawableTop = top; 1266 dr.mDrawableRight = right; 1267 dr.mDrawableBottom = bottom; 1268 1269 final Rect compoundRect = dr.mCompoundRect; 1270 int[] state = null; 1271 1272 state = getDrawableState(); 1273 1274 if (left != null) { 1275 left.setState(state); 1276 left.copyBounds(compoundRect); 1277 dr.mDrawableSizeLeft = compoundRect.width(); 1278 dr.mDrawableHeightLeft = compoundRect.height(); 1279 } else { 1280 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1281 } 1282 1283 if (right != null) { 1284 right.setState(state); 1285 right.copyBounds(compoundRect); 1286 dr.mDrawableSizeRight = compoundRect.width(); 1287 dr.mDrawableHeightRight = compoundRect.height(); 1288 } else { 1289 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1290 } 1291 1292 if (top != null) { 1293 top.setState(state); 1294 top.copyBounds(compoundRect); 1295 dr.mDrawableSizeTop = compoundRect.height(); 1296 dr.mDrawableWidthTop = compoundRect.width(); 1297 } else { 1298 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1299 } 1300 1301 if (bottom != null) { 1302 bottom.setState(state); 1303 bottom.copyBounds(compoundRect); 1304 dr.mDrawableSizeBottom = compoundRect.height(); 1305 dr.mDrawableWidthBottom = compoundRect.width(); 1306 } else { 1307 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1308 } 1309 } 1310 1311 invalidate(); 1312 requestLayout(); 1313 } 1314 1315 /** 1316 * Sets the Drawables (if any) to appear to the left of, above, 1317 * to the right of, and below the text. Use 0 if you do not 1318 * want a Drawable there. The Drawables' bounds will be set to 1319 * their intrinsic bounds. 1320 * 1321 * @param left Resource identifier of the left Drawable. 1322 * @param top Resource identifier of the top Drawable. 1323 * @param right Resource identifier of the right Drawable. 1324 * @param bottom Resource identifier of the bottom Drawable. 1325 * 1326 * @attr ref android.R.styleable#TextView_drawableLeft 1327 * @attr ref android.R.styleable#TextView_drawableTop 1328 * @attr ref android.R.styleable#TextView_drawableRight 1329 * @attr ref android.R.styleable#TextView_drawableBottom 1330 */ 1331 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 1332 final Resources resources = getContext().getResources(); 1333 setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null, 1334 top != 0 ? resources.getDrawable(top) : null, 1335 right != 0 ? resources.getDrawable(right) : null, 1336 bottom != 0 ? resources.getDrawable(bottom) : null); 1337 } 1338 1339 /** 1340 * Sets the Drawables (if any) to appear to the left of, above, 1341 * to the right of, and below the text. Use null if you do not 1342 * want a Drawable there. The Drawables' bounds will be set to 1343 * their intrinsic bounds. 1344 * 1345 * @attr ref android.R.styleable#TextView_drawableLeft 1346 * @attr ref android.R.styleable#TextView_drawableTop 1347 * @attr ref android.R.styleable#TextView_drawableRight 1348 * @attr ref android.R.styleable#TextView_drawableBottom 1349 */ 1350 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, 1351 Drawable right, Drawable bottom) { 1352 1353 if (left != null) { 1354 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 1355 } 1356 if (right != null) { 1357 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 1358 } 1359 if (top != null) { 1360 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 1361 } 1362 if (bottom != null) { 1363 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 1364 } 1365 setCompoundDrawables(left, top, right, bottom); 1366 } 1367 1368 /** 1369 * Returns drawables for the left, top, right, and bottom borders. 1370 */ 1371 public Drawable[] getCompoundDrawables() { 1372 final Drawables dr = mDrawables; 1373 if (dr != null) { 1374 return new Drawable[] { 1375 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom 1376 }; 1377 } else { 1378 return new Drawable[] { null, null, null, null }; 1379 } 1380 } 1381 1382 /** 1383 * Sets the size of the padding between the compound drawables and 1384 * the text. 1385 * 1386 * @attr ref android.R.styleable#TextView_drawablePadding 1387 */ 1388 public void setCompoundDrawablePadding(int pad) { 1389 Drawables dr = mDrawables; 1390 if (pad == 0) { 1391 if (dr != null) { 1392 dr.mDrawablePadding = pad; 1393 } 1394 } else { 1395 if (dr == null) { 1396 mDrawables = dr = new Drawables(); 1397 } 1398 dr.mDrawablePadding = pad; 1399 } 1400 1401 invalidate(); 1402 requestLayout(); 1403 } 1404 1405 /** 1406 * Returns the padding between the compound drawables and the text. 1407 */ 1408 public int getCompoundDrawablePadding() { 1409 final Drawables dr = mDrawables; 1410 return dr != null ? dr.mDrawablePadding : 0; 1411 } 1412 1413 @Override 1414 public void setPadding(int left, int top, int right, int bottom) { 1415 if (left != getPaddingLeft() || 1416 right != getPaddingRight() || 1417 top != getPaddingTop() || 1418 bottom != getPaddingBottom()) { 1419 nullLayouts(); 1420 } 1421 1422 // the super call will requestLayout() 1423 super.setPadding(left, top, right, bottom); 1424 invalidate(); 1425 } 1426 1427 /** 1428 * Gets the autolink mask of the text. See {@link 1429 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1430 * possible values. 1431 * 1432 * @attr ref android.R.styleable#TextView_autoLink 1433 */ 1434 public final int getAutoLinkMask() { 1435 return mAutoLinkMask; 1436 } 1437 1438 /** 1439 * Sets the text color, size, style, hint color, and highlight color 1440 * from the specified TextAppearance resource. 1441 */ 1442 public void setTextAppearance(Context context, int resid) { 1443 TypedArray appearance = 1444 context.obtainStyledAttributes(resid, 1445 com.android.internal.R.styleable.TextAppearance); 1446 1447 int color; 1448 ColorStateList colors; 1449 int ts; 1450 1451 color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); 1452 if (color != 0) { 1453 setHighlightColor(color); 1454 } 1455 1456 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1457 TextAppearance_textColor); 1458 if (colors != null) { 1459 setTextColor(colors); 1460 } 1461 1462 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 1463 TextAppearance_textSize, 0); 1464 if (ts != 0) { 1465 setRawTextSize(ts); 1466 } 1467 1468 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1469 TextAppearance_textColorHint); 1470 if (colors != null) { 1471 setHintTextColor(colors); 1472 } 1473 1474 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1475 TextAppearance_textColorLink); 1476 if (colors != null) { 1477 setLinkTextColor(colors); 1478 } 1479 1480 int typefaceIndex, styleIndex; 1481 1482 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 1483 TextAppearance_typeface, -1); 1484 styleIndex = appearance.getInt(com.android.internal.R.styleable. 1485 TextAppearance_textStyle, -1); 1486 1487 setTypefaceByIndex(typefaceIndex, styleIndex); 1488 appearance.recycle(); 1489 } 1490 1491 /** 1492 * @return the size (in pixels) of the default text size in this TextView. 1493 */ 1494 public float getTextSize() { 1495 return mTextPaint.getTextSize(); 1496 } 1497 1498 /** 1499 * Set the default text size to the given value, interpreted as "scaled 1500 * pixel" units. This size is adjusted based on the current density and 1501 * user font size preference. 1502 * 1503 * @param size The scaled pixel size. 1504 * 1505 * @attr ref android.R.styleable#TextView_textSize 1506 */ 1507 public void setTextSize(float size) { 1508 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 1509 } 1510 1511 /** 1512 * Set the default text size to a given unit and value. See {@link 1513 * TypedValue} for the possible dimension units. 1514 * 1515 * @param unit The desired dimension unit. 1516 * @param size The desired size in the given units. 1517 * 1518 * @attr ref android.R.styleable#TextView_textSize 1519 */ 1520 public void setTextSize(int unit, float size) { 1521 Context c = getContext(); 1522 Resources r; 1523 1524 if (c == null) 1525 r = Resources.getSystem(); 1526 else 1527 r = c.getResources(); 1528 1529 setRawTextSize(TypedValue.applyDimension( 1530 unit, size, r.getDisplayMetrics())); 1531 } 1532 1533 private void setRawTextSize(float size) { 1534 if (size != mTextPaint.getTextSize()) { 1535 mTextPaint.setTextSize(size); 1536 1537 if (mLayout != null) { 1538 nullLayouts(); 1539 requestLayout(); 1540 invalidate(); 1541 } 1542 } 1543 } 1544 1545 /** 1546 * @return the extent by which text is currently being stretched 1547 * horizontally. This will usually be 1. 1548 */ 1549 public float getTextScaleX() { 1550 return mTextPaint.getTextScaleX(); 1551 } 1552 1553 /** 1554 * Sets the extent by which text should be stretched horizontally. 1555 * 1556 * @attr ref android.R.styleable#TextView_textScaleX 1557 */ 1558 public void setTextScaleX(float size) { 1559 if (size != mTextPaint.getTextScaleX()) { 1560 mTextPaint.setTextScaleX(size); 1561 1562 if (mLayout != null) { 1563 nullLayouts(); 1564 requestLayout(); 1565 invalidate(); 1566 } 1567 } 1568 } 1569 1570 /** 1571 * Sets the typeface and style in which the text should be displayed. 1572 * Note that not all Typeface families actually have bold and italic 1573 * variants, so you may need to use 1574 * {@link #setTypeface(Typeface, int)} to get the appearance 1575 * that you actually want. 1576 * 1577 * @attr ref android.R.styleable#TextView_typeface 1578 * @attr ref android.R.styleable#TextView_textStyle 1579 */ 1580 public void setTypeface(Typeface tf) { 1581 if (mTextPaint.getTypeface() != tf) { 1582 mTextPaint.setTypeface(tf); 1583 1584 if (mLayout != null) { 1585 nullLayouts(); 1586 requestLayout(); 1587 invalidate(); 1588 } 1589 } 1590 } 1591 1592 /** 1593 * @return the current typeface and style in which the text is being 1594 * displayed. 1595 */ 1596 public Typeface getTypeface() { 1597 return mTextPaint.getTypeface(); 1598 } 1599 1600 /** 1601 * Sets the text color for all the states (normal, selected, 1602 * focused) to be this color. 1603 * 1604 * @attr ref android.R.styleable#TextView_textColor 1605 */ 1606 public void setTextColor(int color) { 1607 mTextColor = ColorStateList.valueOf(color); 1608 updateTextColors(); 1609 } 1610 1611 /** 1612 * Sets the text color. 1613 * 1614 * @attr ref android.R.styleable#TextView_textColor 1615 */ 1616 public void setTextColor(ColorStateList colors) { 1617 if (colors == null) { 1618 throw new NullPointerException(); 1619 } 1620 1621 mTextColor = colors; 1622 updateTextColors(); 1623 } 1624 1625 /** 1626 * Return the set of text colors. 1627 * 1628 * @return Returns the set of text colors. 1629 */ 1630 public final ColorStateList getTextColors() { 1631 return mTextColor; 1632 } 1633 1634 /** 1635 * <p>Return the current color selected for normal text.</p> 1636 * 1637 * @return Returns the current text color. 1638 */ 1639 public final int getCurrentTextColor() { 1640 return mCurTextColor; 1641 } 1642 1643 /** 1644 * Sets the color used to display the selection highlight. 1645 * 1646 * @attr ref android.R.styleable#TextView_textColorHighlight 1647 */ 1648 public void setHighlightColor(int color) { 1649 if (mHighlightColor != color) { 1650 mHighlightColor = color; 1651 invalidate(); 1652 } 1653 } 1654 1655 /** 1656 * Gives the text a shadow of the specified radius and color, the specified 1657 * distance from its normal position. 1658 * 1659 * @attr ref android.R.styleable#TextView_shadowColor 1660 * @attr ref android.R.styleable#TextView_shadowDx 1661 * @attr ref android.R.styleable#TextView_shadowDy 1662 * @attr ref android.R.styleable#TextView_shadowRadius 1663 */ 1664 public void setShadowLayer(float radius, float dx, float dy, int color) { 1665 mTextPaint.setShadowLayer(radius, dx, dy, color); 1666 1667 mShadowRadius = radius; 1668 mShadowDx = dx; 1669 mShadowDy = dy; 1670 1671 invalidate(); 1672 } 1673 1674 /** 1675 * @return the base paint used for the text. Please use this only to 1676 * consult the Paint's properties and not to change them. 1677 */ 1678 public TextPaint getPaint() { 1679 return mTextPaint; 1680 } 1681 1682 /** 1683 * Sets the autolink mask of the text. See {@link 1684 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1685 * possible values. 1686 * 1687 * @attr ref android.R.styleable#TextView_autoLink 1688 */ 1689 public final void setAutoLinkMask(int mask) { 1690 mAutoLinkMask = mask; 1691 } 1692 1693 /** 1694 * Sets whether the movement method will automatically be set to 1695 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1696 * set to nonzero and links are detected in {@link #setText}. 1697 * The default is true. 1698 * 1699 * @attr ref android.R.styleable#TextView_linksClickable 1700 */ 1701 public final void setLinksClickable(boolean whether) { 1702 mLinksClickable = whether; 1703 } 1704 1705 /** 1706 * Returns whether the movement method will automatically be set to 1707 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1708 * set to nonzero and links are detected in {@link #setText}. 1709 * The default is true. 1710 * 1711 * @attr ref android.R.styleable#TextView_linksClickable 1712 */ 1713 public final boolean getLinksClickable() { 1714 return mLinksClickable; 1715 } 1716 1717 /** 1718 * Returns the list of URLSpans attached to the text 1719 * (by {@link Linkify} or otherwise) if any. You can call 1720 * {@link URLSpan#getURL} on them to find where they link to 1721 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 1722 * to find the region of the text they are attached to. 1723 */ 1724 public URLSpan[] getUrls() { 1725 if (mText instanceof Spanned) { 1726 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 1727 } else { 1728 return new URLSpan[0]; 1729 } 1730 } 1731 1732 /** 1733 * Sets the color of the hint text. 1734 * 1735 * @attr ref android.R.styleable#TextView_textColorHint 1736 */ 1737 public final void setHintTextColor(int color) { 1738 mHintTextColor = ColorStateList.valueOf(color); 1739 updateTextColors(); 1740 } 1741 1742 /** 1743 * Sets the color of the hint text. 1744 * 1745 * @attr ref android.R.styleable#TextView_textColorHint 1746 */ 1747 public final void setHintTextColor(ColorStateList colors) { 1748 mHintTextColor = colors; 1749 updateTextColors(); 1750 } 1751 1752 /** 1753 * <p>Return the color used to paint the hint text.</p> 1754 * 1755 * @return Returns the list of hint text colors. 1756 */ 1757 public final ColorStateList getHintTextColors() { 1758 return mHintTextColor; 1759 } 1760 1761 /** 1762 * <p>Return the current color selected to paint the hint text.</p> 1763 * 1764 * @return Returns the current hint text color. 1765 */ 1766 public final int getCurrentHintTextColor() { 1767 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 1768 } 1769 1770 /** 1771 * Sets the color of links in the text. 1772 * 1773 * @attr ref android.R.styleable#TextView_textColorLink 1774 */ 1775 public final void setLinkTextColor(int color) { 1776 mLinkTextColor = ColorStateList.valueOf(color); 1777 updateTextColors(); 1778 } 1779 1780 /** 1781 * Sets the color of links in the text. 1782 * 1783 * @attr ref android.R.styleable#TextView_textColorLink 1784 */ 1785 public final void setLinkTextColor(ColorStateList colors) { 1786 mLinkTextColor = colors; 1787 updateTextColors(); 1788 } 1789 1790 /** 1791 * <p>Returns the color used to paint links in the text.</p> 1792 * 1793 * @return Returns the list of link text colors. 1794 */ 1795 public final ColorStateList getLinkTextColors() { 1796 return mLinkTextColor; 1797 } 1798 1799 /** 1800 * Sets the horizontal alignment of the text and the 1801 * vertical gravity that will be used when there is extra space 1802 * in the TextView beyond what is required for the text itself. 1803 * 1804 * @see android.view.Gravity 1805 * @attr ref android.R.styleable#TextView_gravity 1806 */ 1807 public void setGravity(int gravity) { 1808 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { 1809 gravity |= Gravity.LEFT; 1810 } 1811 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 1812 gravity |= Gravity.TOP; 1813 } 1814 1815 boolean newLayout = false; 1816 1817 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 1818 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 1819 newLayout = true; 1820 } 1821 1822 if (gravity != mGravity) { 1823 invalidate(); 1824 } 1825 1826 mGravity = gravity; 1827 1828 if (mLayout != null && newLayout) { 1829 // XXX this is heavy-handed because no actual content changes. 1830 int want = mLayout.getWidth(); 1831 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 1832 1833 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 1834 mRight - mLeft - getCompoundPaddingLeft() - 1835 getCompoundPaddingRight(), true); 1836 } 1837 } 1838 1839 /** 1840 * Returns the horizontal and vertical alignment of this TextView. 1841 * 1842 * @see android.view.Gravity 1843 * @attr ref android.R.styleable#TextView_gravity 1844 */ 1845 public int getGravity() { 1846 return mGravity; 1847 } 1848 1849 /** 1850 * @return the flags on the Paint being used to display the text. 1851 * @see Paint#getFlags 1852 */ 1853 public int getPaintFlags() { 1854 return mTextPaint.getFlags(); 1855 } 1856 1857 /** 1858 * Sets flags on the Paint being used to display the text and 1859 * reflows the text if they are different from the old flags. 1860 * @see Paint#setFlags 1861 */ 1862 public void setPaintFlags(int flags) { 1863 if (mTextPaint.getFlags() != flags) { 1864 mTextPaint.setFlags(flags); 1865 1866 if (mLayout != null) { 1867 nullLayouts(); 1868 requestLayout(); 1869 invalidate(); 1870 } 1871 } 1872 } 1873 1874 /** 1875 * Sets whether the text should be allowed to be wider than the 1876 * View is. If false, it will be wrapped to the width of the View. 1877 * 1878 * @attr ref android.R.styleable#TextView_scrollHorizontally 1879 */ 1880 public void setHorizontallyScrolling(boolean whether) { 1881 mHorizontallyScrolling = whether; 1882 1883 if (mLayout != null) { 1884 nullLayouts(); 1885 requestLayout(); 1886 invalidate(); 1887 } 1888 } 1889 1890 /** 1891 * Makes the TextView at least this many lines tall 1892 * 1893 * @attr ref android.R.styleable#TextView_minLines 1894 */ 1895 public void setMinLines(int minlines) { 1896 mMinimum = minlines; 1897 mMinMode = LINES; 1898 1899 requestLayout(); 1900 invalidate(); 1901 } 1902 1903 /** 1904 * Makes the TextView at least this many pixels tall 1905 * 1906 * @attr ref android.R.styleable#TextView_minHeight 1907 */ 1908 public void setMinHeight(int minHeight) { 1909 mMinimum = minHeight; 1910 mMinMode = PIXELS; 1911 1912 requestLayout(); 1913 invalidate(); 1914 } 1915 1916 /** 1917 * Makes the TextView at most this many lines tall 1918 * 1919 * @attr ref android.R.styleable#TextView_maxLines 1920 */ 1921 public void setMaxLines(int maxlines) { 1922 mMaximum = maxlines; 1923 mMaxMode = LINES; 1924 1925 requestLayout(); 1926 invalidate(); 1927 } 1928 1929 /** 1930 * Makes the TextView at most this many pixels tall 1931 * 1932 * @attr ref android.R.styleable#TextView_maxHeight 1933 */ 1934 public void setMaxHeight(int maxHeight) { 1935 mMaximum = maxHeight; 1936 mMaxMode = PIXELS; 1937 1938 requestLayout(); 1939 invalidate(); 1940 } 1941 1942 /** 1943 * Makes the TextView exactly this many lines tall 1944 * 1945 * @attr ref android.R.styleable#TextView_lines 1946 */ 1947 public void setLines(int lines) { 1948 mMaximum = mMinimum = lines; 1949 mMaxMode = mMinMode = LINES; 1950 1951 requestLayout(); 1952 invalidate(); 1953 } 1954 1955 /** 1956 * Makes the TextView exactly this many pixels tall. 1957 * You could do the same thing by specifying this number in the 1958 * LayoutParams. 1959 * 1960 * @attr ref android.R.styleable#TextView_height 1961 */ 1962 public void setHeight(int pixels) { 1963 mMaximum = mMinimum = pixels; 1964 mMaxMode = mMinMode = PIXELS; 1965 1966 requestLayout(); 1967 invalidate(); 1968 } 1969 1970 /** 1971 * Makes the TextView at least this many ems wide 1972 * 1973 * @attr ref android.R.styleable#TextView_minEms 1974 */ 1975 public void setMinEms(int minems) { 1976 mMinWidth = minems; 1977 mMinWidthMode = EMS; 1978 1979 requestLayout(); 1980 invalidate(); 1981 } 1982 1983 /** 1984 * Makes the TextView at least this many pixels wide 1985 * 1986 * @attr ref android.R.styleable#TextView_minWidth 1987 */ 1988 public void setMinWidth(int minpixels) { 1989 mMinWidth = minpixels; 1990 mMinWidthMode = PIXELS; 1991 1992 requestLayout(); 1993 invalidate(); 1994 } 1995 1996 /** 1997 * Makes the TextView at most this many ems wide 1998 * 1999 * @attr ref android.R.styleable#TextView_maxEms 2000 */ 2001 public void setMaxEms(int maxems) { 2002 mMaxWidth = maxems; 2003 mMaxWidthMode = EMS; 2004 2005 requestLayout(); 2006 invalidate(); 2007 } 2008 2009 /** 2010 * Makes the TextView at most this many pixels wide 2011 * 2012 * @attr ref android.R.styleable#TextView_maxWidth 2013 */ 2014 public void setMaxWidth(int maxpixels) { 2015 mMaxWidth = maxpixels; 2016 mMaxWidthMode = PIXELS; 2017 2018 requestLayout(); 2019 invalidate(); 2020 } 2021 2022 /** 2023 * Makes the TextView exactly this many ems wide 2024 * 2025 * @attr ref android.R.styleable#TextView_ems 2026 */ 2027 public void setEms(int ems) { 2028 mMaxWidth = mMinWidth = ems; 2029 mMaxWidthMode = mMinWidthMode = EMS; 2030 2031 requestLayout(); 2032 invalidate(); 2033 } 2034 2035 /** 2036 * Makes the TextView exactly this many pixels wide. 2037 * You could do the same thing by specifying this number in the 2038 * LayoutParams. 2039 * 2040 * @attr ref android.R.styleable#TextView_width 2041 */ 2042 public void setWidth(int pixels) { 2043 mMaxWidth = mMinWidth = pixels; 2044 mMaxWidthMode = mMinWidthMode = PIXELS; 2045 2046 requestLayout(); 2047 invalidate(); 2048 } 2049 2050 2051 /** 2052 * Sets line spacing for this TextView. Each line will have its height 2053 * multiplied by <code>mult</code> and have <code>add</code> added to it. 2054 * 2055 * @attr ref android.R.styleable#TextView_lineSpacingExtra 2056 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 2057 */ 2058 public void setLineSpacing(float add, float mult) { 2059 mSpacingMult = mult; 2060 mSpacingAdd = add; 2061 2062 if (mLayout != null) { 2063 nullLayouts(); 2064 requestLayout(); 2065 invalidate(); 2066 } 2067 } 2068 2069 /** 2070 * Convenience method: Append the specified text to the TextView's 2071 * display buffer, upgrading it to BufferType.EDITABLE if it was 2072 * not already editable. 2073 */ 2074 public final void append(CharSequence text) { 2075 append(text, 0, text.length()); 2076 } 2077 2078 /** 2079 * Convenience method: Append the specified text slice to the TextView's 2080 * display buffer, upgrading it to BufferType.EDITABLE if it was 2081 * not already editable. 2082 */ 2083 public void append(CharSequence text, int start, int end) { 2084 if (!(mText instanceof Editable)) { 2085 setText(mText, BufferType.EDITABLE); 2086 } 2087 2088 ((Editable) mText).append(text, start, end); 2089 } 2090 2091 private void updateTextColors() { 2092 boolean inval = false; 2093 int color = mTextColor.getColorForState(getDrawableState(), 0); 2094 if (color != mCurTextColor) { 2095 mCurTextColor = color; 2096 inval = true; 2097 } 2098 if (mLinkTextColor != null) { 2099 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 2100 if (color != mTextPaint.linkColor) { 2101 mTextPaint.linkColor = color; 2102 inval = true; 2103 } 2104 } 2105 if (mHintTextColor != null) { 2106 color = mHintTextColor.getColorForState(getDrawableState(), 0); 2107 if (color != mCurHintTextColor && mText.length() == 0) { 2108 mCurHintTextColor = color; 2109 inval = true; 2110 } 2111 } 2112 if (inval) { 2113 invalidate(); 2114 } 2115 } 2116 2117 @Override 2118 protected void drawableStateChanged() { 2119 super.drawableStateChanged(); 2120 if (mTextColor != null && mTextColor.isStateful() 2121 || (mHintTextColor != null && mHintTextColor.isStateful()) 2122 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 2123 updateTextColors(); 2124 } 2125 2126 final Drawables dr = mDrawables; 2127 if (dr != null) { 2128 int[] state = getDrawableState(); 2129 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 2130 dr.mDrawableTop.setState(state); 2131 } 2132 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 2133 dr.mDrawableBottom.setState(state); 2134 } 2135 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 2136 dr.mDrawableLeft.setState(state); 2137 } 2138 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 2139 dr.mDrawableRight.setState(state); 2140 } 2141 } 2142 } 2143 2144 /** 2145 * User interface state that is stored by TextView for implementing 2146 * {@link View#onSaveInstanceState}. 2147 */ 2148 public static class SavedState extends BaseSavedState { 2149 int selStart; 2150 int selEnd; 2151 CharSequence text; 2152 boolean frozenWithFocus; 2153 2154 SavedState(Parcelable superState) { 2155 super(superState); 2156 } 2157 2158 @Override 2159 public void writeToParcel(Parcel out, int flags) { 2160 super.writeToParcel(out, flags); 2161 out.writeInt(selStart); 2162 out.writeInt(selEnd); 2163 out.writeInt(frozenWithFocus ? 1 : 0); 2164 TextUtils.writeToParcel(text, out, flags); 2165 } 2166 2167 @Override 2168 public String toString() { 2169 String str = "TextView.SavedState{" 2170 + Integer.toHexString(System.identityHashCode(this)) 2171 + " start=" + selStart + " end=" + selEnd; 2172 if (text != null) { 2173 str += " text=" + text; 2174 } 2175 return str + "}"; 2176 } 2177 2178 public static final Parcelable.Creator<SavedState> CREATOR 2179 = new Parcelable.Creator<SavedState>() { 2180 public SavedState createFromParcel(Parcel in) { 2181 return new SavedState(in); 2182 } 2183 2184 public SavedState[] newArray(int size) { 2185 return new SavedState[size]; 2186 } 2187 }; 2188 2189 private SavedState(Parcel in) { 2190 super(in); 2191 selStart = in.readInt(); 2192 selEnd = in.readInt(); 2193 frozenWithFocus = (in.readInt() != 0); 2194 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 2195 } 2196 } 2197 2198 @Override 2199 public Parcelable onSaveInstanceState() { 2200 Parcelable superState = super.onSaveInstanceState(); 2201 2202 // Save state if we are forced to 2203 boolean save = mFreezesText; 2204 int start = 0; 2205 int end = 0; 2206 2207 if (mText != null) { 2208 start = Selection.getSelectionStart(mText); 2209 end = Selection.getSelectionEnd(mText); 2210 if (start >= 0 || end >= 0) { 2211 // Or save state if there is a selection 2212 save = true; 2213 } 2214 } 2215 2216 if (save) { 2217 SavedState ss = new SavedState(superState); 2218 // XXX Should also save the current scroll position! 2219 ss.selStart = start; 2220 ss.selEnd = end; 2221 2222 if (mText instanceof Spanned) { 2223 /* 2224 * Calling setText() strips off any ChangeWatchers; 2225 * strip them now to avoid leaking references. 2226 * But do it to a copy so that if there are any 2227 * further changes to the text of this view, it 2228 * won't get into an inconsistent state. 2229 */ 2230 2231 Spannable sp = new SpannableString(mText); 2232 2233 for (ChangeWatcher cw : 2234 sp.getSpans(0, sp.length(), ChangeWatcher.class)) { 2235 sp.removeSpan(cw); 2236 } 2237 2238 ss.text = sp; 2239 } else { 2240 ss.text = mText.toString(); 2241 } 2242 2243 if (isFocused() && start >= 0 && end >= 0) { 2244 ss.frozenWithFocus = true; 2245 } 2246 2247 return ss; 2248 } 2249 2250 return superState; 2251 } 2252 2253 @Override 2254 public void onRestoreInstanceState(Parcelable state) { 2255 if (!(state instanceof SavedState)) { 2256 super.onRestoreInstanceState(state); 2257 return; 2258 } 2259 2260 SavedState ss = (SavedState)state; 2261 super.onRestoreInstanceState(ss.getSuperState()); 2262 2263 // XXX restore buffer type too, as well as lots of other stuff 2264 if (ss.text != null) { 2265 setText(ss.text); 2266 } 2267 2268 if (ss.selStart >= 0 && ss.selEnd >= 0) { 2269 if (mText instanceof Spannable) { 2270 int len = mText.length(); 2271 2272 if (ss.selStart > len || ss.selEnd > len) { 2273 String restored = ""; 2274 2275 if (ss.text != null) { 2276 restored = "(restored) "; 2277 } 2278 2279 Log.e("TextView", "Saved cursor position " + ss.selStart + 2280 "/" + ss.selEnd + " out of range for " + restored + 2281 "text " + mText); 2282 } else { 2283 Selection.setSelection((Spannable) mText, ss.selStart, 2284 ss.selEnd); 2285 2286 if (ss.frozenWithFocus) { 2287 mFrozenWithFocus = true; 2288 } 2289 } 2290 } 2291 } 2292 } 2293 2294 /** 2295 * Control whether this text view saves its entire text contents when 2296 * freezing to an icicle, in addition to dynamic state such as cursor 2297 * position. By default this is false, not saving the text. Set to true 2298 * if the text in the text view is not being saved somewhere else in 2299 * persistent storage (such as in a content provider) so that if the 2300 * view is later thawed the user will not lose their data. 2301 * 2302 * @param freezesText Controls whether a frozen icicle should include the 2303 * entire text data: true to include it, false to not. 2304 * 2305 * @attr ref android.R.styleable#TextView_freezesText 2306 */ 2307 public void setFreezesText(boolean freezesText) { 2308 mFreezesText = freezesText; 2309 } 2310 2311 /** 2312 * Return whether this text view is including its entire text contents 2313 * in frozen icicles. 2314 * 2315 * @return Returns true if text is included, false if it isn't. 2316 * 2317 * @see #setFreezesText 2318 */ 2319 public boolean getFreezesText() { 2320 return mFreezesText; 2321 } 2322 2323 /////////////////////////////////////////////////////////////////////////// 2324 2325 /** 2326 * Sets the Factory used to create new Editables. 2327 */ 2328 public final void setEditableFactory(Editable.Factory factory) { 2329 mEditableFactory = factory; 2330 setText(mText); 2331 } 2332 2333 /** 2334 * Sets the Factory used to create new Spannables. 2335 */ 2336 public final void setSpannableFactory(Spannable.Factory factory) { 2337 mSpannableFactory = factory; 2338 setText(mText); 2339 } 2340 2341 /** 2342 * Sets the string value of the TextView. TextView <em>does not</em> accept 2343 * HTML-like formatting, which you can do with text strings in XML resource files. 2344 * To style your strings, attach android.text.style.* objects to a 2345 * {@link android.text.SpannableString SpannableString}, or see 2346 * <a href="{@docRoot}reference/available-resources.html#stringresources"> 2347 * String Resources</a> for an example of setting formatted text in the XML resource file. 2348 * 2349 * @attr ref android.R.styleable#TextView_text 2350 */ 2351 public final void setText(CharSequence text) { 2352 setText(text, mBufferType); 2353 } 2354 2355 /** 2356 * Like {@link #setText(CharSequence)}, 2357 * except that the cursor position (if any) is retained in the new text. 2358 * 2359 * @param text The new text to place in the text view. 2360 * 2361 * @see #setText(CharSequence) 2362 */ 2363 public final void setTextKeepState(CharSequence text) { 2364 setTextKeepState(text, mBufferType); 2365 } 2366 2367 /** 2368 * Sets the text that this TextView is to display (see 2369 * {@link #setText(CharSequence)}) and also sets whether it is stored 2370 * in a styleable/spannable buffer and whether it is editable. 2371 * 2372 * @attr ref android.R.styleable#TextView_text 2373 * @attr ref android.R.styleable#TextView_bufferType 2374 */ 2375 public void setText(CharSequence text, BufferType type) { 2376 setText(text, type, true, 0); 2377 2378 if (mCharWrapper != null) { 2379 mCharWrapper.mChars = null; 2380 } 2381 } 2382 2383 private void setText(CharSequence text, BufferType type, 2384 boolean notifyBefore, int oldlen) { 2385 if (text == null) { 2386 text = ""; 2387 } 2388 2389 if (text instanceof Spanned && 2390 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 2391 setHorizontalFadingEdgeEnabled(true); 2392 setEllipsize(TextUtils.TruncateAt.MARQUEE); 2393 } 2394 2395 int n = mFilters.length; 2396 for (int i = 0; i < n; i++) { 2397 CharSequence out = mFilters[i].filter(text, 0, text.length(), 2398 EMPTY_SPANNED, 0, 0); 2399 if (out != null) { 2400 text = out; 2401 } 2402 } 2403 2404 if (notifyBefore) { 2405 if (mText != null) { 2406 oldlen = mText.length(); 2407 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 2408 } else { 2409 sendBeforeTextChanged("", 0, 0, text.length()); 2410 } 2411 } 2412 2413 boolean needEditableForNotification = false; 2414 2415 if (mListeners != null && mListeners.size() != 0) { 2416 needEditableForNotification = true; 2417 } 2418 2419 if (type == BufferType.EDITABLE || mInput != null || 2420 needEditableForNotification) { 2421 Editable t = mEditableFactory.newEditable(text); 2422 text = t; 2423 setFilters(t, mFilters); 2424 InputMethodManager imm = InputMethodManager.peekInstance(); 2425 if (imm != null) imm.restartInput(this); 2426 } else if (type == BufferType.SPANNABLE || mMovement != null) { 2427 text = mSpannableFactory.newSpannable(text); 2428 } else if (!(text instanceof CharWrapper)) { 2429 text = TextUtils.stringOrSpannedString(text); 2430 } 2431 2432 if (mAutoLinkMask != 0) { 2433 Spannable s2; 2434 2435 if (type == BufferType.EDITABLE || text instanceof Spannable) { 2436 s2 = (Spannable) text; 2437 } else { 2438 s2 = mSpannableFactory.newSpannable(text); 2439 } 2440 2441 if (Linkify.addLinks(s2, mAutoLinkMask)) { 2442 text = s2; 2443 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 2444 2445 /* 2446 * We must go ahead and set the text before changing the 2447 * movement method, because setMovementMethod() may call 2448 * setText() again to try to upgrade the buffer type. 2449 */ 2450 mText = text; 2451 2452 if (mLinksClickable) { 2453 setMovementMethod(LinkMovementMethod.getInstance()); 2454 } 2455 } 2456 } 2457 2458 mBufferType = type; 2459 mText = text; 2460 2461 if (mTransformation == null) 2462 mTransformed = text; 2463 else 2464 mTransformed = mTransformation.getTransformation(text, this); 2465 2466 final int textLength = text.length(); 2467 2468 if (text instanceof Spannable) { 2469 Spannable sp = (Spannable) text; 2470 2471 // Remove any ChangeWatchers that might have come 2472 // from other TextViews. 2473 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 2474 final int count = watchers.length; 2475 for (int i = 0; i < count; i++) 2476 sp.removeSpan(watchers[i]); 2477 2478 if (mChangeWatcher == null) 2479 mChangeWatcher = new ChangeWatcher(); 2480 2481 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 2482 (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 2483 2484 if (mInput != null) { 2485 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2486 } 2487 2488 if (mTransformation != null) { 2489 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2490 2491 } 2492 2493 if (mMovement != null) { 2494 mMovement.initialize(this, (Spannable) text); 2495 2496 /* 2497 * Initializing the movement method will have set the 2498 * selection, so reset mSelectionMoved to keep that from 2499 * interfering with the normal on-focus selection-setting. 2500 */ 2501 mSelectionMoved = false; 2502 } 2503 } 2504 2505 if (mLayout != null) { 2506 checkForRelayout(); 2507 } 2508 2509 sendOnTextChanged(text, 0, oldlen, textLength); 2510 onTextChanged(text, 0, oldlen, textLength); 2511 2512 if (needEditableForNotification) { 2513 sendAfterTextChanged((Editable) text); 2514 } 2515 } 2516 2517 /** 2518 * Sets the TextView to display the specified slice of the specified 2519 * char array. You must promise that you will not change the contents 2520 * of the array except for right before another call to setText(), 2521 * since the TextView has no way to know that the text 2522 * has changed and that it needs to invalidate and re-layout. 2523 */ 2524 public final void setText(char[] text, int start, int len) { 2525 int oldlen = 0; 2526 2527 if (start < 0 || len < 0 || start + len > text.length) { 2528 throw new IndexOutOfBoundsException(start + ", " + len); 2529 } 2530 2531 /* 2532 * We must do the before-notification here ourselves because if 2533 * the old text is a CharWrapper we destroy it before calling 2534 * into the normal path. 2535 */ 2536 if (mText != null) { 2537 oldlen = mText.length(); 2538 sendBeforeTextChanged(mText, 0, oldlen, len); 2539 } else { 2540 sendBeforeTextChanged("", 0, 0, len); 2541 } 2542 2543 if (mCharWrapper == null) { 2544 mCharWrapper = new CharWrapper(text, start, len); 2545 } else { 2546 mCharWrapper.set(text, start, len); 2547 } 2548 2549 setText(mCharWrapper, mBufferType, false, oldlen); 2550 } 2551 2552 private static class CharWrapper 2553 implements CharSequence, GetChars, GraphicsOperations { 2554 private char[] mChars; 2555 private int mStart, mLength; 2556 2557 public CharWrapper(char[] chars, int start, int len) { 2558 mChars = chars; 2559 mStart = start; 2560 mLength = len; 2561 } 2562 2563 /* package */ void set(char[] chars, int start, int len) { 2564 mChars = chars; 2565 mStart = start; 2566 mLength = len; 2567 } 2568 2569 public int length() { 2570 return mLength; 2571 } 2572 2573 public char charAt(int off) { 2574 return mChars[off + mStart]; 2575 } 2576 2577 public String toString() { 2578 return new String(mChars, mStart, mLength); 2579 } 2580 2581 public CharSequence subSequence(int start, int end) { 2582 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2583 throw new IndexOutOfBoundsException(start + ", " + end); 2584 } 2585 2586 return new String(mChars, start + mStart, end - start); 2587 } 2588 2589 public void getChars(int start, int end, char[] buf, int off) { 2590 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2591 throw new IndexOutOfBoundsException(start + ", " + end); 2592 } 2593 2594 System.arraycopy(mChars, start + mStart, buf, off, end - start); 2595 } 2596 2597 public void drawText(Canvas c, int start, int end, 2598 float x, float y, Paint p) { 2599 c.drawText(mChars, start + mStart, end - start, x, y, p); 2600 } 2601 2602 public float measureText(int start, int end, Paint p) { 2603 return p.measureText(mChars, start + mStart, end - start); 2604 } 2605 2606 public int getTextWidths(int start, int end, float[] widths, Paint p) { 2607 return p.getTextWidths(mChars, start + mStart, end - start, widths); 2608 } 2609 } 2610 2611 /** 2612 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 2613 * except that the cursor position (if any) is retained in the new text. 2614 * 2615 * @see #setText(CharSequence, android.widget.TextView.BufferType) 2616 */ 2617 public final void setTextKeepState(CharSequence text, BufferType type) { 2618 int start = getSelectionStart(); 2619 int end = getSelectionEnd(); 2620 int len = text.length(); 2621 2622 setText(text, type); 2623 2624 if (start >= 0 || end >= 0) { 2625 if (mText instanceof Spannable) { 2626 Selection.setSelection((Spannable) mText, 2627 Math.max(0, Math.min(start, len)), 2628 Math.max(0, Math.min(end, len))); 2629 } 2630 } 2631 } 2632 2633 public final void setText(int resid) { 2634 setText(getContext().getResources().getText(resid)); 2635 } 2636 2637 public final void setText(int resid, BufferType type) { 2638 setText(getContext().getResources().getText(resid), type); 2639 } 2640 2641 /** 2642 * Sets the text to be displayed when the text of the TextView is empty. 2643 * Null means to use the normal empty text. The hint does not currently 2644 * participate in determining the size of the view. 2645 * 2646 * This method is deprecated. Use {link #setHint(int, String)} or 2647 * {link #setHint(CharSequence, String)} instead. 2648 * 2649 * @attr ref android.R.styleable#TextView_hint 2650 */ 2651 public final void setHint(CharSequence hint) { 2652 mHint = TextUtils.stringOrSpannedString(hint); 2653 2654 if (mLayout != null) { 2655 checkForRelayout(); 2656 } 2657 2658 if (mText.length() == 0) 2659 invalidate(); 2660 } 2661 2662 /** 2663 * Sets the text to be displayed when the text of the TextView is empty, 2664 * from a resource. 2665 * 2666 * This method is deprecated. Use {link #setHint(int, String)} or 2667 * {link #setHint(CharSequence, String)} instead. 2668 * 2669 * @attr ref android.R.styleable#TextView_hint 2670 */ 2671 public final void setHint(int resid) { 2672 setHint(getContext().getResources().getText(resid)); 2673 } 2674 2675 /** 2676 * Returns the hint that is displayed when the text of the TextView 2677 * is empty. 2678 * 2679 * @attr ref android.R.styleable#TextView_hint 2680 */ 2681 @ViewDebug.CapturedViewProperty 2682 public CharSequence getHint() { 2683 return mHint; 2684 } 2685 2686 /** 2687 * Set the type of the content with a constant as defined for 2688 * {@link EditorInfo#inputType}. This will take care of changing 2689 * the key listener, by calling {@link #setKeyListener(KeyListener)}, to 2690 * match the given content type. If the given content type is 2691 * {@link EditorInfo#TYPE_NULL} then a soft keyboard will 2692 * not be displayed for this text view. 2693 * 2694 * @see #getInputType() 2695 * @see #setRawInputType(int) 2696 * @see android.text.InputType 2697 * @attr ref android.R.styleable#TextView_inputType 2698 */ 2699 public void setInputType(int type) { 2700 setInputType(type, false); 2701 if ((type&(EditorInfo.TYPE_MASK_CLASS 2702 |EditorInfo.TYPE_MASK_VARIATION)) 2703 == (EditorInfo.TYPE_CLASS_TEXT 2704 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 2705 setTransformationMethod(PasswordTransformationMethod.getInstance()); 2706 setTypefaceByIndex(MONOSPACE, 0); 2707 } 2708 boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS 2709 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 2710 (EditorInfo.TYPE_CLASS_TEXT 2711 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 2712 if (mSingleLine == multiLine) { 2713 setSingleLine(!multiLine); 2714 } 2715 InputMethodManager imm = InputMethodManager.peekInstance(); 2716 if (imm != null) imm.restartInput(this); 2717 } 2718 2719 /** 2720 * Directly change the content type integer of the text view, without 2721 * modifying any other state. 2722 * @see #setContentType 2723 * @see android.text.InputType 2724 * @attr ref android.R.styleable#TextView_inputType 2725 */ 2726 public void setRawInputType(int type) { 2727 mInputType = type; 2728 } 2729 2730 private void setInputType(int type, boolean direct) { 2731 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 2732 KeyListener input; 2733 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 2734 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) 2735 != 0; 2736 TextKeyListener.Capitalize cap; 2737 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 2738 cap = TextKeyListener.Capitalize.CHARACTERS; 2739 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 2740 cap = TextKeyListener.Capitalize.WORDS; 2741 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 2742 cap = TextKeyListener.Capitalize.SENTENCES; 2743 } else { 2744 cap = TextKeyListener.Capitalize.NONE; 2745 } 2746 input = TextKeyListener.getInstance(autotext, cap); 2747 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 2748 input = DigitsKeyListener.getInstance( 2749 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 2750 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 2751 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 2752 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 2753 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 2754 input = DateKeyListener.getInstance(); 2755 break; 2756 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 2757 input = TimeKeyListener.getInstance(); 2758 break; 2759 default: 2760 input = DateTimeKeyListener.getInstance(); 2761 break; 2762 } 2763 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 2764 input = DialerKeyListener.getInstance(); 2765 } else { 2766 input = TextKeyListener.getInstance(); 2767 } 2768 mInputType = type; 2769 if (direct) mInput = input; 2770 else { 2771 setKeyListenerOnly(input); 2772 } 2773 } 2774 2775 /** 2776 * Get the type of the content. 2777 * 2778 * @see #setInputType(int) 2779 * @see android.text.InputType 2780 */ 2781 public int getInputType() { 2782 return mInputType; 2783 } 2784 2785 /** 2786 * Set the private content type of the text, which is the 2787 * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType} 2788 * field that will be filled in when creating an input connection. 2789 * 2790 * @see #getPrivateContentType() 2791 * @see EditorInfo#privateContentType 2792 * @attr ref android.R.styleable#TextView_editorPrivateContentType 2793 */ 2794 public void setPrivateContentType(String type) { 2795 if (mInputContentType == null) mInputContentType = new InputContentType(); 2796 mInputContentType.privateContentType = type; 2797 } 2798 2799 /** 2800 * Get the private type of the content. 2801 * 2802 * @see #setPrivateContentType(String) 2803 * @see EditorInfo#privateContentType 2804 */ 2805 public String getPrivateContentType() { 2806 return mInputContentType != null 2807 ? mInputContentType.privateContentType : null; 2808 } 2809 2810 /** 2811 * Set the extra input data of the text, which is the 2812 * {@link EditorInfo#extras TextBoxAttribute.extras} 2813 * Bundle that will be filled in when creating an input connection. The 2814 * given integer is the resource ID of an XML resource holding an 2815 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 2816 * 2817 * @see #getInputExtras() 2818 * @see EditorInfo#extras 2819 * @attr ref android.R.styleable#TextView_editorExtras 2820 */ 2821 public void setInputExtras(int xmlResId) 2822 throws XmlPullParserException, IOException { 2823 XmlResourceParser parser = getResources().getXml(xmlResId); 2824 if (mInputContentType == null) mInputContentType = new InputContentType(); 2825 mInputContentType.extras = new Bundle(); 2826 getResources().parseBundleExtras(parser, mInputContentType.extras); 2827 } 2828 2829 /** 2830 * Retrieve the input extras currently associated with the text view, which 2831 * can be viewed as well as modified. 2832 * 2833 * @param create If true, the extras will be created if they don't already 2834 * exist. Otherwise, null will be returned if none have been created. 2835 * @see #setInputExtras(int) 2836 * @see EditorInfo#extras 2837 * @attr ref android.R.styleable#TextView_editorExtras 2838 */ 2839 public Bundle getInputExtras(boolean create) { 2840 if (mInputContentType == null) { 2841 if (!create) return null; 2842 mInputContentType = new InputContentType(); 2843 } 2844 if (mInputContentType.extras == null) { 2845 if (!create) return null; 2846 mInputContentType.extras = new Bundle(); 2847 } 2848 return mInputContentType.extras; 2849 } 2850 2851 /** 2852 * Returns the error message that was set to be displayed with 2853 * {@link #setError}, or <code>null</code> if no error was set 2854 * or if it the error was cleared by the widget after user input. 2855 */ 2856 public CharSequence getError() { 2857 return mError; 2858 } 2859 2860 /** 2861 * Sets the right-hand compound drawable of the TextView to the "error" 2862 * icon and sets an error message that will be displayed in a popup when 2863 * the TextView has focus. The icon and error message will be reset to 2864 * null when any key events cause changes to the TextView's text. If the 2865 * <code>error</code> is <code>null</code>, the error message and icon 2866 * will be cleared. 2867 */ 2868 public void setError(CharSequence error) { 2869 if (error == null) { 2870 setError(null, null); 2871 } else { 2872 Drawable dr = getContext().getResources(). 2873 getDrawable(com.android.internal.R.drawable. 2874 indicator_input_error); 2875 2876 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 2877 setError(error, dr); 2878 } 2879 } 2880 2881 /** 2882 * Sets the right-hand compound drawable of the TextView to the specified 2883 * icon and sets an error message that will be displayed in a popup when 2884 * the TextView has focus. The icon and error message will be reset to 2885 * null when any key events cause changes to the TextView's text. The 2886 * drawable must already have had {@link Drawable#setBounds} set on it. 2887 * If the <code>error</code> is <code>null</code>, the error message will 2888 * be cleared (and you should provide a <code>null</code> icon as well). 2889 */ 2890 public void setError(CharSequence error, Drawable icon) { 2891 error = TextUtils.stringOrSpannedString(error); 2892 2893 mError = error; 2894 mErrorWasChanged = true; 2895 final Drawables dr = mDrawables; 2896 if (dr != null) { 2897 setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, 2898 icon, dr.mDrawableBottom); 2899 } else { 2900 setCompoundDrawables(null, null, icon, null); 2901 } 2902 2903 if (error == null) { 2904 if (mPopup != null) { 2905 if (mPopup.isShowing()) { 2906 mPopup.dismiss(); 2907 } 2908 2909 mPopup = null; 2910 } 2911 } else { 2912 if (isFocused()) { 2913 showError(); 2914 } 2915 } 2916 } 2917 2918 private void showError() { 2919 if (mPopup == null) { 2920 LayoutInflater inflater = LayoutInflater.from(getContext()); 2921 TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint, 2922 null); 2923 2924 mPopup = new PopupWindow(err, 200, 50); 2925 mPopup.setFocusable(false); 2926 } 2927 2928 TextView tv = (TextView) mPopup.getContentView(); 2929 chooseSize(mPopup, mError, tv); 2930 tv.setText(mError); 2931 2932 mPopup.showAsDropDown(this, getErrorX(), getErrorY()); 2933 } 2934 2935 /** 2936 * Returns the Y offset to make the pointy top of the error point 2937 * at the middle of the error icon. 2938 */ 2939 private int getErrorX() { 2940 /* 2941 * The "25" is the distance between the point and the right edge 2942 * of the background 2943 */ 2944 2945 final Drawables dr = mDrawables; 2946 return getWidth() - mPopup.getWidth() 2947 - getPaddingRight() 2948 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25; 2949 } 2950 2951 /** 2952 * Returns the Y offset to make the pointy top of the error point 2953 * at the bottom of the error icon. 2954 */ 2955 private int getErrorY() { 2956 /* 2957 * Compound, not extended, because the icon is not clipped 2958 * if the text height is smaller. 2959 */ 2960 int vspace = mBottom - mTop - 2961 getCompoundPaddingBottom() - getCompoundPaddingTop(); 2962 2963 final Drawables dr = mDrawables; 2964 int icontop = getCompoundPaddingTop() 2965 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; 2966 2967 /* 2968 * The "2" is the distance between the point and the top edge 2969 * of the background. 2970 */ 2971 2972 return icontop + (dr != null ? dr.mDrawableHeightRight : 0) 2973 - getHeight() - 2; 2974 } 2975 2976 private void hideError() { 2977 if (mPopup != null) { 2978 if (mPopup.isShowing()) { 2979 mPopup.dismiss(); 2980 } 2981 } 2982 } 2983 2984 private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { 2985 int wid = tv.getPaddingLeft() + tv.getPaddingRight(); 2986 int ht = tv.getPaddingTop() + tv.getPaddingBottom(); 2987 2988 /* 2989 * Figure out how big the text would be if we laid it out to the 2990 * full width of this view minus the border. 2991 */ 2992 int cap = getWidth() - wid; 2993 if (cap < 0) { 2994 cap = 200; // We must not be measured yet -- setFrame() will fix it. 2995 } 2996 2997 Layout l = new StaticLayout(text, tv.getPaint(), cap, 2998 Layout.Alignment.ALIGN_NORMAL, 1, 0, true); 2999 float max = 0; 3000 for (int i = 0; i < l.getLineCount(); i++) { 3001 max = Math.max(max, l.getLineWidth(i)); 3002 } 3003 3004 /* 3005 * Now set the popup size to be big enough for the text plus the border. 3006 */ 3007 pop.setWidth(wid + (int) Math.ceil(max)); 3008 pop.setHeight(ht + l.getHeight()); 3009 } 3010 3011 3012 @Override 3013 protected boolean setFrame(int l, int t, int r, int b) { 3014 boolean result = super.setFrame(l, t, r, b); 3015 3016 if (mPopup != null) { 3017 TextView tv = (TextView) mPopup.getContentView(); 3018 chooseSize(mPopup, mError, tv); 3019 mPopup.update(this, getErrorX(), getErrorY(), -1, -1); 3020 } 3021 3022 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3023 mRestartMarquee = false; 3024 startMarquee(); 3025 } 3026 3027 return result; 3028 } 3029 3030 /** 3031 * Sets the list of input filters that will be used if the buffer is 3032 * Editable. Has no effect otherwise. 3033 * 3034 * @attr ref android.R.styleable#TextView_maxLength 3035 */ 3036 public void setFilters(InputFilter[] filters) { 3037 if (filters == null) { 3038 throw new IllegalArgumentException(); 3039 } 3040 3041 mFilters = filters; 3042 3043 if (mText instanceof Editable) { 3044 setFilters((Editable) mText, filters); 3045 } 3046 } 3047 3048 /** 3049 * Sets the list of input filters on the specified Editable, 3050 * and includes mInput in the list if it is an InputFilter. 3051 */ 3052 private void setFilters(Editable e, InputFilter[] filters) { 3053 if (mInput instanceof InputFilter) { 3054 InputFilter[] nf = new InputFilter[filters.length + 1]; 3055 3056 System.arraycopy(filters, 0, nf, 0, filters.length); 3057 nf[filters.length] = (InputFilter) mInput; 3058 3059 e.setFilters(nf); 3060 } else { 3061 e.setFilters(filters); 3062 } 3063 } 3064 3065 /** 3066 * Returns the current list of input filters. 3067 */ 3068 public InputFilter[] getFilters() { 3069 return mFilters; 3070 } 3071 3072 ///////////////////////////////////////////////////////////////////////// 3073 3074 private int getVerticalOffset(boolean forceNormal) { 3075 int voffset = 0; 3076 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3077 3078 Layout l = mLayout; 3079 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3080 l = mHintLayout; 3081 } 3082 3083 if (gravity != Gravity.TOP) { 3084 int boxht; 3085 3086 if (l == mHintLayout) { 3087 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3088 getCompoundPaddingBottom(); 3089 } else { 3090 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3091 getExtendedPaddingBottom(); 3092 } 3093 int textht = l.getHeight(); 3094 3095 if (textht < boxht) { 3096 if (gravity == Gravity.BOTTOM) 3097 voffset = boxht - textht; 3098 else // (gravity == Gravity.CENTER_VERTICAL) 3099 voffset = (boxht - textht) >> 1; 3100 } 3101 } 3102 return voffset; 3103 } 3104 3105 private int getBottomVerticalOffset(boolean forceNormal) { 3106 int voffset = 0; 3107 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3108 3109 Layout l = mLayout; 3110 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3111 l = mHintLayout; 3112 } 3113 3114 if (gravity != Gravity.BOTTOM) { 3115 int boxht; 3116 3117 if (l == mHintLayout) { 3118 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3119 getCompoundPaddingBottom(); 3120 } else { 3121 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3122 getExtendedPaddingBottom(); 3123 } 3124 int textht = l.getHeight(); 3125 3126 if (textht < boxht) { 3127 if (gravity == Gravity.TOP) 3128 voffset = boxht - textht; 3129 else // (gravity == Gravity.CENTER_VERTICAL) 3130 voffset = (boxht - textht) >> 1; 3131 } 3132 } 3133 return voffset; 3134 } 3135 3136 private void invalidateCursorPath() { 3137 if (mHighlightPathBogus) { 3138 invalidateCursor(); 3139 } else { 3140 synchronized (sTempRect) { 3141 /* 3142 * The reason for this concern about the thickness of the 3143 * cursor and doing the floor/ceil on the coordinates is that 3144 * some EditTexts (notably textfields in the Browser) have 3145 * anti-aliased text where not all the characters are 3146 * necessarily at integer-multiple locations. This should 3147 * make sure the entire cursor gets invalidated instead of 3148 * sometimes missing half a pixel. 3149 */ 3150 3151 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); 3152 if (thick < 1.0f) { 3153 thick = 1.0f; 3154 } 3155 3156 thick /= 2; 3157 3158 mHighlightPath.computeBounds(sTempRect, false); 3159 3160 int left = getCompoundPaddingLeft(); 3161 int top = getExtendedPaddingTop() + getVerticalOffset(true); 3162 3163 invalidate((int) FloatMath.floor(left + sTempRect.left - thick), 3164 (int) FloatMath.floor(top + sTempRect.top - thick), 3165 (int) FloatMath.ceil(left + sTempRect.right + thick), 3166 (int) FloatMath.ceil(top + sTempRect.bottom + thick)); 3167 } 3168 } 3169 } 3170 3171 private void invalidateCursor() { 3172 int where = Selection.getSelectionEnd(mText); 3173 3174 invalidateCursor(where, where, where); 3175 } 3176 3177 private void invalidateCursor(int a, int b, int c) { 3178 if (mLayout == null) { 3179 invalidate(); 3180 } else { 3181 if (a >= 0 || b >= 0 || c >= 0) { 3182 int first = Math.min(Math.min(a, b), c); 3183 int last = Math.max(Math.max(a, b), c); 3184 3185 int line = mLayout.getLineForOffset(first); 3186 int top = mLayout.getLineTop(line); 3187 3188 // This is ridiculous, but the descent from the line above 3189 // can hang down into the line we really want to redraw, 3190 // so we have to invalidate part of the line above to make 3191 // sure everything that needs to be redrawn really is. 3192 // (But not the whole line above, because that would cause 3193 // the same problem with the descenders on the line above it!) 3194 if (line > 0) { 3195 top -= mLayout.getLineDescent(line - 1); 3196 } 3197 3198 int line2; 3199 3200 if (first == last) 3201 line2 = line; 3202 else 3203 line2 = mLayout.getLineForOffset(last); 3204 3205 int bottom = mLayout.getLineTop(line2 + 1); 3206 int voffset = getVerticalOffset(true); 3207 3208 int left = getCompoundPaddingLeft() + mScrollX; 3209 invalidate(left, top + voffset + getExtendedPaddingTop(), 3210 left + getWidth() - getCompoundPaddingLeft() - 3211 getCompoundPaddingRight(), 3212 bottom + voffset + getExtendedPaddingTop()); 3213 } 3214 } 3215 } 3216 3217 private void registerForPreDraw() { 3218 final ViewTreeObserver observer = getViewTreeObserver(); 3219 if (observer == null) { 3220 return; 3221 } 3222 3223 if (mPreDrawState == PREDRAW_NOT_REGISTERED) { 3224 observer.addOnPreDrawListener(this); 3225 mPreDrawState = PREDRAW_PENDING; 3226 } else if (mPreDrawState == PREDRAW_DONE) { 3227 mPreDrawState = PREDRAW_PENDING; 3228 } 3229 3230 // else state is PREDRAW_PENDING, so keep waiting. 3231 } 3232 3233 /** 3234 * {@inheritDoc} 3235 */ 3236 public boolean onPreDraw() { 3237 if (mPreDrawState != PREDRAW_PENDING) { 3238 return true; 3239 } 3240 3241 if (mLayout == null) { 3242 assumeLayout(); 3243 } 3244 3245 boolean changed = false; 3246 3247 if (mMovement != null) { 3248 int curs = Selection.getSelectionEnd(mText); 3249 3250 /* 3251 * TODO: This should really only keep the end in view if 3252 * it already was before the text changed. I'm not sure 3253 * of a good way to tell from here if it was. 3254 */ 3255 if (curs < 0 && 3256 (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 3257 curs = mText.length(); 3258 } 3259 3260 if (curs >= 0) { 3261 changed = bringPointIntoView(curs); 3262 } 3263 } else { 3264 changed = bringTextIntoView(); 3265 } 3266 3267 mPreDrawState = PREDRAW_DONE; 3268 return !changed; 3269 } 3270 3271 @Override 3272 protected void onDetachedFromWindow() { 3273 super.onDetachedFromWindow(); 3274 3275 if (mPreDrawState != PREDRAW_NOT_REGISTERED) { 3276 final ViewTreeObserver observer = getViewTreeObserver(); 3277 if (observer != null) { 3278 observer.removeOnPreDrawListener(this); 3279 mPreDrawState = PREDRAW_NOT_REGISTERED; 3280 } 3281 } 3282 3283 if (mError != null) { 3284 hideError(); 3285 } 3286 } 3287 3288 @Override 3289 protected boolean isPaddingOffsetRequired() { 3290 return mShadowRadius != 0; 3291 } 3292 3293 @Override 3294 protected int getLeftPaddingOffset() { 3295 return (int) Math.min(0, mShadowDx - mShadowRadius); 3296 } 3297 3298 @Override 3299 protected int getTopPaddingOffset() { 3300 return (int) Math.min(0, mShadowDy - mShadowRadius); 3301 } 3302 3303 @Override 3304 protected int getBottomPaddingOffset() { 3305 return (int) Math.max(0, mShadowDy + mShadowRadius); 3306 } 3307 3308 @Override 3309 protected int getRightPaddingOffset() { 3310 return (int) Math.max(0, mShadowDx + mShadowRadius); 3311 } 3312 3313 @Override 3314 protected void onDraw(Canvas canvas) { 3315 // Draw the background for this view 3316 super.onDraw(canvas); 3317 3318 final int compoundPaddingLeft = getCompoundPaddingLeft(); 3319 final int compoundPaddingTop = getCompoundPaddingTop(); 3320 final int compoundPaddingRight = getCompoundPaddingRight(); 3321 final int compoundPaddingBottom = getCompoundPaddingBottom(); 3322 final int scrollX = mScrollX; 3323 final int scrollY = mScrollY; 3324 final int right = mRight; 3325 final int left = mLeft; 3326 final int bottom = mBottom; 3327 final int top = mTop; 3328 3329 final Drawables dr = mDrawables; 3330 if (dr != null) { 3331 /* 3332 * Compound, not extended, because the icon is not clipped 3333 * if the text height is smaller. 3334 */ 3335 3336 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 3337 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 3338 3339 if (dr.mDrawableLeft != null) { 3340 canvas.save(); 3341 canvas.translate(scrollX + mPaddingLeft, 3342 scrollY + compoundPaddingTop + 3343 (vspace - dr.mDrawableHeightLeft) / 2); 3344 dr.mDrawableLeft.draw(canvas); 3345 canvas.restore(); 3346 } 3347 3348 if (dr.mDrawableRight != null) { 3349 canvas.save(); 3350 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, 3351 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 3352 dr.mDrawableRight.draw(canvas); 3353 canvas.restore(); 3354 } 3355 3356 if (dr.mDrawableTop != null) { 3357 canvas.save(); 3358 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, 3359 scrollY + mPaddingTop); 3360 dr.mDrawableTop.draw(canvas); 3361 canvas.restore(); 3362 } 3363 3364 if (dr.mDrawableBottom != null) { 3365 canvas.save(); 3366 canvas.translate(scrollX + compoundPaddingLeft + 3367 (hspace - dr.mDrawableWidthBottom) / 2, 3368 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 3369 dr.mDrawableBottom.draw(canvas); 3370 canvas.restore(); 3371 } 3372 } 3373 3374 if (mPreDrawState == PREDRAW_DONE) { 3375 final ViewTreeObserver observer = getViewTreeObserver(); 3376 if (observer != null) { 3377 observer.removeOnPreDrawListener(this); 3378 mPreDrawState = PREDRAW_NOT_REGISTERED; 3379 } 3380 } 3381 3382 int color = mCurTextColor; 3383 3384 if (mLayout == null) { 3385 assumeLayout(); 3386 } 3387 3388 Layout layout = mLayout; 3389 int cursorcolor = color; 3390 3391 if (mHint != null && mText.length() == 0) { 3392 if (mHintTextColor != null) { 3393 color = mCurHintTextColor; 3394 } 3395 3396 layout = mHintLayout; 3397 } 3398 3399 mTextPaint.setColor(color); 3400 mTextPaint.drawableState = getDrawableState(); 3401 3402 canvas.save(); 3403 /* Would be faster if we didn't have to do this. Can we chop the 3404 (displayable) text so that we don't need to do this ever? 3405 */ 3406 3407 int extendedPaddingTop = getExtendedPaddingTop(); 3408 int extendedPaddingBottom = getExtendedPaddingBottom(); 3409 3410 float clipLeft = compoundPaddingLeft + scrollX; 3411 float clipTop = extendedPaddingTop + scrollY; 3412 float clipRight = right - left - compoundPaddingRight + scrollX; 3413 float clipBottom = bottom - top - extendedPaddingBottom + scrollY; 3414 3415 if (mShadowRadius != 0) { 3416 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 3417 clipRight += Math.max(0, mShadowDx + mShadowRadius); 3418 3419 clipTop += Math.min(0, mShadowDy - mShadowRadius); 3420 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 3421 } 3422 3423 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 3424 3425 int voffsetText = 0; 3426 int voffsetCursor = 0; 3427 3428 // translate in by our padding 3429 { 3430 /* shortcircuit calling getVerticaOffset() */ 3431 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3432 voffsetText = getVerticalOffset(false); 3433 voffsetCursor = getVerticalOffset(true); 3434 } 3435 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 3436 } 3437 3438 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3439 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 3440 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 3441 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - 3442 getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); 3443 } 3444 3445 if (mMarquee != null && mMarquee.isRunning()) { 3446 canvas.translate(-mMarquee.mScroll, 0.0f); 3447 } 3448 } 3449 3450 Path highlight = null; 3451 int selStart = -1, selEnd = -1; 3452 3453 // If there is no movement method, then there can be no selection. 3454 // Check that first and attempt to skip everything having to do with 3455 // the cursor. 3456 // XXX This is not strictly true -- a program could set the 3457 // selection manually if it really wanted to. 3458 if (mMovement != null && (isFocused() || isPressed())) { 3459 selStart = Selection.getSelectionStart(mText); 3460 selEnd = Selection.getSelectionEnd(mText); 3461 3462 if (mCursorVisible && selStart >= 0 && isEnabled()) { 3463 if (mHighlightPath == null) 3464 mHighlightPath = new Path(); 3465 3466 if (selStart == selEnd) { 3467 if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) 3468 < BLINK) { 3469 if (mHighlightPathBogus) { 3470 mHighlightPath.reset(); 3471 mLayout.getCursorPath(selStart, mHighlightPath, mText); 3472 mHighlightPathBogus = false; 3473 } 3474 3475 // XXX should pass to skin instead of drawing directly 3476 mHighlightPaint.setColor(cursorcolor); 3477 mHighlightPaint.setStyle(Paint.Style.STROKE); 3478 3479 highlight = mHighlightPath; 3480 } 3481 } else { 3482 if (mHighlightPathBogus) { 3483 mHighlightPath.reset(); 3484 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 3485 mHighlightPathBogus = false; 3486 } 3487 3488 // XXX should pass to skin instead of drawing directly 3489 mHighlightPaint.setColor(mHighlightColor); 3490 mHighlightPaint.setStyle(Paint.Style.FILL); 3491 3492 highlight = mHighlightPath; 3493 } 3494 } 3495 } 3496 3497 /* Comment out until we decide what to do about animations 3498 boolean isLinearTextOn = false; 3499 if (currentTransformation != null) { 3500 isLinearTextOn = mTextPaint.isLinearTextOn(); 3501 Matrix m = currentTransformation.getMatrix(); 3502 if (!m.isIdentity()) { 3503 // mTextPaint.setLinearTextOn(true); 3504 } 3505 } 3506 */ 3507 3508 InputMethodManager imm = InputMethodManager.peekInstance(); 3509 if (highlight != null && mInputMethodState != null 3510 && !mInputMethodState.mBatchEditing && imm != null) { 3511 if (imm.isActive(this)) { 3512 int candStart = -1; 3513 int candEnd = -1; 3514 if (mText instanceof Spannable) { 3515 Spannable sp = (Spannable)mText; 3516 candStart = EditableInputConnection.getComposingSpanStart(sp); 3517 candEnd = EditableInputConnection.getComposingSpanEnd(sp); 3518 } 3519 imm.updateSelection(this, selStart, selEnd, candStart, candEnd); 3520 } 3521 3522 if (imm.isWatchingCursor(this)) { 3523 final InputMethodState ims = mInputMethodState; 3524 highlight.computeBounds(ims.mTmpRectF, true); 3525 ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; 3526 3527 canvas.getMatrix().mapPoints(ims.mTmpOffset); 3528 ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); 3529 3530 ims.mTmpRectF.offset(0, voffsetCursor - voffsetText); 3531 3532 ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), 3533 (int)(ims.mTmpRectF.top + 0.5), 3534 (int)(ims.mTmpRectF.right + 0.5), 3535 (int)(ims.mTmpRectF.bottom + 0.5)); 3536 3537 imm.updateCursor(this, 3538 ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, 3539 ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); 3540 } 3541 } 3542 3543 layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); 3544 3545 /* Comment out until we decide what to do about animations 3546 if (currentTransformation != null) { 3547 mTextPaint.setLinearTextOn(isLinearTextOn); 3548 } 3549 */ 3550 3551 canvas.restore(); 3552 } 3553 3554 @Override 3555 public void getFocusedRect(Rect r) { 3556 if (mLayout == null) { 3557 super.getFocusedRect(r); 3558 return; 3559 } 3560 3561 int sel = getSelectionEnd(); 3562 if (sel < 0) { 3563 super.getFocusedRect(r); 3564 return; 3565 } 3566 3567 int line = mLayout.getLineForOffset(sel); 3568 r.top = mLayout.getLineTop(line); 3569 r.bottom = mLayout.getLineBottom(line); 3570 3571 r.left = (int) mLayout.getPrimaryHorizontal(sel); 3572 r.right = r.left + 1; 3573 3574 // Adjust for padding and gravity. 3575 int paddingLeft = getCompoundPaddingLeft(); 3576 int paddingTop = getExtendedPaddingTop(); 3577 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3578 paddingTop += getVerticalOffset(false); 3579 } 3580 r.offset(paddingLeft, paddingTop); 3581 } 3582 3583 /** 3584 * Return the number of lines of text, or 0 if the internal Layout has not 3585 * been built. 3586 */ 3587 public int getLineCount() { 3588 return mLayout != null ? mLayout.getLineCount() : 0; 3589 } 3590 3591 /** 3592 * Return the baseline for the specified line (0...getLineCount() - 1) 3593 * If bounds is not null, return the top, left, right, bottom extents 3594 * of the specified line in it. If the internal Layout has not been built, 3595 * return 0 and set bounds to (0, 0, 0, 0) 3596 * @param line which line to examine (0..getLineCount() - 1) 3597 * @param bounds Optional. If not null, it returns the extent of the line 3598 * @return the Y-coordinate of the baseline 3599 */ 3600 public int getLineBounds(int line, Rect bounds) { 3601 if (mLayout == null) { 3602 if (bounds != null) { 3603 bounds.set(0, 0, 0, 0); 3604 } 3605 return 0; 3606 } 3607 else { 3608 int baseline = mLayout.getLineBounds(line, bounds); 3609 3610 int voffset = getExtendedPaddingTop(); 3611 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3612 voffset += getVerticalOffset(true); 3613 } 3614 if (bounds != null) { 3615 bounds.offset(getCompoundPaddingLeft(), voffset); 3616 } 3617 return baseline + voffset; 3618 } 3619 } 3620 3621 @Override 3622 public int getBaseline() { 3623 if (mLayout == null) { 3624 return super.getBaseline(); 3625 } 3626 3627 int voffset = 0; 3628 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3629 voffset = getVerticalOffset(true); 3630 } 3631 3632 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 3633 } 3634 3635 @Override 3636 public boolean onKeyDown(int keyCode, KeyEvent event) { 3637 int which = doKeyDown(keyCode, event); 3638 if (which == 0) { 3639 // Go through default dispatching. 3640 return super.onKeyDown(keyCode, event); 3641 } 3642 3643 return true; 3644 } 3645 3646 @Override 3647 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 3648 KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); 3649 3650 int which = doKeyDown(keyCode, down); 3651 if (which == 0) { 3652 // Go through default dispatching. 3653 return super.onKeyMultiple(keyCode, repeatCount, event); 3654 } 3655 3656 // We are going to dispatch the remaining events to either the input 3657 // or movement method. To do this, we will just send a repeated stream 3658 // of down and up events until we have done the complete repeatCount. 3659 // It would be nice if those interfaces had an onKeyMultiple() method, 3660 // but adding that is a more complicated change. 3661 KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); 3662 if (which == 1) { 3663 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 3664 while (--repeatCount > 0) { 3665 mInput.onKeyDown(this, (Editable)mText, keyCode, down); 3666 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 3667 } 3668 if (mError != null && !mErrorWasChanged) { 3669 setError(null, null); 3670 } 3671 3672 } else if (which == 2) { 3673 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 3674 while (--repeatCount > 0) { 3675 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 3676 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 3677 } 3678 } 3679 3680 return true; 3681 } 3682 3683 private int doKeyDown(int keyCode, KeyEvent event) { 3684 if (!isEnabled()) { 3685 return 0; 3686 } 3687 3688 switch (keyCode) { 3689 case KeyEvent.KEYCODE_DPAD_CENTER: 3690 case KeyEvent.KEYCODE_ENTER: 3691 if (mSingleLine && mInput != null) { 3692 return 0; 3693 } 3694 } 3695 3696 if (mInput != null) { 3697 /* 3698 * Keep track of what the error was before doing the input 3699 * so that if an input filter changed the error, we leave 3700 * that error showing. Otherwise, we take down whatever 3701 * error was showing when the user types something. 3702 */ 3703 mErrorWasChanged = false; 3704 3705 if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) { 3706 if (mError != null && !mErrorWasChanged) { 3707 setError(null, null); 3708 } 3709 return 1; 3710 } 3711 } 3712 3713 // bug 650865: sometimes we get a key event before a layout. 3714 // don't try to move around if we don't know the layout. 3715 3716 if (mMovement != null && mLayout != null) 3717 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) 3718 return 2; 3719 3720 return 0; 3721 } 3722 3723 @Override 3724 public boolean onKeyUp(int keyCode, KeyEvent event) { 3725 if (!isEnabled()) { 3726 return super.onKeyUp(keyCode, event); 3727 } 3728 3729 switch (keyCode) { 3730 case KeyEvent.KEYCODE_DPAD_CENTER: 3731 case KeyEvent.KEYCODE_ENTER: 3732 if (mSingleLine && mInput != null) { 3733 /* 3734 * If there is a click listener, just call through to 3735 * super, which will invoke it. 3736 * 3737 * If there isn't a click listener, try to advance focus, 3738 * but still call through to super, which will reset the 3739 * pressed state and longpress state. (It will also 3740 * call performClick(), but that won't do anything in 3741 * this case.) 3742 */ 3743 if (mOnClickListener == null) { 3744 View v = focusSearch(FOCUS_DOWN); 3745 3746 if (v != null) { 3747 if (!v.requestFocus(FOCUS_DOWN)) { 3748 throw new IllegalStateException("focus search returned a view " + 3749 "that wasn't able to take focus!"); 3750 } 3751 3752 /* 3753 * Return true because we handled the key; super 3754 * will return false because there was no click 3755 * listener. 3756 */ 3757 super.onKeyUp(keyCode, event); 3758 return true; 3759 } 3760 } 3761 3762 return super.onKeyUp(keyCode, event); 3763 } 3764 } 3765 3766 if (mInput != null) 3767 if (mInput.onKeyUp(this, (Editable) mText, keyCode, event)) 3768 return true; 3769 3770 if (mMovement != null && mLayout != null) 3771 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 3772 return true; 3773 3774 return super.onKeyUp(keyCode, event); 3775 } 3776 3777 @Override public boolean onCheckIsTextEditor() { 3778 return mInputType != EditorInfo.TYPE_NULL; 3779 } 3780 3781 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 3782 if (onCheckIsTextEditor()) { 3783 if (mInputMethodState == null) { 3784 mInputMethodState = new InputMethodState(); 3785 } 3786 outAttrs.inputType = mInputType; 3787 outAttrs.hintText = mHint; 3788 if (mInputContentType != null) { 3789 outAttrs.privateContentType = mInputContentType.privateContentType; 3790 outAttrs.extras = mInputContentType.extras; 3791 } 3792 if (mText instanceof Editable) { 3793 InputConnection ic = new EditableInputConnection(this); 3794 outAttrs.initialSelStart = Selection.getSelectionStart(mText); 3795 outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); 3796 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); 3797 return ic; 3798 } 3799 } 3800 return null; 3801 } 3802 3803 /** 3804 * If this TextView contains editable content, extract a portion of it 3805 * based on the information in <var>request</var> in to <var>outText</var>. 3806 * @return Returns true if the text is editable and was successfully 3807 * extracted, else false. 3808 */ 3809 public boolean extractText(ExtractedTextRequest request, 3810 ExtractedText outText) { 3811 Editable content = getEditableText(); 3812 if (content != null) { 3813 outText.text = content.subSequence(0, content.length()); 3814 outText.startOffset = 0; 3815 outText.selectionStart = Selection.getSelectionStart(content); 3816 outText.selectionEnd = Selection.getSelectionEnd(content); 3817 return true; 3818 } 3819 return false; 3820 } 3821 3822 void reportExtractedText() { 3823 if (mInputMethodState != null) { 3824 final ExtractedTextRequest req = mInputMethodState.mExtracting; 3825 if (req != null) { 3826 InputMethodManager imm = InputMethodManager.peekInstance(); 3827 if (imm != null) { 3828 if (extractText(req, mInputMethodState.mTmpExtracted)) { 3829 imm.updateExtractedText(this, req.token, 3830 mInputMethodState.mTmpExtracted); 3831 } 3832 } 3833 } 3834 } 3835 } 3836 3837 /** 3838 * Apply to this text view the given extracted text, as previously 3839 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 3840 */ 3841 public void setExtractedText(ExtractedText text) { 3842 setText(text.text, TextView.BufferType.EDITABLE); 3843 Selection.setSelection((Spannable)getText(), 3844 text.selectionStart, text.selectionEnd); 3845 } 3846 3847 /** 3848 * @hide 3849 */ 3850 public void setExtracting(ExtractedTextRequest req) { 3851 if (mInputMethodState != null) { 3852 mInputMethodState.mExtracting = req; 3853 } 3854 } 3855 3856 /** 3857 * Called by the framework in response to a text completion from 3858 * the current input method, provided by it calling 3859 * {@link InputConnection#commitCompletion 3860 * InputConnection.commitCompletion()}. The default implementation does 3861 * nothing; text views that are supporting auto-completion should override 3862 * this to do their desired behavior. 3863 * 3864 * @param text The auto complete text the user has selected. 3865 */ 3866 public void onCommitCompletion(CompletionInfo text) { 3867 } 3868 3869 /** 3870 * Called by the framework in response to a request to begin a batch 3871 * of edit operations from the current input method, as a result of 3872 * it calling {@link InputConnection#beginBatchEdit 3873 * InputConnection.beginBatchEdit()}. The default implementation sets 3874 * up the TextView's internal state to take care of this; if overriding 3875 * you should call through to the super class. 3876 */ 3877 public void onBeginBatchEdit() { 3878 if (mInputMethodState != null) { 3879 // XXX we should be smarter here, such as not doing invalidates 3880 // until all edits are done. 3881 mInputMethodState.mBatchEditing = true; 3882 } 3883 } 3884 3885 /** 3886 * Called by the framework in response to a request to end a batch 3887 * of edit operations from the current input method, as a result of 3888 * it calling {@link InputConnection#endBatchEdit 3889 * InputConnection.endBatchEdit()}. The default implementation sets 3890 * up the TextView's internal state to take care of this; if overriding 3891 * you should call through to the super class. 3892 */ 3893 public void onEndBatchEdit() { 3894 if (mInputMethodState != null) { 3895 mInputMethodState.mBatchEditing = false; 3896 // Cheezy way to get us to report the current cursor location. 3897 invalidateCursor(); 3898 } 3899 } 3900 3901 /** 3902 * Called by the framework in response to a private command from the 3903 * current method, provided by it calling 3904 * {@link InputConnection#performPrivateCommand 3905 * InputConnection.performPrivateCommand()}. 3906 * 3907 * @param action The action name of the command. 3908 * @param data Any additional data for the command. This may be null. 3909 * @return Return true if you handled the command, else false. 3910 */ 3911 public boolean onPrivateIMECommand(String action, Bundle data) { 3912 return false; 3913 } 3914 3915 private void nullLayouts() { 3916 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 3917 mSavedLayout = (BoringLayout) mLayout; 3918 } 3919 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 3920 mSavedHintLayout = (BoringLayout) mHintLayout; 3921 } 3922 3923 mLayout = mHintLayout = null; 3924 } 3925 3926 /** 3927 * Make a new Layout based on the already-measured size of the view, 3928 * on the assumption that it was measured correctly at some point. 3929 */ 3930 private void assumeLayout() { 3931 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 3932 3933 if (width < 1) { 3934 width = 0; 3935 } 3936 3937 int physicalWidth = width; 3938 3939 if (mHorizontallyScrolling) { 3940 width = VERY_WIDE; 3941 } 3942 3943 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 3944 physicalWidth, false); 3945 } 3946 3947 /** 3948 * The width passed in is now the desired layout width, 3949 * not the full view width with padding. 3950 * {@hide} 3951 */ 3952 protected void makeNewLayout(int w, int hintWidth, 3953 BoringLayout.Metrics boring, 3954 BoringLayout.Metrics hintBoring, 3955 int ellipsisWidth, boolean bringIntoView) { 3956 stopMarquee(); 3957 3958 mHighlightPathBogus = true; 3959 3960 if (w < 0) { 3961 w = 0; 3962 } 3963 if (hintWidth < 0) { 3964 hintWidth = 0; 3965 } 3966 3967 Layout.Alignment alignment; 3968 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 3969 case Gravity.CENTER_HORIZONTAL: 3970 alignment = Layout.Alignment.ALIGN_CENTER; 3971 break; 3972 3973 case Gravity.RIGHT: 3974 alignment = Layout.Alignment.ALIGN_OPPOSITE; 3975 break; 3976 3977 default: 3978 alignment = Layout.Alignment.ALIGN_NORMAL; 3979 } 3980 3981 if (mText instanceof Spannable) { 3982 mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w, 3983 alignment, mSpacingMult, 3984 mSpacingAdd, mIncludePad, mEllipsize, 3985 ellipsisWidth); 3986 } else { 3987 if (boring == UNKNOWN_BORING) { 3988 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 3989 mBoring); 3990 if (boring != null) { 3991 mBoring = boring; 3992 } 3993 } 3994 3995 if (boring != null) { 3996 if (boring.width <= w && 3997 (mEllipsize == null || boring.width <= ellipsisWidth)) { 3998 if (mSavedLayout != null) { 3999 mLayout = mSavedLayout. 4000 replaceOrMake(mTransformed, mTextPaint, 4001 w, alignment, mSpacingMult, mSpacingAdd, 4002 boring, mIncludePad); 4003 } else { 4004 mLayout = BoringLayout.make(mTransformed, mTextPaint, 4005 w, alignment, mSpacingMult, mSpacingAdd, 4006 boring, mIncludePad); 4007 } 4008 // Log.e("aaa", "Boring: " + mTransformed); 4009 4010 mSavedLayout = (BoringLayout) mLayout; 4011 } else if (mEllipsize != null && boring.width <= w) { 4012 if (mSavedLayout != null) { 4013 mLayout = mSavedLayout. 4014 replaceOrMake(mTransformed, mTextPaint, 4015 w, alignment, mSpacingMult, mSpacingAdd, 4016 boring, mIncludePad, mEllipsize, 4017 ellipsisWidth); 4018 } else { 4019 mLayout = BoringLayout.make(mTransformed, mTextPaint, 4020 w, alignment, mSpacingMult, mSpacingAdd, 4021 boring, mIncludePad, mEllipsize, 4022 ellipsisWidth); 4023 } 4024 } else if (mEllipsize != null) { 4025 mLayout = new StaticLayout(mTransformed, 4026 0, mTransformed.length(), 4027 mTextPaint, w, alignment, mSpacingMult, 4028 mSpacingAdd, mIncludePad, mEllipsize, 4029 ellipsisWidth); 4030 } else { 4031 mLayout = new StaticLayout(mTransformed, mTextPaint, 4032 w, alignment, mSpacingMult, mSpacingAdd, 4033 mIncludePad); 4034 // Log.e("aaa", "Boring but wide: " + mTransformed); 4035 } 4036 } else if (mEllipsize != null) { 4037 mLayout = new StaticLayout(mTransformed, 4038 0, mTransformed.length(), 4039 mTextPaint, w, alignment, mSpacingMult, 4040 mSpacingAdd, mIncludePad, mEllipsize, 4041 ellipsisWidth); 4042 } else { 4043 mLayout = new StaticLayout(mTransformed, mTextPaint, 4044 w, alignment, mSpacingMult, mSpacingAdd, 4045 mIncludePad); 4046 } 4047 } 4048 4049 mHintLayout = null; 4050 4051 if (mHint != null) { 4052 if (hintBoring == UNKNOWN_BORING) { 4053 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4054 mHintBoring); 4055 if (hintBoring != null) { 4056 mHintBoring = hintBoring; 4057 } 4058 } 4059 4060 if (hintBoring != null) { 4061 if (hintBoring.width <= hintWidth) { 4062 if (mSavedHintLayout != null) { 4063 mHintLayout = mSavedHintLayout. 4064 replaceOrMake(mHint, mTextPaint, 4065 hintWidth, alignment, mSpacingMult, 4066 mSpacingAdd, hintBoring, mIncludePad); 4067 } else { 4068 mHintLayout = BoringLayout.make(mHint, mTextPaint, 4069 hintWidth, alignment, mSpacingMult, 4070 mSpacingAdd, hintBoring, mIncludePad); 4071 } 4072 4073 mSavedHintLayout = (BoringLayout) mHintLayout; 4074 } else { 4075 mHintLayout = new StaticLayout(mHint, mTextPaint, 4076 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4077 mIncludePad); 4078 } 4079 } else { 4080 mHintLayout = new StaticLayout(mHint, mTextPaint, 4081 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4082 mIncludePad); 4083 } 4084 } 4085 4086 if (bringIntoView) { 4087 registerForPreDraw(); 4088 } 4089 4090 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4091 final int height = mLayoutParams.height; 4092 // If the size of the view does not depend on the size of the text, try to 4093 // start the marquee immediately 4094 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { 4095 startMarquee(); 4096 } else { 4097 // Defer the start of the marquee until we know our width (see setFrame()) 4098 mRestartMarquee = true; 4099 } 4100 } 4101 } 4102 4103 private static int desired(Layout layout) { 4104 int n = layout.getLineCount(); 4105 CharSequence text = layout.getText(); 4106 float max = 0; 4107 4108 // if any line was wrapped, we can't use it. 4109 // but it's ok for the last line not to have a newline 4110 4111 for (int i = 0; i < n - 1; i++) { 4112 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 4113 return -1; 4114 } 4115 4116 for (int i = 0; i < n; i++) { 4117 max = Math.max(max, layout.getLineWidth(i)); 4118 } 4119 4120 return (int) FloatMath.ceil(max); 4121 } 4122 4123 /** 4124 * Set whether the TextView includes extra top and bottom padding to make 4125 * room for accents that go above the normal ascent and descent. 4126 * The default is true. 4127 * 4128 * @attr ref android.R.styleable#TextView_includeFontPadding 4129 */ 4130 public void setIncludeFontPadding(boolean includepad) { 4131 mIncludePad = includepad; 4132 4133 if (mLayout != null) { 4134 nullLayouts(); 4135 requestLayout(); 4136 invalidate(); 4137 } 4138 } 4139 4140 private static final BoringLayout.Metrics UNKNOWN_BORING = 4141 new BoringLayout.Metrics(); 4142 4143 @Override 4144 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 4145 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 4146 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 4147 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 4148 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 4149 4150 int width; 4151 int height; 4152 4153 BoringLayout.Metrics boring = UNKNOWN_BORING; 4154 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 4155 4156 int des = -1; 4157 boolean fromexisting = false; 4158 4159 if (widthMode == MeasureSpec.EXACTLY) { 4160 // Parent has told us how big to be. So be it. 4161 width = widthSize; 4162 } else { 4163 if (mLayout != null && mEllipsize == null) { 4164 des = desired(mLayout); 4165 } 4166 4167 if (des < 0) { 4168 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 4169 mBoring); 4170 if (boring != null) { 4171 mBoring = boring; 4172 } 4173 } else { 4174 fromexisting = true; 4175 } 4176 4177 if (boring == null || boring == UNKNOWN_BORING) { 4178 if (des < 0) { 4179 des = (int) FloatMath.ceil(Layout. 4180 getDesiredWidth(mTransformed, mTextPaint)); 4181 } 4182 4183 width = des; 4184 } else { 4185 width = boring.width; 4186 } 4187 4188 final Drawables dr = mDrawables; 4189 if (dr != null) { 4190 width = Math.max(width, dr.mDrawableWidthTop); 4191 width = Math.max(width, dr.mDrawableWidthBottom); 4192 } 4193 4194 if (mHint != null) { 4195 int hintDes = -1; 4196 int hintWidth; 4197 4198 if (mHintLayout != null) { 4199 hintDes = desired(mHintLayout); 4200 } 4201 4202 if (hintDes < 0) { 4203 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4204 mHintBoring); 4205 if (hintBoring != null) { 4206 mHintBoring = hintBoring; 4207 } 4208 } 4209 4210 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 4211 if (hintDes < 0) { 4212 hintDes = (int) FloatMath.ceil(Layout. 4213 getDesiredWidth(mHint, mTextPaint)); 4214 } 4215 4216 hintWidth = hintDes; 4217 } else { 4218 hintWidth = hintBoring.width; 4219 } 4220 4221 if (hintWidth > width) { 4222 width = hintWidth; 4223 } 4224 } 4225 4226 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 4227 4228 if (mMaxWidthMode == EMS) { 4229 width = Math.min(width, mMaxWidth * getLineHeight()); 4230 } else { 4231 width = Math.min(width, mMaxWidth); 4232 } 4233 4234 if (mMinWidthMode == EMS) { 4235 width = Math.max(width, mMinWidth * getLineHeight()); 4236 } else { 4237 width = Math.max(width, mMinWidth); 4238 } 4239 4240 // Check against our minimum width 4241 width = Math.max(width, getSuggestedMinimumWidth()); 4242 4243 if (widthMode == MeasureSpec.AT_MOST) { 4244 width = Math.min(widthSize, width); 4245 } 4246 } 4247 4248 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4249 int unpaddedWidth = want; 4250 int hintWant = want; 4251 4252 if (mHorizontallyScrolling) 4253 want = VERY_WIDE; 4254 4255 int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth(); 4256 4257 if (mLayout == null) { 4258 makeNewLayout(want, hintWant, boring, hintBoring, 4259 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 4260 false); 4261 } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) || 4262 (mLayout.getEllipsizedWidth() != 4263 width - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 4264 if (mHint == null && mEllipsize == null && 4265 want > mLayout.getWidth() && 4266 (mLayout instanceof BoringLayout || 4267 (fromexisting && des >= 0 && des <= want))) { 4268 mLayout.increaseWidthTo(want); 4269 } else { 4270 makeNewLayout(want, hintWant, boring, hintBoring, 4271 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 4272 false); 4273 } 4274 } else { 4275 // Width has not changed. 4276 } 4277 4278 if (heightMode == MeasureSpec.EXACTLY) { 4279 // Parent has told us how big to be. So be it. 4280 height = heightSize; 4281 mDesiredHeightAtMeasure = -1; 4282 } else { 4283 int desired = getDesiredHeight(); 4284 4285 height = desired; 4286 mDesiredHeightAtMeasure = desired; 4287 4288 if (heightMode == MeasureSpec.AT_MOST) { 4289 height = Math.min(desired, height); 4290 } 4291 } 4292 4293 int unpaddedHeight = height - getCompoundPaddingTop() - 4294 getCompoundPaddingBottom(); 4295 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 4296 unpaddedHeight = Math.min(unpaddedHeight, 4297 mLayout.getLineTop(mMaximum)); 4298 } 4299 4300 /* 4301 * We didn't let makeNewLayout() register to bring the cursor into view, 4302 * so do it here if there is any possibility that it is needed. 4303 */ 4304 if (mMovement != null || 4305 mLayout.getWidth() > unpaddedWidth || 4306 mLayout.getHeight() > unpaddedHeight) { 4307 registerForPreDraw(); 4308 } else { 4309 scrollTo(0, 0); 4310 } 4311 4312 setMeasuredDimension(width, height); 4313 } 4314 4315 private int getDesiredHeight() { 4316 return Math.max(getDesiredHeight(mLayout, true), 4317 getDesiredHeight(mHintLayout, false)); 4318 } 4319 4320 private int getDesiredHeight(Layout layout, boolean cap) { 4321 if (layout == null) { 4322 return 0; 4323 } 4324 4325 int linecount = layout.getLineCount(); 4326 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 4327 int desired = layout.getLineTop(linecount); 4328 4329 final Drawables dr = mDrawables; 4330 if (dr != null) { 4331 desired = Math.max(desired, dr.mDrawableHeightLeft); 4332 desired = Math.max(desired, dr.mDrawableHeightRight); 4333 } 4334 4335 desired += pad; 4336 4337 if (mMaxMode == LINES) { 4338 /* 4339 * Don't cap the hint to a certain number of lines. 4340 * (Do cap it, though, if we have a maximum pixel height.) 4341 */ 4342 if (cap) { 4343 if (linecount > mMaximum) { 4344 desired = layout.getLineTop(mMaximum) + 4345 layout.getBottomPadding(); 4346 4347 if (dr != null) { 4348 desired = Math.max(desired, dr.mDrawableHeightLeft); 4349 desired = Math.max(desired, dr.mDrawableHeightRight); 4350 } 4351 4352 desired += pad; 4353 linecount = mMaximum; 4354 } 4355 } 4356 } else { 4357 desired = Math.min(desired, mMaximum); 4358 } 4359 4360 if (mMinMode == LINES) { 4361 if (linecount < mMinimum) { 4362 desired += getLineHeight() * (mMinimum - linecount); 4363 } 4364 } else { 4365 desired = Math.max(desired, mMinimum); 4366 } 4367 4368 // Check against our minimum height 4369 desired = Math.max(desired, getSuggestedMinimumHeight()); 4370 4371 return desired; 4372 } 4373 4374 /** 4375 * Check whether a change to the existing text layout requires a 4376 * new view layout. 4377 */ 4378 private void checkForResize() { 4379 boolean sizeChanged = false; 4380 4381 if (mLayout != null) { 4382 // Check if our width changed 4383 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 4384 sizeChanged = true; 4385 invalidate(); 4386 } 4387 4388 // Check if our height changed 4389 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 4390 int desiredHeight = getDesiredHeight(); 4391 4392 if (desiredHeight != this.getHeight()) { 4393 sizeChanged = true; 4394 } 4395 } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) { 4396 if (mDesiredHeightAtMeasure >= 0) { 4397 int desiredHeight = getDesiredHeight(); 4398 4399 if (desiredHeight != mDesiredHeightAtMeasure) { 4400 sizeChanged = true; 4401 } 4402 } 4403 } 4404 } 4405 4406 if (sizeChanged) { 4407 requestLayout(); 4408 // caller will have already invalidated 4409 } 4410 } 4411 4412 /** 4413 * Check whether entirely new text requires a new view layout 4414 * or merely a new text layout. 4415 */ 4416 private void checkForRelayout() { 4417 // If we have a fixed width, we can just swap in a new text layout 4418 // if the text height stays the same or if the view height is fixed. 4419 4420 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 4421 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 4422 (mHint == null || mHintLayout != null) && 4423 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 4424 // Static width, so try making a new text layout. 4425 4426 int oldht = mLayout.getHeight(); 4427 int want = mLayout.getWidth(); 4428 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 4429 4430 /* 4431 * No need to bring the text into view, since the size is not 4432 * changing (unless we do the requestLayout(), in which case it 4433 * will happen at measure). 4434 */ 4435 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 4436 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 4437 4438 // In a fixed-height view, so use our new text layout. 4439 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 4440 mLayoutParams.height != LayoutParams.FILL_PARENT) { 4441 invalidate(); 4442 return; 4443 } 4444 4445 // Dynamic height, but height has stayed the same, 4446 // so use our new text layout. 4447 if (mLayout.getHeight() == oldht && 4448 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 4449 invalidate(); 4450 return; 4451 } 4452 4453 // We lose: the height has changed and we have a dynamic height. 4454 // Request a new view layout using our new text layout. 4455 requestLayout(); 4456 invalidate(); 4457 } else { 4458 // Dynamic width, so we have no choice but to request a new 4459 // view layout with a new text layout. 4460 4461 nullLayouts(); 4462 requestLayout(); 4463 invalidate(); 4464 } 4465 } 4466 4467 /** 4468 * Returns true if anything changed. 4469 */ 4470 private boolean bringTextIntoView() { 4471 int line = 0; 4472 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4473 line = mLayout.getLineCount() - 1; 4474 } 4475 4476 Layout.Alignment a = mLayout.getParagraphAlignment(line); 4477 int dir = mLayout.getParagraphDirection(line); 4478 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4479 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 4480 int ht = mLayout.getHeight(); 4481 4482 int scrollx, scrolly; 4483 4484 if (a == Layout.Alignment.ALIGN_CENTER) { 4485 /* 4486 * Keep centered if possible, or, if it is too wide to fit, 4487 * keep leading edge in view. 4488 */ 4489 4490 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4491 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4492 4493 if (right - left < hspace) { 4494 scrollx = (right + left) / 2 - hspace / 2; 4495 } else { 4496 if (dir < 0) { 4497 scrollx = right - hspace; 4498 } else { 4499 scrollx = left; 4500 } 4501 } 4502 } else if (a == Layout.Alignment.ALIGN_NORMAL) { 4503 /* 4504 * Keep leading edge in view. 4505 */ 4506 4507 if (dir < 0) { 4508 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4509 scrollx = right - hspace; 4510 } else { 4511 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4512 } 4513 } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ { 4514 /* 4515 * Keep trailing edge in view. 4516 */ 4517 4518 if (dir < 0) { 4519 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4520 } else { 4521 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4522 scrollx = right - hspace; 4523 } 4524 } 4525 4526 if (ht < vspace) { 4527 scrolly = 0; 4528 } else { 4529 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4530 scrolly = ht - vspace; 4531 } else { 4532 scrolly = 0; 4533 } 4534 } 4535 4536 if (scrollx != mScrollX || scrolly != mScrollY) { 4537 scrollTo(scrollx, scrolly); 4538 return true; 4539 } else { 4540 return false; 4541 } 4542 } 4543 4544 /** 4545 * Returns true if anything changed. 4546 */ 4547 private boolean bringPointIntoView(int offset) { 4548 boolean changed = false; 4549 4550 int line = mLayout.getLineForOffset(offset); 4551 4552 // FIXME: Is it okay to truncate this, or should we round? 4553 final int x = (int)mLayout.getPrimaryHorizontal(offset); 4554 final int top = mLayout.getLineTop(line); 4555 final int bottom = mLayout.getLineTop(line+1); 4556 4557 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4558 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4559 int ht = mLayout.getHeight(); 4560 4561 int grav; 4562 4563 switch (mLayout.getParagraphAlignment(line)) { 4564 case ALIGN_NORMAL: 4565 grav = 1; 4566 break; 4567 4568 case ALIGN_OPPOSITE: 4569 grav = -1; 4570 break; 4571 4572 default: 4573 grav = 0; 4574 } 4575 4576 grav *= mLayout.getParagraphDirection(line); 4577 4578 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4579 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 4580 4581 int hslack = (bottom - top) / 2; 4582 int vslack = hslack; 4583 4584 if (vslack > vspace / 4) 4585 vslack = vspace / 4; 4586 if (hslack > hspace / 4) 4587 hslack = hspace / 4; 4588 4589 int hs = mScrollX; 4590 int vs = mScrollY; 4591 4592 if (top - vs < vslack) 4593 vs = top - vslack; 4594 if (bottom - vs > vspace - vslack) 4595 vs = bottom - (vspace - vslack); 4596 if (ht - vs < vspace) 4597 vs = ht - vspace; 4598 if (0 - vs > 0) 4599 vs = 0; 4600 4601 if (grav != 0) { 4602 if (x - hs < hslack) { 4603 hs = x - hslack; 4604 } 4605 if (x - hs > hspace - hslack) { 4606 hs = x - (hspace - hslack); 4607 } 4608 } 4609 4610 if (grav < 0) { 4611 if (left - hs > 0) 4612 hs = left; 4613 if (right - hs < hspace) 4614 hs = right - hspace; 4615 } else if (grav > 0) { 4616 if (right - hs < hspace) 4617 hs = right - hspace; 4618 if (left - hs > 0) 4619 hs = left; 4620 } else /* grav == 0 */ { 4621 if (right - left <= hspace) { 4622 /* 4623 * If the entire text fits, center it exactly. 4624 */ 4625 hs = left - (hspace - (right - left)) / 2; 4626 } else if (x > right - hslack) { 4627 /* 4628 * If we are near the right edge, keep the right edge 4629 * at the edge of the view. 4630 */ 4631 hs = right - hspace; 4632 } else if (x < left + hslack) { 4633 /* 4634 * If we are near the left edge, keep the left edge 4635 * at the edge of the view. 4636 */ 4637 hs = left; 4638 } else if (left > hs) { 4639 /* 4640 * Is there whitespace visible at the left? Fix it if so. 4641 */ 4642 hs = left; 4643 } else if (right < hs + hspace) { 4644 /* 4645 * Is there whitespace visible at the right? Fix it if so. 4646 */ 4647 hs = right - hspace; 4648 } else { 4649 /* 4650 * Otherwise, float as needed. 4651 */ 4652 if (x - hs < hslack) { 4653 hs = x - hslack; 4654 } 4655 if (x - hs > hspace - hslack) { 4656 hs = x - (hspace - hslack); 4657 } 4658 } 4659 } 4660 4661 if (hs != mScrollX || vs != mScrollY) { 4662 if (mScroller == null) { 4663 scrollTo(hs, vs); 4664 } else { 4665 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 4666 int dx = hs - mScrollX; 4667 int dy = vs - mScrollY; 4668 4669 if (duration > ANIMATED_SCROLL_GAP) { 4670 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 4671 invalidate(); 4672 } else { 4673 if (!mScroller.isFinished()) { 4674 mScroller.abortAnimation(); 4675 } 4676 4677 scrollBy(dx, dy); 4678 } 4679 4680 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 4681 } 4682 4683 changed = true; 4684 } 4685 4686 if (isFocused()) { 4687 // This offsets because getInterestingRect() is in terms of 4688 // viewport coordinates, but requestRectangleOnScreen() 4689 // is in terms of content coordinates. 4690 4691 Rect r = new Rect(); 4692 getInterestingRect(r, x, top, bottom, line); 4693 r.offset(mScrollX, mScrollY); 4694 4695 if (requestRectangleOnScreen(r)) { 4696 changed = true; 4697 } 4698 } 4699 4700 return changed; 4701 } 4702 4703 @Override 4704 public void computeScroll() { 4705 if (mScroller != null) { 4706 if (mScroller.computeScrollOffset()) { 4707 mScrollX = mScroller.getCurrX(); 4708 mScrollY = mScroller.getCurrY(); 4709 postInvalidate(); // So we draw again 4710 } 4711 } 4712 } 4713 4714 private void getInterestingRect(Rect r, int h, int top, int bottom, 4715 int line) { 4716 int paddingTop = getExtendedPaddingTop(); 4717 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4718 paddingTop += getVerticalOffset(false); 4719 } 4720 top += paddingTop; 4721 bottom += paddingTop; 4722 h += getCompoundPaddingLeft(); 4723 4724 if (line == 0) 4725 top -= getExtendedPaddingTop(); 4726 if (line == mLayout.getLineCount() - 1) 4727 bottom += getExtendedPaddingBottom(); 4728 4729 r.set(h, top, h+1, bottom); 4730 r.offset(-mScrollX, -mScrollY); 4731 } 4732 4733 @Override 4734 public void debug(int depth) { 4735 super.debug(depth); 4736 4737 String output = debugIndent(depth); 4738 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 4739 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 4740 + "} "; 4741 4742 if (mText != null) { 4743 4744 output += "mText=\"" + mText + "\" "; 4745 if (mLayout != null) { 4746 output += "mLayout width=" + mLayout.getWidth() 4747 + " height=" + mLayout.getHeight(); 4748 } 4749 } else { 4750 output += "mText=NULL"; 4751 } 4752 Log.d(VIEW_LOG_TAG, output); 4753 } 4754 4755 /** 4756 * Convenience for {@link Selection#getSelectionStart}. 4757 */ 4758 public int getSelectionStart() { 4759 return Selection.getSelectionStart(getText()); 4760 } 4761 4762 /** 4763 * Convenience for {@link Selection#getSelectionEnd}. 4764 */ 4765 public int getSelectionEnd() { 4766 return Selection.getSelectionEnd(getText()); 4767 } 4768 4769 /** 4770 * Return true iff there is a selection inside this text view. 4771 */ 4772 public boolean hasSelection() { 4773 return getSelectionStart() != getSelectionEnd(); 4774 } 4775 4776 /** 4777 * Sets the properties of this field (lines, horizontally scrolling, 4778 * transformation method) to be for a single-line input. 4779 * 4780 * @attr ref android.R.styleable#TextView_singleLine 4781 */ 4782 public void setSingleLine() { 4783 setSingleLine(true); 4784 } 4785 4786 /** 4787 * If true, sets the properties of this field (lines, horizontally 4788 * scrolling, transformation method) to be for a single-line input; 4789 * if false, restores these to the default conditions. 4790 * Note that calling this with false restores default conditions, 4791 * not necessarily those that were in effect prior to calling 4792 * it with true. 4793 * 4794 * @attr ref android.R.styleable#TextView_singleLine 4795 */ 4796 public void setSingleLine(boolean singleLine) { 4797 mSingleLine = singleLine; 4798 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 4799 == EditorInfo.TYPE_CLASS_TEXT) { 4800 if (singleLine) { 4801 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 4802 } else { 4803 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 4804 } 4805 } 4806 4807 if (singleLine) { 4808 setLines(1); 4809 setHorizontallyScrolling(true); 4810 setTransformationMethod(SingleLineTransformationMethod. 4811 getInstance()); 4812 } else { 4813 setMaxLines(Integer.MAX_VALUE); 4814 setHorizontallyScrolling(false); 4815 setTransformationMethod(null); 4816 } 4817 } 4818 4819 /** 4820 * Causes words in the text that are longer than the view is wide 4821 * to be ellipsized instead of broken in the middle. You may also 4822 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 4823 * to constrain the text toa single line. Use <code>null</code> 4824 * to turn off ellipsizing. 4825 * 4826 * @attr ref android.R.styleable#TextView_ellipsize 4827 */ 4828 public void setEllipsize(TextUtils.TruncateAt where) { 4829 mEllipsize = where; 4830 4831 if (mLayout != null) { 4832 nullLayouts(); 4833 requestLayout(); 4834 invalidate(); 4835 } 4836 } 4837 4838 /** 4839 * Sets how many times to repeat the marquee animation. Only applied if the 4840 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 4841 * 4842 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 4843 */ 4844 public void setMarqueeRepeatLimit(int marqueeLimit) { 4845 mMarqueeRepeatLimit = marqueeLimit; 4846 } 4847 4848 /** 4849 * Returns where, if anywhere, words that are longer than the view 4850 * is wide should be ellipsized. 4851 */ 4852 @ViewDebug.ExportedProperty 4853 public TextUtils.TruncateAt getEllipsize() { 4854 return mEllipsize; 4855 } 4856 4857 /** 4858 * Set the TextView so that when it takes focus, all the text is 4859 * selected. 4860 * 4861 * @attr ref android.R.styleable#TextView_selectAllOnFocus 4862 */ 4863 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 4864 mSelectAllOnFocus = selectAllOnFocus; 4865 4866 if (selectAllOnFocus && !(mText instanceof Spannable)) { 4867 setText(mText, BufferType.SPANNABLE); 4868 } 4869 } 4870 4871 /** 4872 * Set whether the cursor is visible. The default is true. 4873 * 4874 * @attr ref android.R.styleable#TextView_cursorVisible 4875 */ 4876 public void setCursorVisible(boolean visible) { 4877 mCursorVisible = visible; 4878 invalidate(); 4879 4880 if (visible) { 4881 makeBlink(); 4882 } else if (mBlink != null) { 4883 mBlink.removeCallbacks(mBlink); 4884 } 4885 } 4886 4887 private boolean canMarquee() { 4888 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 4889 return width > 0 && mLayout.getLineWidth(0) > width; 4890 } 4891 4892 private void startMarquee() { 4893 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 4894 getLineCount() == 1 && canMarquee()) { 4895 if (mMarquee == null) mMarquee = new Marquee(this); 4896 mMarquee.start(mMarqueeRepeatLimit); 4897 } 4898 } 4899 4900 private void stopMarquee() { 4901 if (mMarquee != null && !mMarquee.isStopped()) { 4902 mMarquee.stop(); 4903 } 4904 } 4905 4906 private void startStopMarquee(boolean start) { 4907 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4908 if (start) { 4909 startMarquee(); 4910 } else { 4911 stopMarquee(); 4912 } 4913 } 4914 } 4915 4916 private static final class Marquee extends Handler { 4917 // TODO: Add an option to configure this 4918 private static final int MARQUEE_DELAY = 1200; 4919 private static final int MARQUEE_RESTART_DELAY = 1200; 4920 private static final int MARQUEE_RESOLUTION = 1000 / 30; 4921 private static final int MARQUEE_PIXELS_PER_SECOND = 30; 4922 4923 private static final byte MARQUEE_STOPPED = 0x0; 4924 private static final byte MARQUEE_STARTING = 0x1; 4925 private static final byte MARQUEE_RUNNING = 0x2; 4926 4927 private static final int MESSAGE_START = 0x1; 4928 private static final int MESSAGE_TICK = 0x2; 4929 private static final int MESSAGE_RESTART = 0x3; 4930 4931 private final WeakReference<TextView> mView; 4932 4933 private byte mStatus = MARQUEE_STOPPED; 4934 private float mScrollUnit; 4935 private float mMaxScroll; 4936 private int mRepeatLimit; 4937 4938 float mScroll; 4939 4940 Marquee(TextView v) { 4941 mView = new WeakReference<TextView>(v); 4942 } 4943 4944 @Override 4945 public void handleMessage(Message msg) { 4946 switch (msg.what) { 4947 case MESSAGE_START: 4948 mStatus = MARQUEE_RUNNING; 4949 tick(); 4950 break; 4951 case MESSAGE_TICK: 4952 tick(); 4953 break; 4954 case MESSAGE_RESTART: 4955 if (mStatus == MARQUEE_RUNNING) { 4956 if (mRepeatLimit >= 0) { 4957 mRepeatLimit--; 4958 } 4959 start(mRepeatLimit); 4960 } 4961 break; 4962 } 4963 } 4964 4965 void tick() { 4966 if (mStatus != MARQUEE_RUNNING) { 4967 return; 4968 } 4969 4970 removeMessages(MESSAGE_TICK); 4971 4972 final TextView textView = mView.get(); 4973 if (textView != null && (textView.isFocused() || textView.isSelected())) { 4974 mScroll += mScrollUnit; 4975 if (mScroll > mMaxScroll) { 4976 mScroll = mMaxScroll; 4977 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); 4978 } else { 4979 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); 4980 } 4981 textView.invalidate(); 4982 } 4983 } 4984 4985 void stop() { 4986 mStatus = MARQUEE_STOPPED; 4987 removeMessages(MESSAGE_START); 4988 removeMessages(MESSAGE_RESTART); 4989 removeMessages(MESSAGE_TICK); 4990 resetScroll(); 4991 } 4992 4993 private void resetScroll() { 4994 mScroll = 0.0f; 4995 final TextView textView = mView.get(); 4996 if (textView != null) textView.invalidate(); 4997 } 4998 4999 void start(int repeatLimit) { 5000 if (repeatLimit == 0) { 5001 stop(); 5002 return; 5003 } 5004 mRepeatLimit = repeatLimit; 5005 final TextView textView = mView.get(); 5006 if (textView != null && textView.mLayout != null) { 5007 mStatus = MARQUEE_STARTING; 5008 mScroll = 0.0f; 5009 mScrollUnit = MARQUEE_PIXELS_PER_SECOND / (float) MARQUEE_RESOLUTION; 5010 mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() - 5011 textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight()); 5012 textView.invalidate(); 5013 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); 5014 } 5015 } 5016 5017 boolean isRunning() { 5018 return mStatus == MARQUEE_RUNNING; 5019 } 5020 5021 boolean isStopped() { 5022 return mStatus == MARQUEE_STOPPED; 5023 } 5024 } 5025 5026 /** 5027 * This method is called when the text is changed, in case any 5028 * subclasses would like to know. 5029 * 5030 * @param text The text the TextView is displaying. 5031 * @param start The offset of the start of the range of the text 5032 * that was modified. 5033 * @param before The offset of the former end of the range of the 5034 * text that was modified. If text was simply inserted, 5035 * this will be the same as <code>start</code>. 5036 * If text was replaced with new text or deleted, the 5037 * length of the old text was <code>before-start</code>. 5038 * @param after The offset of the end of the range of the text 5039 * that was modified. If text was simply deleted, 5040 * this will be the same as <code>start</code>. 5041 * If text was replaced with new text or inserted, 5042 * the length of the new text is <code>after-start</code>. 5043 */ 5044 protected void onTextChanged(CharSequence text, 5045 int start, int before, int after) { 5046 } 5047 5048 /** 5049 * Adds a TextWatcher to the list of those whose methods are called 5050 * whenever this TextView's text changes. 5051 * <p> 5052 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 5053 * not called after {@link #setText} calls. Now, doing {@link #setText} 5054 * if there are any text changed listeners forces the buffer type to 5055 * Editable if it would not otherwise be and does call this method. 5056 */ 5057 public void addTextChangedListener(TextWatcher watcher) { 5058 if (mListeners == null) { 5059 mListeners = new ArrayList<TextWatcher>(); 5060 } 5061 5062 mListeners.add(watcher); 5063 } 5064 5065 /** 5066 * Removes the specified TextWatcher from the list of those whose 5067 * methods are called 5068 * whenever this TextView's text changes. 5069 */ 5070 public void removeTextChangedListener(TextWatcher watcher) { 5071 if (mListeners != null) { 5072 int i = mListeners.indexOf(watcher); 5073 5074 if (i >= 0) { 5075 mListeners.remove(i); 5076 } 5077 } 5078 } 5079 5080 private void sendBeforeTextChanged(CharSequence text, int start, int before, 5081 int after) { 5082 if (mListeners != null) { 5083 final ArrayList<TextWatcher> list = mListeners; 5084 final int count = list.size(); 5085 for (int i = 0; i < count; i++) { 5086 list.get(i).beforeTextChanged(text, start, before, after); 5087 } 5088 } 5089 } 5090 5091 /** 5092 * Not private so it can be called from an inner class without going 5093 * through a thunk. 5094 */ 5095 void sendOnTextChanged(CharSequence text, int start, int before, 5096 int after) { 5097 if (mListeners != null) { 5098 final ArrayList<TextWatcher> list = mListeners; 5099 final int count = list.size(); 5100 for (int i = 0; i < count; i++) { 5101 list.get(i).onTextChanged(text, start, before, after); 5102 } 5103 } 5104 } 5105 5106 /** 5107 * Not private so it can be called from an inner class without going 5108 * through a thunk. 5109 */ 5110 void sendAfterTextChanged(Editable text) { 5111 if (mListeners != null) { 5112 final ArrayList<TextWatcher> list = mListeners; 5113 final int count = list.size(); 5114 for (int i = 0; i < count; i++) { 5115 list.get(i).afterTextChanged(text); 5116 } 5117 } 5118 } 5119 5120 /** 5121 * Not private so it can be called from an inner class without going 5122 * through a thunk. 5123 */ 5124 void handleTextChanged(CharSequence buffer, int start, 5125 int before, int after) { 5126 invalidate(); 5127 5128 int curs = Selection.getSelectionStart(buffer); 5129 5130 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == 5131 Gravity.BOTTOM) { 5132 registerForPreDraw(); 5133 } 5134 5135 if (curs >= 0) { 5136 mHighlightPathBogus = true; 5137 5138 if (isFocused()) { 5139 mShowCursor = SystemClock.uptimeMillis(); 5140 makeBlink(); 5141 } 5142 } 5143 5144 checkForResize(); 5145 5146 sendOnTextChanged(buffer, start, before, after); 5147 onTextChanged(buffer, start, before, after); 5148 } 5149 5150 /** 5151 * Not private so it can be called from an inner class without going 5152 * through a thunk. 5153 */ 5154 void spanChange(Spanned buf, Object what, int o, int n) { 5155 // XXX Make the start and end move together if this ends up 5156 // spending too much time invalidating. 5157 5158 if (what == Selection.SELECTION_END) { 5159 mHighlightPathBogus = true; 5160 5161 if (!isFocused()) { 5162 mSelectionMoved = true; 5163 } 5164 5165 if (o >= 0 || n >= 0) { 5166 invalidateCursor(Selection.getSelectionStart(buf), o, n); 5167 registerForPreDraw(); 5168 5169 if (isFocused()) { 5170 mShowCursor = SystemClock.uptimeMillis(); 5171 makeBlink(); 5172 } 5173 } 5174 } 5175 5176 if (what == Selection.SELECTION_START) { 5177 mHighlightPathBogus = true; 5178 5179 if (!isFocused()) { 5180 mSelectionMoved = true; 5181 } 5182 5183 if (o >= 0 || n >= 0) { 5184 invalidateCursor(Selection.getSelectionEnd(buf), o, n); 5185 } 5186 } 5187 5188 if (what instanceof UpdateAppearance || 5189 what instanceof ParagraphStyle) { 5190 invalidate(); 5191 mHighlightPathBogus = true; 5192 checkForResize(); 5193 } 5194 5195 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 5196 mHighlightPathBogus = true; 5197 5198 if (Selection.getSelectionStart(buf) >= 0) { 5199 invalidateCursor(); 5200 } 5201 } 5202 } 5203 5204 private class ChangeWatcher 5205 implements TextWatcher, SpanWatcher { 5206 public void beforeTextChanged(CharSequence buffer, int start, 5207 int before, int after) { 5208 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 5209 } 5210 5211 public void onTextChanged(CharSequence buffer, int start, 5212 int before, int after) { 5213 TextView.this.handleTextChanged(buffer, start, before, after); 5214 } 5215 5216 public void afterTextChanged(Editable buffer) { 5217 TextView.this.sendAfterTextChanged(buffer); 5218 5219 if (MetaKeyKeyListener.getMetaState(buffer, 5220 MetaKeyKeyListener.META_SELECTING) != 0) { 5221 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 5222 } 5223 5224 TextView.this.reportExtractedText(); 5225 } 5226 5227 public void onSpanChanged(Spannable buf, 5228 Object what, int s, int e, int st, int en) { 5229 TextView.this.spanChange(buf, what, s, st); 5230 } 5231 5232 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 5233 TextView.this.spanChange(buf, what, -1, s); 5234 } 5235 5236 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 5237 TextView.this.spanChange(buf, what, s, -1); 5238 } 5239 } 5240 5241 private void makeBlink() { 5242 if (!mCursorVisible) { 5243 if (mBlink != null) { 5244 mBlink.removeCallbacks(mBlink); 5245 } 5246 5247 return; 5248 } 5249 5250 if (mBlink == null) 5251 mBlink = new Blink(this); 5252 5253 mBlink.removeCallbacks(mBlink); 5254 mBlink.postAtTime(mBlink, mShowCursor + BLINK); 5255 } 5256 5257 @Override 5258 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 5259 mShowCursor = SystemClock.uptimeMillis(); 5260 5261 if (focused) { 5262 int selStart = getSelectionStart(); 5263 int selEnd = getSelectionEnd(); 5264 5265 if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { 5266 boolean selMoved = mSelectionMoved; 5267 5268 if (mMovement != null) { 5269 mMovement.onTakeFocus(this, (Spannable) mText, direction); 5270 } 5271 5272 if (mSelectAllOnFocus) { 5273 Selection.setSelection((Spannable) mText, 0, mText.length()); 5274 } 5275 5276 if (selMoved && selStart >= 0 && selEnd >= 0) { 5277 /* 5278 * Someone intentionally set the selection, so let them 5279 * do whatever it is that they wanted to do instead of 5280 * the default on-focus behavior. We reset the selection 5281 * here instead of just skipping the onTakeFocus() call 5282 * because some movement methods do something other than 5283 * just setting the selection in theirs and we still 5284 * need to go through that path. 5285 */ 5286 5287 Selection.setSelection((Spannable) mText, selStart, selEnd); 5288 } 5289 } 5290 5291 mFrozenWithFocus = false; 5292 mSelectionMoved = false; 5293 5294 if (mText instanceof Spannable) { 5295 Spannable sp = (Spannable) mText; 5296 MetaKeyKeyListener.resetMetaState(sp); 5297 } 5298 5299 makeBlink(); 5300 5301 if (mError != null) { 5302 showError(); 5303 } 5304 } else { 5305 if (mError != null) { 5306 hideError(); 5307 } 5308 // Don't leave us in the middle of a batch edit. 5309 onEndBatchEdit(); 5310 } 5311 5312 startStopMarquee(focused); 5313 5314 if (mTransformation != null) { 5315 mTransformation.onFocusChanged(this, mText, focused, direction, 5316 previouslyFocusedRect); 5317 } 5318 5319 super.onFocusChanged(focused, direction, previouslyFocusedRect); 5320 } 5321 5322 @Override 5323 public void onWindowFocusChanged(boolean hasWindowFocus) { 5324 super.onWindowFocusChanged(hasWindowFocus); 5325 5326 if (hasWindowFocus) { 5327 if (mBlink != null) { 5328 mBlink.uncancel(); 5329 5330 if (isFocused()) { 5331 mShowCursor = SystemClock.uptimeMillis(); 5332 makeBlink(); 5333 } 5334 } 5335 } else { 5336 if (mBlink != null) { 5337 mBlink.cancel(); 5338 } 5339 // Don't leave us in the middle of a batch edit. 5340 onEndBatchEdit(); 5341 } 5342 5343 startStopMarquee(hasWindowFocus); 5344 } 5345 5346 @Override 5347 public void setSelected(boolean selected) { 5348 boolean wasSelected = isSelected(); 5349 5350 super.setSelected(selected); 5351 5352 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5353 if (selected) { 5354 startMarquee(); 5355 } else { 5356 stopMarquee(); 5357 } 5358 } 5359 } 5360 5361 @Override 5362 public boolean onTouchEvent(MotionEvent event) { 5363 final boolean superResult = super.onTouchEvent(event); 5364 5365 /* 5366 * Don't handle the release after a long press, because it will 5367 * move the selection away from whatever the menu action was 5368 * trying to affect. 5369 */ 5370 if (mEatTouchRelease && event.getAction() == MotionEvent.ACTION_UP) { 5371 mEatTouchRelease = false; 5372 return superResult; 5373 } 5374 5375 if (mMovement != null && mText instanceof Spannable && 5376 mLayout != null) { 5377 boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event); 5378 5379 if (mText instanceof Editable && onCheckIsTextEditor()) { 5380 if (event.getAction() == MotionEvent.ACTION_UP && isFocused()) { 5381 InputMethodManager imm = (InputMethodManager) 5382 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 5383 imm.showSoftInput(this, 0); 5384 } 5385 } 5386 5387 if (moved) { 5388 return true; 5389 } 5390 } 5391 5392 return superResult; 5393 } 5394 5395 @Override 5396 public boolean onTrackballEvent(MotionEvent event) { 5397 if (mMovement != null && mText instanceof Spannable && 5398 mLayout != null) { 5399 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 5400 return true; 5401 } 5402 } 5403 5404 return super.onTrackballEvent(event); 5405 } 5406 5407 public void setScroller(Scroller s) { 5408 mScroller = s; 5409 } 5410 5411 private static class Blink extends Handler 5412 implements Runnable { 5413 private WeakReference<TextView> mView; 5414 private boolean mCancelled; 5415 5416 public Blink(TextView v) { 5417 mView = new WeakReference<TextView>(v); 5418 } 5419 5420 public void run() { 5421 if (mCancelled) { 5422 return; 5423 } 5424 5425 removeCallbacks(Blink.this); 5426 5427 TextView tv = mView.get(); 5428 5429 if (tv != null && tv.isFocused()) { 5430 int st = Selection.getSelectionStart(tv.mText); 5431 int en = Selection.getSelectionEnd(tv.mText); 5432 5433 if (st == en && st >= 0 && en >= 0) { 5434 if (tv.mLayout != null) { 5435 tv.invalidateCursorPath(); 5436 } 5437 5438 postAtTime(this, SystemClock.uptimeMillis() + BLINK); 5439 } 5440 } 5441 } 5442 5443 void cancel() { 5444 if (!mCancelled) { 5445 removeCallbacks(Blink.this); 5446 mCancelled = true; 5447 } 5448 } 5449 5450 void uncancel() { 5451 mCancelled = false; 5452 } 5453 } 5454 5455 @Override 5456 protected float getLeftFadingEdgeStrength() { 5457 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5458 if (mMarquee != null && !mMarquee.isStopped()) { 5459 final Marquee marquee = mMarquee; 5460 return marquee.mScroll / getHorizontalFadingEdgeLength(); 5461 } else if (getLineCount() == 1) { 5462 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 5463 case Gravity.LEFT: 5464 return 0.0f; 5465 case Gravity.RIGHT: 5466 return (mLayout.getLineRight(0) - (mRight - mLeft) - 5467 getCompoundPaddingLeft() - getCompoundPaddingRight() - 5468 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 5469 case Gravity.CENTER_HORIZONTAL: 5470 return 0.0f; 5471 } 5472 } 5473 } 5474 return super.getLeftFadingEdgeStrength(); 5475 } 5476 5477 @Override 5478 protected float getRightFadingEdgeStrength() { 5479 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5480 if (mMarquee != null && !mMarquee.isStopped()) { 5481 final Marquee marquee = mMarquee; 5482 return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); 5483 } else if (getLineCount() == 1) { 5484 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 5485 case Gravity.LEFT: 5486 return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) - 5487 getCompoundPaddingLeft() - getCompoundPaddingRight()) / 5488 getHorizontalFadingEdgeLength(); 5489 case Gravity.RIGHT: 5490 return 0.0f; 5491 case Gravity.CENTER_HORIZONTAL: 5492 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 5493 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 5494 getHorizontalFadingEdgeLength(); 5495 } 5496 } 5497 } 5498 return super.getRightFadingEdgeStrength(); 5499 } 5500 5501 @Override 5502 protected int computeHorizontalScrollRange() { 5503 if (mLayout != null) 5504 return mLayout.getWidth(); 5505 5506 return super.computeHorizontalScrollRange(); 5507 } 5508 5509 @Override 5510 protected int computeVerticalScrollRange() { 5511 if (mLayout != null) 5512 return mLayout.getHeight(); 5513 5514 return super.computeVerticalScrollRange(); 5515 } 5516 5517 public enum BufferType { 5518 NORMAL, SPANNABLE, EDITABLE, 5519 } 5520 5521 /** 5522 * Returns the TextView_textColor attribute from the 5523 * Resources.StyledAttributes, if set, or the TextAppearance_textColor 5524 * from the TextView_textAppearance attribute, if TextView_textColor 5525 * was not set directly. 5526 */ 5527 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 5528 ColorStateList colors; 5529 colors = attrs.getColorStateList(com.android.internal.R.styleable. 5530 TextView_textColor); 5531 5532 if (colors == null) { 5533 int ap = attrs.getResourceId(com.android.internal.R.styleable. 5534 TextView_textAppearance, -1); 5535 if (ap != -1) { 5536 TypedArray appearance; 5537 appearance = context.obtainStyledAttributes(ap, 5538 com.android.internal.R.styleable.TextAppearance); 5539 colors = appearance.getColorStateList(com.android.internal.R.styleable. 5540 TextAppearance_textColor); 5541 appearance.recycle(); 5542 } 5543 } 5544 5545 return colors; 5546 } 5547 5548 /** 5549 * Returns the default color from the TextView_textColor attribute 5550 * from the AttributeSet, if set, or the default color from the 5551 * TextAppearance_textColor from the TextView_textAppearance attribute, 5552 * if TextView_textColor was not set directly. 5553 */ 5554 public static int getTextColor(Context context, 5555 TypedArray attrs, 5556 int def) { 5557 ColorStateList colors = getTextColors(context, attrs); 5558 5559 if (colors == null) { 5560 return def; 5561 } else { 5562 return colors.getDefaultColor(); 5563 } 5564 } 5565 5566 @Override 5567 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 5568 switch (keyCode) { 5569 case KeyEvent.KEYCODE_A: 5570 if (canSelectAll()) { 5571 return onMenu(ID_SELECT_ALL); 5572 } 5573 5574 break; 5575 5576 case KeyEvent.KEYCODE_X: 5577 if (canCut()) { 5578 return onMenu(ID_CUT); 5579 } 5580 5581 break; 5582 5583 case KeyEvent.KEYCODE_C: 5584 if (canCopy()) { 5585 return onMenu(ID_COPY); 5586 } 5587 5588 break; 5589 5590 case KeyEvent.KEYCODE_V: 5591 if (canPaste()) { 5592 return onMenu(ID_PASTE); 5593 } 5594 5595 break; 5596 } 5597 5598 return super.onKeyShortcut(keyCode, event); 5599 } 5600 5601 private boolean canSelectAll() { 5602 if (mText instanceof Spannable && mText.length() != 0 && 5603 mMovement != null && mMovement.canSelectArbitrarily()) { 5604 return true; 5605 } 5606 5607 return false; 5608 } 5609 5610 private boolean canSelectText() { 5611 if (mText instanceof Spannable && mText.length() != 0 && 5612 mMovement != null && mMovement.canSelectArbitrarily()) { 5613 return true; 5614 } 5615 5616 return false; 5617 } 5618 5619 private boolean canCut() { 5620 if (mTransformation instanceof PasswordTransformationMethod) { 5621 return false; 5622 } 5623 5624 if (mText.length() > 0 && getSelectionStart() >= 0) { 5625 if (mText instanceof Editable && mInput != null) { 5626 return true; 5627 } 5628 } 5629 5630 return false; 5631 } 5632 5633 private boolean canCopy() { 5634 if (mTransformation instanceof PasswordTransformationMethod) { 5635 return false; 5636 } 5637 5638 if (mText.length() > 0 && getSelectionStart() >= 0) { 5639 return true; 5640 } 5641 5642 return false; 5643 } 5644 5645 private boolean canPaste() { 5646 if (mText instanceof Editable && mInput != null && 5647 getSelectionStart() >= 0 && getSelectionEnd() >= 0) { 5648 ClipboardManager clip = (ClipboardManager)getContext() 5649 .getSystemService(Context.CLIPBOARD_SERVICE); 5650 if (clip.hasText()) { 5651 return true; 5652 } 5653 } 5654 5655 return false; 5656 } 5657 5658 @Override 5659 protected void onCreateContextMenu(ContextMenu menu) { 5660 super.onCreateContextMenu(menu); 5661 boolean added = false; 5662 5663 if (!isFocused()) { 5664 if (isFocusable() && mInput != null) { 5665 if (canCopy()) { 5666 MenuHandler handler = new MenuHandler(); 5667 int name = com.android.internal.R.string.copyAll; 5668 5669 menu.add(0, ID_COPY, 0, name). 5670 setOnMenuItemClickListener(handler). 5671 setAlphabeticShortcut('c'); 5672 menu.setHeaderTitle(com.android.internal.R.string. 5673 editTextMenuTitle); 5674 } 5675 } 5676 5677 return; 5678 } 5679 5680 MenuHandler handler = new MenuHandler(); 5681 5682 if (canSelectAll()) { 5683 menu.add(0, ID_SELECT_ALL, 0, 5684 com.android.internal.R.string.selectAll). 5685 setOnMenuItemClickListener(handler). 5686 setAlphabeticShortcut('a'); 5687 added = true; 5688 } 5689 5690 boolean selection = getSelectionStart() != getSelectionEnd(); 5691 5692 if (canSelectText()) { 5693 if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { 5694 menu.add(0, ID_STOP_SELECTING_TEXT, 0, 5695 com.android.internal.R.string.stopSelectingText). 5696 setOnMenuItemClickListener(handler); 5697 added = true; 5698 } else { 5699 menu.add(0, ID_SELECT_TEXT, 0, 5700 com.android.internal.R.string.selectText). 5701 setOnMenuItemClickListener(handler); 5702 added = true; 5703 } 5704 } 5705 5706 if (canCut()) { 5707 int name; 5708 if (selection) { 5709 name = com.android.internal.R.string.cut; 5710 } else { 5711 name = com.android.internal.R.string.cutAll; 5712 } 5713 5714 menu.add(0, ID_CUT, 0, name). 5715 setOnMenuItemClickListener(handler). 5716 setAlphabeticShortcut('x'); 5717 added = true; 5718 } 5719 5720 if (canCopy()) { 5721 int name; 5722 if (selection) { 5723 name = com.android.internal.R.string.copy; 5724 } else { 5725 name = com.android.internal.R.string.copyAll; 5726 } 5727 5728 menu.add(0, ID_COPY, 0, name). 5729 setOnMenuItemClickListener(handler). 5730 setAlphabeticShortcut('c'); 5731 added = true; 5732 } 5733 5734 if (canPaste()) { 5735 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). 5736 setOnMenuItemClickListener(handler). 5737 setAlphabeticShortcut('v'); 5738 added = true; 5739 } 5740 5741 if (mText instanceof Spanned) { 5742 int selStart = getSelectionStart(); 5743 int selEnd = getSelectionEnd(); 5744 5745 int min = Math.min(selStart, selEnd); 5746 int max = Math.max(selStart, selEnd); 5747 5748 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 5749 URLSpan.class); 5750 if (urls.length == 1) { 5751 menu.add(0, ID_COPY_URL, 0, 5752 com.android.internal.R.string.copyUrl). 5753 setOnMenuItemClickListener(handler); 5754 added = true; 5755 } 5756 } 5757 5758 InputMethodManager imm = InputMethodManager.peekInstance(); 5759 if (imm != null && imm.isActive(this)) { 5760 menu.add(1, ID_SWITCH_IME, 0, com.android.internal.R.string.inputMethod). 5761 setOnMenuItemClickListener(handler); 5762 added = true; 5763 } 5764 5765 if (added) { 5766 menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); 5767 } 5768 } 5769 5770 private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll; 5771 private static final int ID_SELECT_TEXT = com.android.internal.R.id.selectText; 5772 private static final int ID_STOP_SELECTING_TEXT = com.android.internal.R.id.stopSelectingText; 5773 private static final int ID_CUT = com.android.internal.R.id.cut; 5774 private static final int ID_COPY = com.android.internal.R.id.copy; 5775 private static final int ID_PASTE = com.android.internal.R.id.paste; 5776 private static final int ID_COPY_URL = com.android.internal.R.id.copyUrl; 5777 private static final int ID_SWITCH_IME = com.android.internal.R.id.inputMethod; 5778 5779 private class MenuHandler implements MenuItem.OnMenuItemClickListener { 5780 public boolean onMenuItemClick(MenuItem item) { 5781 return onMenu(item.getItemId()); 5782 } 5783 } 5784 5785 private boolean onMenu(int id) { 5786 int selStart = getSelectionStart(); 5787 int selEnd = getSelectionEnd(); 5788 5789 if (!isFocused()) { 5790 selStart = 0; 5791 selEnd = mText.length(); 5792 } 5793 5794 int min = Math.min(selStart, selEnd); 5795 int max = Math.max(selStart, selEnd); 5796 5797 if (min < 0) { 5798 min = 0; 5799 } 5800 if (max < 0) { 5801 max = 0; 5802 } 5803 5804 ClipboardManager clip = (ClipboardManager)getContext() 5805 .getSystemService(Context.CLIPBOARD_SERVICE); 5806 5807 switch (id) { 5808 case ID_SELECT_ALL: 5809 Selection.setSelection((Spannable) mText, 0, 5810 mText.length()); 5811 return true; 5812 5813 case ID_SELECT_TEXT: 5814 MetaKeyKeyListener.startSelecting(this, (Spannable) mText); 5815 return true; 5816 5817 case ID_STOP_SELECTING_TEXT: 5818 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 5819 Selection.setSelection((Spannable) mText, getSelectionEnd()); 5820 return true; 5821 5822 case ID_CUT: 5823 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 5824 5825 if (min == max) { 5826 min = 0; 5827 max = mText.length(); 5828 } 5829 5830 clip.setText(mTransformed.subSequence(min, max)); 5831 ((Editable) mText).delete(min, max); 5832 return true; 5833 5834 case ID_COPY: 5835 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 5836 5837 if (min == max) { 5838 min = 0; 5839 max = mText.length(); 5840 } 5841 5842 clip.setText(mTransformed.subSequence(min, max)); 5843 return true; 5844 5845 case ID_PASTE: 5846 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 5847 5848 CharSequence paste = clip.getText(); 5849 5850 if (paste != null) { 5851 Selection.setSelection((Spannable) mText, max); 5852 ((Editable) mText).replace(min, max, paste); 5853 } 5854 5855 return true; 5856 5857 case ID_COPY_URL: 5858 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 5859 5860 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 5861 URLSpan.class); 5862 if (urls.length == 1) { 5863 clip.setText(urls[0].getURL()); 5864 } 5865 5866 return true; 5867 5868 case ID_SWITCH_IME: 5869 InputMethodManager imm = InputMethodManager.peekInstance(); 5870 if (imm != null) { 5871 imm.showInputMethodPicker(); 5872 } 5873 return true; 5874 } 5875 5876 return false; 5877 } 5878 5879 public boolean performLongClick() { 5880 if (super.performLongClick()) { 5881 mEatTouchRelease = true; 5882 return true; 5883 } 5884 5885 return false; 5886 } 5887 5888 private boolean mEatTouchRelease = false; 5889 5890 @ViewDebug.ExportedProperty 5891 private CharSequence mText; 5892 private CharSequence mTransformed; 5893 private BufferType mBufferType = BufferType.NORMAL; 5894 5895 private int mInputType = EditorInfo.TYPE_NULL; 5896 private CharSequence mHint; 5897 private Layout mHintLayout; 5898 5899 private KeyListener mInput; 5900 5901 private MovementMethod mMovement; 5902 private TransformationMethod mTransformation; 5903 private ChangeWatcher mChangeWatcher; 5904 5905 private ArrayList<TextWatcher> mListeners = null; 5906 5907 // display attributes 5908 private TextPaint mTextPaint; 5909 private Paint mHighlightPaint; 5910 private int mHighlightColor = 0xFFBBDDFF; 5911 private Layout mLayout; 5912 5913 private long mShowCursor; 5914 private Blink mBlink; 5915 private boolean mCursorVisible = true; 5916 5917 private boolean mSelectAllOnFocus = false; 5918 5919 private int mGravity = Gravity.TOP | Gravity.LEFT; 5920 private boolean mHorizontallyScrolling; 5921 5922 private int mAutoLinkMask; 5923 private boolean mLinksClickable = true; 5924 5925 private float mSpacingMult = 1; 5926 private float mSpacingAdd = 0; 5927 5928 private static final int LINES = 1; 5929 private static final int EMS = LINES; 5930 private static final int PIXELS = 2; 5931 5932 private int mMaximum = Integer.MAX_VALUE; 5933 private int mMaxMode = LINES; 5934 private int mMinimum = 0; 5935 private int mMinMode = LINES; 5936 5937 private int mMaxWidth = Integer.MAX_VALUE; 5938 private int mMaxWidthMode = PIXELS; 5939 private int mMinWidth = 0; 5940 private int mMinWidthMode = PIXELS; 5941 5942 private boolean mSingleLine; 5943 private int mDesiredHeightAtMeasure = -1; 5944 private boolean mIncludePad = true; 5945 5946 // tmp primitives, so we don't alloc them on each draw 5947 private Path mHighlightPath; 5948 private boolean mHighlightPathBogus = true; 5949 private static final RectF sTempRect = new RectF(); 5950 5951 // XXX should be much larger 5952 private static final int VERY_WIDE = 16384; 5953 5954 private static final int BLINK = 500; 5955 5956 private static final int ANIMATED_SCROLL_GAP = 250; 5957 private long mLastScroll; 5958 private Scroller mScroller = null; 5959 5960 private BoringLayout.Metrics mBoring; 5961 private BoringLayout.Metrics mHintBoring; 5962 5963 private BoringLayout mSavedLayout, mSavedHintLayout; 5964 5965 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 5966 private InputFilter[] mFilters = NO_FILTERS; 5967 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 5968} 5969