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