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