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