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 17 package android.widget; 18 19 import android.R; 20 import android.content.ClipData; 21 import android.content.ClipboardManager; 22 import android.content.Context; 23 import android.content.res.ColorStateList; 24 import android.content.res.CompatibilityInfo; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.graphics.Canvas; 29 import android.graphics.Paint; 30 import android.graphics.Path; 31 import android.graphics.Rect; 32 import android.graphics.RectF; 33 import android.graphics.Typeface; 34 import android.graphics.drawable.Drawable; 35 import android.inputmethodservice.ExtractEditText; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.Message; 39 import android.os.Parcel; 40 import android.os.Parcelable; 41 import android.os.SystemClock; 42 import android.provider.Settings; 43 import android.text.BoringLayout; 44 import android.text.DynamicLayout; 45 import android.text.Editable; 46 import android.text.GetChars; 47 import android.text.GraphicsOperations; 48 import android.text.InputFilter; 49 import android.text.InputType; 50 import android.text.Layout; 51 import android.text.ParcelableSpan; 52 import android.text.Selection; 53 import android.text.SpanWatcher; 54 import android.text.Spannable; 55 import android.text.SpannableString; 56 import android.text.Spanned; 57 import android.text.SpannedString; 58 import android.text.StaticLayout; 59 import android.text.TextDirectionHeuristic; 60 import android.text.TextDirectionHeuristics; 61 import android.text.TextPaint; 62 import android.text.TextUtils; 63 import android.text.TextUtils.TruncateAt; 64 import android.text.TextWatcher; 65 import android.text.method.AllCapsTransformationMethod; 66 import android.text.method.ArrowKeyMovementMethod; 67 import android.text.method.DateKeyListener; 68 import android.text.method.DateTimeKeyListener; 69 import android.text.method.DialerKeyListener; 70 import android.text.method.DigitsKeyListener; 71 import android.text.method.KeyListener; 72 import android.text.method.LinkMovementMethod; 73 import android.text.method.MetaKeyKeyListener; 74 import android.text.method.MovementMethod; 75 import android.text.method.PasswordTransformationMethod; 76 import android.text.method.SingleLineTransformationMethod; 77 import android.text.method.TextKeyListener; 78 import android.text.method.TimeKeyListener; 79 import android.text.method.TransformationMethod; 80 import android.text.method.TransformationMethod2; 81 import android.text.method.WordIterator; 82 import android.text.style.CharacterStyle; 83 import android.text.style.ClickableSpan; 84 import android.text.style.ParagraphStyle; 85 import android.text.style.SpellCheckSpan; 86 import android.text.style.SuggestionSpan; 87 import android.text.style.URLSpan; 88 import android.text.style.UpdateAppearance; 89 import android.text.util.Linkify; 90 import android.util.AttributeSet; 91 import android.util.FloatMath; 92 import android.util.Log; 93 import android.util.TypedValue; 94 import android.view.AccessibilityIterators.TextSegmentIterator; 95 import android.view.ActionMode; 96 import android.view.DragEvent; 97 import android.view.Gravity; 98 import android.view.HapticFeedbackConstants; 99 import android.view.KeyCharacterMap; 100 import android.view.KeyEvent; 101 import android.view.Menu; 102 import android.view.MenuItem; 103 import android.view.MotionEvent; 104 import android.view.View; 105 import android.view.ViewConfiguration; 106 import android.view.ViewDebug; 107 import android.view.ViewGroup.LayoutParams; 108 import android.view.ViewRootImpl; 109 import android.view.ViewTreeObserver; 110 import android.view.accessibility.AccessibilityEvent; 111 import android.view.accessibility.AccessibilityManager; 112 import android.view.accessibility.AccessibilityNodeInfo; 113 import android.view.animation.AnimationUtils; 114 import android.view.inputmethod.BaseInputConnection; 115 import android.view.inputmethod.CompletionInfo; 116 import android.view.inputmethod.CorrectionInfo; 117 import android.view.inputmethod.EditorInfo; 118 import android.view.inputmethod.ExtractedText; 119 import android.view.inputmethod.ExtractedTextRequest; 120 import android.view.inputmethod.InputConnection; 121 import android.view.inputmethod.InputMethodManager; 122 import android.view.textservice.SpellCheckerSubtype; 123 import android.view.textservice.TextServicesManager; 124 import android.widget.RemoteViews.RemoteView; 125 126 import com.android.internal.util.FastMath; 127 import com.android.internal.widget.EditableInputConnection; 128 129 import org.xmlpull.v1.XmlPullParserException; 130 131 import java.io.IOException; 132 import java.lang.ref.WeakReference; 133 import java.util.ArrayList; 134 import java.util.Locale; 135 136 /** 137 * Displays text to the user and optionally allows them to edit it. A TextView 138 * is a complete text editor, however the basic class is configured to not 139 * allow editing; see {@link EditText} for a subclass that configures the text 140 * view for editing. 141 * 142 * <p> 143 * <b>XML attributes</b> 144 * <p> 145 * See {@link android.R.styleable#TextView TextView Attributes}, 146 * {@link android.R.styleable#View View Attributes} 147 * 148 * @attr ref android.R.styleable#TextView_text 149 * @attr ref android.R.styleable#TextView_bufferType 150 * @attr ref android.R.styleable#TextView_hint 151 * @attr ref android.R.styleable#TextView_textColor 152 * @attr ref android.R.styleable#TextView_textColorHighlight 153 * @attr ref android.R.styleable#TextView_textColorHint 154 * @attr ref android.R.styleable#TextView_textAppearance 155 * @attr ref android.R.styleable#TextView_textColorLink 156 * @attr ref android.R.styleable#TextView_textSize 157 * @attr ref android.R.styleable#TextView_textScaleX 158 * @attr ref android.R.styleable#TextView_fontFamily 159 * @attr ref android.R.styleable#TextView_typeface 160 * @attr ref android.R.styleable#TextView_textStyle 161 * @attr ref android.R.styleable#TextView_cursorVisible 162 * @attr ref android.R.styleable#TextView_maxLines 163 * @attr ref android.R.styleable#TextView_maxHeight 164 * @attr ref android.R.styleable#TextView_lines 165 * @attr ref android.R.styleable#TextView_height 166 * @attr ref android.R.styleable#TextView_minLines 167 * @attr ref android.R.styleable#TextView_minHeight 168 * @attr ref android.R.styleable#TextView_maxEms 169 * @attr ref android.R.styleable#TextView_maxWidth 170 * @attr ref android.R.styleable#TextView_ems 171 * @attr ref android.R.styleable#TextView_width 172 * @attr ref android.R.styleable#TextView_minEms 173 * @attr ref android.R.styleable#TextView_minWidth 174 * @attr ref android.R.styleable#TextView_gravity 175 * @attr ref android.R.styleable#TextView_scrollHorizontally 176 * @attr ref android.R.styleable#TextView_password 177 * @attr ref android.R.styleable#TextView_singleLine 178 * @attr ref android.R.styleable#TextView_selectAllOnFocus 179 * @attr ref android.R.styleable#TextView_includeFontPadding 180 * @attr ref android.R.styleable#TextView_maxLength 181 * @attr ref android.R.styleable#TextView_shadowColor 182 * @attr ref android.R.styleable#TextView_shadowDx 183 * @attr ref android.R.styleable#TextView_shadowDy 184 * @attr ref android.R.styleable#TextView_shadowRadius 185 * @attr ref android.R.styleable#TextView_autoLink 186 * @attr ref android.R.styleable#TextView_linksClickable 187 * @attr ref android.R.styleable#TextView_numeric 188 * @attr ref android.R.styleable#TextView_digits 189 * @attr ref android.R.styleable#TextView_phoneNumber 190 * @attr ref android.R.styleable#TextView_inputMethod 191 * @attr ref android.R.styleable#TextView_capitalize 192 * @attr ref android.R.styleable#TextView_autoText 193 * @attr ref android.R.styleable#TextView_editable 194 * @attr ref android.R.styleable#TextView_freezesText 195 * @attr ref android.R.styleable#TextView_ellipsize 196 * @attr ref android.R.styleable#TextView_drawableTop 197 * @attr ref android.R.styleable#TextView_drawableBottom 198 * @attr ref android.R.styleable#TextView_drawableRight 199 * @attr ref android.R.styleable#TextView_drawableLeft 200 * @attr ref android.R.styleable#TextView_drawableStart 201 * @attr ref android.R.styleable#TextView_drawableEnd 202 * @attr ref android.R.styleable#TextView_drawablePadding 203 * @attr ref android.R.styleable#TextView_lineSpacingExtra 204 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 205 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 206 * @attr ref android.R.styleable#TextView_inputType 207 * @attr ref android.R.styleable#TextView_imeOptions 208 * @attr ref android.R.styleable#TextView_privateImeOptions 209 * @attr ref android.R.styleable#TextView_imeActionLabel 210 * @attr ref android.R.styleable#TextView_imeActionId 211 * @attr ref android.R.styleable#TextView_editorExtras 212 */ 213 @RemoteView 214 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 215 static final String LOG_TAG = "TextView"; 216 static final boolean DEBUG_EXTRACT = false; 217 218 // Enum for the "typeface" XML parameter. 219 // TODO: How can we get this from the XML instead of hardcoding it here? 220 private static final int SANS = 1; 221 private static final int SERIF = 2; 222 private static final int MONOSPACE = 3; 223 224 // Bitfield for the "numeric" XML parameter. 225 // TODO: How can we get this from the XML instead of hardcoding it here? 226 private static final int SIGNED = 2; 227 private static final int DECIMAL = 4; 228 229 /** 230 * Draw marquee text with fading edges as usual 231 */ 232 private static final int MARQUEE_FADE_NORMAL = 0; 233 234 /** 235 * Draw marquee text as ellipsize end while inactive instead of with the fade. 236 * (Useful for devices where the fade can be expensive if overdone) 237 */ 238 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 239 240 /** 241 * Draw marquee text with fading edges because it is currently active/animating. 242 */ 243 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 244 245 private static final int LINES = 1; 246 private static final int EMS = LINES; 247 private static final int PIXELS = 2; 248 249 private static final RectF TEMP_RECTF = new RectF(); 250 251 // XXX should be much larger 252 private static final int VERY_WIDE = 1024*1024; 253 private static final int ANIMATED_SCROLL_GAP = 250; 254 255 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 256 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 257 258 private static final int CHANGE_WATCHER_PRIORITY = 100; 259 260 // New state used to change background based on whether this TextView is multiline. 261 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 262 263 // System wide time for last cut or copy action. 264 static long LAST_CUT_OR_COPY_TIME; 265 266 private ColorStateList mTextColor; 267 private ColorStateList mHintTextColor; 268 private ColorStateList mLinkTextColor; 269 private int mCurTextColor; 270 private int mCurHintTextColor; 271 private boolean mFreezesText; 272 private boolean mTemporaryDetach; 273 private boolean mDispatchTemporaryDetach; 274 275 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 276 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 277 278 private float mShadowRadius, mShadowDx, mShadowDy; 279 280 private boolean mPreDrawRegistered; 281 282 private TextUtils.TruncateAt mEllipsize; 283 284 static class Drawables { 285 final Rect mCompoundRect = new Rect(); 286 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight, 287 mDrawableStart, mDrawableEnd; 288 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 289 mDrawableSizeStart, mDrawableSizeEnd; 290 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 291 mDrawableHeightStart, mDrawableHeightEnd; 292 int mDrawablePadding; 293 } 294 Drawables mDrawables; 295 296 private CharWrapper mCharWrapper; 297 298 private Marquee mMarquee; 299 private boolean mRestartMarquee; 300 301 private int mMarqueeRepeatLimit = 3; 302 303 // The alignment to pass to Layout, or null if not resolved. 304 private Layout.Alignment mLayoutAlignment; 305 306 private boolean mResolvedDrawables; 307 308 /** 309 * On some devices the fading edges add a performance penalty if used 310 * extensively in the same layout. This mode indicates how the marquee 311 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 312 */ 313 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 314 315 /** 316 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 317 * the layout that should be used when the mode switches. 318 */ 319 private Layout mSavedMarqueeModeLayout; 320 321 @ViewDebug.ExportedProperty(category = "text") 322 private CharSequence mText; 323 private CharSequence mTransformed; 324 private BufferType mBufferType = BufferType.NORMAL; 325 326 private CharSequence mHint; 327 private Layout mHintLayout; 328 329 private MovementMethod mMovement; 330 331 private TransformationMethod mTransformation; 332 private boolean mAllowTransformationLengthChange; 333 private ChangeWatcher mChangeWatcher; 334 335 private ArrayList<TextWatcher> mListeners; 336 337 // display attributes 338 private final TextPaint mTextPaint; 339 private boolean mUserSetTextScaleX; 340 private Layout mLayout; 341 342 private int mGravity = Gravity.TOP | Gravity.START; 343 private boolean mHorizontallyScrolling; 344 345 private int mAutoLinkMask; 346 private boolean mLinksClickable = true; 347 348 private float mSpacingMult = 1.0f; 349 private float mSpacingAdd = 0.0f; 350 351 private int mMaximum = Integer.MAX_VALUE; 352 private int mMaxMode = LINES; 353 private int mMinimum = 0; 354 private int mMinMode = LINES; 355 356 private int mOldMaximum = mMaximum; 357 private int mOldMaxMode = mMaxMode; 358 359 private int mMaxWidth = Integer.MAX_VALUE; 360 private int mMaxWidthMode = PIXELS; 361 private int mMinWidth = 0; 362 private int mMinWidthMode = PIXELS; 363 364 private boolean mSingleLine; 365 private int mDesiredHeightAtMeasure = -1; 366 private boolean mIncludePad = true; 367 368 // tmp primitives, so we don't alloc them on each draw 369 private Rect mTempRect; 370 private long mLastScroll; 371 private Scroller mScroller; 372 373 private BoringLayout.Metrics mBoring, mHintBoring; 374 private BoringLayout mSavedLayout, mSavedHintLayout; 375 376 private TextDirectionHeuristic mTextDir; 377 378 private InputFilter[] mFilters = NO_FILTERS; 379 380 // It is possible to have a selection even when mEditor is null (programmatically set, like when 381 // a link is pressed). These highlight-related fields do not go in mEditor. 382 int mHighlightColor = 0x6633B5E5; 383 private Path mHighlightPath; 384 private final Paint mHighlightPaint; 385 private boolean mHighlightPathBogus = true; 386 387 // Although these fields are specific to editable text, they are not added to Editor because 388 // they are defined by the TextView's style and are theme-dependent. 389 int mCursorDrawableRes; 390 // These four fields, could be moved to Editor, since we know their default values and we 391 // could condition the creation of the Editor to a non standard value. This is however 392 // brittle since the hardcoded values here (such as 393 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the 394 // default style is modified. 395 int mTextSelectHandleLeftRes; 396 int mTextSelectHandleRightRes; 397 int mTextSelectHandleRes; 398 int mTextEditSuggestionItemLayout; 399 400 /** 401 * EditText specific data, created on demand when one of the Editor fields is used. 402 * See {@link #createEditorIfNeeded()}. 403 */ 404 private Editor mEditor; 405 406 /* 407 * Kick-start the font cache for the zygote process (to pay the cost of 408 * initializing freetype for our default font only once). 409 */ 410 static { 411 Paint p = new Paint(); 412 p.setAntiAlias(true); 413 // We don't care about the result, just the side-effect of measuring. 414 p.measureText("H"); 415 } 416 417 /** 418 * Interface definition for a callback to be invoked when an action is 419 * performed on the editor. 420 */ 421 public interface OnEditorActionListener { 422 /** 423 * Called when an action is being performed. 424 * 425 * @param v The view that was clicked. 426 * @param actionId Identifier of the action. This will be either the 427 * identifier you supplied, or {@link EditorInfo#IME_NULL 428 * EditorInfo.IME_NULL} if being called due to the enter key 429 * being pressed. 430 * @param event If triggered by an enter key, this is the event; 431 * otherwise, this is null. 432 * @return Return true if you have consumed the action, else false. 433 */ onEditorAction(TextView v, int actionId, KeyEvent event)434 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 435 } 436 TextView(Context context)437 public TextView(Context context) { 438 this(context, null); 439 } 440 TextView(Context context, AttributeSet attrs)441 public TextView(Context context, AttributeSet attrs) { 442 this(context, attrs, com.android.internal.R.attr.textViewStyle); 443 } 444 445 @SuppressWarnings("deprecation") TextView(Context context, AttributeSet attrs, int defStyle)446 public TextView(Context context, AttributeSet attrs, int defStyle) { 447 super(context, attrs, defStyle); 448 mText = ""; 449 450 final Resources res = getResources(); 451 final CompatibilityInfo compat = res.getCompatibilityInfo(); 452 453 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 454 mTextPaint.density = res.getDisplayMetrics().density; 455 mTextPaint.setCompatibilityScaling(compat.applicationScale); 456 457 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 458 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 459 460 mMovement = getDefaultMovementMethod(); 461 462 mTransformation = null; 463 464 int textColorHighlight = 0; 465 ColorStateList textColor = null; 466 ColorStateList textColorHint = null; 467 ColorStateList textColorLink = null; 468 int textSize = 15; 469 String fontFamily = null; 470 int typefaceIndex = -1; 471 int styleIndex = -1; 472 boolean allCaps = false; 473 474 final Resources.Theme theme = context.getTheme(); 475 476 /* 477 * Look the appearance up without checking first if it exists because 478 * almost every TextView has one and it greatly simplifies the logic 479 * to be able to parse the appearance first and then let specific tags 480 * for this View override it. 481 */ 482 TypedArray a = theme.obtainStyledAttributes( 483 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0); 484 TypedArray appearance = null; 485 int ap = a.getResourceId( 486 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 487 a.recycle(); 488 if (ap != -1) { 489 appearance = theme.obtainStyledAttributes( 490 ap, com.android.internal.R.styleable.TextAppearance); 491 } 492 if (appearance != null) { 493 int n = appearance.getIndexCount(); 494 for (int i = 0; i < n; i++) { 495 int attr = appearance.getIndex(i); 496 497 switch (attr) { 498 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 499 textColorHighlight = appearance.getColor(attr, textColorHighlight); 500 break; 501 502 case com.android.internal.R.styleable.TextAppearance_textColor: 503 textColor = appearance.getColorStateList(attr); 504 break; 505 506 case com.android.internal.R.styleable.TextAppearance_textColorHint: 507 textColorHint = appearance.getColorStateList(attr); 508 break; 509 510 case com.android.internal.R.styleable.TextAppearance_textColorLink: 511 textColorLink = appearance.getColorStateList(attr); 512 break; 513 514 case com.android.internal.R.styleable.TextAppearance_textSize: 515 textSize = appearance.getDimensionPixelSize(attr, textSize); 516 break; 517 518 case com.android.internal.R.styleable.TextAppearance_typeface: 519 typefaceIndex = appearance.getInt(attr, -1); 520 break; 521 522 case com.android.internal.R.styleable.TextAppearance_fontFamily: 523 fontFamily = appearance.getString(attr); 524 break; 525 526 case com.android.internal.R.styleable.TextAppearance_textStyle: 527 styleIndex = appearance.getInt(attr, -1); 528 break; 529 530 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 531 allCaps = appearance.getBoolean(attr, false); 532 break; 533 } 534 } 535 536 appearance.recycle(); 537 } 538 539 boolean editable = getDefaultEditable(); 540 CharSequence inputMethod = null; 541 int numeric = 0; 542 CharSequence digits = null; 543 boolean phone = false; 544 boolean autotext = false; 545 int autocap = -1; 546 int buffertype = 0; 547 boolean selectallonfocus = false; 548 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 549 drawableBottom = null, drawableStart = null, drawableEnd = null; 550 int drawablePadding = 0; 551 int ellipsize = -1; 552 boolean singleLine = false; 553 int maxlength = -1; 554 CharSequence text = ""; 555 CharSequence hint = null; 556 int shadowcolor = 0; 557 float dx = 0, dy = 0, r = 0; 558 boolean password = false; 559 int inputType = EditorInfo.TYPE_NULL; 560 561 a = theme.obtainStyledAttributes( 562 attrs, com.android.internal.R.styleable.TextView, defStyle, 0); 563 564 int n = a.getIndexCount(); 565 for (int i = 0; i < n; i++) { 566 int attr = a.getIndex(i); 567 568 switch (attr) { 569 case com.android.internal.R.styleable.TextView_editable: 570 editable = a.getBoolean(attr, editable); 571 break; 572 573 case com.android.internal.R.styleable.TextView_inputMethod: 574 inputMethod = a.getText(attr); 575 break; 576 577 case com.android.internal.R.styleable.TextView_numeric: 578 numeric = a.getInt(attr, numeric); 579 break; 580 581 case com.android.internal.R.styleable.TextView_digits: 582 digits = a.getText(attr); 583 break; 584 585 case com.android.internal.R.styleable.TextView_phoneNumber: 586 phone = a.getBoolean(attr, phone); 587 break; 588 589 case com.android.internal.R.styleable.TextView_autoText: 590 autotext = a.getBoolean(attr, autotext); 591 break; 592 593 case com.android.internal.R.styleable.TextView_capitalize: 594 autocap = a.getInt(attr, autocap); 595 break; 596 597 case com.android.internal.R.styleable.TextView_bufferType: 598 buffertype = a.getInt(attr, buffertype); 599 break; 600 601 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 602 selectallonfocus = a.getBoolean(attr, selectallonfocus); 603 break; 604 605 case com.android.internal.R.styleable.TextView_autoLink: 606 mAutoLinkMask = a.getInt(attr, 0); 607 break; 608 609 case com.android.internal.R.styleable.TextView_linksClickable: 610 mLinksClickable = a.getBoolean(attr, true); 611 break; 612 613 case com.android.internal.R.styleable.TextView_drawableLeft: 614 drawableLeft = a.getDrawable(attr); 615 break; 616 617 case com.android.internal.R.styleable.TextView_drawableTop: 618 drawableTop = a.getDrawable(attr); 619 break; 620 621 case com.android.internal.R.styleable.TextView_drawableRight: 622 drawableRight = a.getDrawable(attr); 623 break; 624 625 case com.android.internal.R.styleable.TextView_drawableBottom: 626 drawableBottom = a.getDrawable(attr); 627 break; 628 629 case com.android.internal.R.styleable.TextView_drawableStart: 630 drawableStart = a.getDrawable(attr); 631 break; 632 633 case com.android.internal.R.styleable.TextView_drawableEnd: 634 drawableEnd = a.getDrawable(attr); 635 break; 636 637 case com.android.internal.R.styleable.TextView_drawablePadding: 638 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 639 break; 640 641 case com.android.internal.R.styleable.TextView_maxLines: 642 setMaxLines(a.getInt(attr, -1)); 643 break; 644 645 case com.android.internal.R.styleable.TextView_maxHeight: 646 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 647 break; 648 649 case com.android.internal.R.styleable.TextView_lines: 650 setLines(a.getInt(attr, -1)); 651 break; 652 653 case com.android.internal.R.styleable.TextView_height: 654 setHeight(a.getDimensionPixelSize(attr, -1)); 655 break; 656 657 case com.android.internal.R.styleable.TextView_minLines: 658 setMinLines(a.getInt(attr, -1)); 659 break; 660 661 case com.android.internal.R.styleable.TextView_minHeight: 662 setMinHeight(a.getDimensionPixelSize(attr, -1)); 663 break; 664 665 case com.android.internal.R.styleable.TextView_maxEms: 666 setMaxEms(a.getInt(attr, -1)); 667 break; 668 669 case com.android.internal.R.styleable.TextView_maxWidth: 670 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 671 break; 672 673 case com.android.internal.R.styleable.TextView_ems: 674 setEms(a.getInt(attr, -1)); 675 break; 676 677 case com.android.internal.R.styleable.TextView_width: 678 setWidth(a.getDimensionPixelSize(attr, -1)); 679 break; 680 681 case com.android.internal.R.styleable.TextView_minEms: 682 setMinEms(a.getInt(attr, -1)); 683 break; 684 685 case com.android.internal.R.styleable.TextView_minWidth: 686 setMinWidth(a.getDimensionPixelSize(attr, -1)); 687 break; 688 689 case com.android.internal.R.styleable.TextView_gravity: 690 setGravity(a.getInt(attr, -1)); 691 break; 692 693 case com.android.internal.R.styleable.TextView_hint: 694 hint = a.getText(attr); 695 break; 696 697 case com.android.internal.R.styleable.TextView_text: 698 text = a.getText(attr); 699 break; 700 701 case com.android.internal.R.styleable.TextView_scrollHorizontally: 702 if (a.getBoolean(attr, false)) { 703 setHorizontallyScrolling(true); 704 } 705 break; 706 707 case com.android.internal.R.styleable.TextView_singleLine: 708 singleLine = a.getBoolean(attr, singleLine); 709 break; 710 711 case com.android.internal.R.styleable.TextView_ellipsize: 712 ellipsize = a.getInt(attr, ellipsize); 713 break; 714 715 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 716 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 717 break; 718 719 case com.android.internal.R.styleable.TextView_includeFontPadding: 720 if (!a.getBoolean(attr, true)) { 721 setIncludeFontPadding(false); 722 } 723 break; 724 725 case com.android.internal.R.styleable.TextView_cursorVisible: 726 if (!a.getBoolean(attr, true)) { 727 setCursorVisible(false); 728 } 729 break; 730 731 case com.android.internal.R.styleable.TextView_maxLength: 732 maxlength = a.getInt(attr, -1); 733 break; 734 735 case com.android.internal.R.styleable.TextView_textScaleX: 736 setTextScaleX(a.getFloat(attr, 1.0f)); 737 break; 738 739 case com.android.internal.R.styleable.TextView_freezesText: 740 mFreezesText = a.getBoolean(attr, false); 741 break; 742 743 case com.android.internal.R.styleable.TextView_shadowColor: 744 shadowcolor = a.getInt(attr, 0); 745 break; 746 747 case com.android.internal.R.styleable.TextView_shadowDx: 748 dx = a.getFloat(attr, 0); 749 break; 750 751 case com.android.internal.R.styleable.TextView_shadowDy: 752 dy = a.getFloat(attr, 0); 753 break; 754 755 case com.android.internal.R.styleable.TextView_shadowRadius: 756 r = a.getFloat(attr, 0); 757 break; 758 759 case com.android.internal.R.styleable.TextView_enabled: 760 setEnabled(a.getBoolean(attr, isEnabled())); 761 break; 762 763 case com.android.internal.R.styleable.TextView_textColorHighlight: 764 textColorHighlight = a.getColor(attr, textColorHighlight); 765 break; 766 767 case com.android.internal.R.styleable.TextView_textColor: 768 textColor = a.getColorStateList(attr); 769 break; 770 771 case com.android.internal.R.styleable.TextView_textColorHint: 772 textColorHint = a.getColorStateList(attr); 773 break; 774 775 case com.android.internal.R.styleable.TextView_textColorLink: 776 textColorLink = a.getColorStateList(attr); 777 break; 778 779 case com.android.internal.R.styleable.TextView_textSize: 780 textSize = a.getDimensionPixelSize(attr, textSize); 781 break; 782 783 case com.android.internal.R.styleable.TextView_typeface: 784 typefaceIndex = a.getInt(attr, typefaceIndex); 785 break; 786 787 case com.android.internal.R.styleable.TextView_textStyle: 788 styleIndex = a.getInt(attr, styleIndex); 789 break; 790 791 case com.android.internal.R.styleable.TextView_fontFamily: 792 fontFamily = a.getString(attr); 793 break; 794 795 case com.android.internal.R.styleable.TextView_password: 796 password = a.getBoolean(attr, password); 797 break; 798 799 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 800 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 801 break; 802 803 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 804 mSpacingMult = a.getFloat(attr, mSpacingMult); 805 break; 806 807 case com.android.internal.R.styleable.TextView_inputType: 808 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 809 break; 810 811 case com.android.internal.R.styleable.TextView_imeOptions: 812 createEditorIfNeeded(); 813 mEditor.createInputContentTypeIfNeeded(); 814 mEditor.mInputContentType.imeOptions = a.getInt(attr, 815 mEditor.mInputContentType.imeOptions); 816 break; 817 818 case com.android.internal.R.styleable.TextView_imeActionLabel: 819 createEditorIfNeeded(); 820 mEditor.createInputContentTypeIfNeeded(); 821 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 822 break; 823 824 case com.android.internal.R.styleable.TextView_imeActionId: 825 createEditorIfNeeded(); 826 mEditor.createInputContentTypeIfNeeded(); 827 mEditor.mInputContentType.imeActionId = a.getInt(attr, 828 mEditor.mInputContentType.imeActionId); 829 break; 830 831 case com.android.internal.R.styleable.TextView_privateImeOptions: 832 setPrivateImeOptions(a.getString(attr)); 833 break; 834 835 case com.android.internal.R.styleable.TextView_editorExtras: 836 try { 837 setInputExtras(a.getResourceId(attr, 0)); 838 } catch (XmlPullParserException e) { 839 Log.w(LOG_TAG, "Failure reading input extras", e); 840 } catch (IOException e) { 841 Log.w(LOG_TAG, "Failure reading input extras", e); 842 } 843 break; 844 845 case com.android.internal.R.styleable.TextView_textCursorDrawable: 846 mCursorDrawableRes = a.getResourceId(attr, 0); 847 break; 848 849 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 850 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 851 break; 852 853 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 854 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 855 break; 856 857 case com.android.internal.R.styleable.TextView_textSelectHandle: 858 mTextSelectHandleRes = a.getResourceId(attr, 0); 859 break; 860 861 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 862 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 863 break; 864 865 case com.android.internal.R.styleable.TextView_textIsSelectable: 866 setTextIsSelectable(a.getBoolean(attr, false)); 867 break; 868 869 case com.android.internal.R.styleable.TextView_textAllCaps: 870 allCaps = a.getBoolean(attr, false); 871 break; 872 } 873 } 874 a.recycle(); 875 876 BufferType bufferType = BufferType.EDITABLE; 877 878 final int variation = 879 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 880 final boolean passwordInputType = variation 881 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 882 final boolean webPasswordInputType = variation 883 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 884 final boolean numberPasswordInputType = variation 885 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 886 887 if (inputMethod != null) { 888 Class<?> c; 889 890 try { 891 c = Class.forName(inputMethod.toString()); 892 } catch (ClassNotFoundException ex) { 893 throw new RuntimeException(ex); 894 } 895 896 try { 897 createEditorIfNeeded(); 898 mEditor.mKeyListener = (KeyListener) c.newInstance(); 899 } catch (InstantiationException ex) { 900 throw new RuntimeException(ex); 901 } catch (IllegalAccessException ex) { 902 throw new RuntimeException(ex); 903 } 904 try { 905 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 906 ? inputType 907 : mEditor.mKeyListener.getInputType(); 908 } catch (IncompatibleClassChangeError e) { 909 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 910 } 911 } else if (digits != null) { 912 createEditorIfNeeded(); 913 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 914 // If no input type was specified, we will default to generic 915 // text, since we can't tell the IME about the set of digits 916 // that was selected. 917 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 918 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 919 } else if (inputType != EditorInfo.TYPE_NULL) { 920 setInputType(inputType, true); 921 // If set, the input type overrides what was set using the deprecated singleLine flag. 922 singleLine = !isMultilineInputType(inputType); 923 } else if (phone) { 924 createEditorIfNeeded(); 925 mEditor.mKeyListener = DialerKeyListener.getInstance(); 926 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 927 } else if (numeric != 0) { 928 createEditorIfNeeded(); 929 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, 930 (numeric & DECIMAL) != 0); 931 inputType = EditorInfo.TYPE_CLASS_NUMBER; 932 if ((numeric & SIGNED) != 0) { 933 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; 934 } 935 if ((numeric & DECIMAL) != 0) { 936 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; 937 } 938 mEditor.mInputType = inputType; 939 } else if (autotext || autocap != -1) { 940 TextKeyListener.Capitalize cap; 941 942 inputType = EditorInfo.TYPE_CLASS_TEXT; 943 944 switch (autocap) { 945 case 1: 946 cap = TextKeyListener.Capitalize.SENTENCES; 947 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 948 break; 949 950 case 2: 951 cap = TextKeyListener.Capitalize.WORDS; 952 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 953 break; 954 955 case 3: 956 cap = TextKeyListener.Capitalize.CHARACTERS; 957 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 958 break; 959 960 default: 961 cap = TextKeyListener.Capitalize.NONE; 962 break; 963 } 964 965 createEditorIfNeeded(); 966 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 967 mEditor.mInputType = inputType; 968 } else if (isTextSelectable()) { 969 // Prevent text changes from keyboard. 970 if (mEditor != null) { 971 mEditor.mKeyListener = null; 972 mEditor.mInputType = EditorInfo.TYPE_NULL; 973 } 974 bufferType = BufferType.SPANNABLE; 975 // So that selection can be changed using arrow keys and touch is handled. 976 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 977 } else if (editable) { 978 createEditorIfNeeded(); 979 mEditor.mKeyListener = TextKeyListener.getInstance(); 980 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 981 } else { 982 if (mEditor != null) mEditor.mKeyListener = null; 983 984 switch (buffertype) { 985 case 0: 986 bufferType = BufferType.NORMAL; 987 break; 988 case 1: 989 bufferType = BufferType.SPANNABLE; 990 break; 991 case 2: 992 bufferType = BufferType.EDITABLE; 993 break; 994 } 995 } 996 997 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType, 998 webPasswordInputType, numberPasswordInputType); 999 1000 if (selectallonfocus) { 1001 createEditorIfNeeded(); 1002 mEditor.mSelectAllOnFocus = true; 1003 1004 if (bufferType == BufferType.NORMAL) 1005 bufferType = BufferType.SPANNABLE; 1006 } 1007 1008 setCompoundDrawablesWithIntrinsicBounds( 1009 drawableLeft, drawableTop, drawableRight, drawableBottom); 1010 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1011 setCompoundDrawablePadding(drawablePadding); 1012 1013 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1014 // of lines of height are unchanged for multi-line TextViews. 1015 setInputTypeSingleLine(singleLine); 1016 applySingleLine(singleLine, singleLine, singleLine); 1017 1018 if (singleLine && getKeyListener() == null && ellipsize < 0) { 1019 ellipsize = 3; // END 1020 } 1021 1022 switch (ellipsize) { 1023 case 1: 1024 setEllipsize(TextUtils.TruncateAt.START); 1025 break; 1026 case 2: 1027 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1028 break; 1029 case 3: 1030 setEllipsize(TextUtils.TruncateAt.END); 1031 break; 1032 case 4: 1033 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1034 setHorizontalFadingEdgeEnabled(true); 1035 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1036 } else { 1037 setHorizontalFadingEdgeEnabled(false); 1038 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1039 } 1040 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1041 break; 1042 } 1043 1044 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 1045 setHintTextColor(textColorHint); 1046 setLinkTextColor(textColorLink); 1047 if (textColorHighlight != 0) { 1048 setHighlightColor(textColorHighlight); 1049 } 1050 setRawTextSize(textSize); 1051 1052 if (allCaps) { 1053 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 1054 } 1055 1056 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) { 1057 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1058 typefaceIndex = MONOSPACE; 1059 } else if (mEditor != null && 1060 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1061 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 1062 typefaceIndex = MONOSPACE; 1063 } 1064 1065 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex); 1066 1067 if (shadowcolor != 0) { 1068 setShadowLayer(r, dx, dy, shadowcolor); 1069 } 1070 1071 if (maxlength >= 0) { 1072 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1073 } else { 1074 setFilters(NO_FILTERS); 1075 } 1076 1077 setText(text, bufferType); 1078 if (hint != null) setHint(hint); 1079 1080 /* 1081 * Views are not normally focusable unless specified to be. 1082 * However, TextViews that have input or movement methods *are* 1083 * focusable by default. 1084 */ 1085 a = context.obtainStyledAttributes(attrs, 1086 com.android.internal.R.styleable.View, 1087 defStyle, 0); 1088 1089 boolean focusable = mMovement != null || getKeyListener() != null; 1090 boolean clickable = focusable; 1091 boolean longClickable = focusable; 1092 1093 n = a.getIndexCount(); 1094 for (int i = 0; i < n; i++) { 1095 int attr = a.getIndex(i); 1096 1097 switch (attr) { 1098 case com.android.internal.R.styleable.View_focusable: 1099 focusable = a.getBoolean(attr, focusable); 1100 break; 1101 1102 case com.android.internal.R.styleable.View_clickable: 1103 clickable = a.getBoolean(attr, clickable); 1104 break; 1105 1106 case com.android.internal.R.styleable.View_longClickable: 1107 longClickable = a.getBoolean(attr, longClickable); 1108 break; 1109 } 1110 } 1111 a.recycle(); 1112 1113 setFocusable(focusable); 1114 setClickable(clickable); 1115 setLongClickable(longClickable); 1116 1117 if (mEditor != null) mEditor.prepareCursorControllers(); 1118 1119 // If not explicitly specified this view is important for accessibility. 1120 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1121 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1122 } 1123 } 1124 setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex)1125 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) { 1126 Typeface tf = null; 1127 if (familyName != null) { 1128 tf = Typeface.create(familyName, styleIndex); 1129 if (tf != null) { 1130 setTypeface(tf); 1131 return; 1132 } 1133 } 1134 switch (typefaceIndex) { 1135 case SANS: 1136 tf = Typeface.SANS_SERIF; 1137 break; 1138 1139 case SERIF: 1140 tf = Typeface.SERIF; 1141 break; 1142 1143 case MONOSPACE: 1144 tf = Typeface.MONOSPACE; 1145 break; 1146 } 1147 1148 setTypeface(tf, styleIndex); 1149 } 1150 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)1151 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 1152 boolean hasRelativeDrawables = (start != null) || (end != null); 1153 if (hasRelativeDrawables) { 1154 Drawables dr = mDrawables; 1155 if (dr == null) { 1156 mDrawables = dr = new Drawables(); 1157 } 1158 final Rect compoundRect = dr.mCompoundRect; 1159 int[] state = getDrawableState(); 1160 if (start != null) { 1161 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 1162 start.setState(state); 1163 start.copyBounds(compoundRect); 1164 start.setCallback(this); 1165 1166 dr.mDrawableStart = start; 1167 dr.mDrawableSizeStart = compoundRect.width(); 1168 dr.mDrawableHeightStart = compoundRect.height(); 1169 } else { 1170 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 1171 } 1172 if (end != null) { 1173 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 1174 end.setState(state); 1175 end.copyBounds(compoundRect); 1176 end.setCallback(this); 1177 1178 dr.mDrawableEnd = end; 1179 dr.mDrawableSizeEnd = compoundRect.width(); 1180 dr.mDrawableHeightEnd = compoundRect.height(); 1181 } else { 1182 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 1183 } 1184 } 1185 } 1186 1187 @Override setEnabled(boolean enabled)1188 public void setEnabled(boolean enabled) { 1189 if (enabled == isEnabled()) { 1190 return; 1191 } 1192 1193 if (!enabled) { 1194 // Hide the soft input if the currently active TextView is disabled 1195 InputMethodManager imm = InputMethodManager.peekInstance(); 1196 if (imm != null && imm.isActive(this)) { 1197 imm.hideSoftInputFromWindow(getWindowToken(), 0); 1198 } 1199 } 1200 1201 super.setEnabled(enabled); 1202 1203 if (enabled) { 1204 // Make sure IME is updated with current editor info. 1205 InputMethodManager imm = InputMethodManager.peekInstance(); 1206 if (imm != null) imm.restartInput(this); 1207 } 1208 1209 // Will change text color 1210 if (mEditor != null) { 1211 mEditor.invalidateTextDisplayList(); 1212 mEditor.prepareCursorControllers(); 1213 1214 // start or stop the cursor blinking as appropriate 1215 mEditor.makeBlink(); 1216 } 1217 } 1218 1219 /** 1220 * Sets the typeface and style in which the text should be displayed, 1221 * and turns on the fake bold and italic bits in the Paint if the 1222 * Typeface that you provided does not have all the bits in the 1223 * style that you specified. 1224 * 1225 * @attr ref android.R.styleable#TextView_typeface 1226 * @attr ref android.R.styleable#TextView_textStyle 1227 */ setTypeface(Typeface tf, int style)1228 public void setTypeface(Typeface tf, int style) { 1229 if (style > 0) { 1230 if (tf == null) { 1231 tf = Typeface.defaultFromStyle(style); 1232 } else { 1233 tf = Typeface.create(tf, style); 1234 } 1235 1236 setTypeface(tf); 1237 // now compute what (if any) algorithmic styling is needed 1238 int typefaceStyle = tf != null ? tf.getStyle() : 0; 1239 int need = style & ~typefaceStyle; 1240 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 1241 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 1242 } else { 1243 mTextPaint.setFakeBoldText(false); 1244 mTextPaint.setTextSkewX(0); 1245 setTypeface(tf); 1246 } 1247 } 1248 1249 /** 1250 * Subclasses override this to specify that they have a KeyListener 1251 * by default even if not specifically called for in the XML options. 1252 */ getDefaultEditable()1253 protected boolean getDefaultEditable() { 1254 return false; 1255 } 1256 1257 /** 1258 * Subclasses override this to specify a default movement method. 1259 */ getDefaultMovementMethod()1260 protected MovementMethod getDefaultMovementMethod() { 1261 return null; 1262 } 1263 1264 /** 1265 * Return the text the TextView is displaying. If setText() was called with 1266 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast 1267 * the return value from this method to Spannable or Editable, respectively. 1268 * 1269 * Note: The content of the return value should not be modified. If you want 1270 * a modifiable one, you should make your own copy first. 1271 * 1272 * @attr ref android.R.styleable#TextView_text 1273 */ 1274 @ViewDebug.CapturedViewProperty getText()1275 public CharSequence getText() { 1276 return mText; 1277 } 1278 1279 /** 1280 * Returns the length, in characters, of the text managed by this TextView 1281 */ length()1282 public int length() { 1283 return mText.length(); 1284 } 1285 1286 /** 1287 * Return the text the TextView is displaying as an Editable object. If 1288 * the text is not editable, null is returned. 1289 * 1290 * @see #getText 1291 */ getEditableText()1292 public Editable getEditableText() { 1293 return (mText instanceof Editable) ? (Editable)mText : null; 1294 } 1295 1296 /** 1297 * @return the height of one standard line in pixels. Note that markup 1298 * within the text can cause individual lines to be taller or shorter 1299 * than this height, and the layout may contain additional first- 1300 * or last-line padding. 1301 */ getLineHeight()1302 public int getLineHeight() { 1303 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 1304 } 1305 1306 /** 1307 * @return the Layout that is currently being used to display the text. 1308 * This can be null if the text or width has recently changes. 1309 */ getLayout()1310 public final Layout getLayout() { 1311 return mLayout; 1312 } 1313 1314 /** 1315 * @return the Layout that is currently being used to display the hint text. 1316 * This can be null. 1317 */ getHintLayout()1318 final Layout getHintLayout() { 1319 return mHintLayout; 1320 } 1321 1322 /** 1323 * @return the current key listener for this TextView. 1324 * This will frequently be null for non-EditText TextViews. 1325 * 1326 * @attr ref android.R.styleable#TextView_numeric 1327 * @attr ref android.R.styleable#TextView_digits 1328 * @attr ref android.R.styleable#TextView_phoneNumber 1329 * @attr ref android.R.styleable#TextView_inputMethod 1330 * @attr ref android.R.styleable#TextView_capitalize 1331 * @attr ref android.R.styleable#TextView_autoText 1332 */ getKeyListener()1333 public final KeyListener getKeyListener() { 1334 return mEditor == null ? null : mEditor.mKeyListener; 1335 } 1336 1337 /** 1338 * Sets the key listener to be used with this TextView. This can be null 1339 * to disallow user input. Note that this method has significant and 1340 * subtle interactions with soft keyboards and other input method: 1341 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 1342 * for important details. Calling this method will replace the current 1343 * content type of the text view with the content type returned by the 1344 * key listener. 1345 * <p> 1346 * Be warned that if you want a TextView with a key listener or movement 1347 * method not to be focusable, or if you want a TextView without a 1348 * key listener or movement method to be focusable, you must call 1349 * {@link #setFocusable} again after calling this to get the focusability 1350 * back the way you want it. 1351 * 1352 * @attr ref android.R.styleable#TextView_numeric 1353 * @attr ref android.R.styleable#TextView_digits 1354 * @attr ref android.R.styleable#TextView_phoneNumber 1355 * @attr ref android.R.styleable#TextView_inputMethod 1356 * @attr ref android.R.styleable#TextView_capitalize 1357 * @attr ref android.R.styleable#TextView_autoText 1358 */ setKeyListener(KeyListener input)1359 public void setKeyListener(KeyListener input) { 1360 setKeyListenerOnly(input); 1361 fixFocusableAndClickableSettings(); 1362 1363 if (input != null) { 1364 createEditorIfNeeded(); 1365 try { 1366 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 1367 } catch (IncompatibleClassChangeError e) { 1368 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1369 } 1370 // Change inputType, without affecting transformation. 1371 // No need to applySingleLine since mSingleLine is unchanged. 1372 setInputTypeSingleLine(mSingleLine); 1373 } else { 1374 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 1375 } 1376 1377 InputMethodManager imm = InputMethodManager.peekInstance(); 1378 if (imm != null) imm.restartInput(this); 1379 } 1380 setKeyListenerOnly(KeyListener input)1381 private void setKeyListenerOnly(KeyListener input) { 1382 if (mEditor == null && input == null) return; // null is the default value 1383 1384 createEditorIfNeeded(); 1385 if (mEditor.mKeyListener != input) { 1386 mEditor.mKeyListener = input; 1387 if (input != null && !(mText instanceof Editable)) { 1388 setText(mText); 1389 } 1390 1391 setFilters((Editable) mText, mFilters); 1392 } 1393 } 1394 1395 /** 1396 * @return the movement method being used for this TextView. 1397 * This will frequently be null for non-EditText TextViews. 1398 */ getMovementMethod()1399 public final MovementMethod getMovementMethod() { 1400 return mMovement; 1401 } 1402 1403 /** 1404 * Sets the movement method (arrow key handler) to be used for 1405 * this TextView. This can be null to disallow using the arrow keys 1406 * to move the cursor or scroll the view. 1407 * <p> 1408 * Be warned that if you want a TextView with a key listener or movement 1409 * method not to be focusable, or if you want a TextView without a 1410 * key listener or movement method to be focusable, you must call 1411 * {@link #setFocusable} again after calling this to get the focusability 1412 * back the way you want it. 1413 */ setMovementMethod(MovementMethod movement)1414 public final void setMovementMethod(MovementMethod movement) { 1415 if (mMovement != movement) { 1416 mMovement = movement; 1417 1418 if (movement != null && !(mText instanceof Spannable)) { 1419 setText(mText); 1420 } 1421 1422 fixFocusableAndClickableSettings(); 1423 1424 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 1425 // mMovement 1426 if (mEditor != null) mEditor.prepareCursorControllers(); 1427 } 1428 } 1429 fixFocusableAndClickableSettings()1430 private void fixFocusableAndClickableSettings() { 1431 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 1432 setFocusable(true); 1433 setClickable(true); 1434 setLongClickable(true); 1435 } else { 1436 setFocusable(false); 1437 setClickable(false); 1438 setLongClickable(false); 1439 } 1440 } 1441 1442 /** 1443 * @return the current transformation method for this TextView. 1444 * This will frequently be null except for single-line and password 1445 * fields. 1446 * 1447 * @attr ref android.R.styleable#TextView_password 1448 * @attr ref android.R.styleable#TextView_singleLine 1449 */ getTransformationMethod()1450 public final TransformationMethod getTransformationMethod() { 1451 return mTransformation; 1452 } 1453 1454 /** 1455 * Sets the transformation that is applied to the text that this 1456 * TextView is displaying. 1457 * 1458 * @attr ref android.R.styleable#TextView_password 1459 * @attr ref android.R.styleable#TextView_singleLine 1460 */ setTransformationMethod(TransformationMethod method)1461 public final void setTransformationMethod(TransformationMethod method) { 1462 if (method == mTransformation) { 1463 // Avoid the setText() below if the transformation is 1464 // the same. 1465 return; 1466 } 1467 if (mTransformation != null) { 1468 if (mText instanceof Spannable) { 1469 ((Spannable) mText).removeSpan(mTransformation); 1470 } 1471 } 1472 1473 mTransformation = method; 1474 1475 if (method instanceof TransformationMethod2) { 1476 TransformationMethod2 method2 = (TransformationMethod2) method; 1477 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 1478 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 1479 } else { 1480 mAllowTransformationLengthChange = false; 1481 } 1482 1483 setText(mText); 1484 1485 if (hasPasswordTransformationMethod()) { 1486 notifyAccessibilityStateChanged(); 1487 } 1488 } 1489 1490 /** 1491 * Returns the top padding of the view, plus space for the top 1492 * Drawable if any. 1493 */ getCompoundPaddingTop()1494 public int getCompoundPaddingTop() { 1495 final Drawables dr = mDrawables; 1496 if (dr == null || dr.mDrawableTop == null) { 1497 return mPaddingTop; 1498 } else { 1499 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 1500 } 1501 } 1502 1503 /** 1504 * Returns the bottom padding of the view, plus space for the bottom 1505 * Drawable if any. 1506 */ getCompoundPaddingBottom()1507 public int getCompoundPaddingBottom() { 1508 final Drawables dr = mDrawables; 1509 if (dr == null || dr.mDrawableBottom == null) { 1510 return mPaddingBottom; 1511 } else { 1512 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 1513 } 1514 } 1515 1516 /** 1517 * Returns the left padding of the view, plus space for the left 1518 * Drawable if any. 1519 */ getCompoundPaddingLeft()1520 public int getCompoundPaddingLeft() { 1521 final Drawables dr = mDrawables; 1522 if (dr == null || dr.mDrawableLeft == null) { 1523 return mPaddingLeft; 1524 } else { 1525 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 1526 } 1527 } 1528 1529 /** 1530 * Returns the right padding of the view, plus space for the right 1531 * Drawable if any. 1532 */ getCompoundPaddingRight()1533 public int getCompoundPaddingRight() { 1534 final Drawables dr = mDrawables; 1535 if (dr == null || dr.mDrawableRight == null) { 1536 return mPaddingRight; 1537 } else { 1538 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 1539 } 1540 } 1541 1542 /** 1543 * Returns the start padding of the view, plus space for the start 1544 * Drawable if any. 1545 * @hide 1546 */ getCompoundPaddingStart()1547 public int getCompoundPaddingStart() { 1548 resolveDrawables(); 1549 switch(getResolvedLayoutDirection()) { 1550 default: 1551 case LAYOUT_DIRECTION_LTR: 1552 return getCompoundPaddingLeft(); 1553 case LAYOUT_DIRECTION_RTL: 1554 return getCompoundPaddingRight(); 1555 } 1556 } 1557 1558 /** 1559 * Returns the end padding of the view, plus space for the end 1560 * Drawable if any. 1561 * @hide 1562 */ getCompoundPaddingEnd()1563 public int getCompoundPaddingEnd() { 1564 resolveDrawables(); 1565 switch(getResolvedLayoutDirection()) { 1566 default: 1567 case LAYOUT_DIRECTION_LTR: 1568 return getCompoundPaddingRight(); 1569 case LAYOUT_DIRECTION_RTL: 1570 return getCompoundPaddingLeft(); 1571 } 1572 } 1573 1574 /** 1575 * Returns the extended top padding of the view, including both the 1576 * top Drawable if any and any extra space to keep more than maxLines 1577 * of text from showing. It is only valid to call this after measuring. 1578 */ getExtendedPaddingTop()1579 public int getExtendedPaddingTop() { 1580 if (mMaxMode != LINES) { 1581 return getCompoundPaddingTop(); 1582 } 1583 1584 if (mLayout.getLineCount() <= mMaximum) { 1585 return getCompoundPaddingTop(); 1586 } 1587 1588 int top = getCompoundPaddingTop(); 1589 int bottom = getCompoundPaddingBottom(); 1590 int viewht = getHeight() - top - bottom; 1591 int layoutht = mLayout.getLineTop(mMaximum); 1592 1593 if (layoutht >= viewht) { 1594 return top; 1595 } 1596 1597 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1598 if (gravity == Gravity.TOP) { 1599 return top; 1600 } else if (gravity == Gravity.BOTTOM) { 1601 return top + viewht - layoutht; 1602 } else { // (gravity == Gravity.CENTER_VERTICAL) 1603 return top + (viewht - layoutht) / 2; 1604 } 1605 } 1606 1607 /** 1608 * Returns the extended bottom padding of the view, including both the 1609 * bottom Drawable if any and any extra space to keep more than maxLines 1610 * of text from showing. It is only valid to call this after measuring. 1611 */ getExtendedPaddingBottom()1612 public int getExtendedPaddingBottom() { 1613 if (mMaxMode != LINES) { 1614 return getCompoundPaddingBottom(); 1615 } 1616 1617 if (mLayout.getLineCount() <= mMaximum) { 1618 return getCompoundPaddingBottom(); 1619 } 1620 1621 int top = getCompoundPaddingTop(); 1622 int bottom = getCompoundPaddingBottom(); 1623 int viewht = getHeight() - top - bottom; 1624 int layoutht = mLayout.getLineTop(mMaximum); 1625 1626 if (layoutht >= viewht) { 1627 return bottom; 1628 } 1629 1630 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1631 if (gravity == Gravity.TOP) { 1632 return bottom + viewht - layoutht; 1633 } else if (gravity == Gravity.BOTTOM) { 1634 return bottom; 1635 } else { // (gravity == Gravity.CENTER_VERTICAL) 1636 return bottom + (viewht - layoutht) / 2; 1637 } 1638 } 1639 1640 /** 1641 * Returns the total left padding of the view, including the left 1642 * Drawable if any. 1643 */ getTotalPaddingLeft()1644 public int getTotalPaddingLeft() { 1645 return getCompoundPaddingLeft(); 1646 } 1647 1648 /** 1649 * Returns the total right padding of the view, including the right 1650 * Drawable if any. 1651 */ getTotalPaddingRight()1652 public int getTotalPaddingRight() { 1653 return getCompoundPaddingRight(); 1654 } 1655 1656 /** 1657 * Returns the total start padding of the view, including the start 1658 * Drawable if any. 1659 * @hide 1660 */ getTotalPaddingStart()1661 public int getTotalPaddingStart() { 1662 return getCompoundPaddingStart(); 1663 } 1664 1665 /** 1666 * Returns the total end padding of the view, including the end 1667 * Drawable if any. 1668 * @hide 1669 */ getTotalPaddingEnd()1670 public int getTotalPaddingEnd() { 1671 return getCompoundPaddingEnd(); 1672 } 1673 1674 /** 1675 * Returns the total top padding of the view, including the top 1676 * Drawable if any, the extra space to keep more than maxLines 1677 * from showing, and the vertical offset for gravity, if any. 1678 */ getTotalPaddingTop()1679 public int getTotalPaddingTop() { 1680 return getExtendedPaddingTop() + getVerticalOffset(true); 1681 } 1682 1683 /** 1684 * Returns the total bottom padding of the view, including the bottom 1685 * Drawable if any, the extra space to keep more than maxLines 1686 * from showing, and the vertical offset for gravity, if any. 1687 */ getTotalPaddingBottom()1688 public int getTotalPaddingBottom() { 1689 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 1690 } 1691 1692 /** 1693 * Sets the Drawables (if any) to appear to the left of, above, 1694 * to the right of, and below the text. Use null if you do not 1695 * want a Drawable there. The Drawables must already have had 1696 * {@link Drawable#setBounds} called. 1697 * 1698 * @attr ref android.R.styleable#TextView_drawableLeft 1699 * @attr ref android.R.styleable#TextView_drawableTop 1700 * @attr ref android.R.styleable#TextView_drawableRight 1701 * @attr ref android.R.styleable#TextView_drawableBottom 1702 */ setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)1703 public void setCompoundDrawables(Drawable left, Drawable top, 1704 Drawable right, Drawable bottom) { 1705 Drawables dr = mDrawables; 1706 1707 final boolean drawables = left != null || top != null 1708 || right != null || bottom != null; 1709 1710 if (!drawables) { 1711 // Clearing drawables... can we free the data structure? 1712 if (dr != null) { 1713 if (dr.mDrawablePadding == 0) { 1714 mDrawables = null; 1715 } else { 1716 // We need to retain the last set padding, so just clear 1717 // out all of the fields in the existing structure. 1718 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); 1719 dr.mDrawableLeft = null; 1720 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); 1721 dr.mDrawableTop = null; 1722 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); 1723 dr.mDrawableRight = null; 1724 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); 1725 dr.mDrawableBottom = null; 1726 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1727 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1728 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1729 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1730 } 1731 } 1732 } else { 1733 if (dr == null) { 1734 mDrawables = dr = new Drawables(); 1735 } 1736 1737 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) { 1738 dr.mDrawableLeft.setCallback(null); 1739 } 1740 dr.mDrawableLeft = left; 1741 1742 if (dr.mDrawableTop != top && dr.mDrawableTop != null) { 1743 dr.mDrawableTop.setCallback(null); 1744 } 1745 dr.mDrawableTop = top; 1746 1747 if (dr.mDrawableRight != right && dr.mDrawableRight != null) { 1748 dr.mDrawableRight.setCallback(null); 1749 } 1750 dr.mDrawableRight = right; 1751 1752 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { 1753 dr.mDrawableBottom.setCallback(null); 1754 } 1755 dr.mDrawableBottom = bottom; 1756 1757 final Rect compoundRect = dr.mCompoundRect; 1758 int[] state; 1759 1760 state = getDrawableState(); 1761 1762 if (left != null) { 1763 left.setState(state); 1764 left.copyBounds(compoundRect); 1765 left.setCallback(this); 1766 dr.mDrawableSizeLeft = compoundRect.width(); 1767 dr.mDrawableHeightLeft = compoundRect.height(); 1768 } else { 1769 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1770 } 1771 1772 if (right != null) { 1773 right.setState(state); 1774 right.copyBounds(compoundRect); 1775 right.setCallback(this); 1776 dr.mDrawableSizeRight = compoundRect.width(); 1777 dr.mDrawableHeightRight = compoundRect.height(); 1778 } else { 1779 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1780 } 1781 1782 if (top != null) { 1783 top.setState(state); 1784 top.copyBounds(compoundRect); 1785 top.setCallback(this); 1786 dr.mDrawableSizeTop = compoundRect.height(); 1787 dr.mDrawableWidthTop = compoundRect.width(); 1788 } else { 1789 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1790 } 1791 1792 if (bottom != null) { 1793 bottom.setState(state); 1794 bottom.copyBounds(compoundRect); 1795 bottom.setCallback(this); 1796 dr.mDrawableSizeBottom = compoundRect.height(); 1797 dr.mDrawableWidthBottom = compoundRect.width(); 1798 } else { 1799 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1800 } 1801 } 1802 1803 invalidate(); 1804 requestLayout(); 1805 } 1806 1807 /** 1808 * Sets the Drawables (if any) to appear to the left of, above, 1809 * to the right of, and below the text. Use 0 if you do not 1810 * want a Drawable there. The Drawables' bounds will be set to 1811 * their intrinsic bounds. 1812 * 1813 * @param left Resource identifier of the left Drawable. 1814 * @param top Resource identifier of the top Drawable. 1815 * @param right Resource identifier of the right Drawable. 1816 * @param bottom Resource identifier of the bottom Drawable. 1817 * 1818 * @attr ref android.R.styleable#TextView_drawableLeft 1819 * @attr ref android.R.styleable#TextView_drawableTop 1820 * @attr ref android.R.styleable#TextView_drawableRight 1821 * @attr ref android.R.styleable#TextView_drawableBottom 1822 */ 1823 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)1824 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 1825 final Resources resources = getContext().getResources(); 1826 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null, 1827 top != 0 ? resources.getDrawable(top) : null, 1828 right != 0 ? resources.getDrawable(right) : null, 1829 bottom != 0 ? resources.getDrawable(bottom) : null); 1830 } 1831 1832 /** 1833 * Sets the Drawables (if any) to appear to the left of, above, 1834 * to the right of, and below the text. Use null if you do not 1835 * want a Drawable there. The Drawables' bounds will be set to 1836 * their intrinsic bounds. 1837 * 1838 * @attr ref android.R.styleable#TextView_drawableLeft 1839 * @attr ref android.R.styleable#TextView_drawableTop 1840 * @attr ref android.R.styleable#TextView_drawableRight 1841 * @attr ref android.R.styleable#TextView_drawableBottom 1842 */ setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)1843 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, 1844 Drawable right, Drawable bottom) { 1845 1846 if (left != null) { 1847 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 1848 } 1849 if (right != null) { 1850 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 1851 } 1852 if (top != null) { 1853 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 1854 } 1855 if (bottom != null) { 1856 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 1857 } 1858 setCompoundDrawables(left, top, right, bottom); 1859 } 1860 1861 /** 1862 * Sets the Drawables (if any) to appear to the start of, above, 1863 * to the end of, and below the text. Use null if you do not 1864 * want a Drawable there. The Drawables must already have had 1865 * {@link Drawable#setBounds} called. 1866 * 1867 * @attr ref android.R.styleable#TextView_drawableStart 1868 * @attr ref android.R.styleable#TextView_drawableTop 1869 * @attr ref android.R.styleable#TextView_drawableEnd 1870 * @attr ref android.R.styleable#TextView_drawableBottom 1871 * @hide 1872 */ setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end, Drawable bottom)1873 public void setCompoundDrawablesRelative(Drawable start, Drawable top, 1874 Drawable end, Drawable bottom) { 1875 Drawables dr = mDrawables; 1876 1877 final boolean drawables = start != null || top != null 1878 || end != null || bottom != null; 1879 1880 if (!drawables) { 1881 // Clearing drawables... can we free the data structure? 1882 if (dr != null) { 1883 if (dr.mDrawablePadding == 0) { 1884 mDrawables = null; 1885 } else { 1886 // We need to retain the last set padding, so just clear 1887 // out all of the fields in the existing structure. 1888 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 1889 dr.mDrawableStart = null; 1890 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); 1891 dr.mDrawableTop = null; 1892 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 1893 dr.mDrawableEnd = null; 1894 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); 1895 dr.mDrawableBottom = null; 1896 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 1897 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 1898 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1899 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1900 } 1901 } 1902 } else { 1903 if (dr == null) { 1904 mDrawables = dr = new Drawables(); 1905 } 1906 1907 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 1908 dr.mDrawableStart.setCallback(null); 1909 } 1910 dr.mDrawableStart = start; 1911 1912 if (dr.mDrawableTop != top && dr.mDrawableTop != null) { 1913 dr.mDrawableTop.setCallback(null); 1914 } 1915 dr.mDrawableTop = top; 1916 1917 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 1918 dr.mDrawableEnd.setCallback(null); 1919 } 1920 dr.mDrawableEnd = end; 1921 1922 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { 1923 dr.mDrawableBottom.setCallback(null); 1924 } 1925 dr.mDrawableBottom = bottom; 1926 1927 final Rect compoundRect = dr.mCompoundRect; 1928 int[] state; 1929 1930 state = getDrawableState(); 1931 1932 if (start != null) { 1933 start.setState(state); 1934 start.copyBounds(compoundRect); 1935 start.setCallback(this); 1936 dr.mDrawableSizeStart = compoundRect.width(); 1937 dr.mDrawableHeightStart = compoundRect.height(); 1938 } else { 1939 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 1940 } 1941 1942 if (end != null) { 1943 end.setState(state); 1944 end.copyBounds(compoundRect); 1945 end.setCallback(this); 1946 dr.mDrawableSizeEnd = compoundRect.width(); 1947 dr.mDrawableHeightEnd = compoundRect.height(); 1948 } else { 1949 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 1950 } 1951 1952 if (top != null) { 1953 top.setState(state); 1954 top.copyBounds(compoundRect); 1955 top.setCallback(this); 1956 dr.mDrawableSizeTop = compoundRect.height(); 1957 dr.mDrawableWidthTop = compoundRect.width(); 1958 } else { 1959 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1960 } 1961 1962 if (bottom != null) { 1963 bottom.setState(state); 1964 bottom.copyBounds(compoundRect); 1965 bottom.setCallback(this); 1966 dr.mDrawableSizeBottom = compoundRect.height(); 1967 dr.mDrawableWidthBottom = compoundRect.width(); 1968 } else { 1969 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1970 } 1971 } 1972 1973 resolveDrawables(); 1974 invalidate(); 1975 requestLayout(); 1976 } 1977 1978 /** 1979 * Sets the Drawables (if any) to appear to the start of, above, 1980 * to the end of, and below the text. Use 0 if you do not 1981 * want a Drawable there. The Drawables' bounds will be set to 1982 * their intrinsic bounds. 1983 * 1984 * @param start Resource identifier of the start Drawable. 1985 * @param top Resource identifier of the top Drawable. 1986 * @param end Resource identifier of the end Drawable. 1987 * @param bottom Resource identifier of the bottom Drawable. 1988 * 1989 * @attr ref android.R.styleable#TextView_drawableStart 1990 * @attr ref android.R.styleable#TextView_drawableTop 1991 * @attr ref android.R.styleable#TextView_drawableEnd 1992 * @attr ref android.R.styleable#TextView_drawableBottom 1993 * @hide 1994 */ 1995 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom)1996 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, 1997 int bottom) { 1998 resetResolvedDrawables(); 1999 final Resources resources = getContext().getResources(); 2000 setCompoundDrawablesRelativeWithIntrinsicBounds( 2001 start != 0 ? resources.getDrawable(start) : null, 2002 top != 0 ? resources.getDrawable(top) : null, 2003 end != 0 ? resources.getDrawable(end) : null, 2004 bottom != 0 ? resources.getDrawable(bottom) : null); 2005 } 2006 2007 /** 2008 * Sets the Drawables (if any) to appear to the start of, above, 2009 * to the end of, and below the text. Use null if you do not 2010 * want a Drawable there. The Drawables' bounds will be set to 2011 * their intrinsic bounds. 2012 * 2013 * @attr ref android.R.styleable#TextView_drawableStart 2014 * @attr ref android.R.styleable#TextView_drawableTop 2015 * @attr ref android.R.styleable#TextView_drawableEnd 2016 * @attr ref android.R.styleable#TextView_drawableBottom 2017 * @hide 2018 */ setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top, Drawable end, Drawable bottom)2019 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top, 2020 Drawable end, Drawable bottom) { 2021 2022 resetResolvedDrawables(); 2023 if (start != null) { 2024 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2025 } 2026 if (end != null) { 2027 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2028 } 2029 if (top != null) { 2030 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2031 } 2032 if (bottom != null) { 2033 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2034 } 2035 setCompoundDrawablesRelative(start, top, end, bottom); 2036 } 2037 2038 /** 2039 * Returns drawables for the left, top, right, and bottom borders. 2040 * 2041 * @attr ref android.R.styleable#TextView_drawableLeft 2042 * @attr ref android.R.styleable#TextView_drawableTop 2043 * @attr ref android.R.styleable#TextView_drawableRight 2044 * @attr ref android.R.styleable#TextView_drawableBottom 2045 */ getCompoundDrawables()2046 public Drawable[] getCompoundDrawables() { 2047 final Drawables dr = mDrawables; 2048 if (dr != null) { 2049 return new Drawable[] { 2050 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom 2051 }; 2052 } else { 2053 return new Drawable[] { null, null, null, null }; 2054 } 2055 } 2056 2057 /** 2058 * Returns drawables for the start, top, end, and bottom borders. 2059 * 2060 * @attr ref android.R.styleable#TextView_drawableStart 2061 * @attr ref android.R.styleable#TextView_drawableTop 2062 * @attr ref android.R.styleable#TextView_drawableEnd 2063 * @attr ref android.R.styleable#TextView_drawableBottom 2064 * @hide 2065 */ getCompoundDrawablesRelative()2066 public Drawable[] getCompoundDrawablesRelative() { 2067 final Drawables dr = mDrawables; 2068 if (dr != null) { 2069 return new Drawable[] { 2070 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom 2071 }; 2072 } else { 2073 return new Drawable[] { null, null, null, null }; 2074 } 2075 } 2076 2077 /** 2078 * Sets the size of the padding between the compound drawables and 2079 * the text. 2080 * 2081 * @attr ref android.R.styleable#TextView_drawablePadding 2082 */ 2083 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)2084 public void setCompoundDrawablePadding(int pad) { 2085 Drawables dr = mDrawables; 2086 if (pad == 0) { 2087 if (dr != null) { 2088 dr.mDrawablePadding = pad; 2089 } 2090 } else { 2091 if (dr == null) { 2092 mDrawables = dr = new Drawables(); 2093 } 2094 dr.mDrawablePadding = pad; 2095 } 2096 2097 invalidate(); 2098 requestLayout(); 2099 } 2100 2101 /** 2102 * Returns the padding between the compound drawables and the text. 2103 * 2104 * @attr ref android.R.styleable#TextView_drawablePadding 2105 */ getCompoundDrawablePadding()2106 public int getCompoundDrawablePadding() { 2107 final Drawables dr = mDrawables; 2108 return dr != null ? dr.mDrawablePadding : 0; 2109 } 2110 2111 @Override setPadding(int left, int top, int right, int bottom)2112 public void setPadding(int left, int top, int right, int bottom) { 2113 if (left != mPaddingLeft || 2114 right != mPaddingRight || 2115 top != mPaddingTop || 2116 bottom != mPaddingBottom) { 2117 nullLayouts(); 2118 } 2119 2120 // the super call will requestLayout() 2121 super.setPadding(left, top, right, bottom); 2122 invalidate(); 2123 } 2124 2125 @Override setPaddingRelative(int start, int top, int end, int bottom)2126 public void setPaddingRelative(int start, int top, int end, int bottom) { 2127 if (start != getPaddingStart() || 2128 end != getPaddingEnd() || 2129 top != mPaddingTop || 2130 bottom != mPaddingBottom) { 2131 nullLayouts(); 2132 } 2133 2134 // the super call will requestLayout() 2135 super.setPaddingRelative(start, top, end, bottom); 2136 invalidate(); 2137 } 2138 2139 /** 2140 * Gets the autolink mask of the text. See {@link 2141 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 2142 * possible values. 2143 * 2144 * @attr ref android.R.styleable#TextView_autoLink 2145 */ getAutoLinkMask()2146 public final int getAutoLinkMask() { 2147 return mAutoLinkMask; 2148 } 2149 2150 /** 2151 * Sets the text color, size, style, hint color, and highlight color 2152 * from the specified TextAppearance resource. 2153 */ setTextAppearance(Context context, int resid)2154 public void setTextAppearance(Context context, int resid) { 2155 TypedArray appearance = 2156 context.obtainStyledAttributes(resid, 2157 com.android.internal.R.styleable.TextAppearance); 2158 2159 int color; 2160 ColorStateList colors; 2161 int ts; 2162 2163 color = appearance.getColor( 2164 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); 2165 if (color != 0) { 2166 setHighlightColor(color); 2167 } 2168 2169 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2170 TextAppearance_textColor); 2171 if (colors != null) { 2172 setTextColor(colors); 2173 } 2174 2175 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 2176 TextAppearance_textSize, 0); 2177 if (ts != 0) { 2178 setRawTextSize(ts); 2179 } 2180 2181 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2182 TextAppearance_textColorHint); 2183 if (colors != null) { 2184 setHintTextColor(colors); 2185 } 2186 2187 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2188 TextAppearance_textColorLink); 2189 if (colors != null) { 2190 setLinkTextColor(colors); 2191 } 2192 2193 String familyName; 2194 int typefaceIndex, styleIndex; 2195 2196 familyName = appearance.getString(com.android.internal.R.styleable. 2197 TextAppearance_fontFamily); 2198 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 2199 TextAppearance_typeface, -1); 2200 styleIndex = appearance.getInt(com.android.internal.R.styleable. 2201 TextAppearance_textStyle, -1); 2202 2203 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex); 2204 2205 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps, 2206 false)) { 2207 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 2208 } 2209 2210 appearance.recycle(); 2211 } 2212 2213 /** 2214 * @return the size (in pixels) of the default text size in this TextView. 2215 */ 2216 @ViewDebug.ExportedProperty(category = "text") getTextSize()2217 public float getTextSize() { 2218 return mTextPaint.getTextSize(); 2219 } 2220 2221 /** 2222 * Set the default text size to the given value, interpreted as "scaled 2223 * pixel" units. This size is adjusted based on the current density and 2224 * user font size preference. 2225 * 2226 * @param size The scaled pixel size. 2227 * 2228 * @attr ref android.R.styleable#TextView_textSize 2229 */ 2230 @android.view.RemotableViewMethod setTextSize(float size)2231 public void setTextSize(float size) { 2232 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 2233 } 2234 2235 /** 2236 * Set the default text size to a given unit and value. See {@link 2237 * TypedValue} for the possible dimension units. 2238 * 2239 * @param unit The desired dimension unit. 2240 * @param size The desired size in the given units. 2241 * 2242 * @attr ref android.R.styleable#TextView_textSize 2243 */ setTextSize(int unit, float size)2244 public void setTextSize(int unit, float size) { 2245 Context c = getContext(); 2246 Resources r; 2247 2248 if (c == null) 2249 r = Resources.getSystem(); 2250 else 2251 r = c.getResources(); 2252 2253 setRawTextSize(TypedValue.applyDimension( 2254 unit, size, r.getDisplayMetrics())); 2255 } 2256 setRawTextSize(float size)2257 private void setRawTextSize(float size) { 2258 if (size != mTextPaint.getTextSize()) { 2259 mTextPaint.setTextSize(size); 2260 2261 if (mLayout != null) { 2262 nullLayouts(); 2263 requestLayout(); 2264 invalidate(); 2265 } 2266 } 2267 } 2268 2269 /** 2270 * @return the extent by which text is currently being stretched 2271 * horizontally. This will usually be 1. 2272 */ getTextScaleX()2273 public float getTextScaleX() { 2274 return mTextPaint.getTextScaleX(); 2275 } 2276 2277 /** 2278 * Sets the extent by which text should be stretched horizontally. 2279 * 2280 * @attr ref android.R.styleable#TextView_textScaleX 2281 */ 2282 @android.view.RemotableViewMethod setTextScaleX(float size)2283 public void setTextScaleX(float size) { 2284 if (size != mTextPaint.getTextScaleX()) { 2285 mUserSetTextScaleX = true; 2286 mTextPaint.setTextScaleX(size); 2287 2288 if (mLayout != null) { 2289 nullLayouts(); 2290 requestLayout(); 2291 invalidate(); 2292 } 2293 } 2294 } 2295 2296 /** 2297 * Sets the typeface and style in which the text should be displayed. 2298 * Note that not all Typeface families actually have bold and italic 2299 * variants, so you may need to use 2300 * {@link #setTypeface(Typeface, int)} to get the appearance 2301 * that you actually want. 2302 * 2303 * @see #getTypeface() 2304 * 2305 * @attr ref android.R.styleable#TextView_fontFamily 2306 * @attr ref android.R.styleable#TextView_typeface 2307 * @attr ref android.R.styleable#TextView_textStyle 2308 */ setTypeface(Typeface tf)2309 public void setTypeface(Typeface tf) { 2310 if (mTextPaint.getTypeface() != tf) { 2311 mTextPaint.setTypeface(tf); 2312 2313 if (mLayout != null) { 2314 nullLayouts(); 2315 requestLayout(); 2316 invalidate(); 2317 } 2318 } 2319 } 2320 2321 /** 2322 * @return the current typeface and style in which the text is being 2323 * displayed. 2324 * 2325 * @see #setTypeface(Typeface) 2326 * 2327 * @attr ref android.R.styleable#TextView_fontFamily 2328 * @attr ref android.R.styleable#TextView_typeface 2329 * @attr ref android.R.styleable#TextView_textStyle 2330 */ getTypeface()2331 public Typeface getTypeface() { 2332 return mTextPaint.getTypeface(); 2333 } 2334 2335 /** 2336 * Sets the text color for all the states (normal, selected, 2337 * focused) to be this color. 2338 * 2339 * @see #setTextColor(ColorStateList) 2340 * @see #getTextColors() 2341 * 2342 * @attr ref android.R.styleable#TextView_textColor 2343 */ 2344 @android.view.RemotableViewMethod setTextColor(int color)2345 public void setTextColor(int color) { 2346 mTextColor = ColorStateList.valueOf(color); 2347 updateTextColors(); 2348 } 2349 2350 /** 2351 * Sets the text color. 2352 * 2353 * @see #setTextColor(int) 2354 * @see #getTextColors() 2355 * @see #setHintTextColor(ColorStateList) 2356 * @see #setLinkTextColor(ColorStateList) 2357 * 2358 * @attr ref android.R.styleable#TextView_textColor 2359 */ setTextColor(ColorStateList colors)2360 public void setTextColor(ColorStateList colors) { 2361 if (colors == null) { 2362 throw new NullPointerException(); 2363 } 2364 2365 mTextColor = colors; 2366 updateTextColors(); 2367 } 2368 2369 /** 2370 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 2371 * 2372 * @see #setTextColor(ColorStateList) 2373 * @see #setTextColor(int) 2374 * 2375 * @attr ref android.R.styleable#TextView_textColor 2376 */ getTextColors()2377 public final ColorStateList getTextColors() { 2378 return mTextColor; 2379 } 2380 2381 /** 2382 * <p>Return the current color selected for normal text.</p> 2383 * 2384 * @return Returns the current text color. 2385 */ getCurrentTextColor()2386 public final int getCurrentTextColor() { 2387 return mCurTextColor; 2388 } 2389 2390 /** 2391 * Sets the color used to display the selection highlight. 2392 * 2393 * @attr ref android.R.styleable#TextView_textColorHighlight 2394 */ 2395 @android.view.RemotableViewMethod setHighlightColor(int color)2396 public void setHighlightColor(int color) { 2397 if (mHighlightColor != color) { 2398 mHighlightColor = color; 2399 invalidate(); 2400 } 2401 } 2402 2403 /** 2404 * @return the color used to display the selection highlight 2405 * 2406 * @see #setHighlightColor(int) 2407 * 2408 * @attr ref android.R.styleable#TextView_textColorHighlight 2409 */ getHighlightColor()2410 public int getHighlightColor() { 2411 return mHighlightColor; 2412 } 2413 2414 /** 2415 * Sets whether the soft input method will be made visible when this 2416 * TextView gets focused. The default is true. 2417 * @hide 2418 */ 2419 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)2420 public final void setShowSoftInputOnFocus(boolean show) { 2421 createEditorIfNeeded(); 2422 mEditor.mShowSoftInputOnFocus = show; 2423 } 2424 2425 /** 2426 * Returns whether the soft input method will be made visible when this 2427 * TextView gets focused. The default is true. 2428 * @hide 2429 */ getShowSoftInputOnFocus()2430 public final boolean getShowSoftInputOnFocus() { 2431 // When there is no Editor, return default true value 2432 return mEditor == null || mEditor.mShowSoftInputOnFocus; 2433 } 2434 2435 /** 2436 * Gives the text a shadow of the specified radius and color, the specified 2437 * distance from its normal position. 2438 * 2439 * @attr ref android.R.styleable#TextView_shadowColor 2440 * @attr ref android.R.styleable#TextView_shadowDx 2441 * @attr ref android.R.styleable#TextView_shadowDy 2442 * @attr ref android.R.styleable#TextView_shadowRadius 2443 */ setShadowLayer(float radius, float dx, float dy, int color)2444 public void setShadowLayer(float radius, float dx, float dy, int color) { 2445 mTextPaint.setShadowLayer(radius, dx, dy, color); 2446 2447 mShadowRadius = radius; 2448 mShadowDx = dx; 2449 mShadowDy = dy; 2450 2451 // Will change text clip region 2452 if (mEditor != null) mEditor.invalidateTextDisplayList(); 2453 invalidate(); 2454 } 2455 2456 /** 2457 * Gets the radius of the shadow layer. 2458 * 2459 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 2460 * 2461 * @see #setShadowLayer(float, float, float, int) 2462 * 2463 * @attr ref android.R.styleable#TextView_shadowRadius 2464 */ getShadowRadius()2465 public float getShadowRadius() { 2466 return mShadowRadius; 2467 } 2468 2469 /** 2470 * @return the horizontal offset of the shadow layer 2471 * 2472 * @see #setShadowLayer(float, float, float, int) 2473 * 2474 * @attr ref android.R.styleable#TextView_shadowDx 2475 */ getShadowDx()2476 public float getShadowDx() { 2477 return mShadowDx; 2478 } 2479 2480 /** 2481 * @return the vertical offset of the shadow layer 2482 * 2483 * @see #setShadowLayer(float, float, float, int) 2484 * 2485 * @attr ref android.R.styleable#TextView_shadowDy 2486 */ getShadowDy()2487 public float getShadowDy() { 2488 return mShadowDy; 2489 } 2490 2491 /** 2492 * @return the color of the shadow layer 2493 * 2494 * @see #setShadowLayer(float, float, float, int) 2495 * 2496 * @attr ref android.R.styleable#TextView_shadowColor 2497 */ getShadowColor()2498 public int getShadowColor() { 2499 return mTextPaint.shadowColor; 2500 } 2501 2502 /** 2503 * @return the base paint used for the text. Please use this only to 2504 * consult the Paint's properties and not to change them. 2505 */ getPaint()2506 public TextPaint getPaint() { 2507 return mTextPaint; 2508 } 2509 2510 /** 2511 * Sets the autolink mask of the text. See {@link 2512 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 2513 * possible values. 2514 * 2515 * @attr ref android.R.styleable#TextView_autoLink 2516 */ 2517 @android.view.RemotableViewMethod setAutoLinkMask(int mask)2518 public final void setAutoLinkMask(int mask) { 2519 mAutoLinkMask = mask; 2520 } 2521 2522 /** 2523 * Sets whether the movement method will automatically be set to 2524 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 2525 * set to nonzero and links are detected in {@link #setText}. 2526 * The default is true. 2527 * 2528 * @attr ref android.R.styleable#TextView_linksClickable 2529 */ 2530 @android.view.RemotableViewMethod setLinksClickable(boolean whether)2531 public final void setLinksClickable(boolean whether) { 2532 mLinksClickable = whether; 2533 } 2534 2535 /** 2536 * Returns whether the movement method will automatically be set to 2537 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 2538 * set to nonzero and links are detected in {@link #setText}. 2539 * The default is true. 2540 * 2541 * @attr ref android.R.styleable#TextView_linksClickable 2542 */ getLinksClickable()2543 public final boolean getLinksClickable() { 2544 return mLinksClickable; 2545 } 2546 2547 /** 2548 * Returns the list of URLSpans attached to the text 2549 * (by {@link Linkify} or otherwise) if any. You can call 2550 * {@link URLSpan#getURL} on them to find where they link to 2551 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 2552 * to find the region of the text they are attached to. 2553 */ getUrls()2554 public URLSpan[] getUrls() { 2555 if (mText instanceof Spanned) { 2556 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 2557 } else { 2558 return new URLSpan[0]; 2559 } 2560 } 2561 2562 /** 2563 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 2564 * TextView. 2565 * 2566 * @see #setHintTextColor(ColorStateList) 2567 * @see #getHintTextColors() 2568 * @see #setTextColor(int) 2569 * 2570 * @attr ref android.R.styleable#TextView_textColorHint 2571 */ 2572 @android.view.RemotableViewMethod setHintTextColor(int color)2573 public final void setHintTextColor(int color) { 2574 mHintTextColor = ColorStateList.valueOf(color); 2575 updateTextColors(); 2576 } 2577 2578 /** 2579 * Sets the color of the hint text. 2580 * 2581 * @see #getHintTextColors() 2582 * @see #setHintTextColor(int) 2583 * @see #setTextColor(ColorStateList) 2584 * @see #setLinkTextColor(ColorStateList) 2585 * 2586 * @attr ref android.R.styleable#TextView_textColorHint 2587 */ setHintTextColor(ColorStateList colors)2588 public final void setHintTextColor(ColorStateList colors) { 2589 mHintTextColor = colors; 2590 updateTextColors(); 2591 } 2592 2593 /** 2594 * @return the color of the hint text, for the different states of this TextView. 2595 * 2596 * @see #setHintTextColor(ColorStateList) 2597 * @see #setHintTextColor(int) 2598 * @see #setTextColor(ColorStateList) 2599 * @see #setLinkTextColor(ColorStateList) 2600 * 2601 * @attr ref android.R.styleable#TextView_textColorHint 2602 */ getHintTextColors()2603 public final ColorStateList getHintTextColors() { 2604 return mHintTextColor; 2605 } 2606 2607 /** 2608 * <p>Return the current color selected to paint the hint text.</p> 2609 * 2610 * @return Returns the current hint text color. 2611 */ getCurrentHintTextColor()2612 public final int getCurrentHintTextColor() { 2613 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 2614 } 2615 2616 /** 2617 * Sets the color of links in the text. 2618 * 2619 * @see #setLinkTextColor(ColorStateList) 2620 * @see #getLinkTextColors() 2621 * 2622 * @attr ref android.R.styleable#TextView_textColorLink 2623 */ 2624 @android.view.RemotableViewMethod setLinkTextColor(int color)2625 public final void setLinkTextColor(int color) { 2626 mLinkTextColor = ColorStateList.valueOf(color); 2627 updateTextColors(); 2628 } 2629 2630 /** 2631 * Sets the color of links in the text. 2632 * 2633 * @see #setLinkTextColor(int) 2634 * @see #getLinkTextColors() 2635 * @see #setTextColor(ColorStateList) 2636 * @see #setHintTextColor(ColorStateList) 2637 * 2638 * @attr ref android.R.styleable#TextView_textColorLink 2639 */ setLinkTextColor(ColorStateList colors)2640 public final void setLinkTextColor(ColorStateList colors) { 2641 mLinkTextColor = colors; 2642 updateTextColors(); 2643 } 2644 2645 /** 2646 * @return the list of colors used to paint the links in the text, for the different states of 2647 * this TextView 2648 * 2649 * @see #setLinkTextColor(ColorStateList) 2650 * @see #setLinkTextColor(int) 2651 * 2652 * @attr ref android.R.styleable#TextView_textColorLink 2653 */ getLinkTextColors()2654 public final ColorStateList getLinkTextColors() { 2655 return mLinkTextColor; 2656 } 2657 2658 /** 2659 * Sets the horizontal alignment of the text and the 2660 * vertical gravity that will be used when there is extra space 2661 * in the TextView beyond what is required for the text itself. 2662 * 2663 * @see android.view.Gravity 2664 * @attr ref android.R.styleable#TextView_gravity 2665 */ setGravity(int gravity)2666 public void setGravity(int gravity) { 2667 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 2668 gravity |= Gravity.START; 2669 } 2670 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 2671 gravity |= Gravity.TOP; 2672 } 2673 2674 boolean newLayout = false; 2675 2676 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != 2677 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 2678 newLayout = true; 2679 } 2680 2681 if (gravity != mGravity) { 2682 invalidate(); 2683 mLayoutAlignment = null; 2684 } 2685 2686 mGravity = gravity; 2687 2688 if (mLayout != null && newLayout) { 2689 // XXX this is heavy-handed because no actual content changes. 2690 int want = mLayout.getWidth(); 2691 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 2692 2693 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 2694 mRight - mLeft - getCompoundPaddingLeft() - 2695 getCompoundPaddingRight(), true); 2696 } 2697 } 2698 2699 /** 2700 * Returns the horizontal and vertical alignment of this TextView. 2701 * 2702 * @see android.view.Gravity 2703 * @attr ref android.R.styleable#TextView_gravity 2704 */ getGravity()2705 public int getGravity() { 2706 return mGravity; 2707 } 2708 2709 /** 2710 * @return the flags on the Paint being used to display the text. 2711 * @see Paint#getFlags 2712 */ getPaintFlags()2713 public int getPaintFlags() { 2714 return mTextPaint.getFlags(); 2715 } 2716 2717 /** 2718 * Sets flags on the Paint being used to display the text and 2719 * reflows the text if they are different from the old flags. 2720 * @see Paint#setFlags 2721 */ 2722 @android.view.RemotableViewMethod setPaintFlags(int flags)2723 public void setPaintFlags(int flags) { 2724 if (mTextPaint.getFlags() != flags) { 2725 mTextPaint.setFlags(flags); 2726 2727 if (mLayout != null) { 2728 nullLayouts(); 2729 requestLayout(); 2730 invalidate(); 2731 } 2732 } 2733 } 2734 2735 /** 2736 * Sets whether the text should be allowed to be wider than the 2737 * View is. If false, it will be wrapped to the width of the View. 2738 * 2739 * @attr ref android.R.styleable#TextView_scrollHorizontally 2740 */ setHorizontallyScrolling(boolean whether)2741 public void setHorizontallyScrolling(boolean whether) { 2742 if (mHorizontallyScrolling != whether) { 2743 mHorizontallyScrolling = whether; 2744 2745 if (mLayout != null) { 2746 nullLayouts(); 2747 requestLayout(); 2748 invalidate(); 2749 } 2750 } 2751 } 2752 2753 /** 2754 * Returns whether the text is allowed to be wider than the View is. 2755 * If false, the text will be wrapped to the width of the View. 2756 * 2757 * @attr ref android.R.styleable#TextView_scrollHorizontally 2758 * @hide 2759 */ getHorizontallyScrolling()2760 public boolean getHorizontallyScrolling() { 2761 return mHorizontallyScrolling; 2762 } 2763 2764 /** 2765 * Makes the TextView at least this many lines tall. 2766 * 2767 * Setting this value overrides any other (minimum) height setting. A single line TextView will 2768 * set this value to 1. 2769 * 2770 * @see #getMinLines() 2771 * 2772 * @attr ref android.R.styleable#TextView_minLines 2773 */ 2774 @android.view.RemotableViewMethod setMinLines(int minlines)2775 public void setMinLines(int minlines) { 2776 mMinimum = minlines; 2777 mMinMode = LINES; 2778 2779 requestLayout(); 2780 invalidate(); 2781 } 2782 2783 /** 2784 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum 2785 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}. 2786 * 2787 * @see #setMinLines(int) 2788 * 2789 * @attr ref android.R.styleable#TextView_minLines 2790 */ getMinLines()2791 public int getMinLines() { 2792 return mMinMode == LINES ? mMinimum : -1; 2793 } 2794 2795 /** 2796 * Makes the TextView at least this many pixels tall. 2797 * 2798 * Setting this value overrides any other (minimum) number of lines setting. 2799 * 2800 * @attr ref android.R.styleable#TextView_minHeight 2801 */ 2802 @android.view.RemotableViewMethod setMinHeight(int minHeight)2803 public void setMinHeight(int minHeight) { 2804 mMinimum = minHeight; 2805 mMinMode = PIXELS; 2806 2807 requestLayout(); 2808 invalidate(); 2809 } 2810 2811 /** 2812 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum 2813 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}. 2814 * 2815 * @see #setMinHeight(int) 2816 * 2817 * @attr ref android.R.styleable#TextView_minHeight 2818 */ getMinHeight()2819 public int getMinHeight() { 2820 return mMinMode == PIXELS ? mMinimum : -1; 2821 } 2822 2823 /** 2824 * Makes the TextView at most this many lines tall. 2825 * 2826 * Setting this value overrides any other (maximum) height setting. 2827 * 2828 * @attr ref android.R.styleable#TextView_maxLines 2829 */ 2830 @android.view.RemotableViewMethod setMaxLines(int maxlines)2831 public void setMaxLines(int maxlines) { 2832 mMaximum = maxlines; 2833 mMaxMode = LINES; 2834 2835 requestLayout(); 2836 invalidate(); 2837 } 2838 2839 /** 2840 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum 2841 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}. 2842 * 2843 * @see #setMaxLines(int) 2844 * 2845 * @attr ref android.R.styleable#TextView_maxLines 2846 */ getMaxLines()2847 public int getMaxLines() { 2848 return mMaxMode == LINES ? mMaximum : -1; 2849 } 2850 2851 /** 2852 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the 2853 * {@link #setMaxLines(int)} method. 2854 * 2855 * Setting this value overrides any other (maximum) number of lines setting. 2856 * 2857 * @attr ref android.R.styleable#TextView_maxHeight 2858 */ 2859 @android.view.RemotableViewMethod setMaxHeight(int maxHeight)2860 public void setMaxHeight(int maxHeight) { 2861 mMaximum = maxHeight; 2862 mMaxMode = PIXELS; 2863 2864 requestLayout(); 2865 invalidate(); 2866 } 2867 2868 /** 2869 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum 2870 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}. 2871 * 2872 * @see #setMaxHeight(int) 2873 * 2874 * @attr ref android.R.styleable#TextView_maxHeight 2875 */ getMaxHeight()2876 public int getMaxHeight() { 2877 return mMaxMode == PIXELS ? mMaximum : -1; 2878 } 2879 2880 /** 2881 * Makes the TextView exactly this many lines tall. 2882 * 2883 * Note that setting this value overrides any other (minimum / maximum) number of lines or 2884 * height setting. A single line TextView will set this value to 1. 2885 * 2886 * @attr ref android.R.styleable#TextView_lines 2887 */ 2888 @android.view.RemotableViewMethod setLines(int lines)2889 public void setLines(int lines) { 2890 mMaximum = mMinimum = lines; 2891 mMaxMode = mMinMode = LINES; 2892 2893 requestLayout(); 2894 invalidate(); 2895 } 2896 2897 /** 2898 * Makes the TextView exactly this many pixels tall. 2899 * You could do the same thing by specifying this number in the 2900 * LayoutParams. 2901 * 2902 * Note that setting this value overrides any other (minimum / maximum) number of lines or 2903 * height setting. 2904 * 2905 * @attr ref android.R.styleable#TextView_height 2906 */ 2907 @android.view.RemotableViewMethod setHeight(int pixels)2908 public void setHeight(int pixels) { 2909 mMaximum = mMinimum = pixels; 2910 mMaxMode = mMinMode = PIXELS; 2911 2912 requestLayout(); 2913 invalidate(); 2914 } 2915 2916 /** 2917 * Makes the TextView at least this many ems wide 2918 * 2919 * @attr ref android.R.styleable#TextView_minEms 2920 */ 2921 @android.view.RemotableViewMethod setMinEms(int minems)2922 public void setMinEms(int minems) { 2923 mMinWidth = minems; 2924 mMinWidthMode = EMS; 2925 2926 requestLayout(); 2927 invalidate(); 2928 } 2929 2930 /** 2931 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width 2932 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}). 2933 * 2934 * @see #setMinEms(int) 2935 * @see #setEms(int) 2936 * 2937 * @attr ref android.R.styleable#TextView_minEms 2938 */ getMinEms()2939 public int getMinEms() { 2940 return mMinWidthMode == EMS ? mMinWidth : -1; 2941 } 2942 2943 /** 2944 * Makes the TextView at least this many pixels wide 2945 * 2946 * @attr ref android.R.styleable#TextView_minWidth 2947 */ 2948 @android.view.RemotableViewMethod setMinWidth(int minpixels)2949 public void setMinWidth(int minpixels) { 2950 mMinWidth = minpixels; 2951 mMinWidthMode = PIXELS; 2952 2953 requestLayout(); 2954 invalidate(); 2955 } 2956 2957 /** 2958 * @return the minimum width of the TextView, in pixels or -1 if the minimum width 2959 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}). 2960 * 2961 * @see #setMinWidth(int) 2962 * @see #setWidth(int) 2963 * 2964 * @attr ref android.R.styleable#TextView_minWidth 2965 */ getMinWidth()2966 public int getMinWidth() { 2967 return mMinWidthMode == PIXELS ? mMinWidth : -1; 2968 } 2969 2970 /** 2971 * Makes the TextView at most this many ems wide 2972 * 2973 * @attr ref android.R.styleable#TextView_maxEms 2974 */ 2975 @android.view.RemotableViewMethod setMaxEms(int maxems)2976 public void setMaxEms(int maxems) { 2977 mMaxWidth = maxems; 2978 mMaxWidthMode = EMS; 2979 2980 requestLayout(); 2981 invalidate(); 2982 } 2983 2984 /** 2985 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width 2986 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}). 2987 * 2988 * @see #setMaxEms(int) 2989 * @see #setEms(int) 2990 * 2991 * @attr ref android.R.styleable#TextView_maxEms 2992 */ getMaxEms()2993 public int getMaxEms() { 2994 return mMaxWidthMode == EMS ? mMaxWidth : -1; 2995 } 2996 2997 /** 2998 * Makes the TextView at most this many pixels wide 2999 * 3000 * @attr ref android.R.styleable#TextView_maxWidth 3001 */ 3002 @android.view.RemotableViewMethod setMaxWidth(int maxpixels)3003 public void setMaxWidth(int maxpixels) { 3004 mMaxWidth = maxpixels; 3005 mMaxWidthMode = PIXELS; 3006 3007 requestLayout(); 3008 invalidate(); 3009 } 3010 3011 /** 3012 * @return the maximum width of the TextView, in pixels or -1 if the maximum width 3013 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}). 3014 * 3015 * @see #setMaxWidth(int) 3016 * @see #setWidth(int) 3017 * 3018 * @attr ref android.R.styleable#TextView_maxWidth 3019 */ getMaxWidth()3020 public int getMaxWidth() { 3021 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 3022 } 3023 3024 /** 3025 * Makes the TextView exactly this many ems wide 3026 * 3027 * @see #setMaxEms(int) 3028 * @see #setMinEms(int) 3029 * @see #getMinEms() 3030 * @see #getMaxEms() 3031 * 3032 * @attr ref android.R.styleable#TextView_ems 3033 */ 3034 @android.view.RemotableViewMethod setEms(int ems)3035 public void setEms(int ems) { 3036 mMaxWidth = mMinWidth = ems; 3037 mMaxWidthMode = mMinWidthMode = EMS; 3038 3039 requestLayout(); 3040 invalidate(); 3041 } 3042 3043 /** 3044 * Makes the TextView exactly this many pixels wide. 3045 * You could do the same thing by specifying this number in the 3046 * LayoutParams. 3047 * 3048 * @see #setMaxWidth(int) 3049 * @see #setMinWidth(int) 3050 * @see #getMinWidth() 3051 * @see #getMaxWidth() 3052 * 3053 * @attr ref android.R.styleable#TextView_width 3054 */ 3055 @android.view.RemotableViewMethod setWidth(int pixels)3056 public void setWidth(int pixels) { 3057 mMaxWidth = mMinWidth = pixels; 3058 mMaxWidthMode = mMinWidthMode = PIXELS; 3059 3060 requestLayout(); 3061 invalidate(); 3062 } 3063 3064 /** 3065 * Sets line spacing for this TextView. Each line will have its height 3066 * multiplied by <code>mult</code> and have <code>add</code> added to it. 3067 * 3068 * @attr ref android.R.styleable#TextView_lineSpacingExtra 3069 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 3070 */ setLineSpacing(float add, float mult)3071 public void setLineSpacing(float add, float mult) { 3072 if (mSpacingAdd != add || mSpacingMult != mult) { 3073 mSpacingAdd = add; 3074 mSpacingMult = mult; 3075 3076 if (mLayout != null) { 3077 nullLayouts(); 3078 requestLayout(); 3079 invalidate(); 3080 } 3081 } 3082 } 3083 3084 /** 3085 * Gets the line spacing multiplier 3086 * 3087 * @return the value by which each line's height is multiplied to get its actual height. 3088 * 3089 * @see #setLineSpacing(float, float) 3090 * @see #getLineSpacingExtra() 3091 * 3092 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 3093 */ getLineSpacingMultiplier()3094 public float getLineSpacingMultiplier() { 3095 return mSpacingMult; 3096 } 3097 3098 /** 3099 * Gets the line spacing extra space 3100 * 3101 * @return the extra space that is added to the height of each lines of this TextView. 3102 * 3103 * @see #setLineSpacing(float, float) 3104 * @see #getLineSpacingMultiplier() 3105 * 3106 * @attr ref android.R.styleable#TextView_lineSpacingExtra 3107 */ getLineSpacingExtra()3108 public float getLineSpacingExtra() { 3109 return mSpacingAdd; 3110 } 3111 3112 /** 3113 * Convenience method: Append the specified text to the TextView's 3114 * display buffer, upgrading it to BufferType.EDITABLE if it was 3115 * not already editable. 3116 */ append(CharSequence text)3117 public final void append(CharSequence text) { 3118 append(text, 0, text.length()); 3119 } 3120 3121 /** 3122 * Convenience method: Append the specified text slice to the TextView's 3123 * display buffer, upgrading it to BufferType.EDITABLE if it was 3124 * not already editable. 3125 */ append(CharSequence text, int start, int end)3126 public void append(CharSequence text, int start, int end) { 3127 if (!(mText instanceof Editable)) { 3128 setText(mText, BufferType.EDITABLE); 3129 } 3130 3131 ((Editable) mText).append(text, start, end); 3132 } 3133 updateTextColors()3134 private void updateTextColors() { 3135 boolean inval = false; 3136 int color = mTextColor.getColorForState(getDrawableState(), 0); 3137 if (color != mCurTextColor) { 3138 mCurTextColor = color; 3139 inval = true; 3140 } 3141 if (mLinkTextColor != null) { 3142 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 3143 if (color != mTextPaint.linkColor) { 3144 mTextPaint.linkColor = color; 3145 inval = true; 3146 } 3147 } 3148 if (mHintTextColor != null) { 3149 color = mHintTextColor.getColorForState(getDrawableState(), 0); 3150 if (color != mCurHintTextColor && mText.length() == 0) { 3151 mCurHintTextColor = color; 3152 inval = true; 3153 } 3154 } 3155 if (inval) { 3156 // Text needs to be redrawn with the new color 3157 if (mEditor != null) mEditor.invalidateTextDisplayList(); 3158 invalidate(); 3159 } 3160 } 3161 3162 @Override drawableStateChanged()3163 protected void drawableStateChanged() { 3164 super.drawableStateChanged(); 3165 if (mTextColor != null && mTextColor.isStateful() 3166 || (mHintTextColor != null && mHintTextColor.isStateful()) 3167 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 3168 updateTextColors(); 3169 } 3170 3171 final Drawables dr = mDrawables; 3172 if (dr != null) { 3173 int[] state = getDrawableState(); 3174 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 3175 dr.mDrawableTop.setState(state); 3176 } 3177 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 3178 dr.mDrawableBottom.setState(state); 3179 } 3180 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 3181 dr.mDrawableLeft.setState(state); 3182 } 3183 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 3184 dr.mDrawableRight.setState(state); 3185 } 3186 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) { 3187 dr.mDrawableStart.setState(state); 3188 } 3189 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) { 3190 dr.mDrawableEnd.setState(state); 3191 } 3192 } 3193 } 3194 3195 @Override onSaveInstanceState()3196 public Parcelable onSaveInstanceState() { 3197 Parcelable superState = super.onSaveInstanceState(); 3198 3199 // Save state if we are forced to 3200 boolean save = mFreezesText; 3201 int start = 0; 3202 int end = 0; 3203 3204 if (mText != null) { 3205 start = getSelectionStart(); 3206 end = getSelectionEnd(); 3207 if (start >= 0 || end >= 0) { 3208 // Or save state if there is a selection 3209 save = true; 3210 } 3211 } 3212 3213 if (save) { 3214 SavedState ss = new SavedState(superState); 3215 // XXX Should also save the current scroll position! 3216 ss.selStart = start; 3217 ss.selEnd = end; 3218 3219 if (mText instanceof Spanned) { 3220 /* 3221 * Calling setText() strips off any ChangeWatchers; 3222 * strip them now to avoid leaking references. 3223 * But do it to a copy so that if there are any 3224 * further changes to the text of this view, it 3225 * won't get into an inconsistent state. 3226 */ 3227 3228 Spannable sp = new SpannableString(mText); 3229 3230 for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) { 3231 sp.removeSpan(cw); 3232 } 3233 3234 if (mEditor != null) { 3235 removeMisspelledSpans(sp); 3236 sp.removeSpan(mEditor.mSuggestionRangeSpan); 3237 } 3238 3239 ss.text = sp; 3240 } else { 3241 ss.text = mText.toString(); 3242 } 3243 3244 if (isFocused() && start >= 0 && end >= 0) { 3245 ss.frozenWithFocus = true; 3246 } 3247 3248 ss.error = getError(); 3249 3250 return ss; 3251 } 3252 3253 return superState; 3254 } 3255 removeMisspelledSpans(Spannable spannable)3256 void removeMisspelledSpans(Spannable spannable) { 3257 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 3258 SuggestionSpan.class); 3259 for (int i = 0; i < suggestionSpans.length; i++) { 3260 int flags = suggestionSpans[i].getFlags(); 3261 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 3262 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 3263 spannable.removeSpan(suggestionSpans[i]); 3264 } 3265 } 3266 } 3267 3268 @Override onRestoreInstanceState(Parcelable state)3269 public void onRestoreInstanceState(Parcelable state) { 3270 if (!(state instanceof SavedState)) { 3271 super.onRestoreInstanceState(state); 3272 return; 3273 } 3274 3275 SavedState ss = (SavedState)state; 3276 super.onRestoreInstanceState(ss.getSuperState()); 3277 3278 // XXX restore buffer type too, as well as lots of other stuff 3279 if (ss.text != null) { 3280 setText(ss.text); 3281 } 3282 3283 if (ss.selStart >= 0 && ss.selEnd >= 0) { 3284 if (mText instanceof Spannable) { 3285 int len = mText.length(); 3286 3287 if (ss.selStart > len || ss.selEnd > len) { 3288 String restored = ""; 3289 3290 if (ss.text != null) { 3291 restored = "(restored) "; 3292 } 3293 3294 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + 3295 "/" + ss.selEnd + " out of range for " + restored + 3296 "text " + mText); 3297 } else { 3298 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); 3299 3300 if (ss.frozenWithFocus) { 3301 createEditorIfNeeded(); 3302 mEditor.mFrozenWithFocus = true; 3303 } 3304 } 3305 } 3306 } 3307 3308 if (ss.error != null) { 3309 final CharSequence error = ss.error; 3310 // Display the error later, after the first layout pass 3311 post(new Runnable() { 3312 public void run() { 3313 setError(error); 3314 } 3315 }); 3316 } 3317 } 3318 3319 /** 3320 * Control whether this text view saves its entire text contents when 3321 * freezing to an icicle, in addition to dynamic state such as cursor 3322 * position. By default this is false, not saving the text. Set to true 3323 * if the text in the text view is not being saved somewhere else in 3324 * persistent storage (such as in a content provider) so that if the 3325 * view is later thawed the user will not lose their data. 3326 * 3327 * @param freezesText Controls whether a frozen icicle should include the 3328 * entire text data: true to include it, false to not. 3329 * 3330 * @attr ref android.R.styleable#TextView_freezesText 3331 */ 3332 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)3333 public void setFreezesText(boolean freezesText) { 3334 mFreezesText = freezesText; 3335 } 3336 3337 /** 3338 * Return whether this text view is including its entire text contents 3339 * in frozen icicles. 3340 * 3341 * @return Returns true if text is included, false if it isn't. 3342 * 3343 * @see #setFreezesText 3344 */ getFreezesText()3345 public boolean getFreezesText() { 3346 return mFreezesText; 3347 } 3348 3349 /////////////////////////////////////////////////////////////////////////// 3350 3351 /** 3352 * Sets the Factory used to create new Editables. 3353 */ setEditableFactory(Editable.Factory factory)3354 public final void setEditableFactory(Editable.Factory factory) { 3355 mEditableFactory = factory; 3356 setText(mText); 3357 } 3358 3359 /** 3360 * Sets the Factory used to create new Spannables. 3361 */ setSpannableFactory(Spannable.Factory factory)3362 public final void setSpannableFactory(Spannable.Factory factory) { 3363 mSpannableFactory = factory; 3364 setText(mText); 3365 } 3366 3367 /** 3368 * Sets the string value of the TextView. TextView <em>does not</em> accept 3369 * HTML-like formatting, which you can do with text strings in XML resource files. 3370 * To style your strings, attach android.text.style.* objects to a 3371 * {@link android.text.SpannableString SpannableString}, or see the 3372 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 3373 * Available Resource Types</a> documentation for an example of setting 3374 * formatted text in the XML resource file. 3375 * 3376 * @attr ref android.R.styleable#TextView_text 3377 */ 3378 @android.view.RemotableViewMethod setText(CharSequence text)3379 public final void setText(CharSequence text) { 3380 setText(text, mBufferType); 3381 } 3382 3383 /** 3384 * Like {@link #setText(CharSequence)}, 3385 * except that the cursor position (if any) is retained in the new text. 3386 * 3387 * @param text The new text to place in the text view. 3388 * 3389 * @see #setText(CharSequence) 3390 */ 3391 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)3392 public final void setTextKeepState(CharSequence text) { 3393 setTextKeepState(text, mBufferType); 3394 } 3395 3396 /** 3397 * Sets the text that this TextView is to display (see 3398 * {@link #setText(CharSequence)}) and also sets whether it is stored 3399 * in a styleable/spannable buffer and whether it is editable. 3400 * 3401 * @attr ref android.R.styleable#TextView_text 3402 * @attr ref android.R.styleable#TextView_bufferType 3403 */ setText(CharSequence text, BufferType type)3404 public void setText(CharSequence text, BufferType type) { 3405 setText(text, type, true, 0); 3406 3407 if (mCharWrapper != null) { 3408 mCharWrapper.mChars = null; 3409 } 3410 } 3411 setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)3412 private void setText(CharSequence text, BufferType type, 3413 boolean notifyBefore, int oldlen) { 3414 if (text == null) { 3415 text = ""; 3416 } 3417 3418 // If suggestions are not enabled, remove the suggestion spans from the text 3419 if (!isSuggestionsEnabled()) { 3420 text = removeSuggestionSpans(text); 3421 } 3422 3423 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 3424 3425 if (text instanceof Spanned && 3426 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 3427 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 3428 setHorizontalFadingEdgeEnabled(true); 3429 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 3430 } else { 3431 setHorizontalFadingEdgeEnabled(false); 3432 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 3433 } 3434 setEllipsize(TextUtils.TruncateAt.MARQUEE); 3435 } 3436 3437 int n = mFilters.length; 3438 for (int i = 0; i < n; i++) { 3439 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 3440 if (out != null) { 3441 text = out; 3442 } 3443 } 3444 3445 if (notifyBefore) { 3446 if (mText != null) { 3447 oldlen = mText.length(); 3448 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 3449 } else { 3450 sendBeforeTextChanged("", 0, 0, text.length()); 3451 } 3452 } 3453 3454 boolean needEditableForNotification = false; 3455 3456 if (mListeners != null && mListeners.size() != 0) { 3457 needEditableForNotification = true; 3458 } 3459 3460 if (type == BufferType.EDITABLE || getKeyListener() != null || 3461 needEditableForNotification) { 3462 createEditorIfNeeded(); 3463 Editable t = mEditableFactory.newEditable(text); 3464 text = t; 3465 setFilters(t, mFilters); 3466 InputMethodManager imm = InputMethodManager.peekInstance(); 3467 if (imm != null) imm.restartInput(this); 3468 } else if (type == BufferType.SPANNABLE || mMovement != null) { 3469 text = mSpannableFactory.newSpannable(text); 3470 } else if (!(text instanceof CharWrapper)) { 3471 text = TextUtils.stringOrSpannedString(text); 3472 } 3473 3474 if (mAutoLinkMask != 0) { 3475 Spannable s2; 3476 3477 if (type == BufferType.EDITABLE || text instanceof Spannable) { 3478 s2 = (Spannable) text; 3479 } else { 3480 s2 = mSpannableFactory.newSpannable(text); 3481 } 3482 3483 if (Linkify.addLinks(s2, mAutoLinkMask)) { 3484 text = s2; 3485 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 3486 3487 /* 3488 * We must go ahead and set the text before changing the 3489 * movement method, because setMovementMethod() may call 3490 * setText() again to try to upgrade the buffer type. 3491 */ 3492 mText = text; 3493 3494 // Do not change the movement method for text that support text selection as it 3495 // would prevent an arbitrary cursor displacement. 3496 if (mLinksClickable && !textCanBeSelected()) { 3497 setMovementMethod(LinkMovementMethod.getInstance()); 3498 } 3499 } 3500 } 3501 3502 mBufferType = type; 3503 mText = text; 3504 3505 if (mTransformation == null) { 3506 mTransformed = text; 3507 } else { 3508 mTransformed = mTransformation.getTransformation(text, this); 3509 } 3510 3511 final int textLength = text.length(); 3512 3513 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 3514 Spannable sp = (Spannable) text; 3515 3516 // Remove any ChangeWatchers that might have come from other TextViews. 3517 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 3518 final int count = watchers.length; 3519 for (int i = 0; i < count; i++) { 3520 sp.removeSpan(watchers[i]); 3521 } 3522 3523 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 3524 3525 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 3526 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 3527 3528 if (mEditor != null) mEditor.addSpanWatchers(sp); 3529 3530 if (mTransformation != null) { 3531 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 3532 } 3533 3534 if (mMovement != null) { 3535 mMovement.initialize(this, (Spannable) text); 3536 3537 /* 3538 * Initializing the movement method will have set the 3539 * selection, so reset mSelectionMoved to keep that from 3540 * interfering with the normal on-focus selection-setting. 3541 */ 3542 if (mEditor != null) mEditor.mSelectionMoved = false; 3543 } 3544 } 3545 3546 if (mLayout != null) { 3547 checkForRelayout(); 3548 } 3549 3550 sendOnTextChanged(text, 0, oldlen, textLength); 3551 onTextChanged(text, 0, oldlen, textLength); 3552 3553 if (needEditableForNotification) { 3554 sendAfterTextChanged((Editable) text); 3555 } 3556 3557 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 3558 if (mEditor != null) mEditor.prepareCursorControllers(); 3559 } 3560 3561 /** 3562 * Sets the TextView to display the specified slice of the specified 3563 * char array. You must promise that you will not change the contents 3564 * of the array except for right before another call to setText(), 3565 * since the TextView has no way to know that the text 3566 * has changed and that it needs to invalidate and re-layout. 3567 */ setText(char[] text, int start, int len)3568 public final void setText(char[] text, int start, int len) { 3569 int oldlen = 0; 3570 3571 if (start < 0 || len < 0 || start + len > text.length) { 3572 throw new IndexOutOfBoundsException(start + ", " + len); 3573 } 3574 3575 /* 3576 * We must do the before-notification here ourselves because if 3577 * the old text is a CharWrapper we destroy it before calling 3578 * into the normal path. 3579 */ 3580 if (mText != null) { 3581 oldlen = mText.length(); 3582 sendBeforeTextChanged(mText, 0, oldlen, len); 3583 } else { 3584 sendBeforeTextChanged("", 0, 0, len); 3585 } 3586 3587 if (mCharWrapper == null) { 3588 mCharWrapper = new CharWrapper(text, start, len); 3589 } else { 3590 mCharWrapper.set(text, start, len); 3591 } 3592 3593 setText(mCharWrapper, mBufferType, false, oldlen); 3594 } 3595 3596 /** 3597 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 3598 * except that the cursor position (if any) is retained in the new text. 3599 * 3600 * @see #setText(CharSequence, android.widget.TextView.BufferType) 3601 */ setTextKeepState(CharSequence text, BufferType type)3602 public final void setTextKeepState(CharSequence text, BufferType type) { 3603 int start = getSelectionStart(); 3604 int end = getSelectionEnd(); 3605 int len = text.length(); 3606 3607 setText(text, type); 3608 3609 if (start >= 0 || end >= 0) { 3610 if (mText instanceof Spannable) { 3611 Selection.setSelection((Spannable) mText, 3612 Math.max(0, Math.min(start, len)), 3613 Math.max(0, Math.min(end, len))); 3614 } 3615 } 3616 } 3617 3618 @android.view.RemotableViewMethod setText(int resid)3619 public final void setText(int resid) { 3620 setText(getContext().getResources().getText(resid)); 3621 } 3622 setText(int resid, BufferType type)3623 public final void setText(int resid, BufferType type) { 3624 setText(getContext().getResources().getText(resid), type); 3625 } 3626 3627 /** 3628 * Sets the text to be displayed when the text of the TextView is empty. 3629 * Null means to use the normal empty text. The hint does not currently 3630 * participate in determining the size of the view. 3631 * 3632 * @attr ref android.R.styleable#TextView_hint 3633 */ 3634 @android.view.RemotableViewMethod setHint(CharSequence hint)3635 public final void setHint(CharSequence hint) { 3636 mHint = TextUtils.stringOrSpannedString(hint); 3637 3638 if (mLayout != null) { 3639 checkForRelayout(); 3640 } 3641 3642 if (mText.length() == 0) { 3643 invalidate(); 3644 } 3645 3646 // Invalidate display list if hint is currently used 3647 if (mEditor != null && mText.length() == 0 && mHint != null) { 3648 mEditor.invalidateTextDisplayList(); 3649 } 3650 } 3651 3652 /** 3653 * Sets the text to be displayed when the text of the TextView is empty, 3654 * from a resource. 3655 * 3656 * @attr ref android.R.styleable#TextView_hint 3657 */ 3658 @android.view.RemotableViewMethod setHint(int resid)3659 public final void setHint(int resid) { 3660 setHint(getContext().getResources().getText(resid)); 3661 } 3662 3663 /** 3664 * Returns the hint that is displayed when the text of the TextView 3665 * is empty. 3666 * 3667 * @attr ref android.R.styleable#TextView_hint 3668 */ 3669 @ViewDebug.CapturedViewProperty getHint()3670 public CharSequence getHint() { 3671 return mHint; 3672 } 3673 isSingleLine()3674 boolean isSingleLine() { 3675 return mSingleLine; 3676 } 3677 isMultilineInputType(int type)3678 private static boolean isMultilineInputType(int type) { 3679 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 3680 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 3681 } 3682 3683 /** 3684 * Removes the suggestion spans. 3685 */ removeSuggestionSpans(CharSequence text)3686 CharSequence removeSuggestionSpans(CharSequence text) { 3687 if (text instanceof Spanned) { 3688 Spannable spannable; 3689 if (text instanceof Spannable) { 3690 spannable = (Spannable) text; 3691 } else { 3692 spannable = new SpannableString(text); 3693 text = spannable; 3694 } 3695 3696 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 3697 for (int i = 0; i < spans.length; i++) { 3698 spannable.removeSpan(spans[i]); 3699 } 3700 } 3701 return text; 3702 } 3703 3704 /** 3705 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 3706 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 3707 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 3708 * then a soft keyboard will not be displayed for this text view. 3709 * 3710 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 3711 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 3712 * type. 3713 * 3714 * @see #getInputType() 3715 * @see #setRawInputType(int) 3716 * @see android.text.InputType 3717 * @attr ref android.R.styleable#TextView_inputType 3718 */ setInputType(int type)3719 public void setInputType(int type) { 3720 final boolean wasPassword = isPasswordInputType(getInputType()); 3721 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 3722 setInputType(type, false); 3723 final boolean isPassword = isPasswordInputType(type); 3724 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 3725 boolean forceUpdate = false; 3726 if (isPassword) { 3727 setTransformationMethod(PasswordTransformationMethod.getInstance()); 3728 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 3729 } else if (isVisiblePassword) { 3730 if (mTransformation == PasswordTransformationMethod.getInstance()) { 3731 forceUpdate = true; 3732 } 3733 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 3734 } else if (wasPassword || wasVisiblePassword) { 3735 // not in password mode, clean up typeface and transformation 3736 setTypefaceFromAttrs(null /* fontFamily */, -1, -1); 3737 if (mTransformation == PasswordTransformationMethod.getInstance()) { 3738 forceUpdate = true; 3739 } 3740 } 3741 3742 boolean singleLine = !isMultilineInputType(type); 3743 3744 // We need to update the single line mode if it has changed or we 3745 // were previously in password mode. 3746 if (mSingleLine != singleLine || forceUpdate) { 3747 // Change single line mode, but only change the transformation if 3748 // we are not in password mode. 3749 applySingleLine(singleLine, !isPassword, true); 3750 } 3751 3752 if (!isSuggestionsEnabled()) { 3753 mText = removeSuggestionSpans(mText); 3754 } 3755 3756 InputMethodManager imm = InputMethodManager.peekInstance(); 3757 if (imm != null) imm.restartInput(this); 3758 } 3759 3760 /** 3761 * It would be better to rely on the input type for everything. A password inputType should have 3762 * a password transformation. We should hence use isPasswordInputType instead of this method. 3763 * 3764 * We should: 3765 * - Call setInputType in setKeyListener instead of changing the input type directly (which 3766 * would install the correct transformation). 3767 * - Refuse the installation of a non-password transformation in setTransformation if the input 3768 * type is password. 3769 * 3770 * However, this is like this for legacy reasons and we cannot break existing apps. This method 3771 * is useful since it matches what the user can see (obfuscated text or not). 3772 * 3773 * @return true if the current transformation method is of the password type. 3774 */ hasPasswordTransformationMethod()3775 private boolean hasPasswordTransformationMethod() { 3776 return mTransformation instanceof PasswordTransformationMethod; 3777 } 3778 isPasswordInputType(int inputType)3779 private static boolean isPasswordInputType(int inputType) { 3780 final int variation = 3781 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 3782 return variation 3783 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 3784 || variation 3785 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 3786 || variation 3787 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 3788 } 3789 isVisiblePasswordInputType(int inputType)3790 private static boolean isVisiblePasswordInputType(int inputType) { 3791 final int variation = 3792 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 3793 return variation 3794 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 3795 } 3796 3797 /** 3798 * Directly change the content type integer of the text view, without 3799 * modifying any other state. 3800 * @see #setInputType(int) 3801 * @see android.text.InputType 3802 * @attr ref android.R.styleable#TextView_inputType 3803 */ setRawInputType(int type)3804 public void setRawInputType(int type) { 3805 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 3806 createEditorIfNeeded(); 3807 mEditor.mInputType = type; 3808 } 3809 setInputType(int type, boolean direct)3810 private void setInputType(int type, boolean direct) { 3811 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 3812 KeyListener input; 3813 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 3814 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 3815 TextKeyListener.Capitalize cap; 3816 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 3817 cap = TextKeyListener.Capitalize.CHARACTERS; 3818 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 3819 cap = TextKeyListener.Capitalize.WORDS; 3820 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 3821 cap = TextKeyListener.Capitalize.SENTENCES; 3822 } else { 3823 cap = TextKeyListener.Capitalize.NONE; 3824 } 3825 input = TextKeyListener.getInstance(autotext, cap); 3826 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 3827 input = DigitsKeyListener.getInstance( 3828 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 3829 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 3830 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 3831 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 3832 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 3833 input = DateKeyListener.getInstance(); 3834 break; 3835 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 3836 input = TimeKeyListener.getInstance(); 3837 break; 3838 default: 3839 input = DateTimeKeyListener.getInstance(); 3840 break; 3841 } 3842 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 3843 input = DialerKeyListener.getInstance(); 3844 } else { 3845 input = TextKeyListener.getInstance(); 3846 } 3847 setRawInputType(type); 3848 if (direct) { 3849 createEditorIfNeeded(); 3850 mEditor.mKeyListener = input; 3851 } else { 3852 setKeyListenerOnly(input); 3853 } 3854 } 3855 3856 /** 3857 * Get the type of the editable content. 3858 * 3859 * @see #setInputType(int) 3860 * @see android.text.InputType 3861 */ getInputType()3862 public int getInputType() { 3863 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 3864 } 3865 3866 /** 3867 * Change the editor type integer associated with the text view, which 3868 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 3869 * has focus. 3870 * @see #getImeOptions 3871 * @see android.view.inputmethod.EditorInfo 3872 * @attr ref android.R.styleable#TextView_imeOptions 3873 */ setImeOptions(int imeOptions)3874 public void setImeOptions(int imeOptions) { 3875 createEditorIfNeeded(); 3876 mEditor.createInputContentTypeIfNeeded(); 3877 mEditor.mInputContentType.imeOptions = imeOptions; 3878 } 3879 3880 /** 3881 * Get the type of the IME editor. 3882 * 3883 * @see #setImeOptions(int) 3884 * @see android.view.inputmethod.EditorInfo 3885 */ getImeOptions()3886 public int getImeOptions() { 3887 return mEditor != null && mEditor.mInputContentType != null 3888 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 3889 } 3890 3891 /** 3892 * Change the custom IME action associated with the text view, which 3893 * will be reported to an IME with {@link EditorInfo#actionLabel} 3894 * and {@link EditorInfo#actionId} when it has focus. 3895 * @see #getImeActionLabel 3896 * @see #getImeActionId 3897 * @see android.view.inputmethod.EditorInfo 3898 * @attr ref android.R.styleable#TextView_imeActionLabel 3899 * @attr ref android.R.styleable#TextView_imeActionId 3900 */ setImeActionLabel(CharSequence label, int actionId)3901 public void setImeActionLabel(CharSequence label, int actionId) { 3902 createEditorIfNeeded(); 3903 mEditor.createInputContentTypeIfNeeded(); 3904 mEditor.mInputContentType.imeActionLabel = label; 3905 mEditor.mInputContentType.imeActionId = actionId; 3906 } 3907 3908 /** 3909 * Get the IME action label previous set with {@link #setImeActionLabel}. 3910 * 3911 * @see #setImeActionLabel 3912 * @see android.view.inputmethod.EditorInfo 3913 */ getImeActionLabel()3914 public CharSequence getImeActionLabel() { 3915 return mEditor != null && mEditor.mInputContentType != null 3916 ? mEditor.mInputContentType.imeActionLabel : null; 3917 } 3918 3919 /** 3920 * Get the IME action ID previous set with {@link #setImeActionLabel}. 3921 * 3922 * @see #setImeActionLabel 3923 * @see android.view.inputmethod.EditorInfo 3924 */ getImeActionId()3925 public int getImeActionId() { 3926 return mEditor != null && mEditor.mInputContentType != null 3927 ? mEditor.mInputContentType.imeActionId : 0; 3928 } 3929 3930 /** 3931 * Set a special listener to be called when an action is performed 3932 * on the text view. This will be called when the enter key is pressed, 3933 * or when an action supplied to the IME is selected by the user. Setting 3934 * this means that the normal hard key event will not insert a newline 3935 * into the text view, even if it is multi-line; holding down the ALT 3936 * modifier will, however, allow the user to insert a newline character. 3937 */ setOnEditorActionListener(OnEditorActionListener l)3938 public void setOnEditorActionListener(OnEditorActionListener l) { 3939 createEditorIfNeeded(); 3940 mEditor.createInputContentTypeIfNeeded(); 3941 mEditor.mInputContentType.onEditorActionListener = l; 3942 } 3943 3944 /** 3945 * Called when an attached input method calls 3946 * {@link InputConnection#performEditorAction(int) 3947 * InputConnection.performEditorAction()} 3948 * for this text view. The default implementation will call your action 3949 * listener supplied to {@link #setOnEditorActionListener}, or perform 3950 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 3951 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 3952 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 3953 * EditorInfo.IME_ACTION_DONE}. 3954 * 3955 * <p>For backwards compatibility, if no IME options have been set and the 3956 * text view would not normally advance focus on enter, then 3957 * the NEXT and DONE actions received here will be turned into an enter 3958 * key down/up pair to go through the normal key handling. 3959 * 3960 * @param actionCode The code of the action being performed. 3961 * 3962 * @see #setOnEditorActionListener 3963 */ onEditorAction(int actionCode)3964 public void onEditorAction(int actionCode) { 3965 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 3966 if (ict != null) { 3967 if (ict.onEditorActionListener != null) { 3968 if (ict.onEditorActionListener.onEditorAction(this, 3969 actionCode, null)) { 3970 return; 3971 } 3972 } 3973 3974 // This is the handling for some default action. 3975 // Note that for backwards compatibility we don't do this 3976 // default handling if explicit ime options have not been given, 3977 // instead turning this into the normal enter key codes that an 3978 // app may be expecting. 3979 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 3980 View v = focusSearch(FOCUS_FORWARD); 3981 if (v != null) { 3982 if (!v.requestFocus(FOCUS_FORWARD)) { 3983 throw new IllegalStateException("focus search returned a view " + 3984 "that wasn't able to take focus!"); 3985 } 3986 } 3987 return; 3988 3989 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 3990 View v = focusSearch(FOCUS_BACKWARD); 3991 if (v != null) { 3992 if (!v.requestFocus(FOCUS_BACKWARD)) { 3993 throw new IllegalStateException("focus search returned a view " + 3994 "that wasn't able to take focus!"); 3995 } 3996 } 3997 return; 3998 3999 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 4000 InputMethodManager imm = InputMethodManager.peekInstance(); 4001 if (imm != null && imm.isActive(this)) { 4002 imm.hideSoftInputFromWindow(getWindowToken(), 0); 4003 } 4004 return; 4005 } 4006 } 4007 4008 ViewRootImpl viewRootImpl = getViewRootImpl(); 4009 if (viewRootImpl != null) { 4010 long eventTime = SystemClock.uptimeMillis(); 4011 viewRootImpl.dispatchKeyFromIme( 4012 new KeyEvent(eventTime, eventTime, 4013 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 4014 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4015 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4016 | KeyEvent.FLAG_EDITOR_ACTION)); 4017 viewRootImpl.dispatchKeyFromIme( 4018 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 4019 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 4020 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4021 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4022 | KeyEvent.FLAG_EDITOR_ACTION)); 4023 } 4024 } 4025 4026 /** 4027 * Set the private content type of the text, which is the 4028 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 4029 * field that will be filled in when creating an input connection. 4030 * 4031 * @see #getPrivateImeOptions() 4032 * @see EditorInfo#privateImeOptions 4033 * @attr ref android.R.styleable#TextView_privateImeOptions 4034 */ setPrivateImeOptions(String type)4035 public void setPrivateImeOptions(String type) { 4036 createEditorIfNeeded(); 4037 mEditor.createInputContentTypeIfNeeded(); 4038 mEditor.mInputContentType.privateImeOptions = type; 4039 } 4040 4041 /** 4042 * Get the private type of the content. 4043 * 4044 * @see #setPrivateImeOptions(String) 4045 * @see EditorInfo#privateImeOptions 4046 */ getPrivateImeOptions()4047 public String getPrivateImeOptions() { 4048 return mEditor != null && mEditor.mInputContentType != null 4049 ? mEditor.mInputContentType.privateImeOptions : null; 4050 } 4051 4052 /** 4053 * Set the extra input data of the text, which is the 4054 * {@link EditorInfo#extras TextBoxAttribute.extras} 4055 * Bundle that will be filled in when creating an input connection. The 4056 * given integer is the resource ID of an XML resource holding an 4057 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 4058 * 4059 * @see #getInputExtras(boolean) 4060 * @see EditorInfo#extras 4061 * @attr ref android.R.styleable#TextView_editorExtras 4062 */ setInputExtras(int xmlResId)4063 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException { 4064 createEditorIfNeeded(); 4065 XmlResourceParser parser = getResources().getXml(xmlResId); 4066 mEditor.createInputContentTypeIfNeeded(); 4067 mEditor.mInputContentType.extras = new Bundle(); 4068 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 4069 } 4070 4071 /** 4072 * Retrieve the input extras currently associated with the text view, which 4073 * can be viewed as well as modified. 4074 * 4075 * @param create If true, the extras will be created if they don't already 4076 * exist. Otherwise, null will be returned if none have been created. 4077 * @see #setInputExtras(int) 4078 * @see EditorInfo#extras 4079 * @attr ref android.R.styleable#TextView_editorExtras 4080 */ getInputExtras(boolean create)4081 public Bundle getInputExtras(boolean create) { 4082 if (mEditor == null && !create) return null; 4083 createEditorIfNeeded(); 4084 if (mEditor.mInputContentType == null) { 4085 if (!create) return null; 4086 mEditor.createInputContentTypeIfNeeded(); 4087 } 4088 if (mEditor.mInputContentType.extras == null) { 4089 if (!create) return null; 4090 mEditor.mInputContentType.extras = new Bundle(); 4091 } 4092 return mEditor.mInputContentType.extras; 4093 } 4094 4095 /** 4096 * Returns the error message that was set to be displayed with 4097 * {@link #setError}, or <code>null</code> if no error was set 4098 * or if it the error was cleared by the widget after user input. 4099 */ getError()4100 public CharSequence getError() { 4101 return mEditor == null ? null : mEditor.mError; 4102 } 4103 4104 /** 4105 * Sets the right-hand compound drawable of the TextView to the "error" 4106 * icon and sets an error message that will be displayed in a popup when 4107 * the TextView has focus. The icon and error message will be reset to 4108 * null when any key events cause changes to the TextView's text. If the 4109 * <code>error</code> is <code>null</code>, the error message and icon 4110 * will be cleared. 4111 */ 4112 @android.view.RemotableViewMethod setError(CharSequence error)4113 public void setError(CharSequence error) { 4114 if (error == null) { 4115 setError(null, null); 4116 } else { 4117 Drawable dr = getContext().getResources(). 4118 getDrawable(com.android.internal.R.drawable.indicator_input_error); 4119 4120 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 4121 setError(error, dr); 4122 } 4123 } 4124 4125 /** 4126 * Sets the right-hand compound drawable of the TextView to the specified 4127 * icon and sets an error message that will be displayed in a popup when 4128 * the TextView has focus. The icon and error message will be reset to 4129 * null when any key events cause changes to the TextView's text. The 4130 * drawable must already have had {@link Drawable#setBounds} set on it. 4131 * If the <code>error</code> is <code>null</code>, the error message will 4132 * be cleared (and you should provide a <code>null</code> icon as well). 4133 */ setError(CharSequence error, Drawable icon)4134 public void setError(CharSequence error, Drawable icon) { 4135 createEditorIfNeeded(); 4136 mEditor.setError(error, icon); 4137 } 4138 4139 @Override setFrame(int l, int t, int r, int b)4140 protected boolean setFrame(int l, int t, int r, int b) { 4141 boolean result = super.setFrame(l, t, r, b); 4142 4143 if (mEditor != null) mEditor.setFrame(); 4144 4145 restartMarqueeIfNeeded(); 4146 4147 return result; 4148 } 4149 restartMarqueeIfNeeded()4150 private void restartMarqueeIfNeeded() { 4151 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4152 mRestartMarquee = false; 4153 startMarquee(); 4154 } 4155 } 4156 4157 /** 4158 * Sets the list of input filters that will be used if the buffer is 4159 * Editable. Has no effect otherwise. 4160 * 4161 * @attr ref android.R.styleable#TextView_maxLength 4162 */ setFilters(InputFilter[] filters)4163 public void setFilters(InputFilter[] filters) { 4164 if (filters == null) { 4165 throw new IllegalArgumentException(); 4166 } 4167 4168 mFilters = filters; 4169 4170 if (mText instanceof Editable) { 4171 setFilters((Editable) mText, filters); 4172 } 4173 } 4174 4175 /** 4176 * Sets the list of input filters on the specified Editable, 4177 * and includes mInput in the list if it is an InputFilter. 4178 */ setFilters(Editable e, InputFilter[] filters)4179 private void setFilters(Editable e, InputFilter[] filters) { 4180 if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) { 4181 InputFilter[] nf = new InputFilter[filters.length + 1]; 4182 4183 System.arraycopy(filters, 0, nf, 0, filters.length); 4184 nf[filters.length] = (InputFilter) mEditor.mKeyListener; 4185 4186 e.setFilters(nf); 4187 } else { 4188 e.setFilters(filters); 4189 } 4190 } 4191 4192 /** 4193 * Returns the current list of input filters. 4194 * 4195 * @attr ref android.R.styleable#TextView_maxLength 4196 */ getFilters()4197 public InputFilter[] getFilters() { 4198 return mFilters; 4199 } 4200 4201 ///////////////////////////////////////////////////////////////////////// 4202 getVerticalOffset(boolean forceNormal)4203 int getVerticalOffset(boolean forceNormal) { 4204 int voffset = 0; 4205 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4206 4207 Layout l = mLayout; 4208 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4209 l = mHintLayout; 4210 } 4211 4212 if (gravity != Gravity.TOP) { 4213 int boxht; 4214 4215 if (l == mHintLayout) { 4216 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 4217 getCompoundPaddingBottom(); 4218 } else { 4219 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 4220 getExtendedPaddingBottom(); 4221 } 4222 int textht = l.getHeight(); 4223 4224 if (textht < boxht) { 4225 if (gravity == Gravity.BOTTOM) 4226 voffset = boxht - textht; 4227 else // (gravity == Gravity.CENTER_VERTICAL) 4228 voffset = (boxht - textht) >> 1; 4229 } 4230 } 4231 return voffset; 4232 } 4233 getBottomVerticalOffset(boolean forceNormal)4234 private int getBottomVerticalOffset(boolean forceNormal) { 4235 int voffset = 0; 4236 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4237 4238 Layout l = mLayout; 4239 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4240 l = mHintLayout; 4241 } 4242 4243 if (gravity != Gravity.BOTTOM) { 4244 int boxht; 4245 4246 if (l == mHintLayout) { 4247 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 4248 getCompoundPaddingBottom(); 4249 } else { 4250 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 4251 getExtendedPaddingBottom(); 4252 } 4253 int textht = l.getHeight(); 4254 4255 if (textht < boxht) { 4256 if (gravity == Gravity.TOP) 4257 voffset = boxht - textht; 4258 else // (gravity == Gravity.CENTER_VERTICAL) 4259 voffset = (boxht - textht) >> 1; 4260 } 4261 } 4262 return voffset; 4263 } 4264 invalidateCursorPath()4265 void invalidateCursorPath() { 4266 if (mHighlightPathBogus) { 4267 invalidateCursor(); 4268 } else { 4269 final int horizontalPadding = getCompoundPaddingLeft(); 4270 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4271 4272 if (mEditor.mCursorCount == 0) { 4273 synchronized (TEMP_RECTF) { 4274 /* 4275 * The reason for this concern about the thickness of the 4276 * cursor and doing the floor/ceil on the coordinates is that 4277 * some EditTexts (notably textfields in the Browser) have 4278 * anti-aliased text where not all the characters are 4279 * necessarily at integer-multiple locations. This should 4280 * make sure the entire cursor gets invalidated instead of 4281 * sometimes missing half a pixel. 4282 */ 4283 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); 4284 if (thick < 1.0f) { 4285 thick = 1.0f; 4286 } 4287 4288 thick /= 2.0f; 4289 4290 // mHighlightPath is guaranteed to be non null at that point. 4291 mHighlightPath.computeBounds(TEMP_RECTF, false); 4292 4293 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick), 4294 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick), 4295 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick), 4296 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 4297 } 4298 } else { 4299 for (int i = 0; i < mEditor.mCursorCount; i++) { 4300 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4301 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 4302 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 4303 } 4304 } 4305 } 4306 } 4307 invalidateCursor()4308 void invalidateCursor() { 4309 int where = getSelectionEnd(); 4310 4311 invalidateCursor(where, where, where); 4312 } 4313 invalidateCursor(int a, int b, int c)4314 private void invalidateCursor(int a, int b, int c) { 4315 if (a >= 0 || b >= 0 || c >= 0) { 4316 int start = Math.min(Math.min(a, b), c); 4317 int end = Math.max(Math.max(a, b), c); 4318 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 4319 } 4320 } 4321 4322 /** 4323 * Invalidates the region of text enclosed between the start and end text offsets. 4324 */ invalidateRegion(int start, int end, boolean invalidateCursor)4325 void invalidateRegion(int start, int end, boolean invalidateCursor) { 4326 if (mLayout == null) { 4327 invalidate(); 4328 } else { 4329 int lineStart = mLayout.getLineForOffset(start); 4330 int top = mLayout.getLineTop(lineStart); 4331 4332 // This is ridiculous, but the descent from the line above 4333 // can hang down into the line we really want to redraw, 4334 // so we have to invalidate part of the line above to make 4335 // sure everything that needs to be redrawn really is. 4336 // (But not the whole line above, because that would cause 4337 // the same problem with the descenders on the line above it!) 4338 if (lineStart > 0) { 4339 top -= mLayout.getLineDescent(lineStart - 1); 4340 } 4341 4342 int lineEnd; 4343 4344 if (start == end) 4345 lineEnd = lineStart; 4346 else 4347 lineEnd = mLayout.getLineForOffset(end); 4348 4349 int bottom = mLayout.getLineBottom(lineEnd); 4350 4351 // mEditor can be null in case selection is set programmatically. 4352 if (invalidateCursor && mEditor != null) { 4353 for (int i = 0; i < mEditor.mCursorCount; i++) { 4354 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4355 top = Math.min(top, bounds.top); 4356 bottom = Math.max(bottom, bounds.bottom); 4357 } 4358 } 4359 4360 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4361 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4362 4363 int left, right; 4364 if (lineStart == lineEnd && !invalidateCursor) { 4365 left = (int) mLayout.getPrimaryHorizontal(start); 4366 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 4367 left += compoundPaddingLeft; 4368 right += compoundPaddingLeft; 4369 } else { 4370 // Rectangle bounding box when the region spans several lines 4371 left = compoundPaddingLeft; 4372 right = getWidth() - getCompoundPaddingRight(); 4373 } 4374 4375 invalidate(mScrollX + left, verticalPadding + top, 4376 mScrollX + right, verticalPadding + bottom); 4377 } 4378 } 4379 registerForPreDraw()4380 private void registerForPreDraw() { 4381 if (!mPreDrawRegistered) { 4382 getViewTreeObserver().addOnPreDrawListener(this); 4383 mPreDrawRegistered = true; 4384 } 4385 } 4386 4387 /** 4388 * {@inheritDoc} 4389 */ onPreDraw()4390 public boolean onPreDraw() { 4391 if (mLayout == null) { 4392 assumeLayout(); 4393 } 4394 4395 boolean changed = false; 4396 4397 if (mMovement != null) { 4398 /* This code also provides auto-scrolling when a cursor is moved using a 4399 * CursorController (insertion point or selection limits). 4400 * For selection, ensure start or end is visible depending on controller's state. 4401 */ 4402 int curs = getSelectionEnd(); 4403 // Do not create the controller if it is not already created. 4404 if (mEditor != null && mEditor.mSelectionModifierCursorController != null && 4405 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 4406 curs = getSelectionStart(); 4407 } 4408 4409 /* 4410 * TODO: This should really only keep the end in view if 4411 * it already was before the text changed. I'm not sure 4412 * of a good way to tell from here if it was. 4413 */ 4414 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4415 curs = mText.length(); 4416 } 4417 4418 if (curs >= 0) { 4419 changed = bringPointIntoView(curs); 4420 } 4421 } else { 4422 changed = bringTextIntoView(); 4423 } 4424 4425 // This has to be checked here since: 4426 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 4427 // a screen rotation) since layout is not yet initialized at that point. 4428 if (mEditor != null && mEditor.mCreatedWithASelection) { 4429 mEditor.startSelectionActionMode(); 4430 mEditor.mCreatedWithASelection = false; 4431 } 4432 4433 // Phone specific code (there is no ExtractEditText on tablets). 4434 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can 4435 // not be set. Do the test here instead. 4436 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { 4437 mEditor.startSelectionActionMode(); 4438 } 4439 4440 getViewTreeObserver().removeOnPreDrawListener(this); 4441 mPreDrawRegistered = false; 4442 4443 return !changed; 4444 } 4445 4446 @Override onAttachedToWindow()4447 protected void onAttachedToWindow() { 4448 super.onAttachedToWindow(); 4449 4450 mTemporaryDetach = false; 4451 4452 // Resolve drawables as the layout direction has been resolved 4453 resolveDrawables(); 4454 4455 if (mEditor != null) mEditor.onAttachedToWindow(); 4456 } 4457 4458 @Override onDetachedFromWindow()4459 protected void onDetachedFromWindow() { 4460 super.onDetachedFromWindow(); 4461 4462 if (mPreDrawRegistered) { 4463 getViewTreeObserver().removeOnPreDrawListener(this); 4464 mPreDrawRegistered = false; 4465 } 4466 4467 resetResolvedDrawables(); 4468 4469 if (mEditor != null) mEditor.onDetachedFromWindow(); 4470 } 4471 4472 @Override onScreenStateChanged(int screenState)4473 public void onScreenStateChanged(int screenState) { 4474 super.onScreenStateChanged(screenState); 4475 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 4476 } 4477 4478 @Override isPaddingOffsetRequired()4479 protected boolean isPaddingOffsetRequired() { 4480 return mShadowRadius != 0 || mDrawables != null; 4481 } 4482 4483 @Override getLeftPaddingOffset()4484 protected int getLeftPaddingOffset() { 4485 return getCompoundPaddingLeft() - mPaddingLeft + 4486 (int) Math.min(0, mShadowDx - mShadowRadius); 4487 } 4488 4489 @Override getTopPaddingOffset()4490 protected int getTopPaddingOffset() { 4491 return (int) Math.min(0, mShadowDy - mShadowRadius); 4492 } 4493 4494 @Override getBottomPaddingOffset()4495 protected int getBottomPaddingOffset() { 4496 return (int) Math.max(0, mShadowDy + mShadowRadius); 4497 } 4498 4499 @Override getRightPaddingOffset()4500 protected int getRightPaddingOffset() { 4501 return -(getCompoundPaddingRight() - mPaddingRight) + 4502 (int) Math.max(0, mShadowDx + mShadowRadius); 4503 } 4504 4505 @Override verifyDrawable(Drawable who)4506 protected boolean verifyDrawable(Drawable who) { 4507 final boolean verified = super.verifyDrawable(who); 4508 if (!verified && mDrawables != null) { 4509 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop || 4510 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom || 4511 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd; 4512 } 4513 return verified; 4514 } 4515 4516 @Override jumpDrawablesToCurrentState()4517 public void jumpDrawablesToCurrentState() { 4518 super.jumpDrawablesToCurrentState(); 4519 if (mDrawables != null) { 4520 if (mDrawables.mDrawableLeft != null) { 4521 mDrawables.mDrawableLeft.jumpToCurrentState(); 4522 } 4523 if (mDrawables.mDrawableTop != null) { 4524 mDrawables.mDrawableTop.jumpToCurrentState(); 4525 } 4526 if (mDrawables.mDrawableRight != null) { 4527 mDrawables.mDrawableRight.jumpToCurrentState(); 4528 } 4529 if (mDrawables.mDrawableBottom != null) { 4530 mDrawables.mDrawableBottom.jumpToCurrentState(); 4531 } 4532 if (mDrawables.mDrawableStart != null) { 4533 mDrawables.mDrawableStart.jumpToCurrentState(); 4534 } 4535 if (mDrawables.mDrawableEnd != null) { 4536 mDrawables.mDrawableEnd.jumpToCurrentState(); 4537 } 4538 } 4539 } 4540 4541 @Override invalidateDrawable(Drawable drawable)4542 public void invalidateDrawable(Drawable drawable) { 4543 if (verifyDrawable(drawable)) { 4544 final Rect dirty = drawable.getBounds(); 4545 int scrollX = mScrollX; 4546 int scrollY = mScrollY; 4547 4548 // IMPORTANT: The coordinates below are based on the coordinates computed 4549 // for each compound drawable in onDraw(). Make sure to update each section 4550 // accordingly. 4551 final TextView.Drawables drawables = mDrawables; 4552 if (drawables != null) { 4553 if (drawable == drawables.mDrawableLeft) { 4554 final int compoundPaddingTop = getCompoundPaddingTop(); 4555 final int compoundPaddingBottom = getCompoundPaddingBottom(); 4556 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 4557 4558 scrollX += mPaddingLeft; 4559 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 4560 } else if (drawable == drawables.mDrawableRight) { 4561 final int compoundPaddingTop = getCompoundPaddingTop(); 4562 final int compoundPaddingBottom = getCompoundPaddingBottom(); 4563 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 4564 4565 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 4566 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 4567 } else if (drawable == drawables.mDrawableTop) { 4568 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4569 final int compoundPaddingRight = getCompoundPaddingRight(); 4570 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 4571 4572 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 4573 scrollY += mPaddingTop; 4574 } else if (drawable == drawables.mDrawableBottom) { 4575 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4576 final int compoundPaddingRight = getCompoundPaddingRight(); 4577 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 4578 4579 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 4580 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 4581 } 4582 } 4583 4584 invalidate(dirty.left + scrollX, dirty.top + scrollY, 4585 dirty.right + scrollX, dirty.bottom + scrollY); 4586 } 4587 } 4588 4589 /** 4590 * @hide 4591 */ 4592 @Override getResolvedLayoutDirection(Drawable who)4593 public int getResolvedLayoutDirection(Drawable who) { 4594 if (who == null) return View.LAYOUT_DIRECTION_LTR; 4595 if (mDrawables != null) { 4596 final Drawables drawables = mDrawables; 4597 if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight || 4598 who == drawables.mDrawableTop || who == drawables.mDrawableBottom || 4599 who == drawables.mDrawableStart || who == drawables.mDrawableEnd) { 4600 return getResolvedLayoutDirection(); 4601 } 4602 } 4603 return super.getResolvedLayoutDirection(who); 4604 } 4605 4606 @Override hasOverlappingRendering()4607 public boolean hasOverlappingRendering() { 4608 return (getBackground() != null || mText instanceof Spannable || hasSelection()); 4609 } 4610 4611 /** 4612 * When a TextView is used to display a useful piece of information to the user (such as a 4613 * contact's address), it should be made selectable, so that the user can select and copy this 4614 * content. 4615 * 4616 * Use {@link #setTextIsSelectable(boolean)} or the 4617 * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView 4618 * selectable (text is not selectable by default). 4619 * 4620 * Note that this method simply returns the state of this flag. Although this flag has to be set 4621 * in order to select text in non-editable TextView, the content of an {@link EditText} can 4622 * always be selected, independently of the value of this flag. 4623 * 4624 * @return True if the text displayed in this TextView can be selected by the user. 4625 * 4626 * @attr ref android.R.styleable#TextView_textIsSelectable 4627 */ isTextSelectable()4628 public boolean isTextSelectable() { 4629 return mEditor == null ? false : mEditor.mTextIsSelectable; 4630 } 4631 4632 /** 4633 * Sets whether or not (default) the content of this view is selectable by the user. 4634 * 4635 * Note that this methods affect the {@link #setFocusable(boolean)}, 4636 * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and 4637 * {@link #setLongClickable(boolean)} states and you may want to restore these if they were 4638 * customized. 4639 * 4640 * See {@link #isTextSelectable} for details. 4641 * 4642 * @param selectable Whether or not the content of this TextView should be selectable. 4643 */ setTextIsSelectable(boolean selectable)4644 public void setTextIsSelectable(boolean selectable) { 4645 if (!selectable && mEditor == null) return; // false is default value with no edit data 4646 4647 createEditorIfNeeded(); 4648 if (mEditor.mTextIsSelectable == selectable) return; 4649 4650 mEditor.mTextIsSelectable = selectable; 4651 setFocusableInTouchMode(selectable); 4652 setFocusable(selectable); 4653 setClickable(selectable); 4654 setLongClickable(selectable); 4655 4656 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 4657 4658 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 4659 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 4660 4661 // Called by setText above, but safer in case of future code changes 4662 mEditor.prepareCursorControllers(); 4663 } 4664 4665 @Override onCreateDrawableState(int extraSpace)4666 protected int[] onCreateDrawableState(int extraSpace) { 4667 final int[] drawableState; 4668 4669 if (mSingleLine) { 4670 drawableState = super.onCreateDrawableState(extraSpace); 4671 } else { 4672 drawableState = super.onCreateDrawableState(extraSpace + 1); 4673 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 4674 } 4675 4676 if (isTextSelectable()) { 4677 // Disable pressed state, which was introduced when TextView was made clickable. 4678 // Prevents text color change. 4679 // setClickable(false) would have a similar effect, but it also disables focus changes 4680 // and long press actions, which are both needed by text selection. 4681 final int length = drawableState.length; 4682 for (int i = 0; i < length; i++) { 4683 if (drawableState[i] == R.attr.state_pressed) { 4684 final int[] nonPressedState = new int[length - 1]; 4685 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 4686 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 4687 return nonPressedState; 4688 } 4689 } 4690 } 4691 4692 return drawableState; 4693 } 4694 getUpdatedHighlightPath()4695 private Path getUpdatedHighlightPath() { 4696 Path highlight = null; 4697 Paint highlightPaint = mHighlightPaint; 4698 4699 final int selStart = getSelectionStart(); 4700 final int selEnd = getSelectionEnd(); 4701 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 4702 if (selStart == selEnd) { 4703 if (mEditor != null && mEditor.isCursorVisible() && 4704 (SystemClock.uptimeMillis() - mEditor.mShowCursor) % 4705 (2 * Editor.BLINK) < Editor.BLINK) { 4706 if (mHighlightPathBogus) { 4707 if (mHighlightPath == null) mHighlightPath = new Path(); 4708 mHighlightPath.reset(); 4709 mLayout.getCursorPath(selStart, mHighlightPath, mText); 4710 mEditor.updateCursorsPositions(); 4711 mHighlightPathBogus = false; 4712 } 4713 4714 // XXX should pass to skin instead of drawing directly 4715 highlightPaint.setColor(mCurTextColor); 4716 highlightPaint.setStyle(Paint.Style.STROKE); 4717 highlight = mHighlightPath; 4718 } 4719 } else { 4720 if (mHighlightPathBogus) { 4721 if (mHighlightPath == null) mHighlightPath = new Path(); 4722 mHighlightPath.reset(); 4723 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 4724 mHighlightPathBogus = false; 4725 } 4726 4727 // XXX should pass to skin instead of drawing directly 4728 highlightPaint.setColor(mHighlightColor); 4729 highlightPaint.setStyle(Paint.Style.FILL); 4730 4731 highlight = mHighlightPath; 4732 } 4733 } 4734 return highlight; 4735 } 4736 4737 @Override onDraw(Canvas canvas)4738 protected void onDraw(Canvas canvas) { 4739 restartMarqueeIfNeeded(); 4740 4741 // Draw the background for this view 4742 super.onDraw(canvas); 4743 4744 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4745 final int compoundPaddingTop = getCompoundPaddingTop(); 4746 final int compoundPaddingRight = getCompoundPaddingRight(); 4747 final int compoundPaddingBottom = getCompoundPaddingBottom(); 4748 final int scrollX = mScrollX; 4749 final int scrollY = mScrollY; 4750 final int right = mRight; 4751 final int left = mLeft; 4752 final int bottom = mBottom; 4753 final int top = mTop; 4754 4755 final Drawables dr = mDrawables; 4756 if (dr != null) { 4757 /* 4758 * Compound, not extended, because the icon is not clipped 4759 * if the text height is smaller. 4760 */ 4761 4762 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 4763 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 4764 4765 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 4766 // Make sure to update invalidateDrawable() when changing this code. 4767 if (dr.mDrawableLeft != null) { 4768 canvas.save(); 4769 canvas.translate(scrollX + mPaddingLeft, 4770 scrollY + compoundPaddingTop + 4771 (vspace - dr.mDrawableHeightLeft) / 2); 4772 dr.mDrawableLeft.draw(canvas); 4773 canvas.restore(); 4774 } 4775 4776 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 4777 // Make sure to update invalidateDrawable() when changing this code. 4778 if (dr.mDrawableRight != null) { 4779 canvas.save(); 4780 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, 4781 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 4782 dr.mDrawableRight.draw(canvas); 4783 canvas.restore(); 4784 } 4785 4786 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 4787 // Make sure to update invalidateDrawable() when changing this code. 4788 if (dr.mDrawableTop != null) { 4789 canvas.save(); 4790 canvas.translate(scrollX + compoundPaddingLeft + 4791 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 4792 dr.mDrawableTop.draw(canvas); 4793 canvas.restore(); 4794 } 4795 4796 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 4797 // Make sure to update invalidateDrawable() when changing this code. 4798 if (dr.mDrawableBottom != null) { 4799 canvas.save(); 4800 canvas.translate(scrollX + compoundPaddingLeft + 4801 (hspace - dr.mDrawableWidthBottom) / 2, 4802 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 4803 dr.mDrawableBottom.draw(canvas); 4804 canvas.restore(); 4805 } 4806 } 4807 4808 int color = mCurTextColor; 4809 4810 if (mLayout == null) { 4811 assumeLayout(); 4812 } 4813 4814 Layout layout = mLayout; 4815 4816 if (mHint != null && mText.length() == 0) { 4817 if (mHintTextColor != null) { 4818 color = mCurHintTextColor; 4819 } 4820 4821 layout = mHintLayout; 4822 } 4823 4824 mTextPaint.setColor(color); 4825 mTextPaint.drawableState = getDrawableState(); 4826 4827 canvas.save(); 4828 /* Would be faster if we didn't have to do this. Can we chop the 4829 (displayable) text so that we don't need to do this ever? 4830 */ 4831 4832 int extendedPaddingTop = getExtendedPaddingTop(); 4833 int extendedPaddingBottom = getExtendedPaddingBottom(); 4834 4835 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 4836 final int maxScrollY = mLayout.getHeight() - vspace; 4837 4838 float clipLeft = compoundPaddingLeft + scrollX; 4839 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 4840 float clipRight = right - left - compoundPaddingRight + scrollX; 4841 float clipBottom = bottom - top + scrollY - 4842 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 4843 4844 if (mShadowRadius != 0) { 4845 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 4846 clipRight += Math.max(0, mShadowDx + mShadowRadius); 4847 4848 clipTop += Math.min(0, mShadowDy - mShadowRadius); 4849 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 4850 } 4851 4852 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 4853 4854 int voffsetText = 0; 4855 int voffsetCursor = 0; 4856 4857 // translate in by our padding 4858 /* shortcircuit calling getVerticaOffset() */ 4859 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4860 voffsetText = getVerticalOffset(false); 4861 voffsetCursor = getVerticalOffset(true); 4862 } 4863 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 4864 4865 final int layoutDirection = getResolvedLayoutDirection(); 4866 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 4867 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 4868 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 4869 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 4870 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 4871 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - 4872 getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); 4873 } 4874 4875 if (mMarquee != null && mMarquee.isRunning()) { 4876 canvas.translate(-mMarquee.mScroll, 0.0f); 4877 } 4878 } 4879 4880 final int cursorOffsetVertical = voffsetCursor - voffsetText; 4881 4882 Path highlight = getUpdatedHighlightPath(); 4883 if (mEditor != null) { 4884 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 4885 } else { 4886 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 4887 } 4888 4889 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 4890 canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); 4891 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 4892 } 4893 4894 canvas.restore(); 4895 } 4896 4897 @Override getFocusedRect(Rect r)4898 public void getFocusedRect(Rect r) { 4899 if (mLayout == null) { 4900 super.getFocusedRect(r); 4901 return; 4902 } 4903 4904 int selEnd = getSelectionEnd(); 4905 if (selEnd < 0) { 4906 super.getFocusedRect(r); 4907 return; 4908 } 4909 4910 int selStart = getSelectionStart(); 4911 if (selStart < 0 || selStart >= selEnd) { 4912 int line = mLayout.getLineForOffset(selEnd); 4913 r.top = mLayout.getLineTop(line); 4914 r.bottom = mLayout.getLineBottom(line); 4915 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 4916 r.right = r.left + 4; 4917 } else { 4918 int lineStart = mLayout.getLineForOffset(selStart); 4919 int lineEnd = mLayout.getLineForOffset(selEnd); 4920 r.top = mLayout.getLineTop(lineStart); 4921 r.bottom = mLayout.getLineBottom(lineEnd); 4922 if (lineStart == lineEnd) { 4923 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 4924 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 4925 } else { 4926 // Selection extends across multiple lines -- make the focused 4927 // rect cover the entire width. 4928 if (mHighlightPathBogus) { 4929 if (mHighlightPath == null) mHighlightPath = new Path(); 4930 mHighlightPath.reset(); 4931 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 4932 mHighlightPathBogus = false; 4933 } 4934 synchronized (TEMP_RECTF) { 4935 mHighlightPath.computeBounds(TEMP_RECTF, true); 4936 r.left = (int)TEMP_RECTF.left-1; 4937 r.right = (int)TEMP_RECTF.right+1; 4938 } 4939 } 4940 } 4941 4942 // Adjust for padding and gravity. 4943 int paddingLeft = getCompoundPaddingLeft(); 4944 int paddingTop = getExtendedPaddingTop(); 4945 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4946 paddingTop += getVerticalOffset(false); 4947 } 4948 r.offset(paddingLeft, paddingTop); 4949 int paddingBottom = getExtendedPaddingBottom(); 4950 r.bottom += paddingBottom; 4951 } 4952 4953 /** 4954 * Return the number of lines of text, or 0 if the internal Layout has not 4955 * been built. 4956 */ getLineCount()4957 public int getLineCount() { 4958 return mLayout != null ? mLayout.getLineCount() : 0; 4959 } 4960 4961 /** 4962 * Return the baseline for the specified line (0...getLineCount() - 1) 4963 * If bounds is not null, return the top, left, right, bottom extents 4964 * of the specified line in it. If the internal Layout has not been built, 4965 * return 0 and set bounds to (0, 0, 0, 0) 4966 * @param line which line to examine (0..getLineCount() - 1) 4967 * @param bounds Optional. If not null, it returns the extent of the line 4968 * @return the Y-coordinate of the baseline 4969 */ getLineBounds(int line, Rect bounds)4970 public int getLineBounds(int line, Rect bounds) { 4971 if (mLayout == null) { 4972 if (bounds != null) { 4973 bounds.set(0, 0, 0, 0); 4974 } 4975 return 0; 4976 } 4977 else { 4978 int baseline = mLayout.getLineBounds(line, bounds); 4979 4980 int voffset = getExtendedPaddingTop(); 4981 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4982 voffset += getVerticalOffset(true); 4983 } 4984 if (bounds != null) { 4985 bounds.offset(getCompoundPaddingLeft(), voffset); 4986 } 4987 return baseline + voffset; 4988 } 4989 } 4990 4991 @Override getBaseline()4992 public int getBaseline() { 4993 if (mLayout == null) { 4994 return super.getBaseline(); 4995 } 4996 4997 int voffset = 0; 4998 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4999 voffset = getVerticalOffset(true); 5000 } 5001 5002 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 5003 } 5004 5005 /** 5006 * @hide 5007 */ 5008 @Override getFadeTop(boolean offsetRequired)5009 protected int getFadeTop(boolean offsetRequired) { 5010 if (mLayout == null) return 0; 5011 5012 int voffset = 0; 5013 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5014 voffset = getVerticalOffset(true); 5015 } 5016 5017 if (offsetRequired) voffset += getTopPaddingOffset(); 5018 5019 return getExtendedPaddingTop() + voffset; 5020 } 5021 5022 /** 5023 * @hide 5024 */ 5025 @Override getFadeHeight(boolean offsetRequired)5026 protected int getFadeHeight(boolean offsetRequired) { 5027 return mLayout != null ? mLayout.getHeight() : 0; 5028 } 5029 5030 @Override onKeyPreIme(int keyCode, KeyEvent event)5031 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 5032 if (keyCode == KeyEvent.KEYCODE_BACK) { 5033 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null; 5034 5035 if (isInSelectionMode) { 5036 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 5037 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5038 if (state != null) { 5039 state.startTracking(event, this); 5040 } 5041 return true; 5042 } else if (event.getAction() == KeyEvent.ACTION_UP) { 5043 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5044 if (state != null) { 5045 state.handleUpEvent(event); 5046 } 5047 if (event.isTracking() && !event.isCanceled()) { 5048 stopSelectionActionMode(); 5049 return true; 5050 } 5051 } 5052 } 5053 } 5054 return super.onKeyPreIme(keyCode, event); 5055 } 5056 5057 @Override onKeyDown(int keyCode, KeyEvent event)5058 public boolean onKeyDown(int keyCode, KeyEvent event) { 5059 int which = doKeyDown(keyCode, event, null); 5060 if (which == 0) { 5061 // Go through default dispatching. 5062 return super.onKeyDown(keyCode, event); 5063 } 5064 5065 return true; 5066 } 5067 5068 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)5069 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 5070 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 5071 5072 int which = doKeyDown(keyCode, down, event); 5073 if (which == 0) { 5074 // Go through default dispatching. 5075 return super.onKeyMultiple(keyCode, repeatCount, event); 5076 } 5077 if (which == -1) { 5078 // Consumed the whole thing. 5079 return true; 5080 } 5081 5082 repeatCount--; 5083 5084 // We are going to dispatch the remaining events to either the input 5085 // or movement method. To do this, we will just send a repeated stream 5086 // of down and up events until we have done the complete repeatCount. 5087 // It would be nice if those interfaces had an onKeyMultiple() method, 5088 // but adding that is a more complicated change. 5089 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 5090 if (which == 1) { 5091 // mEditor and mEditor.mInput are not null from doKeyDown 5092 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5093 while (--repeatCount > 0) { 5094 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down); 5095 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5096 } 5097 hideErrorIfUnchanged(); 5098 5099 } else if (which == 2) { 5100 // mMovement is not null from doKeyDown 5101 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5102 while (--repeatCount > 0) { 5103 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 5104 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5105 } 5106 } 5107 5108 return true; 5109 } 5110 5111 /** 5112 * Returns true if pressing ENTER in this field advances focus instead 5113 * of inserting the character. This is true mostly in single-line fields, 5114 * but also in mail addresses and subjects which will display on multiple 5115 * lines but where it doesn't make sense to insert newlines. 5116 */ shouldAdvanceFocusOnEnter()5117 private boolean shouldAdvanceFocusOnEnter() { 5118 if (getKeyListener() == null) { 5119 return false; 5120 } 5121 5122 if (mSingleLine) { 5123 return true; 5124 } 5125 5126 if (mEditor != null && 5127 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5128 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5129 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 5130 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 5131 return true; 5132 } 5133 } 5134 5135 return false; 5136 } 5137 5138 /** 5139 * Returns true if pressing TAB in this field advances focus instead 5140 * of inserting the character. Insert tabs only in multi-line editors. 5141 */ shouldAdvanceFocusOnTab()5142 private boolean shouldAdvanceFocusOnTab() { 5143 if (getKeyListener() != null && !mSingleLine && mEditor != null && 5144 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5145 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5146 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 5147 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 5148 return false; 5149 } 5150 } 5151 return true; 5152 } 5153 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)5154 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 5155 if (!isEnabled()) { 5156 return 0; 5157 } 5158 5159 switch (keyCode) { 5160 case KeyEvent.KEYCODE_ENTER: 5161 if (event.hasNoModifiers()) { 5162 // When mInputContentType is set, we know that we are 5163 // running in a "modern" cupcake environment, so don't need 5164 // to worry about the application trying to capture 5165 // enter key events. 5166 if (mEditor != null && mEditor.mInputContentType != null) { 5167 // If there is an action listener, given them a 5168 // chance to consume the event. 5169 if (mEditor.mInputContentType.onEditorActionListener != null && 5170 mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5171 this, EditorInfo.IME_NULL, event)) { 5172 mEditor.mInputContentType.enterDown = true; 5173 // We are consuming the enter key for them. 5174 return -1; 5175 } 5176 } 5177 5178 // If our editor should move focus when enter is pressed, or 5179 // this is a generated event from an IME action button, then 5180 // don't let it be inserted into the text. 5181 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5182 || shouldAdvanceFocusOnEnter()) { 5183 if (hasOnClickListeners()) { 5184 return 0; 5185 } 5186 return -1; 5187 } 5188 } 5189 break; 5190 5191 case KeyEvent.KEYCODE_DPAD_CENTER: 5192 if (event.hasNoModifiers()) { 5193 if (shouldAdvanceFocusOnEnter()) { 5194 return 0; 5195 } 5196 } 5197 break; 5198 5199 case KeyEvent.KEYCODE_TAB: 5200 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 5201 if (shouldAdvanceFocusOnTab()) { 5202 return 0; 5203 } 5204 } 5205 break; 5206 5207 // Has to be done on key down (and not on key up) to correctly be intercepted. 5208 case KeyEvent.KEYCODE_BACK: 5209 if (mEditor != null && mEditor.mSelectionActionMode != null) { 5210 stopSelectionActionMode(); 5211 return -1; 5212 } 5213 break; 5214 } 5215 5216 if (mEditor != null && mEditor.mKeyListener != null) { 5217 resetErrorChangedFlag(); 5218 5219 boolean doDown = true; 5220 if (otherEvent != null) { 5221 try { 5222 beginBatchEdit(); 5223 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 5224 otherEvent); 5225 hideErrorIfUnchanged(); 5226 doDown = false; 5227 if (handled) { 5228 return -1; 5229 } 5230 } catch (AbstractMethodError e) { 5231 // onKeyOther was added after 1.0, so if it isn't 5232 // implemented we need to try to dispatch as a regular down. 5233 } finally { 5234 endBatchEdit(); 5235 } 5236 } 5237 5238 if (doDown) { 5239 beginBatchEdit(); 5240 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 5241 keyCode, event); 5242 endBatchEdit(); 5243 hideErrorIfUnchanged(); 5244 if (handled) return 1; 5245 } 5246 } 5247 5248 // bug 650865: sometimes we get a key event before a layout. 5249 // don't try to move around if we don't know the layout. 5250 5251 if (mMovement != null && mLayout != null) { 5252 boolean doDown = true; 5253 if (otherEvent != null) { 5254 try { 5255 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 5256 otherEvent); 5257 doDown = false; 5258 if (handled) { 5259 return -1; 5260 } 5261 } catch (AbstractMethodError e) { 5262 // onKeyOther was added after 1.0, so if it isn't 5263 // implemented we need to try to dispatch as a regular down. 5264 } 5265 } 5266 if (doDown) { 5267 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) 5268 return 2; 5269 } 5270 } 5271 5272 return 0; 5273 } 5274 5275 /** 5276 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 5277 * can be recorded. 5278 * @hide 5279 */ resetErrorChangedFlag()5280 public void resetErrorChangedFlag() { 5281 /* 5282 * Keep track of what the error was before doing the input 5283 * so that if an input filter changed the error, we leave 5284 * that error showing. Otherwise, we take down whatever 5285 * error was showing when the user types something. 5286 */ 5287 if (mEditor != null) mEditor.mErrorWasChanged = false; 5288 } 5289 5290 /** 5291 * @hide 5292 */ hideErrorIfUnchanged()5293 public void hideErrorIfUnchanged() { 5294 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 5295 setError(null, null); 5296 } 5297 } 5298 5299 @Override onKeyUp(int keyCode, KeyEvent event)5300 public boolean onKeyUp(int keyCode, KeyEvent event) { 5301 if (!isEnabled()) { 5302 return super.onKeyUp(keyCode, event); 5303 } 5304 5305 switch (keyCode) { 5306 case KeyEvent.KEYCODE_DPAD_CENTER: 5307 if (event.hasNoModifiers()) { 5308 /* 5309 * If there is a click listener, just call through to 5310 * super, which will invoke it. 5311 * 5312 * If there isn't a click listener, try to show the soft 5313 * input method. (It will also 5314 * call performClick(), but that won't do anything in 5315 * this case.) 5316 */ 5317 if (!hasOnClickListeners()) { 5318 if (mMovement != null && mText instanceof Editable 5319 && mLayout != null && onCheckIsTextEditor()) { 5320 InputMethodManager imm = InputMethodManager.peekInstance(); 5321 viewClicked(imm); 5322 if (imm != null && getShowSoftInputOnFocus()) { 5323 imm.showSoftInput(this, 0); 5324 } 5325 } 5326 } 5327 } 5328 return super.onKeyUp(keyCode, event); 5329 5330 case KeyEvent.KEYCODE_ENTER: 5331 if (event.hasNoModifiers()) { 5332 if (mEditor != null && mEditor.mInputContentType != null 5333 && mEditor.mInputContentType.onEditorActionListener != null 5334 && mEditor.mInputContentType.enterDown) { 5335 mEditor.mInputContentType.enterDown = false; 5336 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5337 this, EditorInfo.IME_NULL, event)) { 5338 return true; 5339 } 5340 } 5341 5342 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5343 || shouldAdvanceFocusOnEnter()) { 5344 /* 5345 * If there is a click listener, just call through to 5346 * super, which will invoke it. 5347 * 5348 * If there isn't a click listener, try to advance focus, 5349 * but still call through to super, which will reset the 5350 * pressed state and longpress state. (It will also 5351 * call performClick(), but that won't do anything in 5352 * this case.) 5353 */ 5354 if (!hasOnClickListeners()) { 5355 View v = focusSearch(FOCUS_DOWN); 5356 5357 if (v != null) { 5358 if (!v.requestFocus(FOCUS_DOWN)) { 5359 throw new IllegalStateException( 5360 "focus search returned a view " + 5361 "that wasn't able to take focus!"); 5362 } 5363 5364 /* 5365 * Return true because we handled the key; super 5366 * will return false because there was no click 5367 * listener. 5368 */ 5369 super.onKeyUp(keyCode, event); 5370 return true; 5371 } else if ((event.getFlags() 5372 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 5373 // No target for next focus, but make sure the IME 5374 // if this came from it. 5375 InputMethodManager imm = InputMethodManager.peekInstance(); 5376 if (imm != null && imm.isActive(this)) { 5377 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5378 } 5379 } 5380 } 5381 } 5382 return super.onKeyUp(keyCode, event); 5383 } 5384 break; 5385 } 5386 5387 if (mEditor != null && mEditor.mKeyListener != null) 5388 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) 5389 return true; 5390 5391 if (mMovement != null && mLayout != null) 5392 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 5393 return true; 5394 5395 return super.onKeyUp(keyCode, event); 5396 } 5397 5398 @Override onCheckIsTextEditor()5399 public boolean onCheckIsTextEditor() { 5400 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 5401 } 5402 5403 @Override onCreateInputConnection(EditorInfo outAttrs)5404 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5405 if (onCheckIsTextEditor() && isEnabled()) { 5406 mEditor.createInputMethodStateIfNeeded(); 5407 outAttrs.inputType = getInputType(); 5408 if (mEditor.mInputContentType != null) { 5409 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 5410 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 5411 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 5412 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 5413 outAttrs.extras = mEditor.mInputContentType.extras; 5414 } else { 5415 outAttrs.imeOptions = EditorInfo.IME_NULL; 5416 } 5417 if (focusSearch(FOCUS_DOWN) != null) { 5418 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 5419 } 5420 if (focusSearch(FOCUS_UP) != null) { 5421 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 5422 } 5423 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) 5424 == EditorInfo.IME_ACTION_UNSPECIFIED) { 5425 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 5426 // An action has not been set, but the enter key will move to 5427 // the next focus, so set the action to that. 5428 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 5429 } else { 5430 // An action has not been set, and there is no focus to move 5431 // to, so let's just supply a "done" action. 5432 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 5433 } 5434 if (!shouldAdvanceFocusOnEnter()) { 5435 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 5436 } 5437 } 5438 if (isMultilineInputType(outAttrs.inputType)) { 5439 // Multi-line text editors should always show an enter key. 5440 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 5441 } 5442 outAttrs.hintText = mHint; 5443 if (mText instanceof Editable) { 5444 InputConnection ic = new EditableInputConnection(this); 5445 outAttrs.initialSelStart = getSelectionStart(); 5446 outAttrs.initialSelEnd = getSelectionEnd(); 5447 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 5448 return ic; 5449 } 5450 } 5451 return null; 5452 } 5453 5454 /** 5455 * If this TextView contains editable content, extract a portion of it 5456 * based on the information in <var>request</var> in to <var>outText</var>. 5457 * @return Returns true if the text was successfully extracted, else false. 5458 */ extractText(ExtractedTextRequest request, ExtractedText outText)5459 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 5460 createEditorIfNeeded(); 5461 return mEditor.extractText(request, outText); 5462 } 5463 5464 /** 5465 * This is used to remove all style-impacting spans from text before new 5466 * extracted text is being replaced into it, so that we don't have any 5467 * lingering spans applied during the replace. 5468 */ removeParcelableSpans(Spannable spannable, int start, int end)5469 static void removeParcelableSpans(Spannable spannable, int start, int end) { 5470 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 5471 int i = spans.length; 5472 while (i > 0) { 5473 i--; 5474 spannable.removeSpan(spans[i]); 5475 } 5476 } 5477 5478 /** 5479 * Apply to this text view the given extracted text, as previously 5480 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 5481 */ setExtractedText(ExtractedText text)5482 public void setExtractedText(ExtractedText text) { 5483 Editable content = getEditableText(); 5484 if (text.text != null) { 5485 if (content == null) { 5486 setText(text.text, TextView.BufferType.EDITABLE); 5487 } else if (text.partialStartOffset < 0) { 5488 removeParcelableSpans(content, 0, content.length()); 5489 content.replace(0, content.length(), text.text); 5490 } else { 5491 final int N = content.length(); 5492 int start = text.partialStartOffset; 5493 if (start > N) start = N; 5494 int end = text.partialEndOffset; 5495 if (end > N) end = N; 5496 removeParcelableSpans(content, start, end); 5497 content.replace(start, end, text.text); 5498 } 5499 } 5500 5501 // Now set the selection position... make sure it is in range, to 5502 // avoid crashes. If this is a partial update, it is possible that 5503 // the underlying text may have changed, causing us problems here. 5504 // Also we just don't want to trust clients to do the right thing. 5505 Spannable sp = (Spannable)getText(); 5506 final int N = sp.length(); 5507 int start = text.selectionStart; 5508 if (start < 0) start = 0; 5509 else if (start > N) start = N; 5510 int end = text.selectionEnd; 5511 if (end < 0) end = 0; 5512 else if (end > N) end = N; 5513 Selection.setSelection(sp, start, end); 5514 5515 // Finally, update the selection mode. 5516 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { 5517 MetaKeyKeyListener.startSelecting(this, sp); 5518 } else { 5519 MetaKeyKeyListener.stopSelecting(this, sp); 5520 } 5521 } 5522 5523 /** 5524 * @hide 5525 */ setExtracting(ExtractedTextRequest req)5526 public void setExtracting(ExtractedTextRequest req) { 5527 if (mEditor.mInputMethodState != null) { 5528 mEditor.mInputMethodState.mExtractedTextRequest = req; 5529 } 5530 // This would stop a possible selection mode, but no such mode is started in case 5531 // extracted mode will start. Some text is selected though, and will trigger an action mode 5532 // in the extracted view. 5533 mEditor.hideControllers(); 5534 } 5535 5536 /** 5537 * Called by the framework in response to a text completion from 5538 * the current input method, provided by it calling 5539 * {@link InputConnection#commitCompletion 5540 * InputConnection.commitCompletion()}. The default implementation does 5541 * nothing; text views that are supporting auto-completion should override 5542 * this to do their desired behavior. 5543 * 5544 * @param text The auto complete text the user has selected. 5545 */ onCommitCompletion(CompletionInfo text)5546 public void onCommitCompletion(CompletionInfo text) { 5547 // intentionally empty 5548 } 5549 5550 /** 5551 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 5552 * a dictionnary) from the current input method, provided by it calling 5553 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default 5554 * implementation flashes the background of the corrected word to provide feedback to the user. 5555 * 5556 * @param info The auto correct info about the text that was corrected. 5557 */ onCommitCorrection(CorrectionInfo info)5558 public void onCommitCorrection(CorrectionInfo info) { 5559 if (mEditor != null) mEditor.onCommitCorrection(info); 5560 } 5561 beginBatchEdit()5562 public void beginBatchEdit() { 5563 if (mEditor != null) mEditor.beginBatchEdit(); 5564 } 5565 endBatchEdit()5566 public void endBatchEdit() { 5567 if (mEditor != null) mEditor.endBatchEdit(); 5568 } 5569 5570 /** 5571 * Called by the framework in response to a request to begin a batch 5572 * of edit operations through a call to link {@link #beginBatchEdit()}. 5573 */ onBeginBatchEdit()5574 public void onBeginBatchEdit() { 5575 // intentionally empty 5576 } 5577 5578 /** 5579 * Called by the framework in response to a request to end a batch 5580 * of edit operations through a call to link {@link #endBatchEdit}. 5581 */ onEndBatchEdit()5582 public void onEndBatchEdit() { 5583 // intentionally empty 5584 } 5585 5586 /** 5587 * Called by the framework in response to a private command from the 5588 * current method, provided by it calling 5589 * {@link InputConnection#performPrivateCommand 5590 * InputConnection.performPrivateCommand()}. 5591 * 5592 * @param action The action name of the command. 5593 * @param data Any additional data for the command. This may be null. 5594 * @return Return true if you handled the command, else false. 5595 */ onPrivateIMECommand(String action, Bundle data)5596 public boolean onPrivateIMECommand(String action, Bundle data) { 5597 return false; 5598 } 5599 nullLayouts()5600 private void nullLayouts() { 5601 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 5602 mSavedLayout = (BoringLayout) mLayout; 5603 } 5604 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 5605 mSavedHintLayout = (BoringLayout) mHintLayout; 5606 } 5607 5608 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 5609 5610 mBoring = mHintBoring = null; 5611 5612 // Since it depends on the value of mLayout 5613 if (mEditor != null) mEditor.prepareCursorControllers(); 5614 } 5615 5616 /** 5617 * Make a new Layout based on the already-measured size of the view, 5618 * on the assumption that it was measured correctly at some point. 5619 */ assumeLayout()5620 private void assumeLayout() { 5621 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 5622 5623 if (width < 1) { 5624 width = 0; 5625 } 5626 5627 int physicalWidth = width; 5628 5629 if (mHorizontallyScrolling) { 5630 width = VERY_WIDE; 5631 } 5632 5633 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 5634 physicalWidth, false); 5635 } 5636 5637 /** @hide */ 5638 @Override onResolvedLayoutDirectionReset()5639 public void onResolvedLayoutDirectionReset() { 5640 if (mLayoutAlignment != null) { 5641 int resolvedTextAlignment = getResolvedTextAlignment(); 5642 if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || 5643 resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { 5644 mLayoutAlignment = null; 5645 } 5646 } 5647 } 5648 getLayoutAlignment()5649 private Layout.Alignment getLayoutAlignment() { 5650 if (mLayoutAlignment == null) { 5651 int textAlign = getResolvedTextAlignment(); 5652 switch (textAlign) { 5653 case TEXT_ALIGNMENT_GRAVITY: 5654 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 5655 case Gravity.START: 5656 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; 5657 break; 5658 case Gravity.END: 5659 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; 5660 break; 5661 case Gravity.LEFT: 5662 mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; 5663 break; 5664 case Gravity.RIGHT: 5665 mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; 5666 break; 5667 case Gravity.CENTER_HORIZONTAL: 5668 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; 5669 break; 5670 default: 5671 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; 5672 break; 5673 } 5674 break; 5675 case TEXT_ALIGNMENT_TEXT_START: 5676 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; 5677 break; 5678 case TEXT_ALIGNMENT_TEXT_END: 5679 mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; 5680 break; 5681 case TEXT_ALIGNMENT_CENTER: 5682 mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; 5683 break; 5684 case TEXT_ALIGNMENT_VIEW_START: 5685 mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 5686 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 5687 break; 5688 case TEXT_ALIGNMENT_VIEW_END: 5689 mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 5690 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 5691 break; 5692 case TEXT_ALIGNMENT_INHERIT: 5693 // This should never happen as we have already resolved the text alignment 5694 // but better safe than sorry so we just fall through 5695 default: 5696 mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; 5697 break; 5698 } 5699 } 5700 return mLayoutAlignment; 5701 } 5702 5703 /** 5704 * The width passed in is now the desired layout width, 5705 * not the full view width with padding. 5706 * {@hide} 5707 */ makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)5708 protected void makeNewLayout(int wantWidth, int hintWidth, 5709 BoringLayout.Metrics boring, 5710 BoringLayout.Metrics hintBoring, 5711 int ellipsisWidth, boolean bringIntoView) { 5712 stopMarquee(); 5713 5714 // Update "old" cached values 5715 mOldMaximum = mMaximum; 5716 mOldMaxMode = mMaxMode; 5717 5718 mHighlightPathBogus = true; 5719 5720 if (wantWidth < 0) { 5721 wantWidth = 0; 5722 } 5723 if (hintWidth < 0) { 5724 hintWidth = 0; 5725 } 5726 5727 Layout.Alignment alignment = getLayoutAlignment(); 5728 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 5729 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE && 5730 mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 5731 TruncateAt effectiveEllipsize = mEllipsize; 5732 if (mEllipsize == TruncateAt.MARQUEE && 5733 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 5734 effectiveEllipsize = TruncateAt.END_SMALL; 5735 } 5736 5737 if (mTextDir == null) { 5738 resolveTextDirection(); 5739 } 5740 5741 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 5742 effectiveEllipsize, effectiveEllipsize == mEllipsize); 5743 if (switchEllipsize) { 5744 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? 5745 TruncateAt.END : TruncateAt.MARQUEE; 5746 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 5747 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 5748 } 5749 5750 shouldEllipsize = mEllipsize != null; 5751 mHintLayout = null; 5752 5753 if (mHint != null) { 5754 if (shouldEllipsize) hintWidth = wantWidth; 5755 5756 if (hintBoring == UNKNOWN_BORING) { 5757 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 5758 mHintBoring); 5759 if (hintBoring != null) { 5760 mHintBoring = hintBoring; 5761 } 5762 } 5763 5764 if (hintBoring != null) { 5765 if (hintBoring.width <= hintWidth && 5766 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 5767 if (mSavedHintLayout != null) { 5768 mHintLayout = mSavedHintLayout. 5769 replaceOrMake(mHint, mTextPaint, 5770 hintWidth, alignment, mSpacingMult, mSpacingAdd, 5771 hintBoring, mIncludePad); 5772 } else { 5773 mHintLayout = BoringLayout.make(mHint, mTextPaint, 5774 hintWidth, alignment, mSpacingMult, mSpacingAdd, 5775 hintBoring, mIncludePad); 5776 } 5777 5778 mSavedHintLayout = (BoringLayout) mHintLayout; 5779 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 5780 if (mSavedHintLayout != null) { 5781 mHintLayout = mSavedHintLayout. 5782 replaceOrMake(mHint, mTextPaint, 5783 hintWidth, alignment, mSpacingMult, mSpacingAdd, 5784 hintBoring, mIncludePad, mEllipsize, 5785 ellipsisWidth); 5786 } else { 5787 mHintLayout = BoringLayout.make(mHint, mTextPaint, 5788 hintWidth, alignment, mSpacingMult, mSpacingAdd, 5789 hintBoring, mIncludePad, mEllipsize, 5790 ellipsisWidth); 5791 } 5792 } else if (shouldEllipsize) { 5793 mHintLayout = new StaticLayout(mHint, 5794 0, mHint.length(), 5795 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 5796 mSpacingAdd, mIncludePad, mEllipsize, 5797 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 5798 } else { 5799 mHintLayout = new StaticLayout(mHint, mTextPaint, 5800 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 5801 mIncludePad); 5802 } 5803 } else if (shouldEllipsize) { 5804 mHintLayout = new StaticLayout(mHint, 5805 0, mHint.length(), 5806 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 5807 mSpacingAdd, mIncludePad, mEllipsize, 5808 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 5809 } else { 5810 mHintLayout = new StaticLayout(mHint, mTextPaint, 5811 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 5812 mIncludePad); 5813 } 5814 } 5815 5816 if (bringIntoView) { 5817 registerForPreDraw(); 5818 } 5819 5820 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5821 if (!compressText(ellipsisWidth)) { 5822 final int height = mLayoutParams.height; 5823 // If the size of the view does not depend on the size of the text, try to 5824 // start the marquee immediately 5825 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 5826 startMarquee(); 5827 } else { 5828 // Defer the start of the marquee until we know our width (see setFrame()) 5829 mRestartMarquee = true; 5830 } 5831 } 5832 } 5833 5834 // CursorControllers need a non-null mLayout 5835 if (mEditor != null) mEditor.prepareCursorControllers(); 5836 } 5837 makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)5838 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 5839 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 5840 boolean useSaved) { 5841 Layout result = null; 5842 if (mText instanceof Spannable) { 5843 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 5844 alignment, mTextDir, mSpacingMult, 5845 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null, 5846 ellipsisWidth); 5847 } else { 5848 if (boring == UNKNOWN_BORING) { 5849 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 5850 if (boring != null) { 5851 mBoring = boring; 5852 } 5853 } 5854 5855 if (boring != null) { 5856 if (boring.width <= wantWidth && 5857 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 5858 if (useSaved && mSavedLayout != null) { 5859 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 5860 wantWidth, alignment, mSpacingMult, mSpacingAdd, 5861 boring, mIncludePad); 5862 } else { 5863 result = BoringLayout.make(mTransformed, mTextPaint, 5864 wantWidth, alignment, mSpacingMult, mSpacingAdd, 5865 boring, mIncludePad); 5866 } 5867 5868 if (useSaved) { 5869 mSavedLayout = (BoringLayout) result; 5870 } 5871 } else if (shouldEllipsize && boring.width <= wantWidth) { 5872 if (useSaved && mSavedLayout != null) { 5873 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 5874 wantWidth, alignment, mSpacingMult, mSpacingAdd, 5875 boring, mIncludePad, effectiveEllipsize, 5876 ellipsisWidth); 5877 } else { 5878 result = BoringLayout.make(mTransformed, mTextPaint, 5879 wantWidth, alignment, mSpacingMult, mSpacingAdd, 5880 boring, mIncludePad, effectiveEllipsize, 5881 ellipsisWidth); 5882 } 5883 } else if (shouldEllipsize) { 5884 result = new StaticLayout(mTransformed, 5885 0, mTransformed.length(), 5886 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 5887 mSpacingAdd, mIncludePad, effectiveEllipsize, 5888 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 5889 } else { 5890 result = new StaticLayout(mTransformed, mTextPaint, 5891 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 5892 mIncludePad); 5893 } 5894 } else if (shouldEllipsize) { 5895 result = new StaticLayout(mTransformed, 5896 0, mTransformed.length(), 5897 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 5898 mSpacingAdd, mIncludePad, effectiveEllipsize, 5899 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 5900 } else { 5901 result = new StaticLayout(mTransformed, mTextPaint, 5902 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 5903 mIncludePad); 5904 } 5905 } 5906 return result; 5907 } 5908 compressText(float width)5909 private boolean compressText(float width) { 5910 if (isHardwareAccelerated()) return false; 5911 5912 // Only compress the text if it hasn't been compressed by the previous pass 5913 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && 5914 mTextPaint.getTextScaleX() == 1.0f) { 5915 final float textWidth = mLayout.getLineWidth(0); 5916 final float overflow = (textWidth + 1.0f - width) / width; 5917 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 5918 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 5919 post(new Runnable() { 5920 public void run() { 5921 requestLayout(); 5922 } 5923 }); 5924 return true; 5925 } 5926 } 5927 5928 return false; 5929 } 5930 desired(Layout layout)5931 private static int desired(Layout layout) { 5932 int n = layout.getLineCount(); 5933 CharSequence text = layout.getText(); 5934 float max = 0; 5935 5936 // if any line was wrapped, we can't use it. 5937 // but it's ok for the last line not to have a newline 5938 5939 for (int i = 0; i < n - 1; i++) { 5940 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 5941 return -1; 5942 } 5943 5944 for (int i = 0; i < n; i++) { 5945 max = Math.max(max, layout.getLineWidth(i)); 5946 } 5947 5948 return (int) FloatMath.ceil(max); 5949 } 5950 5951 /** 5952 * Set whether the TextView includes extra top and bottom padding to make 5953 * room for accents that go above the normal ascent and descent. 5954 * The default is true. 5955 * 5956 * @see #getIncludeFontPadding() 5957 * 5958 * @attr ref android.R.styleable#TextView_includeFontPadding 5959 */ setIncludeFontPadding(boolean includepad)5960 public void setIncludeFontPadding(boolean includepad) { 5961 if (mIncludePad != includepad) { 5962 mIncludePad = includepad; 5963 5964 if (mLayout != null) { 5965 nullLayouts(); 5966 requestLayout(); 5967 invalidate(); 5968 } 5969 } 5970 } 5971 5972 /** 5973 * Gets whether the TextView includes extra top and bottom padding to make 5974 * room for accents that go above the normal ascent and descent. 5975 * 5976 * @see #setIncludeFontPadding(boolean) 5977 * 5978 * @attr ref android.R.styleable#TextView_includeFontPadding 5979 */ getIncludeFontPadding()5980 public boolean getIncludeFontPadding() { 5981 return mIncludePad; 5982 } 5983 5984 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 5985 5986 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)5987 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 5988 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 5989 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 5990 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 5991 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 5992 5993 int width; 5994 int height; 5995 5996 BoringLayout.Metrics boring = UNKNOWN_BORING; 5997 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 5998 5999 if (mTextDir == null) { 6000 resolveTextDirection(); 6001 } 6002 6003 int des = -1; 6004 boolean fromexisting = false; 6005 6006 if (widthMode == MeasureSpec.EXACTLY) { 6007 // Parent has told us how big to be. So be it. 6008 width = widthSize; 6009 } else { 6010 if (mLayout != null && mEllipsize == null) { 6011 des = desired(mLayout); 6012 } 6013 6014 if (des < 0) { 6015 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6016 if (boring != null) { 6017 mBoring = boring; 6018 } 6019 } else { 6020 fromexisting = true; 6021 } 6022 6023 if (boring == null || boring == UNKNOWN_BORING) { 6024 if (des < 0) { 6025 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); 6026 } 6027 width = des; 6028 } else { 6029 width = boring.width; 6030 } 6031 6032 final Drawables dr = mDrawables; 6033 if (dr != null) { 6034 width = Math.max(width, dr.mDrawableWidthTop); 6035 width = Math.max(width, dr.mDrawableWidthBottom); 6036 } 6037 6038 if (mHint != null) { 6039 int hintDes = -1; 6040 int hintWidth; 6041 6042 if (mHintLayout != null && mEllipsize == null) { 6043 hintDes = desired(mHintLayout); 6044 } 6045 6046 if (hintDes < 0) { 6047 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 6048 if (hintBoring != null) { 6049 mHintBoring = hintBoring; 6050 } 6051 } 6052 6053 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 6054 if (hintDes < 0) { 6055 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint)); 6056 } 6057 hintWidth = hintDes; 6058 } else { 6059 hintWidth = hintBoring.width; 6060 } 6061 6062 if (hintWidth > width) { 6063 width = hintWidth; 6064 } 6065 } 6066 6067 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 6068 6069 if (mMaxWidthMode == EMS) { 6070 width = Math.min(width, mMaxWidth * getLineHeight()); 6071 } else { 6072 width = Math.min(width, mMaxWidth); 6073 } 6074 6075 if (mMinWidthMode == EMS) { 6076 width = Math.max(width, mMinWidth * getLineHeight()); 6077 } else { 6078 width = Math.max(width, mMinWidth); 6079 } 6080 6081 // Check against our minimum width 6082 width = Math.max(width, getSuggestedMinimumWidth()); 6083 6084 if (widthMode == MeasureSpec.AT_MOST) { 6085 width = Math.min(widthSize, width); 6086 } 6087 } 6088 6089 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6090 int unpaddedWidth = want; 6091 6092 if (mHorizontallyScrolling) want = VERY_WIDE; 6093 6094 int hintWant = want; 6095 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 6096 6097 if (mLayout == null) { 6098 makeNewLayout(want, hintWant, boring, hintBoring, 6099 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6100 } else { 6101 final boolean layoutChanged = (mLayout.getWidth() != want) || 6102 (hintWidth != hintWant) || 6103 (mLayout.getEllipsizedWidth() != 6104 width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 6105 6106 final boolean widthChanged = (mHint == null) && 6107 (mEllipsize == null) && 6108 (want > mLayout.getWidth()) && 6109 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want)); 6110 6111 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 6112 6113 if (layoutChanged || maximumChanged) { 6114 if (!maximumChanged && widthChanged) { 6115 mLayout.increaseWidthTo(want); 6116 } else { 6117 makeNewLayout(want, hintWant, boring, hintBoring, 6118 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6119 } 6120 } else { 6121 // Nothing has changed 6122 } 6123 } 6124 6125 if (heightMode == MeasureSpec.EXACTLY) { 6126 // Parent has told us how big to be. So be it. 6127 height = heightSize; 6128 mDesiredHeightAtMeasure = -1; 6129 } else { 6130 int desired = getDesiredHeight(); 6131 6132 height = desired; 6133 mDesiredHeightAtMeasure = desired; 6134 6135 if (heightMode == MeasureSpec.AT_MOST) { 6136 height = Math.min(desired, heightSize); 6137 } 6138 } 6139 6140 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 6141 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 6142 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 6143 } 6144 6145 /* 6146 * We didn't let makeNewLayout() register to bring the cursor into view, 6147 * so do it here if there is any possibility that it is needed. 6148 */ 6149 if (mMovement != null || 6150 mLayout.getWidth() > unpaddedWidth || 6151 mLayout.getHeight() > unpaddedHeight) { 6152 registerForPreDraw(); 6153 } else { 6154 scrollTo(0, 0); 6155 } 6156 6157 setMeasuredDimension(width, height); 6158 } 6159 getDesiredHeight()6160 private int getDesiredHeight() { 6161 return Math.max( 6162 getDesiredHeight(mLayout, true), 6163 getDesiredHeight(mHintLayout, mEllipsize != null)); 6164 } 6165 getDesiredHeight(Layout layout, boolean cap)6166 private int getDesiredHeight(Layout layout, boolean cap) { 6167 if (layout == null) { 6168 return 0; 6169 } 6170 6171 int linecount = layout.getLineCount(); 6172 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 6173 int desired = layout.getLineTop(linecount); 6174 6175 final Drawables dr = mDrawables; 6176 if (dr != null) { 6177 desired = Math.max(desired, dr.mDrawableHeightLeft); 6178 desired = Math.max(desired, dr.mDrawableHeightRight); 6179 } 6180 6181 desired += pad; 6182 6183 if (mMaxMode == LINES) { 6184 /* 6185 * Don't cap the hint to a certain number of lines. 6186 * (Do cap it, though, if we have a maximum pixel height.) 6187 */ 6188 if (cap) { 6189 if (linecount > mMaximum) { 6190 desired = layout.getLineTop(mMaximum); 6191 6192 if (dr != null) { 6193 desired = Math.max(desired, dr.mDrawableHeightLeft); 6194 desired = Math.max(desired, dr.mDrawableHeightRight); 6195 } 6196 6197 desired += pad; 6198 linecount = mMaximum; 6199 } 6200 } 6201 } else { 6202 desired = Math.min(desired, mMaximum); 6203 } 6204 6205 if (mMinMode == LINES) { 6206 if (linecount < mMinimum) { 6207 desired += getLineHeight() * (mMinimum - linecount); 6208 } 6209 } else { 6210 desired = Math.max(desired, mMinimum); 6211 } 6212 6213 // Check against our minimum height 6214 desired = Math.max(desired, getSuggestedMinimumHeight()); 6215 6216 return desired; 6217 } 6218 6219 /** 6220 * Check whether a change to the existing text layout requires a 6221 * new view layout. 6222 */ checkForResize()6223 private void checkForResize() { 6224 boolean sizeChanged = false; 6225 6226 if (mLayout != null) { 6227 // Check if our width changed 6228 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 6229 sizeChanged = true; 6230 invalidate(); 6231 } 6232 6233 // Check if our height changed 6234 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 6235 int desiredHeight = getDesiredHeight(); 6236 6237 if (desiredHeight != this.getHeight()) { 6238 sizeChanged = true; 6239 } 6240 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 6241 if (mDesiredHeightAtMeasure >= 0) { 6242 int desiredHeight = getDesiredHeight(); 6243 6244 if (desiredHeight != mDesiredHeightAtMeasure) { 6245 sizeChanged = true; 6246 } 6247 } 6248 } 6249 } 6250 6251 if (sizeChanged) { 6252 requestLayout(); 6253 // caller will have already invalidated 6254 } 6255 } 6256 6257 /** 6258 * Check whether entirely new text requires a new view layout 6259 * or merely a new text layout. 6260 */ checkForRelayout()6261 private void checkForRelayout() { 6262 // If we have a fixed width, we can just swap in a new text layout 6263 // if the text height stays the same or if the view height is fixed. 6264 6265 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 6266 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 6267 (mHint == null || mHintLayout != null) && 6268 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 6269 // Static width, so try making a new text layout. 6270 6271 int oldht = mLayout.getHeight(); 6272 int want = mLayout.getWidth(); 6273 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 6274 6275 /* 6276 * No need to bring the text into view, since the size is not 6277 * changing (unless we do the requestLayout(), in which case it 6278 * will happen at measure). 6279 */ 6280 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 6281 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 6282 false); 6283 6284 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 6285 // In a fixed-height view, so use our new text layout. 6286 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 6287 mLayoutParams.height != LayoutParams.MATCH_PARENT) { 6288 invalidate(); 6289 return; 6290 } 6291 6292 // Dynamic height, but height has stayed the same, 6293 // so use our new text layout. 6294 if (mLayout.getHeight() == oldht && 6295 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 6296 invalidate(); 6297 return; 6298 } 6299 } 6300 6301 // We lose: the height has changed and we have a dynamic height. 6302 // Request a new view layout using our new text layout. 6303 requestLayout(); 6304 invalidate(); 6305 } else { 6306 // Dynamic width, so we have no choice but to request a new 6307 // view layout with a new text layout. 6308 nullLayouts(); 6309 requestLayout(); 6310 invalidate(); 6311 } 6312 } 6313 6314 @Override onLayout(boolean changed, int left, int top, int right, int bottom)6315 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 6316 super.onLayout(changed, left, top, right, bottom); 6317 if (changed && mEditor != null) mEditor.invalidateTextDisplayList(); 6318 } 6319 isShowingHint()6320 private boolean isShowingHint() { 6321 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 6322 } 6323 6324 /** 6325 * Returns true if anything changed. 6326 */ bringTextIntoView()6327 private boolean bringTextIntoView() { 6328 Layout layout = isShowingHint() ? mHintLayout : mLayout; 6329 int line = 0; 6330 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6331 line = layout.getLineCount() - 1; 6332 } 6333 6334 Layout.Alignment a = layout.getParagraphAlignment(line); 6335 int dir = layout.getParagraphDirection(line); 6336 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6337 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 6338 int ht = layout.getHeight(); 6339 6340 int scrollx, scrolly; 6341 6342 // Convert to left, center, or right alignment. 6343 if (a == Layout.Alignment.ALIGN_NORMAL) { 6344 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT : 6345 Layout.Alignment.ALIGN_RIGHT; 6346 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){ 6347 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT : 6348 Layout.Alignment.ALIGN_LEFT; 6349 } 6350 6351 if (a == Layout.Alignment.ALIGN_CENTER) { 6352 /* 6353 * Keep centered if possible, or, if it is too wide to fit, 6354 * keep leading edge in view. 6355 */ 6356 6357 int left = (int) FloatMath.floor(layout.getLineLeft(line)); 6358 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 6359 6360 if (right - left < hspace) { 6361 scrollx = (right + left) / 2 - hspace / 2; 6362 } else { 6363 if (dir < 0) { 6364 scrollx = right - hspace; 6365 } else { 6366 scrollx = left; 6367 } 6368 } 6369 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 6370 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 6371 scrollx = right - hspace; 6372 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 6373 scrollx = (int) FloatMath.floor(layout.getLineLeft(line)); 6374 } 6375 6376 if (ht < vspace) { 6377 scrolly = 0; 6378 } else { 6379 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6380 scrolly = ht - vspace; 6381 } else { 6382 scrolly = 0; 6383 } 6384 } 6385 6386 if (scrollx != mScrollX || scrolly != mScrollY) { 6387 scrollTo(scrollx, scrolly); 6388 return true; 6389 } else { 6390 return false; 6391 } 6392 } 6393 6394 /** 6395 * Move the point, specified by the offset, into the view if it is needed. 6396 * This has to be called after layout. Returns true if anything changed. 6397 */ bringPointIntoView(int offset)6398 public boolean bringPointIntoView(int offset) { 6399 boolean changed = false; 6400 6401 Layout layout = isShowingHint() ? mHintLayout: mLayout; 6402 6403 if (layout == null) return changed; 6404 6405 int line = layout.getLineForOffset(offset); 6406 6407 // FIXME: Is it okay to truncate this, or should we round? 6408 final int x = (int)layout.getPrimaryHorizontal(offset); 6409 final int top = layout.getLineTop(line); 6410 final int bottom = layout.getLineTop(line + 1); 6411 6412 int left = (int) FloatMath.floor(layout.getLineLeft(line)); 6413 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 6414 int ht = layout.getHeight(); 6415 6416 int grav; 6417 6418 switch (layout.getParagraphAlignment(line)) { 6419 case ALIGN_LEFT: 6420 grav = 1; 6421 break; 6422 case ALIGN_RIGHT: 6423 grav = -1; 6424 break; 6425 case ALIGN_NORMAL: 6426 grav = layout.getParagraphDirection(line); 6427 break; 6428 case ALIGN_OPPOSITE: 6429 grav = -layout.getParagraphDirection(line); 6430 break; 6431 case ALIGN_CENTER: 6432 default: 6433 grav = 0; 6434 break; 6435 } 6436 6437 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6438 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 6439 6440 int hslack = (bottom - top) / 2; 6441 int vslack = hslack; 6442 6443 if (vslack > vspace / 4) 6444 vslack = vspace / 4; 6445 if (hslack > hspace / 4) 6446 hslack = hspace / 4; 6447 6448 int hs = mScrollX; 6449 int vs = mScrollY; 6450 6451 if (top - vs < vslack) 6452 vs = top - vslack; 6453 if (bottom - vs > vspace - vslack) 6454 vs = bottom - (vspace - vslack); 6455 if (ht - vs < vspace) 6456 vs = ht - vspace; 6457 if (0 - vs > 0) 6458 vs = 0; 6459 6460 if (grav != 0) { 6461 if (x - hs < hslack) { 6462 hs = x - hslack; 6463 } 6464 if (x - hs > hspace - hslack) { 6465 hs = x - (hspace - hslack); 6466 } 6467 } 6468 6469 if (grav < 0) { 6470 if (left - hs > 0) 6471 hs = left; 6472 if (right - hs < hspace) 6473 hs = right - hspace; 6474 } else if (grav > 0) { 6475 if (right - hs < hspace) 6476 hs = right - hspace; 6477 if (left - hs > 0) 6478 hs = left; 6479 } else /* grav == 0 */ { 6480 if (right - left <= hspace) { 6481 /* 6482 * If the entire text fits, center it exactly. 6483 */ 6484 hs = left - (hspace - (right - left)) / 2; 6485 } else if (x > right - hslack) { 6486 /* 6487 * If we are near the right edge, keep the right edge 6488 * at the edge of the view. 6489 */ 6490 hs = right - hspace; 6491 } else if (x < left + hslack) { 6492 /* 6493 * If we are near the left edge, keep the left edge 6494 * at the edge of the view. 6495 */ 6496 hs = left; 6497 } else if (left > hs) { 6498 /* 6499 * Is there whitespace visible at the left? Fix it if so. 6500 */ 6501 hs = left; 6502 } else if (right < hs + hspace) { 6503 /* 6504 * Is there whitespace visible at the right? Fix it if so. 6505 */ 6506 hs = right - hspace; 6507 } else { 6508 /* 6509 * Otherwise, float as needed. 6510 */ 6511 if (x - hs < hslack) { 6512 hs = x - hslack; 6513 } 6514 if (x - hs > hspace - hslack) { 6515 hs = x - (hspace - hslack); 6516 } 6517 } 6518 } 6519 6520 if (hs != mScrollX || vs != mScrollY) { 6521 if (mScroller == null) { 6522 scrollTo(hs, vs); 6523 } else { 6524 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 6525 int dx = hs - mScrollX; 6526 int dy = vs - mScrollY; 6527 6528 if (duration > ANIMATED_SCROLL_GAP) { 6529 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 6530 awakenScrollBars(mScroller.getDuration()); 6531 invalidate(); 6532 } else { 6533 if (!mScroller.isFinished()) { 6534 mScroller.abortAnimation(); 6535 } 6536 6537 scrollBy(dx, dy); 6538 } 6539 6540 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 6541 } 6542 6543 changed = true; 6544 } 6545 6546 if (isFocused()) { 6547 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 6548 // requestRectangleOnScreen() is in terms of content coordinates. 6549 6550 // The offsets here are to ensure the rectangle we are using is 6551 // within our view bounds, in case the cursor is on the far left 6552 // or right. If it isn't withing the bounds, then this request 6553 // will be ignored. 6554 if (mTempRect == null) mTempRect = new Rect(); 6555 mTempRect.set(x - 2, top, x + 2, bottom); 6556 getInterestingRect(mTempRect, line); 6557 mTempRect.offset(mScrollX, mScrollY); 6558 6559 if (requestRectangleOnScreen(mTempRect)) { 6560 changed = true; 6561 } 6562 } 6563 6564 return changed; 6565 } 6566 6567 /** 6568 * Move the cursor, if needed, so that it is at an offset that is visible 6569 * to the user. This will not move the cursor if it represents more than 6570 * one character (a selection range). This will only work if the 6571 * TextView contains spannable text; otherwise it will do nothing. 6572 * 6573 * @return True if the cursor was actually moved, false otherwise. 6574 */ moveCursorToVisibleOffset()6575 public boolean moveCursorToVisibleOffset() { 6576 if (!(mText instanceof Spannable)) { 6577 return false; 6578 } 6579 int start = getSelectionStart(); 6580 int end = getSelectionEnd(); 6581 if (start != end) { 6582 return false; 6583 } 6584 6585 // First: make sure the line is visible on screen: 6586 6587 int line = mLayout.getLineForOffset(start); 6588 6589 final int top = mLayout.getLineTop(line); 6590 final int bottom = mLayout.getLineTop(line + 1); 6591 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 6592 int vslack = (bottom - top) / 2; 6593 if (vslack > vspace / 4) 6594 vslack = vspace / 4; 6595 final int vs = mScrollY; 6596 6597 if (top < (vs+vslack)) { 6598 line = mLayout.getLineForVertical(vs+vslack+(bottom-top)); 6599 } else if (bottom > (vspace+vs-vslack)) { 6600 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top)); 6601 } 6602 6603 // Next: make sure the character is visible on screen: 6604 6605 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6606 final int hs = mScrollX; 6607 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 6608 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs); 6609 6610 // line might contain bidirectional text 6611 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 6612 final int highChar = leftChar > rightChar ? leftChar : rightChar; 6613 6614 int newStart = start; 6615 if (newStart < lowChar) { 6616 newStart = lowChar; 6617 } else if (newStart > highChar) { 6618 newStart = highChar; 6619 } 6620 6621 if (newStart != start) { 6622 Selection.setSelection((Spannable)mText, newStart); 6623 return true; 6624 } 6625 6626 return false; 6627 } 6628 6629 @Override computeScroll()6630 public void computeScroll() { 6631 if (mScroller != null) { 6632 if (mScroller.computeScrollOffset()) { 6633 mScrollX = mScroller.getCurrX(); 6634 mScrollY = mScroller.getCurrY(); 6635 invalidateParentCaches(); 6636 postInvalidate(); // So we draw again 6637 } 6638 } 6639 } 6640 getInterestingRect(Rect r, int line)6641 private void getInterestingRect(Rect r, int line) { 6642 convertFromViewportToContentCoordinates(r); 6643 6644 // Rectangle can can be expanded on first and last line to take 6645 // padding into account. 6646 // TODO Take left/right padding into account too? 6647 if (line == 0) r.top -= getExtendedPaddingTop(); 6648 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 6649 } 6650 convertFromViewportToContentCoordinates(Rect r)6651 private void convertFromViewportToContentCoordinates(Rect r) { 6652 final int horizontalOffset = viewportToContentHorizontalOffset(); 6653 r.left += horizontalOffset; 6654 r.right += horizontalOffset; 6655 6656 final int verticalOffset = viewportToContentVerticalOffset(); 6657 r.top += verticalOffset; 6658 r.bottom += verticalOffset; 6659 } 6660 viewportToContentHorizontalOffset()6661 int viewportToContentHorizontalOffset() { 6662 return getCompoundPaddingLeft() - mScrollX; 6663 } 6664 viewportToContentVerticalOffset()6665 int viewportToContentVerticalOffset() { 6666 int offset = getExtendedPaddingTop() - mScrollY; 6667 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6668 offset += getVerticalOffset(false); 6669 } 6670 return offset; 6671 } 6672 6673 @Override debug(int depth)6674 public void debug(int depth) { 6675 super.debug(depth); 6676 6677 String output = debugIndent(depth); 6678 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 6679 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 6680 + "} "; 6681 6682 if (mText != null) { 6683 6684 output += "mText=\"" + mText + "\" "; 6685 if (mLayout != null) { 6686 output += "mLayout width=" + mLayout.getWidth() 6687 + " height=" + mLayout.getHeight(); 6688 } 6689 } else { 6690 output += "mText=NULL"; 6691 } 6692 Log.d(VIEW_LOG_TAG, output); 6693 } 6694 6695 /** 6696 * Convenience for {@link Selection#getSelectionStart}. 6697 */ 6698 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()6699 public int getSelectionStart() { 6700 return Selection.getSelectionStart(getText()); 6701 } 6702 6703 /** 6704 * Convenience for {@link Selection#getSelectionEnd}. 6705 */ 6706 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()6707 public int getSelectionEnd() { 6708 return Selection.getSelectionEnd(getText()); 6709 } 6710 6711 /** 6712 * Return true iff there is a selection inside this text view. 6713 */ hasSelection()6714 public boolean hasSelection() { 6715 final int selectionStart = getSelectionStart(); 6716 final int selectionEnd = getSelectionEnd(); 6717 6718 return selectionStart >= 0 && selectionStart != selectionEnd; 6719 } 6720 6721 /** 6722 * Sets the properties of this field (lines, horizontally scrolling, 6723 * transformation method) to be for a single-line input. 6724 * 6725 * @attr ref android.R.styleable#TextView_singleLine 6726 */ setSingleLine()6727 public void setSingleLine() { 6728 setSingleLine(true); 6729 } 6730 6731 /** 6732 * Sets the properties of this field to transform input to ALL CAPS 6733 * display. This may use a "small caps" formatting if available. 6734 * This setting will be ignored if this field is editable or selectable. 6735 * 6736 * This call replaces the current transformation method. Disabling this 6737 * will not necessarily restore the previous behavior from before this 6738 * was enabled. 6739 * 6740 * @see #setTransformationMethod(TransformationMethod) 6741 * @attr ref android.R.styleable#TextView_textAllCaps 6742 */ setAllCaps(boolean allCaps)6743 public void setAllCaps(boolean allCaps) { 6744 if (allCaps) { 6745 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 6746 } else { 6747 setTransformationMethod(null); 6748 } 6749 } 6750 6751 /** 6752 * If true, sets the properties of this field (number of lines, horizontally scrolling, 6753 * transformation method) to be for a single-line input; if false, restores these to the default 6754 * conditions. 6755 * 6756 * Note that the default conditions are not necessarily those that were in effect prior this 6757 * method, and you may want to reset these properties to your custom values. 6758 * 6759 * @attr ref android.R.styleable#TextView_singleLine 6760 */ 6761 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)6762 public void setSingleLine(boolean singleLine) { 6763 // Could be used, but may break backward compatibility. 6764 // if (mSingleLine == singleLine) return; 6765 setInputTypeSingleLine(singleLine); 6766 applySingleLine(singleLine, true, true); 6767 } 6768 6769 /** 6770 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 6771 * @param singleLine 6772 */ setInputTypeSingleLine(boolean singleLine)6773 private void setInputTypeSingleLine(boolean singleLine) { 6774 if (mEditor != null && 6775 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 6776 if (singleLine) { 6777 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 6778 } else { 6779 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 6780 } 6781 } 6782 } 6783 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)6784 private void applySingleLine(boolean singleLine, boolean applyTransformation, 6785 boolean changeMaxLines) { 6786 mSingleLine = singleLine; 6787 if (singleLine) { 6788 setLines(1); 6789 setHorizontallyScrolling(true); 6790 if (applyTransformation) { 6791 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 6792 } 6793 } else { 6794 if (changeMaxLines) { 6795 setMaxLines(Integer.MAX_VALUE); 6796 } 6797 setHorizontallyScrolling(false); 6798 if (applyTransformation) { 6799 setTransformationMethod(null); 6800 } 6801 } 6802 } 6803 6804 /** 6805 * Causes words in the text that are longer than the view is wide 6806 * to be ellipsized instead of broken in the middle. You may also 6807 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 6808 * to constrain the text to a single line. Use <code>null</code> 6809 * to turn off ellipsizing. 6810 * 6811 * If {@link #setMaxLines} has been used to set two or more lines, 6812 * {@link android.text.TextUtils.TruncateAt#END} and 6813 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported 6814 * (other ellipsizing types will not do anything). 6815 * 6816 * @attr ref android.R.styleable#TextView_ellipsize 6817 */ setEllipsize(TextUtils.TruncateAt where)6818 public void setEllipsize(TextUtils.TruncateAt where) { 6819 // TruncateAt is an enum. != comparison is ok between these singleton objects. 6820 if (mEllipsize != where) { 6821 mEllipsize = where; 6822 6823 if (mLayout != null) { 6824 nullLayouts(); 6825 requestLayout(); 6826 invalidate(); 6827 } 6828 } 6829 } 6830 6831 /** 6832 * Sets how many times to repeat the marquee animation. Only applied if the 6833 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 6834 * 6835 * @see #getMarqueeRepeatLimit() 6836 * 6837 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 6838 */ setMarqueeRepeatLimit(int marqueeLimit)6839 public void setMarqueeRepeatLimit(int marqueeLimit) { 6840 mMarqueeRepeatLimit = marqueeLimit; 6841 } 6842 6843 /** 6844 * Gets the number of times the marquee animation is repeated. Only meaningful if the 6845 * TextView has marquee enabled. 6846 * 6847 * @return the number of times the marquee animation is repeated. -1 if the animation 6848 * repeats indefinitely 6849 * 6850 * @see #setMarqueeRepeatLimit(int) 6851 * 6852 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 6853 */ getMarqueeRepeatLimit()6854 public int getMarqueeRepeatLimit() { 6855 return mMarqueeRepeatLimit; 6856 } 6857 6858 /** 6859 * Returns where, if anywhere, words that are longer than the view 6860 * is wide should be ellipsized. 6861 */ 6862 @ViewDebug.ExportedProperty getEllipsize()6863 public TextUtils.TruncateAt getEllipsize() { 6864 return mEllipsize; 6865 } 6866 6867 /** 6868 * Set the TextView so that when it takes focus, all the text is 6869 * selected. 6870 * 6871 * @attr ref android.R.styleable#TextView_selectAllOnFocus 6872 */ 6873 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)6874 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 6875 createEditorIfNeeded(); 6876 mEditor.mSelectAllOnFocus = selectAllOnFocus; 6877 6878 if (selectAllOnFocus && !(mText instanceof Spannable)) { 6879 setText(mText, BufferType.SPANNABLE); 6880 } 6881 } 6882 6883 /** 6884 * Set whether the cursor is visible. The default is true. Note that this property only 6885 * makes sense for editable TextView. 6886 * 6887 * @see #isCursorVisible() 6888 * 6889 * @attr ref android.R.styleable#TextView_cursorVisible 6890 */ 6891 @android.view.RemotableViewMethod setCursorVisible(boolean visible)6892 public void setCursorVisible(boolean visible) { 6893 if (visible && mEditor == null) return; // visible is the default value with no edit data 6894 createEditorIfNeeded(); 6895 if (mEditor.mCursorVisible != visible) { 6896 mEditor.mCursorVisible = visible; 6897 invalidate(); 6898 6899 mEditor.makeBlink(); 6900 6901 // InsertionPointCursorController depends on mCursorVisible 6902 mEditor.prepareCursorControllers(); 6903 } 6904 } 6905 6906 /** 6907 * @return whether or not the cursor is visible (assuming this TextView is editable) 6908 * 6909 * @see #setCursorVisible(boolean) 6910 * 6911 * @attr ref android.R.styleable#TextView_cursorVisible 6912 */ isCursorVisible()6913 public boolean isCursorVisible() { 6914 // true is the default value 6915 return mEditor == null ? true : mEditor.mCursorVisible; 6916 } 6917 canMarquee()6918 private boolean canMarquee() { 6919 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 6920 return width > 0 && (mLayout.getLineWidth(0) > width || 6921 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null && 6922 mSavedMarqueeModeLayout.getLineWidth(0) > width)); 6923 } 6924 startMarquee()6925 private void startMarquee() { 6926 // Do not ellipsize EditText 6927 if (getKeyListener() != null) return; 6928 6929 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 6930 return; 6931 } 6932 6933 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 6934 getLineCount() == 1 && canMarquee()) { 6935 6936 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 6937 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 6938 final Layout tmp = mLayout; 6939 mLayout = mSavedMarqueeModeLayout; 6940 mSavedMarqueeModeLayout = tmp; 6941 setHorizontalFadingEdgeEnabled(true); 6942 requestLayout(); 6943 invalidate(); 6944 } 6945 6946 if (mMarquee == null) mMarquee = new Marquee(this); 6947 mMarquee.start(mMarqueeRepeatLimit); 6948 } 6949 } 6950 stopMarquee()6951 private void stopMarquee() { 6952 if (mMarquee != null && !mMarquee.isStopped()) { 6953 mMarquee.stop(); 6954 } 6955 6956 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 6957 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6958 final Layout tmp = mSavedMarqueeModeLayout; 6959 mSavedMarqueeModeLayout = mLayout; 6960 mLayout = tmp; 6961 setHorizontalFadingEdgeEnabled(false); 6962 requestLayout(); 6963 invalidate(); 6964 } 6965 } 6966 startStopMarquee(boolean start)6967 private void startStopMarquee(boolean start) { 6968 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6969 if (start) { 6970 startMarquee(); 6971 } else { 6972 stopMarquee(); 6973 } 6974 } 6975 } 6976 6977 /** 6978 * This method is called when the text is changed, in case any subclasses 6979 * would like to know. 6980 * 6981 * Within <code>text</code>, the <code>lengthAfter</code> characters 6982 * beginning at <code>start</code> have just replaced old text that had 6983 * length <code>lengthBefore</code>. It is an error to attempt to make 6984 * changes to <code>text</code> from this callback. 6985 * 6986 * @param text The text the TextView is displaying 6987 * @param start The offset of the start of the range of the text that was 6988 * modified 6989 * @param lengthBefore The length of the former text that has been replaced 6990 * @param lengthAfter The length of the replacement modified text 6991 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)6992 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 6993 // intentionally empty, template pattern method can be overridden by subclasses 6994 } 6995 6996 /** 6997 * This method is called when the selection has changed, in case any 6998 * subclasses would like to know. 6999 * 7000 * @param selStart The new selection start location. 7001 * @param selEnd The new selection end location. 7002 */ onSelectionChanged(int selStart, int selEnd)7003 protected void onSelectionChanged(int selStart, int selEnd) { 7004 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 7005 } 7006 7007 /** 7008 * Adds a TextWatcher to the list of those whose methods are called 7009 * whenever this TextView's text changes. 7010 * <p> 7011 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 7012 * not called after {@link #setText} calls. Now, doing {@link #setText} 7013 * if there are any text changed listeners forces the buffer type to 7014 * Editable if it would not otherwise be and does call this method. 7015 */ addTextChangedListener(TextWatcher watcher)7016 public void addTextChangedListener(TextWatcher watcher) { 7017 if (mListeners == null) { 7018 mListeners = new ArrayList<TextWatcher>(); 7019 } 7020 7021 mListeners.add(watcher); 7022 } 7023 7024 /** 7025 * Removes the specified TextWatcher from the list of those whose 7026 * methods are called 7027 * whenever this TextView's text changes. 7028 */ removeTextChangedListener(TextWatcher watcher)7029 public void removeTextChangedListener(TextWatcher watcher) { 7030 if (mListeners != null) { 7031 int i = mListeners.indexOf(watcher); 7032 7033 if (i >= 0) { 7034 mListeners.remove(i); 7035 } 7036 } 7037 } 7038 sendBeforeTextChanged(CharSequence text, int start, int before, int after)7039 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 7040 if (mListeners != null) { 7041 final ArrayList<TextWatcher> list = mListeners; 7042 final int count = list.size(); 7043 for (int i = 0; i < count; i++) { 7044 list.get(i).beforeTextChanged(text, start, before, after); 7045 } 7046 } 7047 7048 // The spans that are inside or intersect the modified region no longer make sense 7049 removeIntersectingSpans(start, start + before, SpellCheckSpan.class); 7050 removeIntersectingSpans(start, start + before, SuggestionSpan.class); 7051 } 7052 7053 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingSpans(int start, int end, Class<T> type)7054 private <T> void removeIntersectingSpans(int start, int end, Class<T> type) { 7055 if (!(mText instanceof Editable)) return; 7056 Editable text = (Editable) mText; 7057 7058 T[] spans = text.getSpans(start, end, type); 7059 final int length = spans.length; 7060 for (int i = 0; i < length; i++) { 7061 final int s = text.getSpanStart(spans[i]); 7062 final int e = text.getSpanEnd(spans[i]); 7063 // Spans that are adjacent to the edited region will be handled in 7064 // updateSpellCheckSpans. Result depends on what will be added (space or text) 7065 if (e == start || s == end) break; 7066 text.removeSpan(spans[i]); 7067 } 7068 } 7069 7070 /** 7071 * Not private so it can be called from an inner class without going 7072 * through a thunk. 7073 */ sendOnTextChanged(CharSequence text, int start, int before, int after)7074 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 7075 if (mListeners != null) { 7076 final ArrayList<TextWatcher> list = mListeners; 7077 final int count = list.size(); 7078 for (int i = 0; i < count; i++) { 7079 list.get(i).onTextChanged(text, start, before, after); 7080 } 7081 } 7082 7083 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 7084 } 7085 7086 /** 7087 * Not private so it can be called from an inner class without going 7088 * through a thunk. 7089 */ sendAfterTextChanged(Editable text)7090 void sendAfterTextChanged(Editable text) { 7091 if (mListeners != null) { 7092 final ArrayList<TextWatcher> list = mListeners; 7093 final int count = list.size(); 7094 for (int i = 0; i < count; i++) { 7095 list.get(i).afterTextChanged(text); 7096 } 7097 } 7098 } 7099 updateAfterEdit()7100 void updateAfterEdit() { 7101 invalidate(); 7102 int curs = getSelectionStart(); 7103 7104 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7105 registerForPreDraw(); 7106 } 7107 7108 if (curs >= 0) { 7109 mHighlightPathBogus = true; 7110 if (mEditor != null) mEditor.makeBlink(); 7111 bringPointIntoView(curs); 7112 } 7113 7114 checkForResize(); 7115 } 7116 7117 /** 7118 * Not private so it can be called from an inner class without going 7119 * through a thunk. 7120 */ handleTextChanged(CharSequence buffer, int start, int before, int after)7121 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 7122 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7123 if (ims == null || ims.mBatchEditNesting == 0) { 7124 updateAfterEdit(); 7125 } 7126 if (ims != null) { 7127 ims.mContentChanged = true; 7128 if (ims.mChangedStart < 0) { 7129 ims.mChangedStart = start; 7130 ims.mChangedEnd = start+before; 7131 } else { 7132 ims.mChangedStart = Math.min(ims.mChangedStart, start); 7133 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 7134 } 7135 ims.mChangedDelta += after-before; 7136 } 7137 7138 sendOnTextChanged(buffer, start, before, after); 7139 onTextChanged(buffer, start, before, after); 7140 } 7141 7142 /** 7143 * Not private so it can be called from an inner class without going 7144 * through a thunk. 7145 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)7146 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 7147 // XXX Make the start and end move together if this ends up 7148 // spending too much time invalidating. 7149 7150 boolean selChanged = false; 7151 int newSelStart=-1, newSelEnd=-1; 7152 7153 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7154 7155 if (what == Selection.SELECTION_END) { 7156 selChanged = true; 7157 newSelEnd = newStart; 7158 7159 if (oldStart >= 0 || newStart >= 0) { 7160 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 7161 registerForPreDraw(); 7162 if (mEditor != null) mEditor.makeBlink(); 7163 } 7164 } 7165 7166 if (what == Selection.SELECTION_START) { 7167 selChanged = true; 7168 newSelStart = newStart; 7169 7170 if (oldStart >= 0 || newStart >= 0) { 7171 int end = Selection.getSelectionEnd(buf); 7172 invalidateCursor(end, oldStart, newStart); 7173 } 7174 } 7175 7176 if (selChanged) { 7177 mHighlightPathBogus = true; 7178 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 7179 7180 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) { 7181 if (newSelStart < 0) { 7182 newSelStart = Selection.getSelectionStart(buf); 7183 } 7184 if (newSelEnd < 0) { 7185 newSelEnd = Selection.getSelectionEnd(buf); 7186 } 7187 onSelectionChanged(newSelStart, newSelEnd); 7188 } 7189 } 7190 7191 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle || 7192 what instanceof CharacterStyle) { 7193 if (ims == null || ims.mBatchEditNesting == 0) { 7194 invalidate(); 7195 mHighlightPathBogus = true; 7196 checkForResize(); 7197 } else { 7198 ims.mContentChanged = true; 7199 } 7200 if (mEditor != null) { 7201 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 7202 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 7203 } 7204 } 7205 7206 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 7207 mHighlightPathBogus = true; 7208 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 7209 ims.mSelectionModeChanged = true; 7210 } 7211 7212 if (Selection.getSelectionStart(buf) >= 0) { 7213 if (ims == null || ims.mBatchEditNesting == 0) { 7214 invalidateCursor(); 7215 } else { 7216 ims.mCursorChanged = true; 7217 } 7218 } 7219 } 7220 7221 if (what instanceof ParcelableSpan) { 7222 // If this is a span that can be sent to a remote process, 7223 // the current extract editor would be interested in it. 7224 if (ims != null && ims.mExtractedTextRequest != null) { 7225 if (ims.mBatchEditNesting != 0) { 7226 if (oldStart >= 0) { 7227 if (ims.mChangedStart > oldStart) { 7228 ims.mChangedStart = oldStart; 7229 } 7230 if (ims.mChangedStart > oldEnd) { 7231 ims.mChangedStart = oldEnd; 7232 } 7233 } 7234 if (newStart >= 0) { 7235 if (ims.mChangedStart > newStart) { 7236 ims.mChangedStart = newStart; 7237 } 7238 if (ims.mChangedStart > newEnd) { 7239 ims.mChangedStart = newEnd; 7240 } 7241 } 7242 } else { 7243 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: " 7244 + oldStart + "-" + oldEnd + "," 7245 + newStart + "-" + newEnd + " " + what); 7246 ims.mContentChanged = true; 7247 } 7248 } 7249 } 7250 7251 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 && 7252 what instanceof SpellCheckSpan) { 7253 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 7254 } 7255 } 7256 7257 /** 7258 * @hide 7259 */ 7260 @Override dispatchFinishTemporaryDetach()7261 public void dispatchFinishTemporaryDetach() { 7262 mDispatchTemporaryDetach = true; 7263 super.dispatchFinishTemporaryDetach(); 7264 mDispatchTemporaryDetach = false; 7265 } 7266 7267 @Override onStartTemporaryDetach()7268 public void onStartTemporaryDetach() { 7269 super.onStartTemporaryDetach(); 7270 // Only track when onStartTemporaryDetach() is called directly, 7271 // usually because this instance is an editable field in a list 7272 if (!mDispatchTemporaryDetach) mTemporaryDetach = true; 7273 7274 // Tell the editor that we are temporarily detached. It can use this to preserve 7275 // selection state as needed. 7276 if (mEditor != null) mEditor.mTemporaryDetach = true; 7277 } 7278 7279 @Override onFinishTemporaryDetach()7280 public void onFinishTemporaryDetach() { 7281 super.onFinishTemporaryDetach(); 7282 // Only track when onStartTemporaryDetach() is called directly, 7283 // usually because this instance is an editable field in a list 7284 if (!mDispatchTemporaryDetach) mTemporaryDetach = false; 7285 if (mEditor != null) mEditor.mTemporaryDetach = false; 7286 } 7287 7288 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)7289 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 7290 if (mTemporaryDetach) { 7291 // If we are temporarily in the detach state, then do nothing. 7292 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7293 return; 7294 } 7295 7296 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 7297 7298 if (focused) { 7299 if (mText instanceof Spannable) { 7300 Spannable sp = (Spannable) mText; 7301 MetaKeyKeyListener.resetMetaState(sp); 7302 } 7303 } 7304 7305 startStopMarquee(focused); 7306 7307 if (mTransformation != null) { 7308 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 7309 } 7310 7311 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7312 } 7313 7314 @Override onWindowFocusChanged(boolean hasWindowFocus)7315 public void onWindowFocusChanged(boolean hasWindowFocus) { 7316 super.onWindowFocusChanged(hasWindowFocus); 7317 7318 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 7319 7320 startStopMarquee(hasWindowFocus); 7321 } 7322 7323 @Override onVisibilityChanged(View changedView, int visibility)7324 protected void onVisibilityChanged(View changedView, int visibility) { 7325 super.onVisibilityChanged(changedView, visibility); 7326 if (mEditor != null && visibility != VISIBLE) { 7327 mEditor.hideControllers(); 7328 } 7329 } 7330 7331 /** 7332 * Use {@link BaseInputConnection#removeComposingSpans 7333 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 7334 * state from this text view. 7335 */ clearComposingText()7336 public void clearComposingText() { 7337 if (mText instanceof Spannable) { 7338 BaseInputConnection.removeComposingSpans((Spannable)mText); 7339 } 7340 } 7341 7342 @Override setSelected(boolean selected)7343 public void setSelected(boolean selected) { 7344 boolean wasSelected = isSelected(); 7345 7346 super.setSelected(selected); 7347 7348 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7349 if (selected) { 7350 startMarquee(); 7351 } else { 7352 stopMarquee(); 7353 } 7354 } 7355 } 7356 7357 @Override onTouchEvent(MotionEvent event)7358 public boolean onTouchEvent(MotionEvent event) { 7359 final int action = event.getActionMasked(); 7360 7361 if (mEditor != null) mEditor.onTouchEvent(event); 7362 7363 final boolean superResult = super.onTouchEvent(event); 7364 7365 /* 7366 * Don't handle the release after a long press, because it will 7367 * move the selection away from whatever the menu action was 7368 * trying to affect. 7369 */ 7370 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 7371 mEditor.mDiscardNextActionUp = false; 7372 return superResult; 7373 } 7374 7375 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && 7376 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 7377 7378 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 7379 && mText instanceof Spannable && mLayout != null) { 7380 boolean handled = false; 7381 7382 if (mMovement != null) { 7383 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 7384 } 7385 7386 final boolean textIsSelectable = isTextSelectable(); 7387 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 7388 // The LinkMovementMethod which should handle taps on links has not been installed 7389 // on non editable text that support text selection. 7390 // We reproduce its behavior here to open links for these. 7391 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 7392 getSelectionEnd(), ClickableSpan.class); 7393 7394 if (links.length > 0) { 7395 links[0].onClick(this); 7396 handled = true; 7397 } 7398 } 7399 7400 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 7401 // Show the IME, except when selecting in read-only text. 7402 final InputMethodManager imm = InputMethodManager.peekInstance(); 7403 viewClicked(imm); 7404 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) { 7405 handled |= imm != null && imm.showSoftInput(this, 0); 7406 } 7407 7408 // The above condition ensures that the mEditor is not null 7409 mEditor.onTouchUpEvent(event); 7410 7411 handled = true; 7412 } 7413 7414 if (handled) { 7415 return true; 7416 } 7417 } 7418 7419 return superResult; 7420 } 7421 7422 @Override onGenericMotionEvent(MotionEvent event)7423 public boolean onGenericMotionEvent(MotionEvent event) { 7424 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 7425 try { 7426 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 7427 return true; 7428 } 7429 } catch (AbstractMethodError ex) { 7430 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 7431 // Ignore its absence in case third party applications implemented the 7432 // interface directly. 7433 } 7434 } 7435 return super.onGenericMotionEvent(event); 7436 } 7437 7438 /** 7439 * @return True iff this TextView contains a text that can be edited, or if this is 7440 * a selectable TextView. 7441 */ isTextEditable()7442 boolean isTextEditable() { 7443 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 7444 } 7445 7446 /** 7447 * Returns true, only while processing a touch gesture, if the initial 7448 * touch down event caused focus to move to the text view and as a result 7449 * its selection changed. Only valid while processing the touch gesture 7450 * of interest, in an editable text view. 7451 */ didTouchFocusSelect()7452 public boolean didTouchFocusSelect() { 7453 return mEditor != null && mEditor.mTouchFocusSelected; 7454 } 7455 7456 @Override cancelLongPress()7457 public void cancelLongPress() { 7458 super.cancelLongPress(); 7459 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 7460 } 7461 7462 @Override onTrackballEvent(MotionEvent event)7463 public boolean onTrackballEvent(MotionEvent event) { 7464 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 7465 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 7466 return true; 7467 } 7468 } 7469 7470 return super.onTrackballEvent(event); 7471 } 7472 setScroller(Scroller s)7473 public void setScroller(Scroller s) { 7474 mScroller = s; 7475 } 7476 7477 @Override getLeftFadingEdgeStrength()7478 protected float getLeftFadingEdgeStrength() { 7479 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 7480 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7481 if (mMarquee != null && !mMarquee.isStopped()) { 7482 final Marquee marquee = mMarquee; 7483 if (marquee.shouldDrawLeftFade()) { 7484 return marquee.mScroll / getHorizontalFadingEdgeLength(); 7485 } else { 7486 return 0.0f; 7487 } 7488 } else if (getLineCount() == 1) { 7489 final int layoutDirection = getResolvedLayoutDirection(); 7490 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 7491 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 7492 case Gravity.LEFT: 7493 return 0.0f; 7494 case Gravity.RIGHT: 7495 return (mLayout.getLineRight(0) - (mRight - mLeft) - 7496 getCompoundPaddingLeft() - getCompoundPaddingRight() - 7497 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 7498 case Gravity.CENTER_HORIZONTAL: 7499 return 0.0f; 7500 } 7501 } 7502 } 7503 return super.getLeftFadingEdgeStrength(); 7504 } 7505 7506 @Override getRightFadingEdgeStrength()7507 protected float getRightFadingEdgeStrength() { 7508 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 7509 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7510 if (mMarquee != null && !mMarquee.isStopped()) { 7511 final Marquee marquee = mMarquee; 7512 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); 7513 } else if (getLineCount() == 1) { 7514 final int layoutDirection = getResolvedLayoutDirection(); 7515 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 7516 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 7517 case Gravity.LEFT: 7518 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() - 7519 getCompoundPaddingRight(); 7520 final float lineWidth = mLayout.getLineWidth(0); 7521 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength(); 7522 case Gravity.RIGHT: 7523 return 0.0f; 7524 case Gravity.CENTER_HORIZONTAL: 7525 case Gravity.FILL_HORIZONTAL: 7526 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 7527 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 7528 getHorizontalFadingEdgeLength(); 7529 } 7530 } 7531 } 7532 return super.getRightFadingEdgeStrength(); 7533 } 7534 7535 @Override computeHorizontalScrollRange()7536 protected int computeHorizontalScrollRange() { 7537 if (mLayout != null) { 7538 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ? 7539 (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 7540 } 7541 7542 return super.computeHorizontalScrollRange(); 7543 } 7544 7545 @Override computeVerticalScrollRange()7546 protected int computeVerticalScrollRange() { 7547 if (mLayout != null) 7548 return mLayout.getHeight(); 7549 7550 return super.computeVerticalScrollRange(); 7551 } 7552 7553 @Override computeVerticalScrollExtent()7554 protected int computeVerticalScrollExtent() { 7555 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 7556 } 7557 7558 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)7559 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 7560 super.findViewsWithText(outViews, searched, flags); 7561 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 7562 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 7563 String searchedLowerCase = searched.toString().toLowerCase(); 7564 String textLowerCase = mText.toString().toLowerCase(); 7565 if (textLowerCase.contains(searchedLowerCase)) { 7566 outViews.add(this); 7567 } 7568 } 7569 } 7570 7571 public enum BufferType { 7572 NORMAL, SPANNABLE, EDITABLE, 7573 } 7574 7575 /** 7576 * Returns the TextView_textColor attribute from the 7577 * Resources.StyledAttributes, if set, or the TextAppearance_textColor 7578 * from the TextView_textAppearance attribute, if TextView_textColor 7579 * was not set directly. 7580 */ getTextColors(Context context, TypedArray attrs)7581 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 7582 ColorStateList colors; 7583 colors = attrs.getColorStateList(com.android.internal.R.styleable. 7584 TextView_textColor); 7585 7586 if (colors == null) { 7587 int ap = attrs.getResourceId(com.android.internal.R.styleable. 7588 TextView_textAppearance, -1); 7589 if (ap != -1) { 7590 TypedArray appearance; 7591 appearance = context.obtainStyledAttributes(ap, 7592 com.android.internal.R.styleable.TextAppearance); 7593 colors = appearance.getColorStateList(com.android.internal.R.styleable. 7594 TextAppearance_textColor); 7595 appearance.recycle(); 7596 } 7597 } 7598 7599 return colors; 7600 } 7601 7602 /** 7603 * Returns the default color from the TextView_textColor attribute 7604 * from the AttributeSet, if set, or the default color from the 7605 * TextAppearance_textColor from the TextView_textAppearance attribute, 7606 * if TextView_textColor was not set directly. 7607 */ getTextColor(Context context, TypedArray attrs, int def)7608 public static int getTextColor(Context context, 7609 TypedArray attrs, 7610 int def) { 7611 ColorStateList colors = getTextColors(context, attrs); 7612 7613 if (colors == null) { 7614 return def; 7615 } else { 7616 return colors.getDefaultColor(); 7617 } 7618 } 7619 7620 @Override onKeyShortcut(int keyCode, KeyEvent event)7621 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 7622 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK; 7623 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) { 7624 switch (keyCode) { 7625 case KeyEvent.KEYCODE_A: 7626 if (canSelectText()) { 7627 return onTextContextMenuItem(ID_SELECT_ALL); 7628 } 7629 break; 7630 case KeyEvent.KEYCODE_X: 7631 if (canCut()) { 7632 return onTextContextMenuItem(ID_CUT); 7633 } 7634 break; 7635 case KeyEvent.KEYCODE_C: 7636 if (canCopy()) { 7637 return onTextContextMenuItem(ID_COPY); 7638 } 7639 break; 7640 case KeyEvent.KEYCODE_V: 7641 if (canPaste()) { 7642 return onTextContextMenuItem(ID_PASTE); 7643 } 7644 break; 7645 } 7646 } 7647 return super.onKeyShortcut(keyCode, event); 7648 } 7649 7650 /** 7651 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 7652 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 7653 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 7654 * sufficient. 7655 */ canSelectText()7656 private boolean canSelectText() { 7657 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 7658 } 7659 7660 /** 7661 * Test based on the <i>intrinsic</i> charateristics of the TextView. 7662 * The text must be spannable and the movement method must allow for arbitary selection. 7663 * 7664 * See also {@link #canSelectText()}. 7665 */ textCanBeSelected()7666 boolean textCanBeSelected() { 7667 // prepareCursorController() relies on this method. 7668 // If you change this condition, make sure prepareCursorController is called anywhere 7669 // the value of this condition might be changed. 7670 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 7671 return isTextEditable() || 7672 (isTextSelectable() && mText instanceof Spannable && isEnabled()); 7673 } 7674 7675 /** 7676 * This is a temporary method. Future versions may support multi-locale text. 7677 * 7678 * @return The locale that should be used for a word iterator and a spell checker 7679 * in this TextView, based on the current spell checker settings, 7680 * the current IME's locale, or the system default locale. 7681 * @hide 7682 */ getTextServicesLocale()7683 public Locale getTextServicesLocale() { 7684 Locale locale = Locale.getDefault(); 7685 final TextServicesManager textServicesManager = (TextServicesManager) 7686 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 7687 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 7688 if (subtype != null) { 7689 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); 7690 } 7691 return locale; 7692 } 7693 onLocaleChanged()7694 void onLocaleChanged() { 7695 // Will be re-created on demand in getWordIterator with the proper new locale 7696 mEditor.mWordIterator = null; 7697 } 7698 7699 /** 7700 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 7701 * Made available to achieve a consistent behavior. 7702 * @hide 7703 */ getWordIterator()7704 public WordIterator getWordIterator() { 7705 if (mEditor != null) { 7706 return mEditor.getWordIterator(); 7707 } else { 7708 return null; 7709 } 7710 } 7711 7712 @Override onPopulateAccessibilityEvent(AccessibilityEvent event)7713 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 7714 super.onPopulateAccessibilityEvent(event); 7715 7716 final boolean isPassword = hasPasswordTransformationMethod(); 7717 if (!isPassword || shouldSpeakPasswordsForAccessibility()) { 7718 final CharSequence text = getTextForAccessibility(); 7719 if (!TextUtils.isEmpty(text)) { 7720 event.getText().add(text); 7721 } 7722 } 7723 } 7724 7725 /** 7726 * @return true if the user has explicitly allowed accessibility services 7727 * to speak passwords. 7728 */ shouldSpeakPasswordsForAccessibility()7729 private boolean shouldSpeakPasswordsForAccessibility() { 7730 return (Settings.Secure.getInt(mContext.getContentResolver(), 7731 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); 7732 } 7733 7734 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)7735 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 7736 super.onInitializeAccessibilityEvent(event); 7737 7738 event.setClassName(TextView.class.getName()); 7739 final boolean isPassword = hasPasswordTransformationMethod(); 7740 event.setPassword(isPassword); 7741 7742 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 7743 event.setFromIndex(Selection.getSelectionStart(mText)); 7744 event.setToIndex(Selection.getSelectionEnd(mText)); 7745 event.setItemCount(mText.length()); 7746 } 7747 } 7748 7749 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)7750 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 7751 super.onInitializeAccessibilityNodeInfo(info); 7752 7753 info.setClassName(TextView.class.getName()); 7754 final boolean isPassword = hasPasswordTransformationMethod(); 7755 info.setPassword(isPassword); 7756 7757 if (!isPassword) { 7758 info.setText(getTextForAccessibility()); 7759 } 7760 7761 if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) { 7762 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 7763 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 7764 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 7765 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 7766 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 7767 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 7768 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 7769 } 7770 } 7771 7772 @Override sendAccessibilityEvent(int eventType)7773 public void sendAccessibilityEvent(int eventType) { 7774 // Do not send scroll events since first they are not interesting for 7775 // accessibility and second such events a generated too frequently. 7776 // For details see the implementation of bringTextIntoView(). 7777 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 7778 return; 7779 } 7780 super.sendAccessibilityEvent(eventType); 7781 } 7782 7783 /** 7784 * Gets the text reported for accessibility purposes. 7785 * 7786 * @return The accessibility text. 7787 * 7788 * @hide 7789 */ getTextForAccessibility()7790 public CharSequence getTextForAccessibility() { 7791 CharSequence text = getText(); 7792 if (TextUtils.isEmpty(text)) { 7793 text = getHint(); 7794 } 7795 return text; 7796 } 7797 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)7798 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 7799 int fromIndex, int removedCount, int addedCount) { 7800 AccessibilityEvent event = 7801 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 7802 event.setFromIndex(fromIndex); 7803 event.setRemovedCount(removedCount); 7804 event.setAddedCount(addedCount); 7805 event.setBeforeText(beforeText); 7806 sendAccessibilityEventUnchecked(event); 7807 } 7808 7809 /** 7810 * Returns whether this text view is a current input method target. The 7811 * default implementation just checks with {@link InputMethodManager}. 7812 */ isInputMethodTarget()7813 public boolean isInputMethodTarget() { 7814 InputMethodManager imm = InputMethodManager.peekInstance(); 7815 return imm != null && imm.isActive(this); 7816 } 7817 7818 static final int ID_SELECT_ALL = android.R.id.selectAll; 7819 static final int ID_CUT = android.R.id.cut; 7820 static final int ID_COPY = android.R.id.copy; 7821 static final int ID_PASTE = android.R.id.paste; 7822 7823 /** 7824 * Called when a context menu option for the text view is selected. Currently 7825 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 7826 * {@link android.R.id#copy} or {@link android.R.id#paste}. 7827 * 7828 * @return true if the context menu item action was performed. 7829 */ onTextContextMenuItem(int id)7830 public boolean onTextContextMenuItem(int id) { 7831 int min = 0; 7832 int max = mText.length(); 7833 7834 if (isFocused()) { 7835 final int selStart = getSelectionStart(); 7836 final int selEnd = getSelectionEnd(); 7837 7838 min = Math.max(0, Math.min(selStart, selEnd)); 7839 max = Math.max(0, Math.max(selStart, selEnd)); 7840 } 7841 7842 switch (id) { 7843 case ID_SELECT_ALL: 7844 // This does not enter text selection mode. Text is highlighted, so that it can be 7845 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty. 7846 selectAllText(); 7847 return true; 7848 7849 case ID_PASTE: 7850 paste(min, max); 7851 return true; 7852 7853 case ID_CUT: 7854 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 7855 deleteText_internal(min, max); 7856 stopSelectionActionMode(); 7857 return true; 7858 7859 case ID_COPY: 7860 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 7861 stopSelectionActionMode(); 7862 return true; 7863 } 7864 return false; 7865 } 7866 getTransformedText(int start, int end)7867 CharSequence getTransformedText(int start, int end) { 7868 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 7869 } 7870 7871 @Override performLongClick()7872 public boolean performLongClick() { 7873 boolean handled = false; 7874 7875 if (super.performLongClick()) { 7876 handled = true; 7877 } 7878 7879 if (mEditor != null) { 7880 handled |= mEditor.performLongClick(handled); 7881 } 7882 7883 if (handled) { 7884 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 7885 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 7886 } 7887 7888 return handled; 7889 } 7890 7891 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)7892 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 7893 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 7894 if (mEditor != null) { 7895 mEditor.onScrollChanged(); 7896 } 7897 } 7898 7899 /** 7900 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 7901 * by the IME or by the spell checker as the user types. This is done by adding 7902 * {@link SuggestionSpan}s to the text. 7903 * 7904 * When suggestions are enabled (default), this list of suggestions will be displayed when the 7905 * user asks for them on these parts of the text. This value depends on the inputType of this 7906 * TextView. 7907 * 7908 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 7909 * 7910 * In addition, the type variation must be one of 7911 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 7912 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 7913 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 7914 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 7915 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 7916 * 7917 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 7918 * 7919 * @return true if the suggestions popup window is enabled, based on the inputType. 7920 */ isSuggestionsEnabled()7921 public boolean isSuggestionsEnabled() { 7922 if (mEditor == null) return false; 7923 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 7924 return false; 7925 } 7926 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 7927 7928 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 7929 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || 7930 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || 7931 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || 7932 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || 7933 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 7934 } 7935 7936 /** 7937 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 7938 * selection is initiated in this View. 7939 * 7940 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and 7941 * Paste actions, depending on what this View supports. 7942 * 7943 * A custom implementation can add new entries in the default menu in its 7944 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The 7945 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and 7946 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} 7947 * or {@link android.R.id#paste} ids as parameters. 7948 * 7949 * Returning false from 7950 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent 7951 * the action mode from being started. 7952 * 7953 * Action click events should be handled by the custom implementation of 7954 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}. 7955 * 7956 * Note that text selection mode is not started when a TextView receives focus and the 7957 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 7958 * that case, to allow for quick replacement. 7959 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)7960 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 7961 createEditorIfNeeded(); 7962 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 7963 } 7964 7965 /** 7966 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 7967 * 7968 * @return The current custom selection callback. 7969 */ getCustomSelectionActionModeCallback()7970 public ActionMode.Callback getCustomSelectionActionModeCallback() { 7971 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 7972 } 7973 7974 /** 7975 * @hide 7976 */ stopSelectionActionMode()7977 protected void stopSelectionActionMode() { 7978 mEditor.stopSelectionActionMode(); 7979 } 7980 canCut()7981 boolean canCut() { 7982 if (hasPasswordTransformationMethod()) { 7983 return false; 7984 } 7985 7986 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && 7987 mEditor.mKeyListener != null) { 7988 return true; 7989 } 7990 7991 return false; 7992 } 7993 canCopy()7994 boolean canCopy() { 7995 if (hasPasswordTransformationMethod()) { 7996 return false; 7997 } 7998 7999 if (mText.length() > 0 && hasSelection()) { 8000 return true; 8001 } 8002 8003 return false; 8004 } 8005 canPaste()8006 boolean canPaste() { 8007 return (mText instanceof Editable && 8008 mEditor != null && mEditor.mKeyListener != null && 8009 getSelectionStart() >= 0 && 8010 getSelectionEnd() >= 0 && 8011 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). 8012 hasPrimaryClip()); 8013 } 8014 selectAllText()8015 boolean selectAllText() { 8016 final int length = mText.length(); 8017 Selection.setSelection((Spannable) mText, 0, length); 8018 return length > 0; 8019 } 8020 8021 /** 8022 * Prepare text so that there are not zero or two spaces at beginning and end of region defined 8023 * by [min, max] when replacing this region by paste. 8024 * Note that if there were two spaces (or more) at that position before, they are kept. We just 8025 * make sure we do not add an extra one from the paste content. 8026 */ prepareSpacesAroundPaste(int min, int max, CharSequence paste)8027 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) { 8028 if (paste.length() > 0) { 8029 if (min > 0) { 8030 final char charBefore = mTransformed.charAt(min - 1); 8031 final char charAfter = paste.charAt(0); 8032 8033 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { 8034 // Two spaces at beginning of paste: remove one 8035 final int originalLength = mText.length(); 8036 deleteText_internal(min - 1, min); 8037 // Due to filters, there is no guarantee that exactly one character was 8038 // removed: count instead. 8039 final int delta = mText.length() - originalLength; 8040 min += delta; 8041 max += delta; 8042 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && 8043 !Character.isSpaceChar(charAfter) && charAfter != '\n') { 8044 // No space at beginning of paste: add one 8045 final int originalLength = mText.length(); 8046 replaceText_internal(min, min, " "); 8047 // Taking possible filters into account as above. 8048 final int delta = mText.length() - originalLength; 8049 min += delta; 8050 max += delta; 8051 } 8052 } 8053 8054 if (max < mText.length()) { 8055 final char charBefore = paste.charAt(paste.length() - 1); 8056 final char charAfter = mTransformed.charAt(max); 8057 8058 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { 8059 // Two spaces at end of paste: remove one 8060 deleteText_internal(max, max + 1); 8061 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && 8062 !Character.isSpaceChar(charAfter) && charAfter != '\n') { 8063 // No space at end of paste: add one 8064 replaceText_internal(max, max, " "); 8065 } 8066 } 8067 } 8068 8069 return TextUtils.packRangeInLong(min, max); 8070 } 8071 8072 /** 8073 * Paste clipboard content between min and max positions. 8074 */ paste(int min, int max)8075 private void paste(int min, int max) { 8076 ClipboardManager clipboard = 8077 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 8078 ClipData clip = clipboard.getPrimaryClip(); 8079 if (clip != null) { 8080 boolean didFirst = false; 8081 for (int i=0; i<clip.getItemCount(); i++) { 8082 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext()); 8083 if (paste != null) { 8084 if (!didFirst) { 8085 long minMax = prepareSpacesAroundPaste(min, max, paste); 8086 min = TextUtils.unpackRangeStartFromLong(minMax); 8087 max = TextUtils.unpackRangeEndFromLong(minMax); 8088 Selection.setSelection((Spannable) mText, max); 8089 ((Editable) mText).replace(min, max, paste); 8090 didFirst = true; 8091 } else { 8092 ((Editable) mText).insert(getSelectionEnd(), "\n"); 8093 ((Editable) mText).insert(getSelectionEnd(), paste); 8094 } 8095 } 8096 } 8097 stopSelectionActionMode(); 8098 LAST_CUT_OR_COPY_TIME = 0; 8099 } 8100 } 8101 setPrimaryClip(ClipData clip)8102 private void setPrimaryClip(ClipData clip) { 8103 ClipboardManager clipboard = (ClipboardManager) getContext(). 8104 getSystemService(Context.CLIPBOARD_SERVICE); 8105 clipboard.setPrimaryClip(clip); 8106 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis(); 8107 } 8108 8109 /** 8110 * Get the character offset closest to the specified absolute position. A typical use case is to 8111 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 8112 * 8113 * @param x The horizontal absolute position of a point on screen 8114 * @param y The vertical absolute position of a point on screen 8115 * @return the character offset for the character whose position is closest to the specified 8116 * position. Returns -1 if there is no layout. 8117 */ getOffsetForPosition(float x, float y)8118 public int getOffsetForPosition(float x, float y) { 8119 if (getLayout() == null) return -1; 8120 final int line = getLineAtCoordinate(y); 8121 final int offset = getOffsetAtCoordinate(line, x); 8122 return offset; 8123 } 8124 convertToLocalHorizontalCoordinate(float x)8125 float convertToLocalHorizontalCoordinate(float x) { 8126 x -= getTotalPaddingLeft(); 8127 // Clamp the position to inside of the view. 8128 x = Math.max(0.0f, x); 8129 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 8130 x += getScrollX(); 8131 return x; 8132 } 8133 getLineAtCoordinate(float y)8134 int getLineAtCoordinate(float y) { 8135 y -= getTotalPaddingTop(); 8136 // Clamp the position to inside of the view. 8137 y = Math.max(0.0f, y); 8138 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 8139 y += getScrollY(); 8140 return getLayout().getLineForVertical((int) y); 8141 } 8142 getOffsetAtCoordinate(int line, float x)8143 private int getOffsetAtCoordinate(int line, float x) { 8144 x = convertToLocalHorizontalCoordinate(x); 8145 return getLayout().getOffsetForHorizontal(line, x); 8146 } 8147 8148 @Override onDragEvent(DragEvent event)8149 public boolean onDragEvent(DragEvent event) { 8150 switch (event.getAction()) { 8151 case DragEvent.ACTION_DRAG_STARTED: 8152 return mEditor != null && mEditor.hasInsertionController(); 8153 8154 case DragEvent.ACTION_DRAG_ENTERED: 8155 TextView.this.requestFocus(); 8156 return true; 8157 8158 case DragEvent.ACTION_DRAG_LOCATION: 8159 final int offset = getOffsetForPosition(event.getX(), event.getY()); 8160 Selection.setSelection((Spannable)mText, offset); 8161 return true; 8162 8163 case DragEvent.ACTION_DROP: 8164 if (mEditor != null) mEditor.onDrop(event); 8165 return true; 8166 8167 case DragEvent.ACTION_DRAG_ENDED: 8168 case DragEvent.ACTION_DRAG_EXITED: 8169 default: 8170 return true; 8171 } 8172 } 8173 isInBatchEditMode()8174 boolean isInBatchEditMode() { 8175 if (mEditor == null) return false; 8176 final Editor.InputMethodState ims = mEditor.mInputMethodState; 8177 if (ims != null) { 8178 return ims.mBatchEditNesting > 0; 8179 } 8180 return mEditor.mInBatchEditControllers; 8181 } 8182 8183 /** @hide */ 8184 @Override onResolvedTextDirectionChanged()8185 public void onResolvedTextDirectionChanged() { 8186 if (hasPasswordTransformationMethod()) { 8187 // TODO: take care of the content direction to show the password text and dots justified 8188 // to the left or to the right 8189 mTextDir = TextDirectionHeuristics.LOCALE; 8190 return; 8191 } 8192 8193 // Always need to resolve layout direction first 8194 final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL); 8195 8196 // Now, we can select the heuristic 8197 int textDir = getResolvedTextDirection(); 8198 switch (textDir) { 8199 default: 8200 case TEXT_DIRECTION_FIRST_STRONG: 8201 mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 8202 TextDirectionHeuristics.FIRSTSTRONG_LTR); 8203 break; 8204 case TEXT_DIRECTION_ANY_RTL: 8205 mTextDir = TextDirectionHeuristics.ANYRTL_LTR; 8206 break; 8207 case TEXT_DIRECTION_LTR: 8208 mTextDir = TextDirectionHeuristics.LTR; 8209 break; 8210 case TEXT_DIRECTION_RTL: 8211 mTextDir = TextDirectionHeuristics.RTL; 8212 break; 8213 case TEXT_DIRECTION_LOCALE: 8214 mTextDir = TextDirectionHeuristics.LOCALE; 8215 break; 8216 } 8217 } 8218 8219 /** 8220 * Subclasses will need to override this method to implement their own way of resolving 8221 * drawables depending on the layout direction. 8222 * 8223 * A call to the super method will be required from the subclasses implementation. 8224 */ resolveDrawables()8225 protected void resolveDrawables() { 8226 // No need to resolve twice 8227 if (mResolvedDrawables) { 8228 return; 8229 } 8230 // No drawable to resolve 8231 if (mDrawables == null) { 8232 return; 8233 } 8234 // No relative drawable to resolve 8235 if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) { 8236 mResolvedDrawables = true; 8237 return; 8238 } 8239 8240 Drawables dr = mDrawables; 8241 switch(getResolvedLayoutDirection()) { 8242 case LAYOUT_DIRECTION_RTL: 8243 if (dr.mDrawableStart != null) { 8244 dr.mDrawableRight = dr.mDrawableStart; 8245 8246 dr.mDrawableSizeRight = dr.mDrawableSizeStart; 8247 dr.mDrawableHeightRight = dr.mDrawableHeightStart; 8248 } 8249 if (dr.mDrawableEnd != null) { 8250 dr.mDrawableLeft = dr.mDrawableEnd; 8251 8252 dr.mDrawableSizeLeft = dr.mDrawableSizeEnd; 8253 dr.mDrawableHeightLeft = dr.mDrawableHeightEnd; 8254 } 8255 break; 8256 8257 case LAYOUT_DIRECTION_LTR: 8258 default: 8259 if (dr.mDrawableStart != null) { 8260 dr.mDrawableLeft = dr.mDrawableStart; 8261 8262 dr.mDrawableSizeLeft = dr.mDrawableSizeStart; 8263 dr.mDrawableHeightLeft = dr.mDrawableHeightStart; 8264 } 8265 if (dr.mDrawableEnd != null) { 8266 dr.mDrawableRight = dr.mDrawableEnd; 8267 8268 dr.mDrawableSizeRight = dr.mDrawableSizeEnd; 8269 dr.mDrawableHeightRight = dr.mDrawableHeightEnd; 8270 } 8271 break; 8272 } 8273 mResolvedDrawables = true; 8274 } 8275 resetResolvedDrawables()8276 protected void resetResolvedDrawables() { 8277 mResolvedDrawables = false; 8278 } 8279 8280 /** 8281 * @hide 8282 */ viewClicked(InputMethodManager imm)8283 protected void viewClicked(InputMethodManager imm) { 8284 if (imm != null) { 8285 imm.viewClicked(this); 8286 } 8287 } 8288 8289 /** 8290 * Deletes the range of text [start, end[. 8291 * @hide 8292 */ deleteText_internal(int start, int end)8293 protected void deleteText_internal(int start, int end) { 8294 ((Editable) mText).delete(start, end); 8295 } 8296 8297 /** 8298 * Replaces the range of text [start, end[ by replacement text 8299 * @hide 8300 */ replaceText_internal(int start, int end, CharSequence text)8301 protected void replaceText_internal(int start, int end, CharSequence text) { 8302 ((Editable) mText).replace(start, end, text); 8303 } 8304 8305 /** 8306 * Sets a span on the specified range of text 8307 * @hide 8308 */ setSpan_internal(Object span, int start, int end, int flags)8309 protected void setSpan_internal(Object span, int start, int end, int flags) { 8310 ((Editable) mText).setSpan(span, start, end, flags); 8311 } 8312 8313 /** 8314 * Moves the cursor to the specified offset position in text 8315 * @hide 8316 */ setCursorPosition_internal(int start, int end)8317 protected void setCursorPosition_internal(int start, int end) { 8318 Selection.setSelection(((Editable) mText), start, end); 8319 } 8320 8321 /** 8322 * An Editor should be created as soon as any of the editable-specific fields (grouped 8323 * inside the Editor object) is assigned to a non-default value. 8324 * This method will create the Editor if needed. 8325 * 8326 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 8327 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 8328 * Editor for backward compatibility, as soon as one of these fields is assigned. 8329 * 8330 * Also note that for performance reasons, the mEditor is created when needed, but not 8331 * reset when no more edit-specific fields are needed. 8332 */ createEditorIfNeeded()8333 private void createEditorIfNeeded() { 8334 if (mEditor == null) { 8335 mEditor = new Editor(this); 8336 } 8337 } 8338 8339 /** 8340 * @hide 8341 */ 8342 @Override getIterableTextForAccessibility()8343 public CharSequence getIterableTextForAccessibility() { 8344 if (getContentDescription() == null) { 8345 if (!(mText instanceof Spannable)) { 8346 setText(mText, BufferType.SPANNABLE); 8347 } 8348 return mText; 8349 } 8350 return super.getIterableTextForAccessibility(); 8351 } 8352 8353 /** 8354 * @hide 8355 */ 8356 @Override getIteratorForGranularity(int granularity)8357 public TextSegmentIterator getIteratorForGranularity(int granularity) { 8358 switch (granularity) { 8359 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 8360 Spannable text = (Spannable) getIterableTextForAccessibility(); 8361 if (!TextUtils.isEmpty(text) && getLayout() != null) { 8362 AccessibilityIterators.LineTextSegmentIterator iterator = 8363 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 8364 iterator.initialize(text, getLayout()); 8365 return iterator; 8366 } 8367 } break; 8368 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 8369 Spannable text = (Spannable) getIterableTextForAccessibility(); 8370 if (!TextUtils.isEmpty(text) && getLayout() != null) { 8371 AccessibilityIterators.PageTextSegmentIterator iterator = 8372 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 8373 iterator.initialize(this); 8374 return iterator; 8375 } 8376 } break; 8377 } 8378 return super.getIteratorForGranularity(granularity); 8379 } 8380 8381 /** 8382 * @hide 8383 */ 8384 @Override getAccessibilityCursorPosition()8385 public int getAccessibilityCursorPosition() { 8386 if (TextUtils.isEmpty(getContentDescription())) { 8387 final int selectionEnd = getSelectionEnd(); 8388 if (selectionEnd >= 0) { 8389 return selectionEnd; 8390 } 8391 } 8392 return super.getAccessibilityCursorPosition(); 8393 } 8394 8395 /** 8396 * @hide 8397 */ 8398 @Override setAccessibilityCursorPosition(int index)8399 public void setAccessibilityCursorPosition(int index) { 8400 if (getAccessibilityCursorPosition() == index) { 8401 return; 8402 } 8403 if (TextUtils.isEmpty(getContentDescription())) { 8404 if (index >= 0 && index <= mText.length()) { 8405 Selection.setSelection((Spannable) mText, index); 8406 } else { 8407 Selection.removeSelection((Spannable) mText); 8408 } 8409 } else { 8410 super.setAccessibilityCursorPosition(index); 8411 } 8412 } 8413 8414 /** 8415 * User interface state that is stored by TextView for implementing 8416 * {@link View#onSaveInstanceState}. 8417 */ 8418 public static class SavedState extends BaseSavedState { 8419 int selStart; 8420 int selEnd; 8421 CharSequence text; 8422 boolean frozenWithFocus; 8423 CharSequence error; 8424 SavedState(Parcelable superState)8425 SavedState(Parcelable superState) { 8426 super(superState); 8427 } 8428 8429 @Override writeToParcel(Parcel out, int flags)8430 public void writeToParcel(Parcel out, int flags) { 8431 super.writeToParcel(out, flags); 8432 out.writeInt(selStart); 8433 out.writeInt(selEnd); 8434 out.writeInt(frozenWithFocus ? 1 : 0); 8435 TextUtils.writeToParcel(text, out, flags); 8436 8437 if (error == null) { 8438 out.writeInt(0); 8439 } else { 8440 out.writeInt(1); 8441 TextUtils.writeToParcel(error, out, flags); 8442 } 8443 } 8444 8445 @Override toString()8446 public String toString() { 8447 String str = "TextView.SavedState{" 8448 + Integer.toHexString(System.identityHashCode(this)) 8449 + " start=" + selStart + " end=" + selEnd; 8450 if (text != null) { 8451 str += " text=" + text; 8452 } 8453 return str + "}"; 8454 } 8455 8456 @SuppressWarnings("hiding") 8457 public static final Parcelable.Creator<SavedState> CREATOR 8458 = new Parcelable.Creator<SavedState>() { 8459 public SavedState createFromParcel(Parcel in) { 8460 return new SavedState(in); 8461 } 8462 8463 public SavedState[] newArray(int size) { 8464 return new SavedState[size]; 8465 } 8466 }; 8467 SavedState(Parcel in)8468 private SavedState(Parcel in) { 8469 super(in); 8470 selStart = in.readInt(); 8471 selEnd = in.readInt(); 8472 frozenWithFocus = (in.readInt() != 0); 8473 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 8474 8475 if (in.readInt() != 0) { 8476 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 8477 } 8478 } 8479 } 8480 8481 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 8482 private char[] mChars; 8483 private int mStart, mLength; 8484 CharWrapper(char[] chars, int start, int len)8485 public CharWrapper(char[] chars, int start, int len) { 8486 mChars = chars; 8487 mStart = start; 8488 mLength = len; 8489 } 8490 set(char[] chars, int start, int len)8491 /* package */ void set(char[] chars, int start, int len) { 8492 mChars = chars; 8493 mStart = start; 8494 mLength = len; 8495 } 8496 length()8497 public int length() { 8498 return mLength; 8499 } 8500 charAt(int off)8501 public char charAt(int off) { 8502 return mChars[off + mStart]; 8503 } 8504 8505 @Override toString()8506 public String toString() { 8507 return new String(mChars, mStart, mLength); 8508 } 8509 subSequence(int start, int end)8510 public CharSequence subSequence(int start, int end) { 8511 if (start < 0 || end < 0 || start > mLength || end > mLength) { 8512 throw new IndexOutOfBoundsException(start + ", " + end); 8513 } 8514 8515 return new String(mChars, start + mStart, end - start); 8516 } 8517 getChars(int start, int end, char[] buf, int off)8518 public void getChars(int start, int end, char[] buf, int off) { 8519 if (start < 0 || end < 0 || start > mLength || end > mLength) { 8520 throw new IndexOutOfBoundsException(start + ", " + end); 8521 } 8522 8523 System.arraycopy(mChars, start + mStart, buf, off, end - start); 8524 } 8525 drawText(Canvas c, int start, int end, float x, float y, Paint p)8526 public void drawText(Canvas c, int start, int end, 8527 float x, float y, Paint p) { 8528 c.drawText(mChars, start + mStart, end - start, x, y, p); 8529 } 8530 drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, int flags, Paint p)8531 public void drawTextRun(Canvas c, int start, int end, 8532 int contextStart, int contextEnd, float x, float y, int flags, Paint p) { 8533 int count = end - start; 8534 int contextCount = contextEnd - contextStart; 8535 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 8536 contextCount, x, y, flags, p); 8537 } 8538 measureText(int start, int end, Paint p)8539 public float measureText(int start, int end, Paint p) { 8540 return p.measureText(mChars, start + mStart, end - start); 8541 } 8542 getTextWidths(int start, int end, float[] widths, Paint p)8543 public int getTextWidths(int start, int end, float[] widths, Paint p) { 8544 return p.getTextWidths(mChars, start + mStart, end - start, widths); 8545 } 8546 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex, Paint p)8547 public float getTextRunAdvances(int start, int end, int contextStart, 8548 int contextEnd, int flags, float[] advances, int advancesIndex, 8549 Paint p) { 8550 int count = end - start; 8551 int contextCount = contextEnd - contextStart; 8552 return p.getTextRunAdvances(mChars, start + mStart, count, 8553 contextStart + mStart, contextCount, flags, advances, 8554 advancesIndex); 8555 } 8556 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex, Paint p, int reserved)8557 public float getTextRunAdvances(int start, int end, int contextStart, 8558 int contextEnd, int flags, float[] advances, int advancesIndex, 8559 Paint p, int reserved) { 8560 int count = end - start; 8561 int contextCount = contextEnd - contextStart; 8562 return p.getTextRunAdvances(mChars, start + mStart, count, 8563 contextStart + mStart, contextCount, flags, advances, 8564 advancesIndex, reserved); 8565 } 8566 getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, int cursorOpt, Paint p)8567 public int getTextRunCursor(int contextStart, int contextEnd, int flags, 8568 int offset, int cursorOpt, Paint p) { 8569 int contextCount = contextEnd - contextStart; 8570 return p.getTextRunCursor(mChars, contextStart + mStart, 8571 contextCount, flags, offset + mStart, cursorOpt); 8572 } 8573 } 8574 8575 private static final class Marquee extends Handler { 8576 // TODO: Add an option to configure this 8577 private static final float MARQUEE_DELTA_MAX = 0.07f; 8578 private static final int MARQUEE_DELAY = 1200; 8579 private static final int MARQUEE_RESTART_DELAY = 1200; 8580 private static final int MARQUEE_RESOLUTION = 1000 / 30; 8581 private static final int MARQUEE_PIXELS_PER_SECOND = 30; 8582 8583 private static final byte MARQUEE_STOPPED = 0x0; 8584 private static final byte MARQUEE_STARTING = 0x1; 8585 private static final byte MARQUEE_RUNNING = 0x2; 8586 8587 private static final int MESSAGE_START = 0x1; 8588 private static final int MESSAGE_TICK = 0x2; 8589 private static final int MESSAGE_RESTART = 0x3; 8590 8591 private final WeakReference<TextView> mView; 8592 8593 private byte mStatus = MARQUEE_STOPPED; 8594 private final float mScrollUnit; 8595 private float mMaxScroll; 8596 float mMaxFadeScroll; 8597 private float mGhostStart; 8598 private float mGhostOffset; 8599 private float mFadeStop; 8600 private int mRepeatLimit; 8601 8602 float mScroll; 8603 Marquee(TextView v)8604 Marquee(TextView v) { 8605 final float density = v.getContext().getResources().getDisplayMetrics().density; 8606 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION; 8607 mView = new WeakReference<TextView>(v); 8608 } 8609 8610 @Override handleMessage(Message msg)8611 public void handleMessage(Message msg) { 8612 switch (msg.what) { 8613 case MESSAGE_START: 8614 mStatus = MARQUEE_RUNNING; 8615 tick(); 8616 break; 8617 case MESSAGE_TICK: 8618 tick(); 8619 break; 8620 case MESSAGE_RESTART: 8621 if (mStatus == MARQUEE_RUNNING) { 8622 if (mRepeatLimit >= 0) { 8623 mRepeatLimit--; 8624 } 8625 start(mRepeatLimit); 8626 } 8627 break; 8628 } 8629 } 8630 tick()8631 void tick() { 8632 if (mStatus != MARQUEE_RUNNING) { 8633 return; 8634 } 8635 8636 removeMessages(MESSAGE_TICK); 8637 8638 final TextView textView = mView.get(); 8639 if (textView != null && (textView.isFocused() || textView.isSelected())) { 8640 mScroll += mScrollUnit; 8641 if (mScroll > mMaxScroll) { 8642 mScroll = mMaxScroll; 8643 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); 8644 } else { 8645 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); 8646 } 8647 textView.invalidate(); 8648 } 8649 } 8650 stop()8651 void stop() { 8652 mStatus = MARQUEE_STOPPED; 8653 removeMessages(MESSAGE_START); 8654 removeMessages(MESSAGE_RESTART); 8655 removeMessages(MESSAGE_TICK); 8656 resetScroll(); 8657 } 8658 resetScroll()8659 private void resetScroll() { 8660 mScroll = 0.0f; 8661 final TextView textView = mView.get(); 8662 if (textView != null) textView.invalidate(); 8663 } 8664 start(int repeatLimit)8665 void start(int repeatLimit) { 8666 if (repeatLimit == 0) { 8667 stop(); 8668 return; 8669 } 8670 mRepeatLimit = repeatLimit; 8671 final TextView textView = mView.get(); 8672 if (textView != null && textView.mLayout != null) { 8673 mStatus = MARQUEE_STARTING; 8674 mScroll = 0.0f; 8675 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - 8676 textView.getCompoundPaddingRight(); 8677 final float lineWidth = textView.mLayout.getLineWidth(0); 8678 final float gap = textWidth / 3.0f; 8679 mGhostStart = lineWidth - textWidth + gap; 8680 mMaxScroll = mGhostStart + textWidth; 8681 mGhostOffset = lineWidth + gap; 8682 mFadeStop = lineWidth + textWidth / 6.0f; 8683 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 8684 8685 textView.invalidate(); 8686 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); 8687 } 8688 } 8689 getGhostOffset()8690 float getGhostOffset() { 8691 return mGhostOffset; 8692 } 8693 shouldDrawLeftFade()8694 boolean shouldDrawLeftFade() { 8695 return mScroll <= mFadeStop; 8696 } 8697 shouldDrawGhost()8698 boolean shouldDrawGhost() { 8699 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 8700 } 8701 isRunning()8702 boolean isRunning() { 8703 return mStatus == MARQUEE_RUNNING; 8704 } 8705 isStopped()8706 boolean isStopped() { 8707 return mStatus == MARQUEE_STOPPED; 8708 } 8709 } 8710 8711 private class ChangeWatcher implements TextWatcher, SpanWatcher { 8712 8713 private CharSequence mBeforeText; 8714 beforeTextChanged(CharSequence buffer, int start, int before, int after)8715 public void beforeTextChanged(CharSequence buffer, int start, 8716 int before, int after) { 8717 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start 8718 + " before=" + before + " after=" + after + ": " + buffer); 8719 8720 if (AccessibilityManager.getInstance(mContext).isEnabled() 8721 && !isPasswordInputType(getInputType()) 8722 && !hasPasswordTransformationMethod()) { 8723 mBeforeText = buffer.toString(); 8724 } 8725 8726 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 8727 } 8728 onTextChanged(CharSequence buffer, int start, int before, int after)8729 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 8730 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start 8731 + " before=" + before + " after=" + after + ": " + buffer); 8732 TextView.this.handleTextChanged(buffer, start, before, after); 8733 8734 if (AccessibilityManager.getInstance(mContext).isEnabled() && 8735 (isFocused() || isSelected() && isShown())) { 8736 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 8737 mBeforeText = null; 8738 } 8739 } 8740 afterTextChanged(Editable buffer)8741 public void afterTextChanged(Editable buffer) { 8742 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer); 8743 TextView.this.sendAfterTextChanged(buffer); 8744 8745 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 8746 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 8747 } 8748 } 8749 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)8750 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 8751 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 8752 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 8753 TextView.this.spanChange(buf, what, s, st, e, en); 8754 } 8755 onSpanAdded(Spannable buf, Object what, int s, int e)8756 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 8757 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e 8758 + " what=" + what + ": " + buf); 8759 TextView.this.spanChange(buf, what, -1, s, -1, e); 8760 } 8761 onSpanRemoved(Spannable buf, Object what, int s, int e)8762 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 8763 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e 8764 + " what=" + what + ": " + buf); 8765 TextView.this.spanChange(buf, what, s, -1, e, -1); 8766 } 8767 } 8768 } 8769