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 */ setTextColor(ColorStateList colors)3258 public void setTextColor(ColorStateList colors) { 3259 if (colors == null) { 3260 throw new NullPointerException(); 3261 } 3262 3263 mTextColor = colors; 3264 updateTextColors(); 3265 } 3266 3267 /** 3268 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 3269 * 3270 * @see #setTextColor(ColorStateList) 3271 * @see #setTextColor(int) 3272 * 3273 * @attr ref android.R.styleable#TextView_textColor 3274 */ getTextColors()3275 public final ColorStateList getTextColors() { 3276 return mTextColor; 3277 } 3278 3279 /** 3280 * <p>Return the current color selected for normal text.</p> 3281 * 3282 * @return Returns the current text color. 3283 */ 3284 @ColorInt getCurrentTextColor()3285 public final int getCurrentTextColor() { 3286 return mCurTextColor; 3287 } 3288 3289 /** 3290 * Sets the color used to display the selection highlight. 3291 * 3292 * @attr ref android.R.styleable#TextView_textColorHighlight 3293 */ 3294 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)3295 public void setHighlightColor(@ColorInt int color) { 3296 if (mHighlightColor != color) { 3297 mHighlightColor = color; 3298 invalidate(); 3299 } 3300 } 3301 3302 /** 3303 * @return the color used to display the selection highlight 3304 * 3305 * @see #setHighlightColor(int) 3306 * 3307 * @attr ref android.R.styleable#TextView_textColorHighlight 3308 */ 3309 @ColorInt getHighlightColor()3310 public int getHighlightColor() { 3311 return mHighlightColor; 3312 } 3313 3314 /** 3315 * Sets whether the soft input method will be made visible when this 3316 * TextView gets focused. The default is true. 3317 */ 3318 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)3319 public final void setShowSoftInputOnFocus(boolean show) { 3320 createEditorIfNeeded(); 3321 mEditor.mShowSoftInputOnFocus = show; 3322 } 3323 3324 /** 3325 * Returns whether the soft input method will be made visible when this 3326 * TextView gets focused. The default is true. 3327 */ getShowSoftInputOnFocus()3328 public final boolean getShowSoftInputOnFocus() { 3329 // When there is no Editor, return default true value 3330 return mEditor == null || mEditor.mShowSoftInputOnFocus; 3331 } 3332 3333 /** 3334 * Gives the text a shadow of the specified blur radius and color, the specified 3335 * distance from its drawn position. 3336 * <p> 3337 * The text shadow produced does not interact with the properties on view 3338 * that are responsible for real time shadows, 3339 * {@link View#getElevation() elevation} and 3340 * {@link View#getTranslationZ() translationZ}. 3341 * 3342 * @see Paint#setShadowLayer(float, float, float, int) 3343 * 3344 * @attr ref android.R.styleable#TextView_shadowColor 3345 * @attr ref android.R.styleable#TextView_shadowDx 3346 * @attr ref android.R.styleable#TextView_shadowDy 3347 * @attr ref android.R.styleable#TextView_shadowRadius 3348 */ setShadowLayer(float radius, float dx, float dy, int color)3349 public void setShadowLayer(float radius, float dx, float dy, int color) { 3350 mTextPaint.setShadowLayer(radius, dx, dy, color); 3351 3352 mShadowRadius = radius; 3353 mShadowDx = dx; 3354 mShadowDy = dy; 3355 mShadowColor = color; 3356 3357 // Will change text clip region 3358 if (mEditor != null) { 3359 mEditor.invalidateTextDisplayList(); 3360 mEditor.invalidateHandlesAndActionMode(); 3361 } 3362 invalidate(); 3363 } 3364 3365 /** 3366 * Gets the radius of the shadow layer. 3367 * 3368 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 3369 * 3370 * @see #setShadowLayer(float, float, float, int) 3371 * 3372 * @attr ref android.R.styleable#TextView_shadowRadius 3373 */ getShadowRadius()3374 public float getShadowRadius() { 3375 return mShadowRadius; 3376 } 3377 3378 /** 3379 * @return the horizontal offset of the shadow layer 3380 * 3381 * @see #setShadowLayer(float, float, float, int) 3382 * 3383 * @attr ref android.R.styleable#TextView_shadowDx 3384 */ getShadowDx()3385 public float getShadowDx() { 3386 return mShadowDx; 3387 } 3388 3389 /** 3390 * @return the vertical offset of the shadow layer 3391 * 3392 * @see #setShadowLayer(float, float, float, int) 3393 * 3394 * @attr ref android.R.styleable#TextView_shadowDy 3395 */ getShadowDy()3396 public float getShadowDy() { 3397 return mShadowDy; 3398 } 3399 3400 /** 3401 * @return the color of the shadow layer 3402 * 3403 * @see #setShadowLayer(float, float, float, int) 3404 * 3405 * @attr ref android.R.styleable#TextView_shadowColor 3406 */ 3407 @ColorInt getShadowColor()3408 public int getShadowColor() { 3409 return mShadowColor; 3410 } 3411 3412 /** 3413 * @return the base paint used for the text. Please use this only to 3414 * consult the Paint's properties and not to change them. 3415 */ getPaint()3416 public TextPaint getPaint() { 3417 return mTextPaint; 3418 } 3419 3420 /** 3421 * Sets the autolink mask of the text. See {@link 3422 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 3423 * possible values. 3424 * 3425 * @attr ref android.R.styleable#TextView_autoLink 3426 */ 3427 @android.view.RemotableViewMethod setAutoLinkMask(int mask)3428 public final void setAutoLinkMask(int mask) { 3429 mAutoLinkMask = mask; 3430 } 3431 3432 /** 3433 * Sets whether the movement method will automatically be set to 3434 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 3435 * set to nonzero and links are detected in {@link #setText}. 3436 * The default is true. 3437 * 3438 * @attr ref android.R.styleable#TextView_linksClickable 3439 */ 3440 @android.view.RemotableViewMethod setLinksClickable(boolean whether)3441 public final void setLinksClickable(boolean whether) { 3442 mLinksClickable = whether; 3443 } 3444 3445 /** 3446 * Returns whether the movement method will automatically be set to 3447 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 3448 * set to nonzero and links are detected in {@link #setText}. 3449 * The default is true. 3450 * 3451 * @attr ref android.R.styleable#TextView_linksClickable 3452 */ getLinksClickable()3453 public final boolean getLinksClickable() { 3454 return mLinksClickable; 3455 } 3456 3457 /** 3458 * Returns the list of URLSpans attached to the text 3459 * (by {@link Linkify} or otherwise) if any. You can call 3460 * {@link URLSpan#getURL} on them to find where they link to 3461 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 3462 * to find the region of the text they are attached to. 3463 */ getUrls()3464 public URLSpan[] getUrls() { 3465 if (mText instanceof Spanned) { 3466 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 3467 } else { 3468 return new URLSpan[0]; 3469 } 3470 } 3471 3472 /** 3473 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 3474 * TextView. 3475 * 3476 * @see #setHintTextColor(ColorStateList) 3477 * @see #getHintTextColors() 3478 * @see #setTextColor(int) 3479 * 3480 * @attr ref android.R.styleable#TextView_textColorHint 3481 */ 3482 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)3483 public final void setHintTextColor(@ColorInt int color) { 3484 mHintTextColor = ColorStateList.valueOf(color); 3485 updateTextColors(); 3486 } 3487 3488 /** 3489 * Sets the color of the hint text. 3490 * 3491 * @see #getHintTextColors() 3492 * @see #setHintTextColor(int) 3493 * @see #setTextColor(ColorStateList) 3494 * @see #setLinkTextColor(ColorStateList) 3495 * 3496 * @attr ref android.R.styleable#TextView_textColorHint 3497 */ setHintTextColor(ColorStateList colors)3498 public final void setHintTextColor(ColorStateList colors) { 3499 mHintTextColor = colors; 3500 updateTextColors(); 3501 } 3502 3503 /** 3504 * @return the color of the hint text, for the different states of this TextView. 3505 * 3506 * @see #setHintTextColor(ColorStateList) 3507 * @see #setHintTextColor(int) 3508 * @see #setTextColor(ColorStateList) 3509 * @see #setLinkTextColor(ColorStateList) 3510 * 3511 * @attr ref android.R.styleable#TextView_textColorHint 3512 */ getHintTextColors()3513 public final ColorStateList getHintTextColors() { 3514 return mHintTextColor; 3515 } 3516 3517 /** 3518 * <p>Return the current color selected to paint the hint text.</p> 3519 * 3520 * @return Returns the current hint text color. 3521 */ 3522 @ColorInt getCurrentHintTextColor()3523 public final int getCurrentHintTextColor() { 3524 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 3525 } 3526 3527 /** 3528 * Sets the color of links in the text. 3529 * 3530 * @see #setLinkTextColor(ColorStateList) 3531 * @see #getLinkTextColors() 3532 * 3533 * @attr ref android.R.styleable#TextView_textColorLink 3534 */ 3535 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)3536 public final void setLinkTextColor(@ColorInt int color) { 3537 mLinkTextColor = ColorStateList.valueOf(color); 3538 updateTextColors(); 3539 } 3540 3541 /** 3542 * Sets the color of links in the text. 3543 * 3544 * @see #setLinkTextColor(int) 3545 * @see #getLinkTextColors() 3546 * @see #setTextColor(ColorStateList) 3547 * @see #setHintTextColor(ColorStateList) 3548 * 3549 * @attr ref android.R.styleable#TextView_textColorLink 3550 */ setLinkTextColor(ColorStateList colors)3551 public final void setLinkTextColor(ColorStateList colors) { 3552 mLinkTextColor = colors; 3553 updateTextColors(); 3554 } 3555 3556 /** 3557 * @return the list of colors used to paint the links in the text, for the different states of 3558 * this TextView 3559 * 3560 * @see #setLinkTextColor(ColorStateList) 3561 * @see #setLinkTextColor(int) 3562 * 3563 * @attr ref android.R.styleable#TextView_textColorLink 3564 */ getLinkTextColors()3565 public final ColorStateList getLinkTextColors() { 3566 return mLinkTextColor; 3567 } 3568 3569 /** 3570 * Sets the horizontal alignment of the text and the 3571 * vertical gravity that will be used when there is extra space 3572 * in the TextView beyond what is required for the text itself. 3573 * 3574 * @see android.view.Gravity 3575 * @attr ref android.R.styleable#TextView_gravity 3576 */ setGravity(int gravity)3577 public void setGravity(int gravity) { 3578 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 3579 gravity |= Gravity.START; 3580 } 3581 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 3582 gravity |= Gravity.TOP; 3583 } 3584 3585 boolean newLayout = false; 3586 3587 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != 3588 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 3589 newLayout = true; 3590 } 3591 3592 if (gravity != mGravity) { 3593 invalidate(); 3594 } 3595 3596 mGravity = gravity; 3597 3598 if (mLayout != null && newLayout) { 3599 // XXX this is heavy-handed because no actual content changes. 3600 int want = mLayout.getWidth(); 3601 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 3602 3603 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 3604 mRight - mLeft - getCompoundPaddingLeft() - 3605 getCompoundPaddingRight(), true); 3606 } 3607 } 3608 3609 /** 3610 * Returns the horizontal and vertical alignment of this TextView. 3611 * 3612 * @see android.view.Gravity 3613 * @attr ref android.R.styleable#TextView_gravity 3614 */ getGravity()3615 public int getGravity() { 3616 return mGravity; 3617 } 3618 3619 /** 3620 * @return the flags on the Paint being used to display the text. 3621 * @see Paint#getFlags 3622 */ getPaintFlags()3623 public int getPaintFlags() { 3624 return mTextPaint.getFlags(); 3625 } 3626 3627 /** 3628 * Sets flags on the Paint being used to display the text and 3629 * reflows the text if they are different from the old flags. 3630 * @see Paint#setFlags 3631 */ 3632 @android.view.RemotableViewMethod setPaintFlags(int flags)3633 public void setPaintFlags(int flags) { 3634 if (mTextPaint.getFlags() != flags) { 3635 mTextPaint.setFlags(flags); 3636 3637 if (mLayout != null) { 3638 nullLayouts(); 3639 requestLayout(); 3640 invalidate(); 3641 } 3642 } 3643 } 3644 3645 /** 3646 * Sets whether the text should be allowed to be wider than the 3647 * View is. If false, it will be wrapped to the width of the View. 3648 * 3649 * @attr ref android.R.styleable#TextView_scrollHorizontally 3650 */ setHorizontallyScrolling(boolean whether)3651 public void setHorizontallyScrolling(boolean whether) { 3652 if (mHorizontallyScrolling != whether) { 3653 mHorizontallyScrolling = whether; 3654 3655 if (mLayout != null) { 3656 nullLayouts(); 3657 requestLayout(); 3658 invalidate(); 3659 } 3660 } 3661 } 3662 3663 /** 3664 * Returns whether the text is allowed to be wider than the View is. 3665 * If false, the text will be wrapped to the width of the View. 3666 * 3667 * @attr ref android.R.styleable#TextView_scrollHorizontally 3668 * @hide 3669 */ getHorizontallyScrolling()3670 public boolean getHorizontallyScrolling() { 3671 return mHorizontallyScrolling; 3672 } 3673 3674 /** 3675 * Makes the TextView at least this many lines tall. 3676 * 3677 * Setting this value overrides any other (minimum) height setting. A single line TextView will 3678 * set this value to 1. 3679 * 3680 * @see #getMinLines() 3681 * 3682 * @attr ref android.R.styleable#TextView_minLines 3683 */ 3684 @android.view.RemotableViewMethod setMinLines(int minlines)3685 public void setMinLines(int minlines) { 3686 mMinimum = minlines; 3687 mMinMode = LINES; 3688 3689 requestLayout(); 3690 invalidate(); 3691 } 3692 3693 /** 3694 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum 3695 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}. 3696 * 3697 * @see #setMinLines(int) 3698 * 3699 * @attr ref android.R.styleable#TextView_minLines 3700 */ getMinLines()3701 public int getMinLines() { 3702 return mMinMode == LINES ? mMinimum : -1; 3703 } 3704 3705 /** 3706 * Makes the TextView at least this many pixels tall. 3707 * 3708 * Setting this value overrides any other (minimum) number of lines setting. 3709 * 3710 * @attr ref android.R.styleable#TextView_minHeight 3711 */ 3712 @android.view.RemotableViewMethod setMinHeight(int minHeight)3713 public void setMinHeight(int minHeight) { 3714 mMinimum = minHeight; 3715 mMinMode = PIXELS; 3716 3717 requestLayout(); 3718 invalidate(); 3719 } 3720 3721 /** 3722 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum 3723 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}. 3724 * 3725 * @see #setMinHeight(int) 3726 * 3727 * @attr ref android.R.styleable#TextView_minHeight 3728 */ getMinHeight()3729 public int getMinHeight() { 3730 return mMinMode == PIXELS ? mMinimum : -1; 3731 } 3732 3733 /** 3734 * Makes the TextView at most this many lines tall. 3735 * 3736 * Setting this value overrides any other (maximum) height setting. 3737 * 3738 * @attr ref android.R.styleable#TextView_maxLines 3739 */ 3740 @android.view.RemotableViewMethod setMaxLines(int maxlines)3741 public void setMaxLines(int maxlines) { 3742 mMaximum = maxlines; 3743 mMaxMode = LINES; 3744 3745 requestLayout(); 3746 invalidate(); 3747 } 3748 3749 /** 3750 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum 3751 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}. 3752 * 3753 * @see #setMaxLines(int) 3754 * 3755 * @attr ref android.R.styleable#TextView_maxLines 3756 */ getMaxLines()3757 public int getMaxLines() { 3758 return mMaxMode == LINES ? mMaximum : -1; 3759 } 3760 3761 /** 3762 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the 3763 * {@link #setMaxLines(int)} method. 3764 * 3765 * Setting this value overrides any other (maximum) number of lines setting. 3766 * 3767 * @attr ref android.R.styleable#TextView_maxHeight 3768 */ 3769 @android.view.RemotableViewMethod setMaxHeight(int maxHeight)3770 public void setMaxHeight(int maxHeight) { 3771 mMaximum = maxHeight; 3772 mMaxMode = PIXELS; 3773 3774 requestLayout(); 3775 invalidate(); 3776 } 3777 3778 /** 3779 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum 3780 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}. 3781 * 3782 * @see #setMaxHeight(int) 3783 * 3784 * @attr ref android.R.styleable#TextView_maxHeight 3785 */ getMaxHeight()3786 public int getMaxHeight() { 3787 return mMaxMode == PIXELS ? mMaximum : -1; 3788 } 3789 3790 /** 3791 * Makes the TextView exactly this many lines tall. 3792 * 3793 * Note that setting this value overrides any other (minimum / maximum) number of lines or 3794 * height setting. A single line TextView will set this value to 1. 3795 * 3796 * @attr ref android.R.styleable#TextView_lines 3797 */ 3798 @android.view.RemotableViewMethod setLines(int lines)3799 public void setLines(int lines) { 3800 mMaximum = mMinimum = lines; 3801 mMaxMode = mMinMode = LINES; 3802 3803 requestLayout(); 3804 invalidate(); 3805 } 3806 3807 /** 3808 * Makes the TextView exactly this many pixels tall. 3809 * You could do the same thing by specifying this number in the 3810 * LayoutParams. 3811 * 3812 * Note that setting this value overrides any other (minimum / maximum) number of lines or 3813 * height setting. 3814 * 3815 * @attr ref android.R.styleable#TextView_height 3816 */ 3817 @android.view.RemotableViewMethod setHeight(int pixels)3818 public void setHeight(int pixels) { 3819 mMaximum = mMinimum = pixels; 3820 mMaxMode = mMinMode = PIXELS; 3821 3822 requestLayout(); 3823 invalidate(); 3824 } 3825 3826 /** 3827 * Makes the TextView at least this many ems wide 3828 * 3829 * @attr ref android.R.styleable#TextView_minEms 3830 */ 3831 @android.view.RemotableViewMethod setMinEms(int minems)3832 public void setMinEms(int minems) { 3833 mMinWidth = minems; 3834 mMinWidthMode = EMS; 3835 3836 requestLayout(); 3837 invalidate(); 3838 } 3839 3840 /** 3841 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width 3842 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}). 3843 * 3844 * @see #setMinEms(int) 3845 * @see #setEms(int) 3846 * 3847 * @attr ref android.R.styleable#TextView_minEms 3848 */ getMinEms()3849 public int getMinEms() { 3850 return mMinWidthMode == EMS ? mMinWidth : -1; 3851 } 3852 3853 /** 3854 * Makes the TextView at least this many pixels wide 3855 * 3856 * @attr ref android.R.styleable#TextView_minWidth 3857 */ 3858 @android.view.RemotableViewMethod setMinWidth(int minpixels)3859 public void setMinWidth(int minpixels) { 3860 mMinWidth = minpixels; 3861 mMinWidthMode = PIXELS; 3862 3863 requestLayout(); 3864 invalidate(); 3865 } 3866 3867 /** 3868 * @return the minimum width of the TextView, in pixels or -1 if the minimum width 3869 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}). 3870 * 3871 * @see #setMinWidth(int) 3872 * @see #setWidth(int) 3873 * 3874 * @attr ref android.R.styleable#TextView_minWidth 3875 */ getMinWidth()3876 public int getMinWidth() { 3877 return mMinWidthMode == PIXELS ? mMinWidth : -1; 3878 } 3879 3880 /** 3881 * Makes the TextView at most this many ems wide 3882 * 3883 * @attr ref android.R.styleable#TextView_maxEms 3884 */ 3885 @android.view.RemotableViewMethod setMaxEms(int maxems)3886 public void setMaxEms(int maxems) { 3887 mMaxWidth = maxems; 3888 mMaxWidthMode = EMS; 3889 3890 requestLayout(); 3891 invalidate(); 3892 } 3893 3894 /** 3895 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width 3896 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}). 3897 * 3898 * @see #setMaxEms(int) 3899 * @see #setEms(int) 3900 * 3901 * @attr ref android.R.styleable#TextView_maxEms 3902 */ getMaxEms()3903 public int getMaxEms() { 3904 return mMaxWidthMode == EMS ? mMaxWidth : -1; 3905 } 3906 3907 /** 3908 * Makes the TextView at most this many pixels wide 3909 * 3910 * @attr ref android.R.styleable#TextView_maxWidth 3911 */ 3912 @android.view.RemotableViewMethod setMaxWidth(int maxpixels)3913 public void setMaxWidth(int maxpixels) { 3914 mMaxWidth = maxpixels; 3915 mMaxWidthMode = PIXELS; 3916 3917 requestLayout(); 3918 invalidate(); 3919 } 3920 3921 /** 3922 * @return the maximum width of the TextView, in pixels or -1 if the maximum width 3923 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}). 3924 * 3925 * @see #setMaxWidth(int) 3926 * @see #setWidth(int) 3927 * 3928 * @attr ref android.R.styleable#TextView_maxWidth 3929 */ getMaxWidth()3930 public int getMaxWidth() { 3931 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 3932 } 3933 3934 /** 3935 * Makes the TextView exactly this many ems wide 3936 * 3937 * @see #setMaxEms(int) 3938 * @see #setMinEms(int) 3939 * @see #getMinEms() 3940 * @see #getMaxEms() 3941 * 3942 * @attr ref android.R.styleable#TextView_ems 3943 */ 3944 @android.view.RemotableViewMethod setEms(int ems)3945 public void setEms(int ems) { 3946 mMaxWidth = mMinWidth = ems; 3947 mMaxWidthMode = mMinWidthMode = EMS; 3948 3949 requestLayout(); 3950 invalidate(); 3951 } 3952 3953 /** 3954 * Makes the TextView exactly this many pixels wide. 3955 * You could do the same thing by specifying this number in the 3956 * LayoutParams. 3957 * 3958 * @see #setMaxWidth(int) 3959 * @see #setMinWidth(int) 3960 * @see #getMinWidth() 3961 * @see #getMaxWidth() 3962 * 3963 * @attr ref android.R.styleable#TextView_width 3964 */ 3965 @android.view.RemotableViewMethod setWidth(int pixels)3966 public void setWidth(int pixels) { 3967 mMaxWidth = mMinWidth = pixels; 3968 mMaxWidthMode = mMinWidthMode = PIXELS; 3969 3970 requestLayout(); 3971 invalidate(); 3972 } 3973 3974 /** 3975 * Sets line spacing for this TextView. Each line will have its height 3976 * multiplied by <code>mult</code> and have <code>add</code> added to it. 3977 * 3978 * @attr ref android.R.styleable#TextView_lineSpacingExtra 3979 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 3980 */ setLineSpacing(float add, float mult)3981 public void setLineSpacing(float add, float mult) { 3982 if (mSpacingAdd != add || mSpacingMult != mult) { 3983 mSpacingAdd = add; 3984 mSpacingMult = mult; 3985 3986 if (mLayout != null) { 3987 nullLayouts(); 3988 requestLayout(); 3989 invalidate(); 3990 } 3991 } 3992 } 3993 3994 /** 3995 * Gets the line spacing multiplier 3996 * 3997 * @return the value by which each line's height is multiplied to get its actual height. 3998 * 3999 * @see #setLineSpacing(float, float) 4000 * @see #getLineSpacingExtra() 4001 * 4002 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 4003 */ getLineSpacingMultiplier()4004 public float getLineSpacingMultiplier() { 4005 return mSpacingMult; 4006 } 4007 4008 /** 4009 * Gets the line spacing extra space 4010 * 4011 * @return the extra space that is added to the height of each lines of this TextView. 4012 * 4013 * @see #setLineSpacing(float, float) 4014 * @see #getLineSpacingMultiplier() 4015 * 4016 * @attr ref android.R.styleable#TextView_lineSpacingExtra 4017 */ getLineSpacingExtra()4018 public float getLineSpacingExtra() { 4019 return mSpacingAdd; 4020 } 4021 4022 /** 4023 * Convenience method: Append the specified text to the TextView's 4024 * display buffer, upgrading it to BufferType.EDITABLE if it was 4025 * not already editable. 4026 */ append(CharSequence text)4027 public final void append(CharSequence text) { 4028 append(text, 0, text.length()); 4029 } 4030 4031 /** 4032 * Convenience method: Append the specified text slice to the TextView's 4033 * display buffer, upgrading it to BufferType.EDITABLE if it was 4034 * not already editable. 4035 */ append(CharSequence text, int start, int end)4036 public void append(CharSequence text, int start, int end) { 4037 if (!(mText instanceof Editable)) { 4038 setText(mText, BufferType.EDITABLE); 4039 } 4040 4041 ((Editable) mText).append(text, start, end); 4042 4043 if (mAutoLinkMask != 0) { 4044 boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask); 4045 // Do not change the movement method for text that support text selection as it 4046 // would prevent an arbitrary cursor displacement. 4047 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 4048 setMovementMethod(LinkMovementMethod.getInstance()); 4049 } 4050 } 4051 } 4052 updateTextColors()4053 private void updateTextColors() { 4054 boolean inval = false; 4055 int color = mTextColor.getColorForState(getDrawableState(), 0); 4056 if (color != mCurTextColor) { 4057 mCurTextColor = color; 4058 inval = true; 4059 } 4060 if (mLinkTextColor != null) { 4061 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 4062 if (color != mTextPaint.linkColor) { 4063 mTextPaint.linkColor = color; 4064 inval = true; 4065 } 4066 } 4067 if (mHintTextColor != null) { 4068 color = mHintTextColor.getColorForState(getDrawableState(), 0); 4069 if (color != mCurHintTextColor) { 4070 mCurHintTextColor = color; 4071 if (mText.length() == 0) { 4072 inval = true; 4073 } 4074 } 4075 } 4076 if (inval) { 4077 // Text needs to be redrawn with the new color 4078 if (mEditor != null) mEditor.invalidateTextDisplayList(); 4079 invalidate(); 4080 } 4081 } 4082 4083 @Override drawableStateChanged()4084 protected void drawableStateChanged() { 4085 super.drawableStateChanged(); 4086 4087 if (mTextColor != null && mTextColor.isStateful() 4088 || (mHintTextColor != null && mHintTextColor.isStateful()) 4089 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 4090 updateTextColors(); 4091 } 4092 4093 if (mDrawables != null) { 4094 final int[] state = getDrawableState(); 4095 for (Drawable dr : mDrawables.mShowing) { 4096 if (dr != null && dr.isStateful() && dr.setState(state)) { 4097 invalidateDrawable(dr); 4098 } 4099 } 4100 } 4101 } 4102 4103 @Override drawableHotspotChanged(float x, float y)4104 public void drawableHotspotChanged(float x, float y) { 4105 super.drawableHotspotChanged(x, y); 4106 4107 if (mDrawables != null) { 4108 for (Drawable dr : mDrawables.mShowing) { 4109 if (dr != null) { 4110 dr.setHotspot(x, y); 4111 } 4112 } 4113 } 4114 } 4115 4116 @Override onSaveInstanceState()4117 public Parcelable onSaveInstanceState() { 4118 Parcelable superState = super.onSaveInstanceState(); 4119 4120 // Save state if we are forced to 4121 final boolean freezesText = getFreezesText(); 4122 boolean hasSelection = false; 4123 int start = -1; 4124 int end = -1; 4125 4126 if (mText != null) { 4127 start = getSelectionStart(); 4128 end = getSelectionEnd(); 4129 if (start >= 0 || end >= 0) { 4130 // Or save state if there is a selection 4131 hasSelection = true; 4132 } 4133 } 4134 4135 if (freezesText || hasSelection) { 4136 SavedState ss = new SavedState(superState); 4137 4138 if (freezesText) { 4139 if (mText instanceof Spanned) { 4140 final Spannable sp = new SpannableStringBuilder(mText); 4141 4142 if (mEditor != null) { 4143 removeMisspelledSpans(sp); 4144 sp.removeSpan(mEditor.mSuggestionRangeSpan); 4145 } 4146 4147 ss.text = sp; 4148 } else { 4149 ss.text = mText.toString(); 4150 } 4151 } 4152 4153 if (hasSelection) { 4154 // XXX Should also save the current scroll position! 4155 ss.selStart = start; 4156 ss.selEnd = end; 4157 } 4158 4159 if (isFocused() && start >= 0 && end >= 0) { 4160 ss.frozenWithFocus = true; 4161 } 4162 4163 ss.error = getError(); 4164 4165 if (mEditor != null) { 4166 ss.editorState = mEditor.saveInstanceState(); 4167 } 4168 return ss; 4169 } 4170 4171 return superState; 4172 } 4173 removeMisspelledSpans(Spannable spannable)4174 void removeMisspelledSpans(Spannable spannable) { 4175 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 4176 SuggestionSpan.class); 4177 for (int i = 0; i < suggestionSpans.length; i++) { 4178 int flags = suggestionSpans[i].getFlags(); 4179 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 4180 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 4181 spannable.removeSpan(suggestionSpans[i]); 4182 } 4183 } 4184 } 4185 4186 @Override onRestoreInstanceState(Parcelable state)4187 public void onRestoreInstanceState(Parcelable state) { 4188 if (!(state instanceof SavedState)) { 4189 super.onRestoreInstanceState(state); 4190 return; 4191 } 4192 4193 SavedState ss = (SavedState)state; 4194 super.onRestoreInstanceState(ss.getSuperState()); 4195 4196 // XXX restore buffer type too, as well as lots of other stuff 4197 if (ss.text != null) { 4198 setText(ss.text); 4199 } 4200 4201 if (ss.selStart >= 0 && ss.selEnd >= 0) { 4202 if (mText instanceof Spannable) { 4203 int len = mText.length(); 4204 4205 if (ss.selStart > len || ss.selEnd > len) { 4206 String restored = ""; 4207 4208 if (ss.text != null) { 4209 restored = "(restored) "; 4210 } 4211 4212 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + 4213 "/" + ss.selEnd + " out of range for " + restored + 4214 "text " + mText); 4215 } else { 4216 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); 4217 4218 if (ss.frozenWithFocus) { 4219 createEditorIfNeeded(); 4220 mEditor.mFrozenWithFocus = true; 4221 } 4222 } 4223 } 4224 } 4225 4226 if (ss.error != null) { 4227 final CharSequence error = ss.error; 4228 // Display the error later, after the first layout pass 4229 post(new Runnable() { 4230 public void run() { 4231 if (mEditor == null || !mEditor.mErrorWasChanged) { 4232 setError(error); 4233 } 4234 } 4235 }); 4236 } 4237 4238 if (ss.editorState != null) { 4239 createEditorIfNeeded(); 4240 mEditor.restoreInstanceState(ss.editorState); 4241 } 4242 } 4243 4244 /** 4245 * Control whether this text view saves its entire text contents when 4246 * freezing to an icicle, in addition to dynamic state such as cursor 4247 * position. By default this is false, not saving the text. Set to true 4248 * if the text in the text view is not being saved somewhere else in 4249 * persistent storage (such as in a content provider) so that if the 4250 * view is later thawed the user will not lose their data. For 4251 * {@link android.widget.EditText} it is always enabled, regardless of 4252 * the value of the attribute. 4253 * 4254 * @param freezesText Controls whether a frozen icicle should include the 4255 * entire text data: true to include it, false to not. 4256 * 4257 * @attr ref android.R.styleable#TextView_freezesText 4258 */ 4259 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)4260 public void setFreezesText(boolean freezesText) { 4261 mFreezesText = freezesText; 4262 } 4263 4264 /** 4265 * Return whether this text view is including its entire text contents 4266 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 4267 * 4268 * @return Returns true if text is included, false if it isn't. 4269 * 4270 * @see #setFreezesText 4271 */ getFreezesText()4272 public boolean getFreezesText() { 4273 return mFreezesText; 4274 } 4275 4276 /////////////////////////////////////////////////////////////////////////// 4277 4278 /** 4279 * Sets the Factory used to create new Editables. 4280 */ setEditableFactory(Editable.Factory factory)4281 public final void setEditableFactory(Editable.Factory factory) { 4282 mEditableFactory = factory; 4283 setText(mText); 4284 } 4285 4286 /** 4287 * Sets the Factory used to create new Spannables. 4288 */ setSpannableFactory(Spannable.Factory factory)4289 public final void setSpannableFactory(Spannable.Factory factory) { 4290 mSpannableFactory = factory; 4291 setText(mText); 4292 } 4293 4294 /** 4295 * Sets the string value of the TextView. TextView <em>does not</em> accept 4296 * HTML-like formatting, which you can do with text strings in XML resource files. 4297 * To style your strings, attach android.text.style.* objects to a 4298 * {@link android.text.SpannableString SpannableString}, or see the 4299 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 4300 * Available Resource Types</a> documentation for an example of setting 4301 * formatted text in the XML resource file. 4302 * 4303 * @attr ref android.R.styleable#TextView_text 4304 */ 4305 @android.view.RemotableViewMethod setText(CharSequence text)4306 public final void setText(CharSequence text) { 4307 setText(text, mBufferType); 4308 } 4309 4310 /** 4311 * Like {@link #setText(CharSequence)}, 4312 * except that the cursor position (if any) is retained in the new text. 4313 * 4314 * @param text The new text to place in the text view. 4315 * 4316 * @see #setText(CharSequence) 4317 */ 4318 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)4319 public final void setTextKeepState(CharSequence text) { 4320 setTextKeepState(text, mBufferType); 4321 } 4322 4323 /** 4324 * Sets the text that this TextView is to display (see 4325 * {@link #setText(CharSequence)}) and also sets whether it is stored 4326 * in a styleable/spannable buffer and whether it is editable. 4327 * 4328 * @attr ref android.R.styleable#TextView_text 4329 * @attr ref android.R.styleable#TextView_bufferType 4330 */ setText(CharSequence text, BufferType type)4331 public void setText(CharSequence text, BufferType type) { 4332 setText(text, type, true, 0); 4333 4334 if (mCharWrapper != null) { 4335 mCharWrapper.mChars = null; 4336 } 4337 } 4338 setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)4339 private void setText(CharSequence text, BufferType type, 4340 boolean notifyBefore, int oldlen) { 4341 if (text == null) { 4342 text = ""; 4343 } 4344 4345 // If suggestions are not enabled, remove the suggestion spans from the text 4346 if (!isSuggestionsEnabled()) { 4347 text = removeSuggestionSpans(text); 4348 } 4349 4350 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 4351 4352 if (text instanceof Spanned && 4353 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 4354 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 4355 setHorizontalFadingEdgeEnabled(true); 4356 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 4357 } else { 4358 setHorizontalFadingEdgeEnabled(false); 4359 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 4360 } 4361 setEllipsize(TextUtils.TruncateAt.MARQUEE); 4362 } 4363 4364 int n = mFilters.length; 4365 for (int i = 0; i < n; i++) { 4366 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 4367 if (out != null) { 4368 text = out; 4369 } 4370 } 4371 4372 if (notifyBefore) { 4373 if (mText != null) { 4374 oldlen = mText.length(); 4375 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 4376 } else { 4377 sendBeforeTextChanged("", 0, 0, text.length()); 4378 } 4379 } 4380 4381 boolean needEditableForNotification = false; 4382 4383 if (mListeners != null && mListeners.size() != 0) { 4384 needEditableForNotification = true; 4385 } 4386 4387 if (type == BufferType.EDITABLE || getKeyListener() != null || 4388 needEditableForNotification) { 4389 createEditorIfNeeded(); 4390 mEditor.forgetUndoRedo(); 4391 Editable t = mEditableFactory.newEditable(text); 4392 text = t; 4393 setFilters(t, mFilters); 4394 InputMethodManager imm = InputMethodManager.peekInstance(); 4395 if (imm != null) imm.restartInput(this); 4396 } else if (type == BufferType.SPANNABLE || mMovement != null) { 4397 text = mSpannableFactory.newSpannable(text); 4398 } else if (!(text instanceof CharWrapper)) { 4399 text = TextUtils.stringOrSpannedString(text); 4400 } 4401 4402 if (mAutoLinkMask != 0) { 4403 Spannable s2; 4404 4405 if (type == BufferType.EDITABLE || text instanceof Spannable) { 4406 s2 = (Spannable) text; 4407 } else { 4408 s2 = mSpannableFactory.newSpannable(text); 4409 } 4410 4411 if (Linkify.addLinks(s2, mAutoLinkMask)) { 4412 text = s2; 4413 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 4414 4415 /* 4416 * We must go ahead and set the text before changing the 4417 * movement method, because setMovementMethod() may call 4418 * setText() again to try to upgrade the buffer type. 4419 */ 4420 mText = text; 4421 4422 // Do not change the movement method for text that support text selection as it 4423 // would prevent an arbitrary cursor displacement. 4424 if (mLinksClickable && !textCanBeSelected()) { 4425 setMovementMethod(LinkMovementMethod.getInstance()); 4426 } 4427 } 4428 } 4429 4430 mBufferType = type; 4431 mText = text; 4432 4433 if (mTransformation == null) { 4434 mTransformed = text; 4435 } else { 4436 mTransformed = mTransformation.getTransformation(text, this); 4437 } 4438 4439 final int textLength = text.length(); 4440 4441 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 4442 Spannable sp = (Spannable) text; 4443 4444 // Remove any ChangeWatchers that might have come from other TextViews. 4445 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 4446 final int count = watchers.length; 4447 for (int i = 0; i < count; i++) { 4448 sp.removeSpan(watchers[i]); 4449 } 4450 4451 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 4452 4453 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 4454 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 4455 4456 if (mEditor != null) mEditor.addSpanWatchers(sp); 4457 4458 if (mTransformation != null) { 4459 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 4460 } 4461 4462 if (mMovement != null) { 4463 mMovement.initialize(this, (Spannable) text); 4464 4465 /* 4466 * Initializing the movement method will have set the 4467 * selection, so reset mSelectionMoved to keep that from 4468 * interfering with the normal on-focus selection-setting. 4469 */ 4470 if (mEditor != null) mEditor.mSelectionMoved = false; 4471 } 4472 } 4473 4474 if (mLayout != null) { 4475 checkForRelayout(); 4476 } 4477 4478 sendOnTextChanged(text, 0, oldlen, textLength); 4479 onTextChanged(text, 0, oldlen, textLength); 4480 4481 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 4482 4483 if (needEditableForNotification) { 4484 sendAfterTextChanged((Editable) text); 4485 } 4486 4487 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 4488 if (mEditor != null) mEditor.prepareCursorControllers(); 4489 } 4490 4491 /** 4492 * Sets the TextView to display the specified slice of the specified 4493 * char array. You must promise that you will not change the contents 4494 * of the array except for right before another call to setText(), 4495 * since the TextView has no way to know that the text 4496 * has changed and that it needs to invalidate and re-layout. 4497 */ setText(char[] text, int start, int len)4498 public final void setText(char[] text, int start, int len) { 4499 int oldlen = 0; 4500 4501 if (start < 0 || len < 0 || start + len > text.length) { 4502 throw new IndexOutOfBoundsException(start + ", " + len); 4503 } 4504 4505 /* 4506 * We must do the before-notification here ourselves because if 4507 * the old text is a CharWrapper we destroy it before calling 4508 * into the normal path. 4509 */ 4510 if (mText != null) { 4511 oldlen = mText.length(); 4512 sendBeforeTextChanged(mText, 0, oldlen, len); 4513 } else { 4514 sendBeforeTextChanged("", 0, 0, len); 4515 } 4516 4517 if (mCharWrapper == null) { 4518 mCharWrapper = new CharWrapper(text, start, len); 4519 } else { 4520 mCharWrapper.set(text, start, len); 4521 } 4522 4523 setText(mCharWrapper, mBufferType, false, oldlen); 4524 } 4525 4526 /** 4527 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 4528 * except that the cursor position (if any) is retained in the new text. 4529 * 4530 * @see #setText(CharSequence, android.widget.TextView.BufferType) 4531 */ setTextKeepState(CharSequence text, BufferType type)4532 public final void setTextKeepState(CharSequence text, BufferType type) { 4533 int start = getSelectionStart(); 4534 int end = getSelectionEnd(); 4535 int len = text.length(); 4536 4537 setText(text, type); 4538 4539 if (start >= 0 || end >= 0) { 4540 if (mText instanceof Spannable) { 4541 Selection.setSelection((Spannable) mText, 4542 Math.max(0, Math.min(start, len)), 4543 Math.max(0, Math.min(end, len))); 4544 } 4545 } 4546 } 4547 4548 @android.view.RemotableViewMethod setText(@tringRes int resid)4549 public final void setText(@StringRes int resid) { 4550 setText(getContext().getResources().getText(resid)); 4551 } 4552 setText(@tringRes int resid, BufferType type)4553 public final void setText(@StringRes int resid, BufferType type) { 4554 setText(getContext().getResources().getText(resid), type); 4555 } 4556 4557 /** 4558 * Sets the text to be displayed when the text of the TextView is empty. 4559 * Null means to use the normal empty text. The hint does not currently 4560 * participate in determining the size of the view. 4561 * 4562 * @attr ref android.R.styleable#TextView_hint 4563 */ 4564 @android.view.RemotableViewMethod setHint(CharSequence hint)4565 public final void setHint(CharSequence hint) { 4566 mHint = TextUtils.stringOrSpannedString(hint); 4567 4568 if (mLayout != null) { 4569 checkForRelayout(); 4570 } 4571 4572 if (mText.length() == 0) { 4573 invalidate(); 4574 } 4575 4576 // Invalidate display list if hint is currently used 4577 if (mEditor != null && mText.length() == 0 && mHint != null) { 4578 mEditor.invalidateTextDisplayList(); 4579 } 4580 } 4581 4582 /** 4583 * Sets the text to be displayed when the text of the TextView is empty, 4584 * from a resource. 4585 * 4586 * @attr ref android.R.styleable#TextView_hint 4587 */ 4588 @android.view.RemotableViewMethod setHint(@tringRes int resid)4589 public final void setHint(@StringRes int resid) { 4590 setHint(getContext().getResources().getText(resid)); 4591 } 4592 4593 /** 4594 * Returns the hint that is displayed when the text of the TextView 4595 * is empty. 4596 * 4597 * @attr ref android.R.styleable#TextView_hint 4598 */ 4599 @ViewDebug.CapturedViewProperty getHint()4600 public CharSequence getHint() { 4601 return mHint; 4602 } 4603 isSingleLine()4604 boolean isSingleLine() { 4605 return mSingleLine; 4606 } 4607 isMultilineInputType(int type)4608 private static boolean isMultilineInputType(int type) { 4609 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 4610 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 4611 } 4612 4613 /** 4614 * Removes the suggestion spans. 4615 */ removeSuggestionSpans(CharSequence text)4616 CharSequence removeSuggestionSpans(CharSequence text) { 4617 if (text instanceof Spanned) { 4618 Spannable spannable; 4619 if (text instanceof Spannable) { 4620 spannable = (Spannable) text; 4621 } else { 4622 spannable = new SpannableString(text); 4623 text = spannable; 4624 } 4625 4626 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 4627 for (int i = 0; i < spans.length; i++) { 4628 spannable.removeSpan(spans[i]); 4629 } 4630 } 4631 return text; 4632 } 4633 4634 /** 4635 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 4636 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 4637 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 4638 * then a soft keyboard will not be displayed for this text view. 4639 * 4640 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 4641 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 4642 * type. 4643 * 4644 * @see #getInputType() 4645 * @see #setRawInputType(int) 4646 * @see android.text.InputType 4647 * @attr ref android.R.styleable#TextView_inputType 4648 */ setInputType(int type)4649 public void setInputType(int type) { 4650 final boolean wasPassword = isPasswordInputType(getInputType()); 4651 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 4652 setInputType(type, false); 4653 final boolean isPassword = isPasswordInputType(type); 4654 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 4655 boolean forceUpdate = false; 4656 if (isPassword) { 4657 setTransformationMethod(PasswordTransformationMethod.getInstance()); 4658 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4659 } else if (isVisiblePassword) { 4660 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4661 forceUpdate = true; 4662 } 4663 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4664 } else if (wasPassword || wasVisiblePassword) { 4665 // not in password mode, clean up typeface and transformation 4666 setTypefaceFromAttrs(null /* fontFamily */, -1, -1); 4667 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4668 forceUpdate = true; 4669 } 4670 } 4671 4672 boolean singleLine = !isMultilineInputType(type); 4673 4674 // We need to update the single line mode if it has changed or we 4675 // were previously in password mode. 4676 if (mSingleLine != singleLine || forceUpdate) { 4677 // Change single line mode, but only change the transformation if 4678 // we are not in password mode. 4679 applySingleLine(singleLine, !isPassword, true); 4680 } 4681 4682 if (!isSuggestionsEnabled()) { 4683 mText = removeSuggestionSpans(mText); 4684 } 4685 4686 InputMethodManager imm = InputMethodManager.peekInstance(); 4687 if (imm != null) imm.restartInput(this); 4688 } 4689 4690 /** 4691 * It would be better to rely on the input type for everything. A password inputType should have 4692 * a password transformation. We should hence use isPasswordInputType instead of this method. 4693 * 4694 * We should: 4695 * - Call setInputType in setKeyListener instead of changing the input type directly (which 4696 * would install the correct transformation). 4697 * - Refuse the installation of a non-password transformation in setTransformation if the input 4698 * type is password. 4699 * 4700 * However, this is like this for legacy reasons and we cannot break existing apps. This method 4701 * is useful since it matches what the user can see (obfuscated text or not). 4702 * 4703 * @return true if the current transformation method is of the password type. 4704 */ hasPasswordTransformationMethod()4705 boolean hasPasswordTransformationMethod() { 4706 return mTransformation instanceof PasswordTransformationMethod; 4707 } 4708 isPasswordInputType(int inputType)4709 private static boolean isPasswordInputType(int inputType) { 4710 final int variation = 4711 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4712 return variation 4713 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 4714 || variation 4715 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 4716 || variation 4717 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 4718 } 4719 isVisiblePasswordInputType(int inputType)4720 private static boolean isVisiblePasswordInputType(int inputType) { 4721 final int variation = 4722 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4723 return variation 4724 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 4725 } 4726 4727 /** 4728 * Directly change the content type integer of the text view, without 4729 * modifying any other state. 4730 * @see #setInputType(int) 4731 * @see android.text.InputType 4732 * @attr ref android.R.styleable#TextView_inputType 4733 */ setRawInputType(int type)4734 public void setRawInputType(int type) { 4735 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 4736 createEditorIfNeeded(); 4737 mEditor.mInputType = type; 4738 } 4739 setInputType(int type, boolean direct)4740 private void setInputType(int type, boolean direct) { 4741 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 4742 KeyListener input; 4743 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 4744 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 4745 TextKeyListener.Capitalize cap; 4746 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 4747 cap = TextKeyListener.Capitalize.CHARACTERS; 4748 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 4749 cap = TextKeyListener.Capitalize.WORDS; 4750 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 4751 cap = TextKeyListener.Capitalize.SENTENCES; 4752 } else { 4753 cap = TextKeyListener.Capitalize.NONE; 4754 } 4755 input = TextKeyListener.getInstance(autotext, cap); 4756 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 4757 input = DigitsKeyListener.getInstance( 4758 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 4759 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 4760 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 4761 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 4762 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 4763 input = DateKeyListener.getInstance(); 4764 break; 4765 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 4766 input = TimeKeyListener.getInstance(); 4767 break; 4768 default: 4769 input = DateTimeKeyListener.getInstance(); 4770 break; 4771 } 4772 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 4773 input = DialerKeyListener.getInstance(); 4774 } else { 4775 input = TextKeyListener.getInstance(); 4776 } 4777 setRawInputType(type); 4778 if (direct) { 4779 createEditorIfNeeded(); 4780 mEditor.mKeyListener = input; 4781 } else { 4782 setKeyListenerOnly(input); 4783 } 4784 } 4785 4786 /** 4787 * Get the type of the editable content. 4788 * 4789 * @see #setInputType(int) 4790 * @see android.text.InputType 4791 */ getInputType()4792 public int getInputType() { 4793 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 4794 } 4795 4796 /** 4797 * Change the editor type integer associated with the text view, which 4798 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 4799 * has focus. 4800 * @see #getImeOptions 4801 * @see android.view.inputmethod.EditorInfo 4802 * @attr ref android.R.styleable#TextView_imeOptions 4803 */ setImeOptions(int imeOptions)4804 public void setImeOptions(int imeOptions) { 4805 createEditorIfNeeded(); 4806 mEditor.createInputContentTypeIfNeeded(); 4807 mEditor.mInputContentType.imeOptions = imeOptions; 4808 } 4809 4810 /** 4811 * Get the type of the IME editor. 4812 * 4813 * @see #setImeOptions(int) 4814 * @see android.view.inputmethod.EditorInfo 4815 */ getImeOptions()4816 public int getImeOptions() { 4817 return mEditor != null && mEditor.mInputContentType != null 4818 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 4819 } 4820 4821 /** 4822 * Change the custom IME action associated with the text view, which 4823 * will be reported to an IME with {@link EditorInfo#actionLabel} 4824 * and {@link EditorInfo#actionId} when it has focus. 4825 * @see #getImeActionLabel 4826 * @see #getImeActionId 4827 * @see android.view.inputmethod.EditorInfo 4828 * @attr ref android.R.styleable#TextView_imeActionLabel 4829 * @attr ref android.R.styleable#TextView_imeActionId 4830 */ setImeActionLabel(CharSequence label, int actionId)4831 public void setImeActionLabel(CharSequence label, int actionId) { 4832 createEditorIfNeeded(); 4833 mEditor.createInputContentTypeIfNeeded(); 4834 mEditor.mInputContentType.imeActionLabel = label; 4835 mEditor.mInputContentType.imeActionId = actionId; 4836 } 4837 4838 /** 4839 * Get the IME action label previous set with {@link #setImeActionLabel}. 4840 * 4841 * @see #setImeActionLabel 4842 * @see android.view.inputmethod.EditorInfo 4843 */ getImeActionLabel()4844 public CharSequence getImeActionLabel() { 4845 return mEditor != null && mEditor.mInputContentType != null 4846 ? mEditor.mInputContentType.imeActionLabel : null; 4847 } 4848 4849 /** 4850 * Get the IME action ID previous set with {@link #setImeActionLabel}. 4851 * 4852 * @see #setImeActionLabel 4853 * @see android.view.inputmethod.EditorInfo 4854 */ getImeActionId()4855 public int getImeActionId() { 4856 return mEditor != null && mEditor.mInputContentType != null 4857 ? mEditor.mInputContentType.imeActionId : 0; 4858 } 4859 4860 /** 4861 * Set a special listener to be called when an action is performed 4862 * on the text view. This will be called when the enter key is pressed, 4863 * or when an action supplied to the IME is selected by the user. Setting 4864 * this means that the normal hard key event will not insert a newline 4865 * into the text view, even if it is multi-line; holding down the ALT 4866 * modifier will, however, allow the user to insert a newline character. 4867 */ setOnEditorActionListener(OnEditorActionListener l)4868 public void setOnEditorActionListener(OnEditorActionListener l) { 4869 createEditorIfNeeded(); 4870 mEditor.createInputContentTypeIfNeeded(); 4871 mEditor.mInputContentType.onEditorActionListener = l; 4872 } 4873 4874 /** 4875 * Called when an attached input method calls 4876 * {@link InputConnection#performEditorAction(int) 4877 * InputConnection.performEditorAction()} 4878 * for this text view. The default implementation will call your action 4879 * listener supplied to {@link #setOnEditorActionListener}, or perform 4880 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 4881 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 4882 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 4883 * EditorInfo.IME_ACTION_DONE}. 4884 * 4885 * <p>For backwards compatibility, if no IME options have been set and the 4886 * text view would not normally advance focus on enter, then 4887 * the NEXT and DONE actions received here will be turned into an enter 4888 * key down/up pair to go through the normal key handling. 4889 * 4890 * @param actionCode The code of the action being performed. 4891 * 4892 * @see #setOnEditorActionListener 4893 */ onEditorAction(int actionCode)4894 public void onEditorAction(int actionCode) { 4895 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 4896 if (ict != null) { 4897 if (ict.onEditorActionListener != null) { 4898 if (ict.onEditorActionListener.onEditorAction(this, 4899 actionCode, null)) { 4900 return; 4901 } 4902 } 4903 4904 // This is the handling for some default action. 4905 // Note that for backwards compatibility we don't do this 4906 // default handling if explicit ime options have not been given, 4907 // instead turning this into the normal enter key codes that an 4908 // app may be expecting. 4909 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 4910 View v = focusSearch(FOCUS_FORWARD); 4911 if (v != null) { 4912 if (!v.requestFocus(FOCUS_FORWARD)) { 4913 throw new IllegalStateException("focus search returned a view " + 4914 "that wasn't able to take focus!"); 4915 } 4916 } 4917 return; 4918 4919 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 4920 View v = focusSearch(FOCUS_BACKWARD); 4921 if (v != null) { 4922 if (!v.requestFocus(FOCUS_BACKWARD)) { 4923 throw new IllegalStateException("focus search returned a view " + 4924 "that wasn't able to take focus!"); 4925 } 4926 } 4927 return; 4928 4929 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 4930 InputMethodManager imm = InputMethodManager.peekInstance(); 4931 if (imm != null && imm.isActive(this)) { 4932 imm.hideSoftInputFromWindow(getWindowToken(), 0); 4933 } 4934 return; 4935 } 4936 } 4937 4938 ViewRootImpl viewRootImpl = getViewRootImpl(); 4939 if (viewRootImpl != null) { 4940 long eventTime = SystemClock.uptimeMillis(); 4941 viewRootImpl.dispatchKeyFromIme( 4942 new KeyEvent(eventTime, eventTime, 4943 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 4944 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4945 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4946 | KeyEvent.FLAG_EDITOR_ACTION)); 4947 viewRootImpl.dispatchKeyFromIme( 4948 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 4949 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 4950 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4951 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4952 | KeyEvent.FLAG_EDITOR_ACTION)); 4953 } 4954 } 4955 4956 /** 4957 * Set the private content type of the text, which is the 4958 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 4959 * field that will be filled in when creating an input connection. 4960 * 4961 * @see #getPrivateImeOptions() 4962 * @see EditorInfo#privateImeOptions 4963 * @attr ref android.R.styleable#TextView_privateImeOptions 4964 */ setPrivateImeOptions(String type)4965 public void setPrivateImeOptions(String type) { 4966 createEditorIfNeeded(); 4967 mEditor.createInputContentTypeIfNeeded(); 4968 mEditor.mInputContentType.privateImeOptions = type; 4969 } 4970 4971 /** 4972 * Get the private type of the content. 4973 * 4974 * @see #setPrivateImeOptions(String) 4975 * @see EditorInfo#privateImeOptions 4976 */ getPrivateImeOptions()4977 public String getPrivateImeOptions() { 4978 return mEditor != null && mEditor.mInputContentType != null 4979 ? mEditor.mInputContentType.privateImeOptions : null; 4980 } 4981 4982 /** 4983 * Set the extra input data of the text, which is the 4984 * {@link EditorInfo#extras TextBoxAttribute.extras} 4985 * Bundle that will be filled in when creating an input connection. The 4986 * given integer is the resource ID of an XML resource holding an 4987 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 4988 * 4989 * @see #getInputExtras(boolean) 4990 * @see EditorInfo#extras 4991 * @attr ref android.R.styleable#TextView_editorExtras 4992 */ setInputExtras(@mlRes int xmlResId)4993 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 4994 createEditorIfNeeded(); 4995 XmlResourceParser parser = getResources().getXml(xmlResId); 4996 mEditor.createInputContentTypeIfNeeded(); 4997 mEditor.mInputContentType.extras = new Bundle(); 4998 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 4999 } 5000 5001 /** 5002 * Retrieve the input extras currently associated with the text view, which 5003 * can be viewed as well as modified. 5004 * 5005 * @param create If true, the extras will be created if they don't already 5006 * exist. Otherwise, null will be returned if none have been created. 5007 * @see #setInputExtras(int) 5008 * @see EditorInfo#extras 5009 * @attr ref android.R.styleable#TextView_editorExtras 5010 */ getInputExtras(boolean create)5011 public Bundle getInputExtras(boolean create) { 5012 if (mEditor == null && !create) return null; 5013 createEditorIfNeeded(); 5014 if (mEditor.mInputContentType == null) { 5015 if (!create) return null; 5016 mEditor.createInputContentTypeIfNeeded(); 5017 } 5018 if (mEditor.mInputContentType.extras == null) { 5019 if (!create) return null; 5020 mEditor.mInputContentType.extras = new Bundle(); 5021 } 5022 return mEditor.mInputContentType.extras; 5023 } 5024 5025 /** 5026 * Change "hint" locales associated with the text view, which will be reported to an IME with 5027 * {@link EditorInfo#hintLocales} when it has focus. 5028 * 5029 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 5030 * call {@link InputMethodManager#restartInput(View)}.</p> 5031 * @param hintLocales List of the languages that the user is supposed to switch to no matter 5032 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 5033 * @see #getImeHIntLocales() 5034 * @see android.view.inputmethod.EditorInfo#hintLocales 5035 */ setImeHintLocales(@ullable LocaleList hintLocales)5036 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 5037 createEditorIfNeeded(); 5038 mEditor.createInputContentTypeIfNeeded(); 5039 mEditor.mInputContentType.imeHintLocales = hintLocales; 5040 } 5041 5042 /** 5043 * @return The current languages list "hint". {@code null} when no "hint" is available. 5044 * @see #setImeHintLocales(LocaleList) 5045 * @see android.view.inputmethod.EditorInfo#hintLocales 5046 */ 5047 @Nullable getImeHintLocales()5048 public LocaleList getImeHintLocales() { 5049 if (mEditor == null) { return null; } 5050 if (mEditor.mInputContentType == null) { return null; } 5051 return mEditor.mInputContentType.imeHintLocales; 5052 } 5053 5054 /** 5055 * Returns the error message that was set to be displayed with 5056 * {@link #setError}, or <code>null</code> if no error was set 5057 * or if it the error was cleared by the widget after user input. 5058 */ getError()5059 public CharSequence getError() { 5060 return mEditor == null ? null : mEditor.mError; 5061 } 5062 5063 /** 5064 * Sets the right-hand compound drawable of the TextView to the "error" 5065 * icon and sets an error message that will be displayed in a popup when 5066 * the TextView has focus. The icon and error message will be reset to 5067 * null when any key events cause changes to the TextView's text. If the 5068 * <code>error</code> is <code>null</code>, the error message and icon 5069 * will be cleared. 5070 */ 5071 @android.view.RemotableViewMethod setError(CharSequence error)5072 public void setError(CharSequence error) { 5073 if (error == null) { 5074 setError(null, null); 5075 } else { 5076 Drawable dr = getContext().getDrawable( 5077 com.android.internal.R.drawable.indicator_input_error); 5078 5079 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 5080 setError(error, dr); 5081 } 5082 } 5083 5084 /** 5085 * Sets the right-hand compound drawable of the TextView to the specified 5086 * icon and sets an error message that will be displayed in a popup when 5087 * the TextView has focus. The icon and error message will be reset to 5088 * null when any key events cause changes to the TextView's text. The 5089 * drawable must already have had {@link Drawable#setBounds} set on it. 5090 * If the <code>error</code> is <code>null</code>, the error message will 5091 * be cleared (and you should provide a <code>null</code> icon as well). 5092 */ setError(CharSequence error, Drawable icon)5093 public void setError(CharSequence error, Drawable icon) { 5094 createEditorIfNeeded(); 5095 mEditor.setError(error, icon); 5096 notifyViewAccessibilityStateChangedIfNeeded( 5097 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 5098 } 5099 5100 @Override setFrame(int l, int t, int r, int b)5101 protected boolean setFrame(int l, int t, int r, int b) { 5102 boolean result = super.setFrame(l, t, r, b); 5103 5104 if (mEditor != null) mEditor.setFrame(); 5105 5106 restartMarqueeIfNeeded(); 5107 5108 return result; 5109 } 5110 restartMarqueeIfNeeded()5111 private void restartMarqueeIfNeeded() { 5112 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5113 mRestartMarquee = false; 5114 startMarquee(); 5115 } 5116 } 5117 5118 /** 5119 * Sets the list of input filters that will be used if the buffer is 5120 * Editable. Has no effect otherwise. 5121 * 5122 * @attr ref android.R.styleable#TextView_maxLength 5123 */ setFilters(InputFilter[] filters)5124 public void setFilters(InputFilter[] filters) { 5125 if (filters == null) { 5126 throw new IllegalArgumentException(); 5127 } 5128 5129 mFilters = filters; 5130 5131 if (mText instanceof Editable) { 5132 setFilters((Editable) mText, filters); 5133 } 5134 } 5135 5136 /** 5137 * Sets the list of input filters on the specified Editable, 5138 * and includes mInput in the list if it is an InputFilter. 5139 */ setFilters(Editable e, InputFilter[] filters)5140 private void setFilters(Editable e, InputFilter[] filters) { 5141 if (mEditor != null) { 5142 final boolean undoFilter = mEditor.mUndoInputFilter != null; 5143 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 5144 int num = 0; 5145 if (undoFilter) num++; 5146 if (keyFilter) num++; 5147 if (num > 0) { 5148 InputFilter[] nf = new InputFilter[filters.length + num]; 5149 5150 System.arraycopy(filters, 0, nf, 0, filters.length); 5151 num = 0; 5152 if (undoFilter) { 5153 nf[filters.length] = mEditor.mUndoInputFilter; 5154 num++; 5155 } 5156 if (keyFilter) { 5157 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 5158 } 5159 5160 e.setFilters(nf); 5161 return; 5162 } 5163 } 5164 e.setFilters(filters); 5165 } 5166 5167 /** 5168 * Returns the current list of input filters. 5169 * 5170 * @attr ref android.R.styleable#TextView_maxLength 5171 */ getFilters()5172 public InputFilter[] getFilters() { 5173 return mFilters; 5174 } 5175 5176 ///////////////////////////////////////////////////////////////////////// 5177 getBoxHeight(Layout l)5178 private int getBoxHeight(Layout l) { 5179 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 5180 int padding = (l == mHintLayout) ? 5181 getCompoundPaddingTop() + getCompoundPaddingBottom() : 5182 getExtendedPaddingTop() + getExtendedPaddingBottom(); 5183 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 5184 } 5185 getVerticalOffset(boolean forceNormal)5186 int getVerticalOffset(boolean forceNormal) { 5187 int voffset = 0; 5188 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 5189 5190 Layout l = mLayout; 5191 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 5192 l = mHintLayout; 5193 } 5194 5195 if (gravity != Gravity.TOP) { 5196 int boxht = getBoxHeight(l); 5197 int textht = l.getHeight(); 5198 5199 if (textht < boxht) { 5200 if (gravity == Gravity.BOTTOM) 5201 voffset = boxht - textht; 5202 else // (gravity == Gravity.CENTER_VERTICAL) 5203 voffset = (boxht - textht) >> 1; 5204 } 5205 } 5206 return voffset; 5207 } 5208 getBottomVerticalOffset(boolean forceNormal)5209 private int getBottomVerticalOffset(boolean forceNormal) { 5210 int voffset = 0; 5211 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 5212 5213 Layout l = mLayout; 5214 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 5215 l = mHintLayout; 5216 } 5217 5218 if (gravity != Gravity.BOTTOM) { 5219 int boxht = getBoxHeight(l); 5220 int textht = l.getHeight(); 5221 5222 if (textht < boxht) { 5223 if (gravity == Gravity.TOP) 5224 voffset = boxht - textht; 5225 else // (gravity == Gravity.CENTER_VERTICAL) 5226 voffset = (boxht - textht) >> 1; 5227 } 5228 } 5229 return voffset; 5230 } 5231 invalidateCursorPath()5232 void invalidateCursorPath() { 5233 if (mHighlightPathBogus) { 5234 invalidateCursor(); 5235 } else { 5236 final int horizontalPadding = getCompoundPaddingLeft(); 5237 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 5238 5239 if (mEditor.mCursorCount == 0) { 5240 synchronized (TEMP_RECTF) { 5241 /* 5242 * The reason for this concern about the thickness of the 5243 * cursor and doing the floor/ceil on the coordinates is that 5244 * some EditTexts (notably textfields in the Browser) have 5245 * anti-aliased text where not all the characters are 5246 * necessarily at integer-multiple locations. This should 5247 * make sure the entire cursor gets invalidated instead of 5248 * sometimes missing half a pixel. 5249 */ 5250 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 5251 if (thick < 1.0f) { 5252 thick = 1.0f; 5253 } 5254 5255 thick /= 2.0f; 5256 5257 // mHighlightPath is guaranteed to be non null at that point. 5258 mHighlightPath.computeBounds(TEMP_RECTF, false); 5259 5260 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 5261 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 5262 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 5263 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 5264 } 5265 } else { 5266 for (int i = 0; i < mEditor.mCursorCount; i++) { 5267 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 5268 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 5269 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 5270 } 5271 } 5272 } 5273 } 5274 invalidateCursor()5275 void invalidateCursor() { 5276 int where = getSelectionEnd(); 5277 5278 invalidateCursor(where, where, where); 5279 } 5280 invalidateCursor(int a, int b, int c)5281 private void invalidateCursor(int a, int b, int c) { 5282 if (a >= 0 || b >= 0 || c >= 0) { 5283 int start = Math.min(Math.min(a, b), c); 5284 int end = Math.max(Math.max(a, b), c); 5285 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 5286 } 5287 } 5288 5289 /** 5290 * Invalidates the region of text enclosed between the start and end text offsets. 5291 */ invalidateRegion(int start, int end, boolean invalidateCursor)5292 void invalidateRegion(int start, int end, boolean invalidateCursor) { 5293 if (mLayout == null) { 5294 invalidate(); 5295 } else { 5296 int lineStart = mLayout.getLineForOffset(start); 5297 int top = mLayout.getLineTop(lineStart); 5298 5299 // This is ridiculous, but the descent from the line above 5300 // can hang down into the line we really want to redraw, 5301 // so we have to invalidate part of the line above to make 5302 // sure everything that needs to be redrawn really is. 5303 // (But not the whole line above, because that would cause 5304 // the same problem with the descenders on the line above it!) 5305 if (lineStart > 0) { 5306 top -= mLayout.getLineDescent(lineStart - 1); 5307 } 5308 5309 int lineEnd; 5310 5311 if (start == end) 5312 lineEnd = lineStart; 5313 else 5314 lineEnd = mLayout.getLineForOffset(end); 5315 5316 int bottom = mLayout.getLineBottom(lineEnd); 5317 5318 // mEditor can be null in case selection is set programmatically. 5319 if (invalidateCursor && mEditor != null) { 5320 for (int i = 0; i < mEditor.mCursorCount; i++) { 5321 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 5322 top = Math.min(top, bounds.top); 5323 bottom = Math.max(bottom, bounds.bottom); 5324 } 5325 } 5326 5327 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5328 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 5329 5330 int left, right; 5331 if (lineStart == lineEnd && !invalidateCursor) { 5332 left = (int) mLayout.getPrimaryHorizontal(start); 5333 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 5334 left += compoundPaddingLeft; 5335 right += compoundPaddingLeft; 5336 } else { 5337 // Rectangle bounding box when the region spans several lines 5338 left = compoundPaddingLeft; 5339 right = getWidth() - getCompoundPaddingRight(); 5340 } 5341 5342 invalidate(mScrollX + left, verticalPadding + top, 5343 mScrollX + right, verticalPadding + bottom); 5344 } 5345 } 5346 registerForPreDraw()5347 private void registerForPreDraw() { 5348 if (!mPreDrawRegistered) { 5349 getViewTreeObserver().addOnPreDrawListener(this); 5350 mPreDrawRegistered = true; 5351 } 5352 } 5353 unregisterForPreDraw()5354 private void unregisterForPreDraw() { 5355 getViewTreeObserver().removeOnPreDrawListener(this); 5356 mPreDrawRegistered = false; 5357 mPreDrawListenerDetached = false; 5358 } 5359 5360 /** 5361 * {@inheritDoc} 5362 */ onPreDraw()5363 public boolean onPreDraw() { 5364 if (mLayout == null) { 5365 assumeLayout(); 5366 } 5367 5368 if (mMovement != null) { 5369 /* This code also provides auto-scrolling when a cursor is moved using a 5370 * CursorController (insertion point or selection limits). 5371 * For selection, ensure start or end is visible depending on controller's state. 5372 */ 5373 int curs = getSelectionEnd(); 5374 // Do not create the controller if it is not already created. 5375 if (mEditor != null && mEditor.mSelectionModifierCursorController != null && 5376 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 5377 curs = getSelectionStart(); 5378 } 5379 5380 /* 5381 * TODO: This should really only keep the end in view if 5382 * it already was before the text changed. I'm not sure 5383 * of a good way to tell from here if it was. 5384 */ 5385 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 5386 curs = mText.length(); 5387 } 5388 5389 if (curs >= 0) { 5390 bringPointIntoView(curs); 5391 } 5392 } else { 5393 bringTextIntoView(); 5394 } 5395 5396 // This has to be checked here since: 5397 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 5398 // a screen rotation) since layout is not yet initialized at that point. 5399 if (mEditor != null && mEditor.mCreatedWithASelection) { 5400 mEditor.refreshTextActionMode(); 5401 mEditor.mCreatedWithASelection = false; 5402 } 5403 5404 unregisterForPreDraw(); 5405 5406 return true; 5407 } 5408 5409 @Override onAttachedToWindow()5410 protected void onAttachedToWindow() { 5411 super.onAttachedToWindow(); 5412 5413 if (mEditor != null) mEditor.onAttachedToWindow(); 5414 5415 if (mPreDrawListenerDetached) { 5416 getViewTreeObserver().addOnPreDrawListener(this); 5417 mPreDrawListenerDetached = false; 5418 } 5419 } 5420 5421 /** @hide */ 5422 @Override onDetachedFromWindowInternal()5423 protected void onDetachedFromWindowInternal() { 5424 if (mPreDrawRegistered) { 5425 getViewTreeObserver().removeOnPreDrawListener(this); 5426 mPreDrawListenerDetached = true; 5427 } 5428 5429 resetResolvedDrawables(); 5430 5431 if (mEditor != null) mEditor.onDetachedFromWindow(); 5432 5433 super.onDetachedFromWindowInternal(); 5434 } 5435 5436 @Override onScreenStateChanged(int screenState)5437 public void onScreenStateChanged(int screenState) { 5438 super.onScreenStateChanged(screenState); 5439 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 5440 } 5441 5442 @Override isPaddingOffsetRequired()5443 protected boolean isPaddingOffsetRequired() { 5444 return mShadowRadius != 0 || mDrawables != null; 5445 } 5446 5447 @Override getLeftPaddingOffset()5448 protected int getLeftPaddingOffset() { 5449 return getCompoundPaddingLeft() - mPaddingLeft + 5450 (int) Math.min(0, mShadowDx - mShadowRadius); 5451 } 5452 5453 @Override getTopPaddingOffset()5454 protected int getTopPaddingOffset() { 5455 return (int) Math.min(0, mShadowDy - mShadowRadius); 5456 } 5457 5458 @Override getBottomPaddingOffset()5459 protected int getBottomPaddingOffset() { 5460 return (int) Math.max(0, mShadowDy + mShadowRadius); 5461 } 5462 5463 @Override getRightPaddingOffset()5464 protected int getRightPaddingOffset() { 5465 return -(getCompoundPaddingRight() - mPaddingRight) + 5466 (int) Math.max(0, mShadowDx + mShadowRadius); 5467 } 5468 5469 @Override verifyDrawable(@onNull Drawable who)5470 protected boolean verifyDrawable(@NonNull Drawable who) { 5471 final boolean verified = super.verifyDrawable(who); 5472 if (!verified && mDrawables != null) { 5473 for (Drawable dr : mDrawables.mShowing) { 5474 if (who == dr) { 5475 return true; 5476 } 5477 } 5478 } 5479 return verified; 5480 } 5481 5482 @Override jumpDrawablesToCurrentState()5483 public void jumpDrawablesToCurrentState() { 5484 super.jumpDrawablesToCurrentState(); 5485 if (mDrawables != null) { 5486 for (Drawable dr : mDrawables.mShowing) { 5487 if (dr != null) { 5488 dr.jumpToCurrentState(); 5489 } 5490 } 5491 } 5492 } 5493 5494 @Override invalidateDrawable(@onNull Drawable drawable)5495 public void invalidateDrawable(@NonNull Drawable drawable) { 5496 boolean handled = false; 5497 5498 if (verifyDrawable(drawable)) { 5499 final Rect dirty = drawable.getBounds(); 5500 int scrollX = mScrollX; 5501 int scrollY = mScrollY; 5502 5503 // IMPORTANT: The coordinates below are based on the coordinates computed 5504 // for each compound drawable in onDraw(). Make sure to update each section 5505 // accordingly. 5506 final TextView.Drawables drawables = mDrawables; 5507 if (drawables != null) { 5508 if (drawable == drawables.mShowing[Drawables.LEFT]) { 5509 final int compoundPaddingTop = getCompoundPaddingTop(); 5510 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5511 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5512 5513 scrollX += mPaddingLeft; 5514 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 5515 handled = true; 5516 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 5517 final int compoundPaddingTop = getCompoundPaddingTop(); 5518 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5519 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5520 5521 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 5522 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 5523 handled = true; 5524 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 5525 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5526 final int compoundPaddingRight = getCompoundPaddingRight(); 5527 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5528 5529 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 5530 scrollY += mPaddingTop; 5531 handled = true; 5532 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 5533 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5534 final int compoundPaddingRight = getCompoundPaddingRight(); 5535 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5536 5537 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 5538 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 5539 handled = true; 5540 } 5541 } 5542 5543 if (handled) { 5544 invalidate(dirty.left + scrollX, dirty.top + scrollY, 5545 dirty.right + scrollX, dirty.bottom + scrollY); 5546 } 5547 } 5548 5549 if (!handled) { 5550 super.invalidateDrawable(drawable); 5551 } 5552 } 5553 5554 @Override hasOverlappingRendering()5555 public boolean hasOverlappingRendering() { 5556 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 5557 return ((getBackground() != null && getBackground().getCurrent() != null) 5558 || mText instanceof Spannable || hasSelection() 5559 || isHorizontalFadingEdgeEnabled()); 5560 } 5561 5562 /** 5563 * 5564 * Returns the state of the {@code textIsSelectable} flag (See 5565 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 5566 * to allow users to select and copy text in a non-editable TextView, the content of an 5567 * {@link EditText} can always be selected, independently of the value of this flag. 5568 * <p> 5569 * 5570 * @return True if the text displayed in this TextView can be selected by the user. 5571 * 5572 * @attr ref android.R.styleable#TextView_textIsSelectable 5573 */ isTextSelectable()5574 public boolean isTextSelectable() { 5575 return mEditor == null ? false : mEditor.mTextIsSelectable; 5576 } 5577 5578 /** 5579 * Sets whether the content of this view is selectable by the user. The default is 5580 * {@code false}, meaning that the content is not selectable. 5581 * <p> 5582 * When you use a TextView to display a useful piece of information to the user (such as a 5583 * contact's address), make it selectable, so that the user can select and copy its 5584 * content. You can also use set the XML attribute 5585 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 5586 * <p> 5587 * When you call this method to set the value of {@code textIsSelectable}, it sets 5588 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 5589 * and {@code longClickable} to the same value. These flags correspond to the attributes 5590 * {@link android.R.styleable#View_focusable android:focusable}, 5591 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 5592 * {@link android.R.styleable#View_clickable android:clickable}, and 5593 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 5594 * flags to a state you had set previously, call one or more of the following methods: 5595 * {@link #setFocusable(boolean) setFocusable()}, 5596 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 5597 * {@link #setClickable(boolean) setClickable()} or 5598 * {@link #setLongClickable(boolean) setLongClickable()}. 5599 * 5600 * @param selectable Whether the content of this TextView should be selectable. 5601 */ setTextIsSelectable(boolean selectable)5602 public void setTextIsSelectable(boolean selectable) { 5603 if (!selectable && mEditor == null) return; // false is default value with no edit data 5604 5605 createEditorIfNeeded(); 5606 if (mEditor.mTextIsSelectable == selectable) return; 5607 5608 mEditor.mTextIsSelectable = selectable; 5609 setFocusableInTouchMode(selectable); 5610 setFocusable(selectable); 5611 setClickable(selectable); 5612 setLongClickable(selectable); 5613 5614 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 5615 5616 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 5617 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 5618 5619 // Called by setText above, but safer in case of future code changes 5620 mEditor.prepareCursorControllers(); 5621 } 5622 5623 @Override onCreateDrawableState(int extraSpace)5624 protected int[] onCreateDrawableState(int extraSpace) { 5625 final int[] drawableState; 5626 5627 if (mSingleLine) { 5628 drawableState = super.onCreateDrawableState(extraSpace); 5629 } else { 5630 drawableState = super.onCreateDrawableState(extraSpace + 1); 5631 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 5632 } 5633 5634 if (isTextSelectable()) { 5635 // Disable pressed state, which was introduced when TextView was made clickable. 5636 // Prevents text color change. 5637 // setClickable(false) would have a similar effect, but it also disables focus changes 5638 // and long press actions, which are both needed by text selection. 5639 final int length = drawableState.length; 5640 for (int i = 0; i < length; i++) { 5641 if (drawableState[i] == R.attr.state_pressed) { 5642 final int[] nonPressedState = new int[length - 1]; 5643 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 5644 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 5645 return nonPressedState; 5646 } 5647 } 5648 } 5649 5650 return drawableState; 5651 } 5652 getUpdatedHighlightPath()5653 private Path getUpdatedHighlightPath() { 5654 Path highlight = null; 5655 Paint highlightPaint = mHighlightPaint; 5656 5657 final int selStart = getSelectionStart(); 5658 final int selEnd = getSelectionEnd(); 5659 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 5660 if (selStart == selEnd) { 5661 if (mEditor != null && mEditor.isCursorVisible() && 5662 (SystemClock.uptimeMillis() - mEditor.mShowCursor) % 5663 (2 * Editor.BLINK) < Editor.BLINK) { 5664 if (mHighlightPathBogus) { 5665 if (mHighlightPath == null) mHighlightPath = new Path(); 5666 mHighlightPath.reset(); 5667 mLayout.getCursorPath(selStart, mHighlightPath, mText); 5668 mEditor.updateCursorsPositions(); 5669 mHighlightPathBogus = false; 5670 } 5671 5672 // XXX should pass to skin instead of drawing directly 5673 highlightPaint.setColor(mCurTextColor); 5674 highlightPaint.setStyle(Paint.Style.STROKE); 5675 highlight = mHighlightPath; 5676 } 5677 } else { 5678 if (mHighlightPathBogus) { 5679 if (mHighlightPath == null) mHighlightPath = new Path(); 5680 mHighlightPath.reset(); 5681 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5682 mHighlightPathBogus = false; 5683 } 5684 5685 // XXX should pass to skin instead of drawing directly 5686 highlightPaint.setColor(mHighlightColor); 5687 highlightPaint.setStyle(Paint.Style.FILL); 5688 5689 highlight = mHighlightPath; 5690 } 5691 } 5692 return highlight; 5693 } 5694 5695 /** 5696 * @hide 5697 */ getHorizontalOffsetForDrawables()5698 public int getHorizontalOffsetForDrawables() { 5699 return 0; 5700 } 5701 5702 @Override onDraw(Canvas canvas)5703 protected void onDraw(Canvas canvas) { 5704 restartMarqueeIfNeeded(); 5705 5706 // Draw the background for this view 5707 super.onDraw(canvas); 5708 5709 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5710 final int compoundPaddingTop = getCompoundPaddingTop(); 5711 final int compoundPaddingRight = getCompoundPaddingRight(); 5712 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5713 final int scrollX = mScrollX; 5714 final int scrollY = mScrollY; 5715 final int right = mRight; 5716 final int left = mLeft; 5717 final int bottom = mBottom; 5718 final int top = mTop; 5719 final boolean isLayoutRtl = isLayoutRtl(); 5720 final int offset = getHorizontalOffsetForDrawables(); 5721 final int leftOffset = isLayoutRtl ? 0 : offset; 5722 final int rightOffset = isLayoutRtl ? offset : 0 ; 5723 5724 final Drawables dr = mDrawables; 5725 if (dr != null) { 5726 /* 5727 * Compound, not extended, because the icon is not clipped 5728 * if the text height is smaller. 5729 */ 5730 5731 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 5732 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 5733 5734 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5735 // Make sure to update invalidateDrawable() when changing this code. 5736 if (dr.mShowing[Drawables.LEFT] != null) { 5737 canvas.save(); 5738 canvas.translate(scrollX + mPaddingLeft + leftOffset, 5739 scrollY + compoundPaddingTop + 5740 (vspace - dr.mDrawableHeightLeft) / 2); 5741 dr.mShowing[Drawables.LEFT].draw(canvas); 5742 canvas.restore(); 5743 } 5744 5745 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5746 // Make sure to update invalidateDrawable() when changing this code. 5747 if (dr.mShowing[Drawables.RIGHT] != null) { 5748 canvas.save(); 5749 canvas.translate(scrollX + right - left - mPaddingRight 5750 - dr.mDrawableSizeRight - rightOffset, 5751 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 5752 dr.mShowing[Drawables.RIGHT].draw(canvas); 5753 canvas.restore(); 5754 } 5755 5756 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5757 // Make sure to update invalidateDrawable() when changing this code. 5758 if (dr.mShowing[Drawables.TOP] != null) { 5759 canvas.save(); 5760 canvas.translate(scrollX + compoundPaddingLeft + 5761 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 5762 dr.mShowing[Drawables.TOP].draw(canvas); 5763 canvas.restore(); 5764 } 5765 5766 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5767 // Make sure to update invalidateDrawable() when changing this code. 5768 if (dr.mShowing[Drawables.BOTTOM] != null) { 5769 canvas.save(); 5770 canvas.translate(scrollX + compoundPaddingLeft + 5771 (hspace - dr.mDrawableWidthBottom) / 2, 5772 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 5773 dr.mShowing[Drawables.BOTTOM].draw(canvas); 5774 canvas.restore(); 5775 } 5776 } 5777 5778 int color = mCurTextColor; 5779 5780 if (mLayout == null) { 5781 assumeLayout(); 5782 } 5783 5784 Layout layout = mLayout; 5785 5786 if (mHint != null && mText.length() == 0) { 5787 if (mHintTextColor != null) { 5788 color = mCurHintTextColor; 5789 } 5790 5791 layout = mHintLayout; 5792 } 5793 5794 mTextPaint.setColor(color); 5795 mTextPaint.drawableState = getDrawableState(); 5796 5797 canvas.save(); 5798 /* Would be faster if we didn't have to do this. Can we chop the 5799 (displayable) text so that we don't need to do this ever? 5800 */ 5801 5802 int extendedPaddingTop = getExtendedPaddingTop(); 5803 int extendedPaddingBottom = getExtendedPaddingBottom(); 5804 5805 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5806 final int maxScrollY = mLayout.getHeight() - vspace; 5807 5808 float clipLeft = compoundPaddingLeft + scrollX; 5809 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 5810 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 5811 float clipBottom = bottom - top + scrollY - 5812 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 5813 5814 if (mShadowRadius != 0) { 5815 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 5816 clipRight += Math.max(0, mShadowDx + mShadowRadius); 5817 5818 clipTop += Math.min(0, mShadowDy - mShadowRadius); 5819 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 5820 } 5821 5822 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 5823 5824 int voffsetText = 0; 5825 int voffsetCursor = 0; 5826 5827 // translate in by our padding 5828 /* shortcircuit calling getVerticaOffset() */ 5829 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5830 voffsetText = getVerticalOffset(false); 5831 voffsetCursor = getVerticalOffset(true); 5832 } 5833 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 5834 5835 final int layoutDirection = getLayoutDirection(); 5836 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 5837 if (isMarqueeFadeEnabled()) { 5838 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 5839 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 5840 final int width = mRight - mLeft; 5841 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 5842 final float dx = mLayout.getLineRight(0) - (width - padding); 5843 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5844 } 5845 5846 if (mMarquee != null && mMarquee.isRunning()) { 5847 final float dx = -mMarquee.getScroll(); 5848 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5849 } 5850 } 5851 5852 final int cursorOffsetVertical = voffsetCursor - voffsetText; 5853 5854 Path highlight = getUpdatedHighlightPath(); 5855 if (mEditor != null) { 5856 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 5857 } else { 5858 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5859 } 5860 5861 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 5862 final float dx = mMarquee.getGhostOffset(); 5863 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5864 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5865 } 5866 5867 canvas.restore(); 5868 } 5869 5870 @Override getFocusedRect(Rect r)5871 public void getFocusedRect(Rect r) { 5872 if (mLayout == null) { 5873 super.getFocusedRect(r); 5874 return; 5875 } 5876 5877 int selEnd = getSelectionEnd(); 5878 if (selEnd < 0) { 5879 super.getFocusedRect(r); 5880 return; 5881 } 5882 5883 int selStart = getSelectionStart(); 5884 if (selStart < 0 || selStart >= selEnd) { 5885 int line = mLayout.getLineForOffset(selEnd); 5886 r.top = mLayout.getLineTop(line); 5887 r.bottom = mLayout.getLineBottom(line); 5888 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 5889 r.right = r.left + 4; 5890 } else { 5891 int lineStart = mLayout.getLineForOffset(selStart); 5892 int lineEnd = mLayout.getLineForOffset(selEnd); 5893 r.top = mLayout.getLineTop(lineStart); 5894 r.bottom = mLayout.getLineBottom(lineEnd); 5895 if (lineStart == lineEnd) { 5896 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 5897 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 5898 } else { 5899 // Selection extends across multiple lines -- make the focused 5900 // rect cover the entire width. 5901 if (mHighlightPathBogus) { 5902 if (mHighlightPath == null) mHighlightPath = new Path(); 5903 mHighlightPath.reset(); 5904 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5905 mHighlightPathBogus = false; 5906 } 5907 synchronized (TEMP_RECTF) { 5908 mHighlightPath.computeBounds(TEMP_RECTF, true); 5909 r.left = (int)TEMP_RECTF.left-1; 5910 r.right = (int)TEMP_RECTF.right+1; 5911 } 5912 } 5913 } 5914 5915 // Adjust for padding and gravity. 5916 int paddingLeft = getCompoundPaddingLeft(); 5917 int paddingTop = getExtendedPaddingTop(); 5918 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5919 paddingTop += getVerticalOffset(false); 5920 } 5921 r.offset(paddingLeft, paddingTop); 5922 int paddingBottom = getExtendedPaddingBottom(); 5923 r.bottom += paddingBottom; 5924 } 5925 5926 /** 5927 * Return the number of lines of text, or 0 if the internal Layout has not 5928 * been built. 5929 */ getLineCount()5930 public int getLineCount() { 5931 return mLayout != null ? mLayout.getLineCount() : 0; 5932 } 5933 5934 /** 5935 * Return the baseline for the specified line (0...getLineCount() - 1) 5936 * If bounds is not null, return the top, left, right, bottom extents 5937 * of the specified line in it. If the internal Layout has not been built, 5938 * return 0 and set bounds to (0, 0, 0, 0) 5939 * @param line which line to examine (0..getLineCount() - 1) 5940 * @param bounds Optional. If not null, it returns the extent of the line 5941 * @return the Y-coordinate of the baseline 5942 */ getLineBounds(int line, Rect bounds)5943 public int getLineBounds(int line, Rect bounds) { 5944 if (mLayout == null) { 5945 if (bounds != null) { 5946 bounds.set(0, 0, 0, 0); 5947 } 5948 return 0; 5949 } 5950 else { 5951 int baseline = mLayout.getLineBounds(line, bounds); 5952 5953 int voffset = getExtendedPaddingTop(); 5954 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5955 voffset += getVerticalOffset(true); 5956 } 5957 if (bounds != null) { 5958 bounds.offset(getCompoundPaddingLeft(), voffset); 5959 } 5960 return baseline + voffset; 5961 } 5962 } 5963 5964 @Override getBaseline()5965 public int getBaseline() { 5966 if (mLayout == null) { 5967 return super.getBaseline(); 5968 } 5969 5970 return getBaselineOffset() + mLayout.getLineBaseline(0); 5971 } 5972 getBaselineOffset()5973 int getBaselineOffset() { 5974 int voffset = 0; 5975 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5976 voffset = getVerticalOffset(true); 5977 } 5978 5979 if (isLayoutModeOptical(mParent)) { 5980 voffset -= getOpticalInsets().top; 5981 } 5982 5983 return getExtendedPaddingTop() + voffset; 5984 } 5985 5986 /** 5987 * @hide 5988 */ 5989 @Override getFadeTop(boolean offsetRequired)5990 protected int getFadeTop(boolean offsetRequired) { 5991 if (mLayout == null) return 0; 5992 5993 int voffset = 0; 5994 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5995 voffset = getVerticalOffset(true); 5996 } 5997 5998 if (offsetRequired) voffset += getTopPaddingOffset(); 5999 6000 return getExtendedPaddingTop() + voffset; 6001 } 6002 6003 /** 6004 * @hide 6005 */ 6006 @Override getFadeHeight(boolean offsetRequired)6007 protected int getFadeHeight(boolean offsetRequired) { 6008 return mLayout != null ? mLayout.getHeight() : 0; 6009 } 6010 6011 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)6012 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 6013 if (mText instanceof Spannable && mLinksClickable) { 6014 final float x = event.getX(pointerIndex); 6015 final float y = event.getY(pointerIndex); 6016 final int offset = getOffsetForPosition(x, y); 6017 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset, 6018 ClickableSpan.class); 6019 if (clickables.length > 0) { 6020 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 6021 } 6022 } 6023 if (isTextSelectable() || isTextEditable()) { 6024 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 6025 } 6026 return super.onResolvePointerIcon(event, pointerIndex); 6027 } 6028 6029 @Override onKeyPreIme(int keyCode, KeyEvent event)6030 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 6031 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 6032 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 6033 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 6034 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 6035 return true; 6036 } 6037 return super.onKeyPreIme(keyCode, event); 6038 } 6039 6040 /** 6041 * @hide 6042 */ handleBackInTextActionModeIfNeeded(KeyEvent event)6043 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 6044 // Do nothing unless mEditor is in text action mode. 6045 if (mEditor == null || mEditor.mTextActionMode == null) { 6046 return false; 6047 } 6048 6049 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 6050 KeyEvent.DispatcherState state = getKeyDispatcherState(); 6051 if (state != null) { 6052 state.startTracking(event, this); 6053 } 6054 return true; 6055 } else if (event.getAction() == KeyEvent.ACTION_UP) { 6056 KeyEvent.DispatcherState state = getKeyDispatcherState(); 6057 if (state != null) { 6058 state.handleUpEvent(event); 6059 } 6060 if (event.isTracking() && !event.isCanceled()) { 6061 stopTextActionMode(); 6062 return true; 6063 } 6064 } 6065 return false; 6066 } 6067 6068 @Override onKeyDown(int keyCode, KeyEvent event)6069 public boolean onKeyDown(int keyCode, KeyEvent event) { 6070 final int which = doKeyDown(keyCode, event, null); 6071 if (which == KEY_EVENT_NOT_HANDLED) { 6072 return super.onKeyDown(keyCode, event); 6073 } 6074 6075 return true; 6076 } 6077 6078 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)6079 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 6080 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 6081 final int which = doKeyDown(keyCode, down, event); 6082 if (which == KEY_EVENT_NOT_HANDLED) { 6083 // Go through default dispatching. 6084 return super.onKeyMultiple(keyCode, repeatCount, event); 6085 } 6086 if (which == KEY_EVENT_HANDLED) { 6087 // Consumed the whole thing. 6088 return true; 6089 } 6090 6091 repeatCount--; 6092 6093 // We are going to dispatch the remaining events to either the input 6094 // or movement method. To do this, we will just send a repeated stream 6095 // of down and up events until we have done the complete repeatCount. 6096 // It would be nice if those interfaces had an onKeyMultiple() method, 6097 // but adding that is a more complicated change. 6098 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 6099 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 6100 // mEditor and mEditor.mInput are not null from doKeyDown 6101 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 6102 while (--repeatCount > 0) { 6103 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down); 6104 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 6105 } 6106 hideErrorIfUnchanged(); 6107 6108 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 6109 // mMovement is not null from doKeyDown 6110 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 6111 while (--repeatCount > 0) { 6112 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 6113 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 6114 } 6115 } 6116 6117 return true; 6118 } 6119 6120 /** 6121 * Returns true if pressing ENTER in this field advances focus instead 6122 * of inserting the character. This is true mostly in single-line fields, 6123 * but also in mail addresses and subjects which will display on multiple 6124 * lines but where it doesn't make sense to insert newlines. 6125 */ shouldAdvanceFocusOnEnter()6126 private boolean shouldAdvanceFocusOnEnter() { 6127 if (getKeyListener() == null) { 6128 return false; 6129 } 6130 6131 if (mSingleLine) { 6132 return true; 6133 } 6134 6135 if (mEditor != null && 6136 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 6137 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 6138 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 6139 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 6140 return true; 6141 } 6142 } 6143 6144 return false; 6145 } 6146 6147 /** 6148 * Returns true if pressing TAB in this field advances focus instead 6149 * of inserting the character. Insert tabs only in multi-line editors. 6150 */ shouldAdvanceFocusOnTab()6151 private boolean shouldAdvanceFocusOnTab() { 6152 if (getKeyListener() != null && !mSingleLine && mEditor != null && 6153 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 6154 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 6155 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 6156 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 6157 return false; 6158 } 6159 } 6160 return true; 6161 } 6162 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)6163 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 6164 if (!isEnabled()) { 6165 return KEY_EVENT_NOT_HANDLED; 6166 } 6167 6168 // If this is the initial keydown, we don't want to prevent a movement away from this view. 6169 // While this shouldn't be necessary because any time we're preventing default movement we 6170 // should be restricting the focus to remain within this view, thus we'll also receive 6171 // the key up event, occasionally key up events will get dropped and we don't want to 6172 // prevent the user from traversing out of this on the next key down. 6173 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 6174 mPreventDefaultMovement = false; 6175 } 6176 6177 switch (keyCode) { 6178 case KeyEvent.KEYCODE_ENTER: 6179 if (event.hasNoModifiers()) { 6180 // When mInputContentType is set, we know that we are 6181 // running in a "modern" cupcake environment, so don't need 6182 // to worry about the application trying to capture 6183 // enter key events. 6184 if (mEditor != null && mEditor.mInputContentType != null) { 6185 // If there is an action listener, given them a 6186 // chance to consume the event. 6187 if (mEditor.mInputContentType.onEditorActionListener != null && 6188 mEditor.mInputContentType.onEditorActionListener.onEditorAction( 6189 this, EditorInfo.IME_NULL, event)) { 6190 mEditor.mInputContentType.enterDown = true; 6191 // We are consuming the enter key for them. 6192 return KEY_EVENT_HANDLED; 6193 } 6194 } 6195 6196 // If our editor should move focus when enter is pressed, or 6197 // this is a generated event from an IME action button, then 6198 // don't let it be inserted into the text. 6199 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 6200 || shouldAdvanceFocusOnEnter()) { 6201 if (hasOnClickListeners()) { 6202 return KEY_EVENT_NOT_HANDLED; 6203 } 6204 return KEY_EVENT_HANDLED; 6205 } 6206 } 6207 break; 6208 6209 case KeyEvent.KEYCODE_DPAD_CENTER: 6210 if (event.hasNoModifiers()) { 6211 if (shouldAdvanceFocusOnEnter()) { 6212 return KEY_EVENT_NOT_HANDLED; 6213 } 6214 } 6215 break; 6216 6217 case KeyEvent.KEYCODE_TAB: 6218 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 6219 if (shouldAdvanceFocusOnTab()) { 6220 return KEY_EVENT_NOT_HANDLED; 6221 } 6222 } 6223 break; 6224 6225 // Has to be done on key down (and not on key up) to correctly be intercepted. 6226 case KeyEvent.KEYCODE_BACK: 6227 if (mEditor != null && mEditor.mTextActionMode != null) { 6228 stopTextActionMode(); 6229 return KEY_EVENT_HANDLED; 6230 } 6231 break; 6232 6233 case KeyEvent.KEYCODE_CUT: 6234 if (event.hasNoModifiers() && canCut()) { 6235 if (onTextContextMenuItem(ID_CUT)) { 6236 return KEY_EVENT_HANDLED; 6237 } 6238 } 6239 break; 6240 6241 case KeyEvent.KEYCODE_COPY: 6242 if (event.hasNoModifiers() && canCopy()) { 6243 if (onTextContextMenuItem(ID_COPY)) { 6244 return KEY_EVENT_HANDLED; 6245 } 6246 } 6247 break; 6248 6249 case KeyEvent.KEYCODE_PASTE: 6250 if (event.hasNoModifiers() && canPaste()) { 6251 if (onTextContextMenuItem(ID_PASTE)) { 6252 return KEY_EVENT_HANDLED; 6253 } 6254 } 6255 break; 6256 } 6257 6258 if (mEditor != null && mEditor.mKeyListener != null) { 6259 boolean doDown = true; 6260 if (otherEvent != null) { 6261 try { 6262 beginBatchEdit(); 6263 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 6264 otherEvent); 6265 hideErrorIfUnchanged(); 6266 doDown = false; 6267 if (handled) { 6268 return KEY_EVENT_HANDLED; 6269 } 6270 } catch (AbstractMethodError e) { 6271 // onKeyOther was added after 1.0, so if it isn't 6272 // implemented we need to try to dispatch as a regular down. 6273 } finally { 6274 endBatchEdit(); 6275 } 6276 } 6277 6278 if (doDown) { 6279 beginBatchEdit(); 6280 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 6281 keyCode, event); 6282 endBatchEdit(); 6283 hideErrorIfUnchanged(); 6284 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 6285 } 6286 } 6287 6288 // bug 650865: sometimes we get a key event before a layout. 6289 // don't try to move around if we don't know the layout. 6290 6291 if (mMovement != null && mLayout != null) { 6292 boolean doDown = true; 6293 if (otherEvent != null) { 6294 try { 6295 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 6296 otherEvent); 6297 doDown = false; 6298 if (handled) { 6299 return KEY_EVENT_HANDLED; 6300 } 6301 } catch (AbstractMethodError e) { 6302 // onKeyOther was added after 1.0, so if it isn't 6303 // implemented we need to try to dispatch as a regular down. 6304 } 6305 } 6306 if (doDown) { 6307 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) { 6308 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 6309 mPreventDefaultMovement = true; 6310 } 6311 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 6312 } 6313 } 6314 } 6315 6316 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? 6317 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 6318 } 6319 6320 /** 6321 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 6322 * can be recorded. 6323 * @hide 6324 */ resetErrorChangedFlag()6325 public void resetErrorChangedFlag() { 6326 /* 6327 * Keep track of what the error was before doing the input 6328 * so that if an input filter changed the error, we leave 6329 * that error showing. Otherwise, we take down whatever 6330 * error was showing when the user types something. 6331 */ 6332 if (mEditor != null) mEditor.mErrorWasChanged = false; 6333 } 6334 6335 /** 6336 * @hide 6337 */ hideErrorIfUnchanged()6338 public void hideErrorIfUnchanged() { 6339 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 6340 setError(null, null); 6341 } 6342 } 6343 6344 @Override onKeyUp(int keyCode, KeyEvent event)6345 public boolean onKeyUp(int keyCode, KeyEvent event) { 6346 if (!isEnabled()) { 6347 return super.onKeyUp(keyCode, event); 6348 } 6349 6350 if (!KeyEvent.isModifierKey(keyCode)) { 6351 mPreventDefaultMovement = false; 6352 } 6353 6354 switch (keyCode) { 6355 case KeyEvent.KEYCODE_DPAD_CENTER: 6356 if (event.hasNoModifiers()) { 6357 /* 6358 * If there is a click listener, just call through to 6359 * super, which will invoke it. 6360 * 6361 * If there isn't a click listener, try to show the soft 6362 * input method. (It will also 6363 * call performClick(), but that won't do anything in 6364 * this case.) 6365 */ 6366 if (!hasOnClickListeners()) { 6367 if (mMovement != null && mText instanceof Editable 6368 && mLayout != null && onCheckIsTextEditor()) { 6369 InputMethodManager imm = InputMethodManager.peekInstance(); 6370 viewClicked(imm); 6371 if (imm != null && getShowSoftInputOnFocus()) { 6372 imm.showSoftInput(this, 0); 6373 } 6374 } 6375 } 6376 } 6377 return super.onKeyUp(keyCode, event); 6378 6379 case KeyEvent.KEYCODE_ENTER: 6380 if (event.hasNoModifiers()) { 6381 if (mEditor != null && mEditor.mInputContentType != null 6382 && mEditor.mInputContentType.onEditorActionListener != null 6383 && mEditor.mInputContentType.enterDown) { 6384 mEditor.mInputContentType.enterDown = false; 6385 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 6386 this, EditorInfo.IME_NULL, event)) { 6387 return true; 6388 } 6389 } 6390 6391 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 6392 || shouldAdvanceFocusOnEnter()) { 6393 /* 6394 * If there is a click listener, just call through to 6395 * super, which will invoke it. 6396 * 6397 * If there isn't a click listener, try to advance focus, 6398 * but still call through to super, which will reset the 6399 * pressed state and longpress state. (It will also 6400 * call performClick(), but that won't do anything in 6401 * this case.) 6402 */ 6403 if (!hasOnClickListeners()) { 6404 View v = focusSearch(FOCUS_DOWN); 6405 6406 if (v != null) { 6407 if (!v.requestFocus(FOCUS_DOWN)) { 6408 throw new IllegalStateException( 6409 "focus search returned a view " + 6410 "that wasn't able to take focus!"); 6411 } 6412 6413 /* 6414 * Return true because we handled the key; super 6415 * will return false because there was no click 6416 * listener. 6417 */ 6418 super.onKeyUp(keyCode, event); 6419 return true; 6420 } else if ((event.getFlags() 6421 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 6422 // No target for next focus, but make sure the IME 6423 // if this came from it. 6424 InputMethodManager imm = InputMethodManager.peekInstance(); 6425 if (imm != null && imm.isActive(this)) { 6426 imm.hideSoftInputFromWindow(getWindowToken(), 0); 6427 } 6428 } 6429 } 6430 } 6431 return super.onKeyUp(keyCode, event); 6432 } 6433 break; 6434 } 6435 6436 if (mEditor != null && mEditor.mKeyListener != null) 6437 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) 6438 return true; 6439 6440 if (mMovement != null && mLayout != null) 6441 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 6442 return true; 6443 6444 return super.onKeyUp(keyCode, event); 6445 } 6446 6447 @Override onCheckIsTextEditor()6448 public boolean onCheckIsTextEditor() { 6449 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 6450 } 6451 6452 @Override onCreateInputConnection(EditorInfo outAttrs)6453 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 6454 if (onCheckIsTextEditor() && isEnabled()) { 6455 mEditor.createInputMethodStateIfNeeded(); 6456 outAttrs.inputType = getInputType(); 6457 if (mEditor.mInputContentType != null) { 6458 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 6459 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 6460 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 6461 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 6462 outAttrs.extras = mEditor.mInputContentType.extras; 6463 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 6464 } else { 6465 outAttrs.imeOptions = EditorInfo.IME_NULL; 6466 outAttrs.hintLocales = null; 6467 } 6468 if (focusSearch(FOCUS_DOWN) != null) { 6469 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 6470 } 6471 if (focusSearch(FOCUS_UP) != null) { 6472 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 6473 } 6474 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) 6475 == EditorInfo.IME_ACTION_UNSPECIFIED) { 6476 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 6477 // An action has not been set, but the enter key will move to 6478 // the next focus, so set the action to that. 6479 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 6480 } else { 6481 // An action has not been set, and there is no focus to move 6482 // to, so let's just supply a "done" action. 6483 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 6484 } 6485 if (!shouldAdvanceFocusOnEnter()) { 6486 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 6487 } 6488 } 6489 if (isMultilineInputType(outAttrs.inputType)) { 6490 // Multi-line text editors should always show an enter key. 6491 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 6492 } 6493 outAttrs.hintText = mHint; 6494 if (mText instanceof Editable) { 6495 InputConnection ic = new EditableInputConnection(this); 6496 outAttrs.initialSelStart = getSelectionStart(); 6497 outAttrs.initialSelEnd = getSelectionEnd(); 6498 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 6499 return ic; 6500 } 6501 } 6502 return null; 6503 } 6504 6505 /** 6506 * If this TextView contains editable content, extract a portion of it 6507 * based on the information in <var>request</var> in to <var>outText</var>. 6508 * @return Returns true if the text was successfully extracted, else false. 6509 */ extractText(ExtractedTextRequest request, ExtractedText outText)6510 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 6511 createEditorIfNeeded(); 6512 return mEditor.extractText(request, outText); 6513 } 6514 6515 /** 6516 * This is used to remove all style-impacting spans from text before new 6517 * extracted text is being replaced into it, so that we don't have any 6518 * lingering spans applied during the replace. 6519 */ removeParcelableSpans(Spannable spannable, int start, int end)6520 static void removeParcelableSpans(Spannable spannable, int start, int end) { 6521 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 6522 int i = spans.length; 6523 while (i > 0) { 6524 i--; 6525 spannable.removeSpan(spans[i]); 6526 } 6527 } 6528 6529 /** 6530 * Apply to this text view the given extracted text, as previously 6531 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 6532 */ setExtractedText(ExtractedText text)6533 public void setExtractedText(ExtractedText text) { 6534 Editable content = getEditableText(); 6535 if (text.text != null) { 6536 if (content == null) { 6537 setText(text.text, TextView.BufferType.EDITABLE); 6538 } else { 6539 int start = 0; 6540 int end = content.length(); 6541 6542 if (text.partialStartOffset >= 0) { 6543 final int N = content.length(); 6544 start = text.partialStartOffset; 6545 if (start > N) start = N; 6546 end = text.partialEndOffset; 6547 if (end > N) end = N; 6548 } 6549 6550 removeParcelableSpans(content, start, end); 6551 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 6552 if (text.text instanceof Spanned) { 6553 // OK to copy spans only. 6554 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 6555 Object.class, content, start); 6556 } 6557 } else { 6558 content.replace(start, end, text.text); 6559 } 6560 } 6561 } 6562 6563 // Now set the selection position... make sure it is in range, to 6564 // avoid crashes. If this is a partial update, it is possible that 6565 // the underlying text may have changed, causing us problems here. 6566 // Also we just don't want to trust clients to do the right thing. 6567 Spannable sp = (Spannable)getText(); 6568 final int N = sp.length(); 6569 int start = text.selectionStart; 6570 if (start < 0) start = 0; 6571 else if (start > N) start = N; 6572 int end = text.selectionEnd; 6573 if (end < 0) end = 0; 6574 else if (end > N) end = N; 6575 Selection.setSelection(sp, start, end); 6576 6577 // Finally, update the selection mode. 6578 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { 6579 MetaKeyKeyListener.startSelecting(this, sp); 6580 } else { 6581 MetaKeyKeyListener.stopSelecting(this, sp); 6582 } 6583 } 6584 6585 /** 6586 * @hide 6587 */ setExtracting(ExtractedTextRequest req)6588 public void setExtracting(ExtractedTextRequest req) { 6589 if (mEditor.mInputMethodState != null) { 6590 mEditor.mInputMethodState.mExtractedTextRequest = req; 6591 } 6592 // This would stop a possible selection mode, but no such mode is started in case 6593 // extracted mode will start. Some text is selected though, and will trigger an action mode 6594 // in the extracted view. 6595 mEditor.hideCursorAndSpanControllers(); 6596 stopTextActionMode(); 6597 if (mEditor.mSelectionModifierCursorController != null) { 6598 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 6599 } 6600 } 6601 6602 /** 6603 * Called by the framework in response to a text completion from 6604 * the current input method, provided by it calling 6605 * {@link InputConnection#commitCompletion 6606 * InputConnection.commitCompletion()}. The default implementation does 6607 * nothing; text views that are supporting auto-completion should override 6608 * this to do their desired behavior. 6609 * 6610 * @param text The auto complete text the user has selected. 6611 */ onCommitCompletion(CompletionInfo text)6612 public void onCommitCompletion(CompletionInfo text) { 6613 // intentionally empty 6614 } 6615 6616 /** 6617 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 6618 * a dictionnary) from the current input method, provided by it calling 6619 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default 6620 * implementation flashes the background of the corrected word to provide feedback to the user. 6621 * 6622 * @param info The auto correct info about the text that was corrected. 6623 */ onCommitCorrection(CorrectionInfo info)6624 public void onCommitCorrection(CorrectionInfo info) { 6625 if (mEditor != null) mEditor.onCommitCorrection(info); 6626 } 6627 beginBatchEdit()6628 public void beginBatchEdit() { 6629 if (mEditor != null) mEditor.beginBatchEdit(); 6630 } 6631 endBatchEdit()6632 public void endBatchEdit() { 6633 if (mEditor != null) mEditor.endBatchEdit(); 6634 } 6635 6636 /** 6637 * Called by the framework in response to a request to begin a batch 6638 * of edit operations through a call to link {@link #beginBatchEdit()}. 6639 */ onBeginBatchEdit()6640 public void onBeginBatchEdit() { 6641 // intentionally empty 6642 } 6643 6644 /** 6645 * Called by the framework in response to a request to end a batch 6646 * of edit operations through a call to link {@link #endBatchEdit}. 6647 */ onEndBatchEdit()6648 public void onEndBatchEdit() { 6649 // intentionally empty 6650 } 6651 6652 /** 6653 * Called by the framework in response to a private command from the 6654 * current method, provided by it calling 6655 * {@link InputConnection#performPrivateCommand 6656 * InputConnection.performPrivateCommand()}. 6657 * 6658 * @param action The action name of the command. 6659 * @param data Any additional data for the command. This may be null. 6660 * @return Return true if you handled the command, else false. 6661 */ onPrivateIMECommand(String action, Bundle data)6662 public boolean onPrivateIMECommand(String action, Bundle data) { 6663 return false; 6664 } 6665 nullLayouts()6666 private void nullLayouts() { 6667 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 6668 mSavedLayout = (BoringLayout) mLayout; 6669 } 6670 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 6671 mSavedHintLayout = (BoringLayout) mHintLayout; 6672 } 6673 6674 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 6675 6676 mBoring = mHintBoring = null; 6677 6678 // Since it depends on the value of mLayout 6679 if (mEditor != null) mEditor.prepareCursorControllers(); 6680 } 6681 6682 /** 6683 * Make a new Layout based on the already-measured size of the view, 6684 * on the assumption that it was measured correctly at some point. 6685 */ assumeLayout()6686 private void assumeLayout() { 6687 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6688 6689 if (width < 1) { 6690 width = 0; 6691 } 6692 6693 int physicalWidth = width; 6694 6695 if (mHorizontallyScrolling) { 6696 width = VERY_WIDE; 6697 } 6698 6699 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 6700 physicalWidth, false); 6701 } 6702 getLayoutAlignment()6703 private Layout.Alignment getLayoutAlignment() { 6704 Layout.Alignment alignment; 6705 switch (getTextAlignment()) { 6706 case TEXT_ALIGNMENT_GRAVITY: 6707 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 6708 case Gravity.START: 6709 alignment = Layout.Alignment.ALIGN_NORMAL; 6710 break; 6711 case Gravity.END: 6712 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6713 break; 6714 case Gravity.LEFT: 6715 alignment = Layout.Alignment.ALIGN_LEFT; 6716 break; 6717 case Gravity.RIGHT: 6718 alignment = Layout.Alignment.ALIGN_RIGHT; 6719 break; 6720 case Gravity.CENTER_HORIZONTAL: 6721 alignment = Layout.Alignment.ALIGN_CENTER; 6722 break; 6723 default: 6724 alignment = Layout.Alignment.ALIGN_NORMAL; 6725 break; 6726 } 6727 break; 6728 case TEXT_ALIGNMENT_TEXT_START: 6729 alignment = Layout.Alignment.ALIGN_NORMAL; 6730 break; 6731 case TEXT_ALIGNMENT_TEXT_END: 6732 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6733 break; 6734 case TEXT_ALIGNMENT_CENTER: 6735 alignment = Layout.Alignment.ALIGN_CENTER; 6736 break; 6737 case TEXT_ALIGNMENT_VIEW_START: 6738 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6739 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 6740 break; 6741 case TEXT_ALIGNMENT_VIEW_END: 6742 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6743 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 6744 break; 6745 case TEXT_ALIGNMENT_INHERIT: 6746 // This should never happen as we have already resolved the text alignment 6747 // but better safe than sorry so we just fall through 6748 default: 6749 alignment = Layout.Alignment.ALIGN_NORMAL; 6750 break; 6751 } 6752 return alignment; 6753 } 6754 6755 /** 6756 * The width passed in is now the desired layout width, 6757 * not the full view width with padding. 6758 * {@hide} 6759 */ makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)6760 protected void makeNewLayout(int wantWidth, int hintWidth, 6761 BoringLayout.Metrics boring, 6762 BoringLayout.Metrics hintBoring, 6763 int ellipsisWidth, boolean bringIntoView) { 6764 stopMarquee(); 6765 6766 // Update "old" cached values 6767 mOldMaximum = mMaximum; 6768 mOldMaxMode = mMaxMode; 6769 6770 mHighlightPathBogus = true; 6771 6772 if (wantWidth < 0) { 6773 wantWidth = 0; 6774 } 6775 if (hintWidth < 0) { 6776 hintWidth = 0; 6777 } 6778 6779 Layout.Alignment alignment = getLayoutAlignment(); 6780 final boolean testDirChange = mSingleLine && mLayout != null && 6781 (alignment == Layout.Alignment.ALIGN_NORMAL || 6782 alignment == Layout.Alignment.ALIGN_OPPOSITE); 6783 int oldDir = 0; 6784 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 6785 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 6786 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE && 6787 mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 6788 TruncateAt effectiveEllipsize = mEllipsize; 6789 if (mEllipsize == TruncateAt.MARQUEE && 6790 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 6791 effectiveEllipsize = TruncateAt.END_SMALL; 6792 } 6793 6794 if (mTextDir == null) { 6795 mTextDir = getTextDirectionHeuristic(); 6796 } 6797 6798 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 6799 effectiveEllipsize, effectiveEllipsize == mEllipsize); 6800 if (switchEllipsize) { 6801 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? 6802 TruncateAt.END : TruncateAt.MARQUEE; 6803 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 6804 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 6805 } 6806 6807 shouldEllipsize = mEllipsize != null; 6808 mHintLayout = null; 6809 6810 if (mHint != null) { 6811 if (shouldEllipsize) hintWidth = wantWidth; 6812 6813 if (hintBoring == UNKNOWN_BORING) { 6814 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 6815 mHintBoring); 6816 if (hintBoring != null) { 6817 mHintBoring = hintBoring; 6818 } 6819 } 6820 6821 if (hintBoring != null) { 6822 if (hintBoring.width <= hintWidth && 6823 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 6824 if (mSavedHintLayout != null) { 6825 mHintLayout = mSavedHintLayout. 6826 replaceOrMake(mHint, mTextPaint, 6827 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6828 hintBoring, mIncludePad); 6829 } else { 6830 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6831 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6832 hintBoring, mIncludePad); 6833 } 6834 6835 mSavedHintLayout = (BoringLayout) mHintLayout; 6836 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 6837 if (mSavedHintLayout != null) { 6838 mHintLayout = mSavedHintLayout. 6839 replaceOrMake(mHint, mTextPaint, 6840 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6841 hintBoring, mIncludePad, mEllipsize, 6842 ellipsisWidth); 6843 } else { 6844 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6845 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6846 hintBoring, mIncludePad, mEllipsize, 6847 ellipsisWidth); 6848 } 6849 } 6850 } 6851 // TODO: code duplication with makeSingleLayout() 6852 if (mHintLayout == null) { 6853 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 6854 mHint.length(), mTextPaint, hintWidth) 6855 .setAlignment(alignment) 6856 .setTextDirection(mTextDir) 6857 .setLineSpacing(mSpacingAdd, mSpacingMult) 6858 .setIncludePad(mIncludePad) 6859 .setBreakStrategy(mBreakStrategy) 6860 .setHyphenationFrequency(mHyphenationFrequency); 6861 if (shouldEllipsize) { 6862 builder.setEllipsize(mEllipsize) 6863 .setEllipsizedWidth(ellipsisWidth) 6864 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6865 } 6866 mHintLayout = builder.build(); 6867 } 6868 } 6869 6870 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 6871 registerForPreDraw(); 6872 } 6873 6874 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6875 if (!compressText(ellipsisWidth)) { 6876 final int height = mLayoutParams.height; 6877 // If the size of the view does not depend on the size of the text, try to 6878 // start the marquee immediately 6879 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 6880 startMarquee(); 6881 } else { 6882 // Defer the start of the marquee until we know our width (see setFrame()) 6883 mRestartMarquee = true; 6884 } 6885 } 6886 } 6887 6888 // CursorControllers need a non-null mLayout 6889 if (mEditor != null) mEditor.prepareCursorControllers(); 6890 } 6891 6892 /** 6893 * @hide 6894 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)6895 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 6896 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 6897 boolean useSaved) { 6898 Layout result = null; 6899 if (mText instanceof Spannable) { 6900 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 6901 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, 6902 mBreakStrategy, mHyphenationFrequency, 6903 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); 6904 } else { 6905 if (boring == UNKNOWN_BORING) { 6906 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6907 if (boring != null) { 6908 mBoring = boring; 6909 } 6910 } 6911 6912 if (boring != null) { 6913 if (boring.width <= wantWidth && 6914 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 6915 if (useSaved && mSavedLayout != null) { 6916 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6917 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6918 boring, mIncludePad); 6919 } else { 6920 result = BoringLayout.make(mTransformed, mTextPaint, 6921 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6922 boring, mIncludePad); 6923 } 6924 6925 if (useSaved) { 6926 mSavedLayout = (BoringLayout) result; 6927 } 6928 } else if (shouldEllipsize && boring.width <= wantWidth) { 6929 if (useSaved && mSavedLayout != null) { 6930 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6931 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6932 boring, mIncludePad, effectiveEllipsize, 6933 ellipsisWidth); 6934 } else { 6935 result = BoringLayout.make(mTransformed, mTextPaint, 6936 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6937 boring, mIncludePad, effectiveEllipsize, 6938 ellipsisWidth); 6939 } 6940 } 6941 } 6942 } 6943 if (result == null) { 6944 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 6945 0, mTransformed.length(), mTextPaint, wantWidth) 6946 .setAlignment(alignment) 6947 .setTextDirection(mTextDir) 6948 .setLineSpacing(mSpacingAdd, mSpacingMult) 6949 .setIncludePad(mIncludePad) 6950 .setBreakStrategy(mBreakStrategy) 6951 .setHyphenationFrequency(mHyphenationFrequency); 6952 if (shouldEllipsize) { 6953 builder.setEllipsize(effectiveEllipsize) 6954 .setEllipsizedWidth(ellipsisWidth) 6955 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6956 } 6957 // TODO: explore always setting maxLines 6958 result = builder.build(); 6959 } 6960 return result; 6961 } 6962 compressText(float width)6963 private boolean compressText(float width) { 6964 if (isHardwareAccelerated()) return false; 6965 6966 // Only compress the text if it hasn't been compressed by the previous pass 6967 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && 6968 mTextPaint.getTextScaleX() == 1.0f) { 6969 final float textWidth = mLayout.getLineWidth(0); 6970 final float overflow = (textWidth + 1.0f - width) / width; 6971 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 6972 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 6973 post(new Runnable() { 6974 public void run() { 6975 requestLayout(); 6976 } 6977 }); 6978 return true; 6979 } 6980 } 6981 6982 return false; 6983 } 6984 desired(Layout layout)6985 private static int desired(Layout layout) { 6986 int n = layout.getLineCount(); 6987 CharSequence text = layout.getText(); 6988 float max = 0; 6989 6990 // if any line was wrapped, we can't use it. 6991 // but it's ok for the last line not to have a newline 6992 6993 for (int i = 0; i < n - 1; i++) { 6994 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 6995 return -1; 6996 } 6997 6998 for (int i = 0; i < n; i++) { 6999 max = Math.max(max, layout.getLineWidth(i)); 7000 } 7001 7002 return (int) Math.ceil(max); 7003 } 7004 7005 /** 7006 * Set whether the TextView includes extra top and bottom padding to make 7007 * room for accents that go above the normal ascent and descent. 7008 * The default is true. 7009 * 7010 * @see #getIncludeFontPadding() 7011 * 7012 * @attr ref android.R.styleable#TextView_includeFontPadding 7013 */ setIncludeFontPadding(boolean includepad)7014 public void setIncludeFontPadding(boolean includepad) { 7015 if (mIncludePad != includepad) { 7016 mIncludePad = includepad; 7017 7018 if (mLayout != null) { 7019 nullLayouts(); 7020 requestLayout(); 7021 invalidate(); 7022 } 7023 } 7024 } 7025 7026 /** 7027 * Gets whether the TextView includes extra top and bottom padding to make 7028 * room for accents that go above the normal ascent and descent. 7029 * 7030 * @see #setIncludeFontPadding(boolean) 7031 * 7032 * @attr ref android.R.styleable#TextView_includeFontPadding 7033 */ getIncludeFontPadding()7034 public boolean getIncludeFontPadding() { 7035 return mIncludePad; 7036 } 7037 7038 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 7039 7040 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)7041 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 7042 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 7043 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 7044 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 7045 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 7046 7047 int width; 7048 int height; 7049 7050 BoringLayout.Metrics boring = UNKNOWN_BORING; 7051 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 7052 7053 if (mTextDir == null) { 7054 mTextDir = getTextDirectionHeuristic(); 7055 } 7056 7057 int des = -1; 7058 boolean fromexisting = false; 7059 7060 if (widthMode == MeasureSpec.EXACTLY) { 7061 // Parent has told us how big to be. So be it. 7062 width = widthSize; 7063 } else { 7064 if (mLayout != null && mEllipsize == null) { 7065 des = desired(mLayout); 7066 } 7067 7068 if (des < 0) { 7069 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 7070 if (boring != null) { 7071 mBoring = boring; 7072 } 7073 } else { 7074 fromexisting = true; 7075 } 7076 7077 if (boring == null || boring == UNKNOWN_BORING) { 7078 if (des < 0) { 7079 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); 7080 } 7081 width = des; 7082 } else { 7083 width = boring.width; 7084 } 7085 7086 final Drawables dr = mDrawables; 7087 if (dr != null) { 7088 width = Math.max(width, dr.mDrawableWidthTop); 7089 width = Math.max(width, dr.mDrawableWidthBottom); 7090 } 7091 7092 if (mHint != null) { 7093 int hintDes = -1; 7094 int hintWidth; 7095 7096 if (mHintLayout != null && mEllipsize == null) { 7097 hintDes = desired(mHintLayout); 7098 } 7099 7100 if (hintDes < 0) { 7101 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 7102 if (hintBoring != null) { 7103 mHintBoring = hintBoring; 7104 } 7105 } 7106 7107 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 7108 if (hintDes < 0) { 7109 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint)); 7110 } 7111 hintWidth = hintDes; 7112 } else { 7113 hintWidth = hintBoring.width; 7114 } 7115 7116 if (hintWidth > width) { 7117 width = hintWidth; 7118 } 7119 } 7120 7121 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 7122 7123 if (mMaxWidthMode == EMS) { 7124 width = Math.min(width, mMaxWidth * getLineHeight()); 7125 } else { 7126 width = Math.min(width, mMaxWidth); 7127 } 7128 7129 if (mMinWidthMode == EMS) { 7130 width = Math.max(width, mMinWidth * getLineHeight()); 7131 } else { 7132 width = Math.max(width, mMinWidth); 7133 } 7134 7135 // Check against our minimum width 7136 width = Math.max(width, getSuggestedMinimumWidth()); 7137 7138 if (widthMode == MeasureSpec.AT_MOST) { 7139 width = Math.min(widthSize, width); 7140 } 7141 } 7142 7143 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7144 int unpaddedWidth = want; 7145 7146 if (mHorizontallyScrolling) want = VERY_WIDE; 7147 7148 int hintWant = want; 7149 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 7150 7151 if (mLayout == null) { 7152 makeNewLayout(want, hintWant, boring, hintBoring, 7153 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 7154 } else { 7155 final boolean layoutChanged = (mLayout.getWidth() != want) || 7156 (hintWidth != hintWant) || 7157 (mLayout.getEllipsizedWidth() != 7158 width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 7159 7160 final boolean widthChanged = (mHint == null) && 7161 (mEllipsize == null) && 7162 (want > mLayout.getWidth()) && 7163 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want)); 7164 7165 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 7166 7167 if (layoutChanged || maximumChanged) { 7168 if (!maximumChanged && widthChanged) { 7169 mLayout.increaseWidthTo(want); 7170 } else { 7171 makeNewLayout(want, hintWant, boring, hintBoring, 7172 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 7173 } 7174 } else { 7175 // Nothing has changed 7176 } 7177 } 7178 7179 if (heightMode == MeasureSpec.EXACTLY) { 7180 // Parent has told us how big to be. So be it. 7181 height = heightSize; 7182 mDesiredHeightAtMeasure = -1; 7183 } else { 7184 int desired = getDesiredHeight(); 7185 7186 height = desired; 7187 mDesiredHeightAtMeasure = desired; 7188 7189 if (heightMode == MeasureSpec.AT_MOST) { 7190 height = Math.min(desired, heightSize); 7191 } 7192 } 7193 7194 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 7195 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 7196 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 7197 } 7198 7199 /* 7200 * We didn't let makeNewLayout() register to bring the cursor into view, 7201 * so do it here if there is any possibility that it is needed. 7202 */ 7203 if (mMovement != null || 7204 mLayout.getWidth() > unpaddedWidth || 7205 mLayout.getHeight() > unpaddedHeight) { 7206 registerForPreDraw(); 7207 } else { 7208 scrollTo(0, 0); 7209 } 7210 7211 setMeasuredDimension(width, height); 7212 } 7213 getDesiredHeight()7214 private int getDesiredHeight() { 7215 return Math.max( 7216 getDesiredHeight(mLayout, true), 7217 getDesiredHeight(mHintLayout, mEllipsize != null)); 7218 } 7219 getDesiredHeight(Layout layout, boolean cap)7220 private int getDesiredHeight(Layout layout, boolean cap) { 7221 if (layout == null) { 7222 return 0; 7223 } 7224 7225 int linecount = layout.getLineCount(); 7226 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 7227 int desired = layout.getLineTop(linecount); 7228 7229 final Drawables dr = mDrawables; 7230 if (dr != null) { 7231 desired = Math.max(desired, dr.mDrawableHeightLeft); 7232 desired = Math.max(desired, dr.mDrawableHeightRight); 7233 } 7234 7235 desired += pad; 7236 7237 if (mMaxMode == LINES) { 7238 /* 7239 * Don't cap the hint to a certain number of lines. 7240 * (Do cap it, though, if we have a maximum pixel height.) 7241 */ 7242 if (cap) { 7243 if (linecount > mMaximum) { 7244 desired = layout.getLineTop(mMaximum); 7245 7246 if (dr != null) { 7247 desired = Math.max(desired, dr.mDrawableHeightLeft); 7248 desired = Math.max(desired, dr.mDrawableHeightRight); 7249 } 7250 7251 desired += pad; 7252 linecount = mMaximum; 7253 } 7254 } 7255 } else { 7256 desired = Math.min(desired, mMaximum); 7257 } 7258 7259 if (mMinMode == LINES) { 7260 if (linecount < mMinimum) { 7261 desired += getLineHeight() * (mMinimum - linecount); 7262 } 7263 } else { 7264 desired = Math.max(desired, mMinimum); 7265 } 7266 7267 // Check against our minimum height 7268 desired = Math.max(desired, getSuggestedMinimumHeight()); 7269 7270 return desired; 7271 } 7272 7273 /** 7274 * Check whether a change to the existing text layout requires a 7275 * new view layout. 7276 */ checkForResize()7277 private void checkForResize() { 7278 boolean sizeChanged = false; 7279 7280 if (mLayout != null) { 7281 // Check if our width changed 7282 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 7283 sizeChanged = true; 7284 invalidate(); 7285 } 7286 7287 // Check if our height changed 7288 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 7289 int desiredHeight = getDesiredHeight(); 7290 7291 if (desiredHeight != this.getHeight()) { 7292 sizeChanged = true; 7293 } 7294 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 7295 if (mDesiredHeightAtMeasure >= 0) { 7296 int desiredHeight = getDesiredHeight(); 7297 7298 if (desiredHeight != mDesiredHeightAtMeasure) { 7299 sizeChanged = true; 7300 } 7301 } 7302 } 7303 } 7304 7305 if (sizeChanged) { 7306 requestLayout(); 7307 // caller will have already invalidated 7308 } 7309 } 7310 7311 /** 7312 * Check whether entirely new text requires a new view layout 7313 * or merely a new text layout. 7314 */ checkForRelayout()7315 private void checkForRelayout() { 7316 // If we have a fixed width, we can just swap in a new text layout 7317 // if the text height stays the same or if the view height is fixed. 7318 7319 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 7320 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 7321 (mHint == null || mHintLayout != null) && 7322 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 7323 // Static width, so try making a new text layout. 7324 7325 int oldht = mLayout.getHeight(); 7326 int want = mLayout.getWidth(); 7327 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 7328 7329 /* 7330 * No need to bring the text into view, since the size is not 7331 * changing (unless we do the requestLayout(), in which case it 7332 * will happen at measure). 7333 */ 7334 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 7335 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 7336 false); 7337 7338 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 7339 // In a fixed-height view, so use our new text layout. 7340 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 7341 mLayoutParams.height != LayoutParams.MATCH_PARENT) { 7342 invalidate(); 7343 return; 7344 } 7345 7346 // Dynamic height, but height has stayed the same, 7347 // so use our new text layout. 7348 if (mLayout.getHeight() == oldht && 7349 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 7350 invalidate(); 7351 return; 7352 } 7353 } 7354 7355 // We lose: the height has changed and we have a dynamic height. 7356 // Request a new view layout using our new text layout. 7357 requestLayout(); 7358 invalidate(); 7359 } else { 7360 // Dynamic width, so we have no choice but to request a new 7361 // view layout with a new text layout. 7362 nullLayouts(); 7363 requestLayout(); 7364 invalidate(); 7365 } 7366 } 7367 7368 @Override onLayout(boolean changed, int left, int top, int right, int bottom)7369 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 7370 super.onLayout(changed, left, top, right, bottom); 7371 if (mDeferScroll >= 0) { 7372 int curs = mDeferScroll; 7373 mDeferScroll = -1; 7374 bringPointIntoView(Math.min(curs, mText.length())); 7375 } 7376 } 7377 isShowingHint()7378 private boolean isShowingHint() { 7379 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 7380 } 7381 7382 /** 7383 * Returns true if anything changed. 7384 */ bringTextIntoView()7385 private boolean bringTextIntoView() { 7386 Layout layout = isShowingHint() ? mHintLayout : mLayout; 7387 int line = 0; 7388 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7389 line = layout.getLineCount() - 1; 7390 } 7391 7392 Layout.Alignment a = layout.getParagraphAlignment(line); 7393 int dir = layout.getParagraphDirection(line); 7394 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7395 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7396 int ht = layout.getHeight(); 7397 7398 int scrollx, scrolly; 7399 7400 // Convert to left, center, or right alignment. 7401 if (a == Layout.Alignment.ALIGN_NORMAL) { 7402 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT : 7403 Layout.Alignment.ALIGN_RIGHT; 7404 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){ 7405 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT : 7406 Layout.Alignment.ALIGN_LEFT; 7407 } 7408 7409 if (a == Layout.Alignment.ALIGN_CENTER) { 7410 /* 7411 * Keep centered if possible, or, if it is too wide to fit, 7412 * keep leading edge in view. 7413 */ 7414 7415 int left = (int) Math.floor(layout.getLineLeft(line)); 7416 int right = (int) Math.ceil(layout.getLineRight(line)); 7417 7418 if (right - left < hspace) { 7419 scrollx = (right + left) / 2 - hspace / 2; 7420 } else { 7421 if (dir < 0) { 7422 scrollx = right - hspace; 7423 } else { 7424 scrollx = left; 7425 } 7426 } 7427 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 7428 int right = (int) Math.ceil(layout.getLineRight(line)); 7429 scrollx = right - hspace; 7430 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 7431 scrollx = (int) Math.floor(layout.getLineLeft(line)); 7432 } 7433 7434 if (ht < vspace) { 7435 scrolly = 0; 7436 } else { 7437 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7438 scrolly = ht - vspace; 7439 } else { 7440 scrolly = 0; 7441 } 7442 } 7443 7444 if (scrollx != mScrollX || scrolly != mScrollY) { 7445 scrollTo(scrollx, scrolly); 7446 return true; 7447 } else { 7448 return false; 7449 } 7450 } 7451 7452 /** 7453 * Move the point, specified by the offset, into the view if it is needed. 7454 * This has to be called after layout. Returns true if anything changed. 7455 */ bringPointIntoView(int offset)7456 public boolean bringPointIntoView(int offset) { 7457 if (isLayoutRequested()) { 7458 mDeferScroll = offset; 7459 return false; 7460 } 7461 boolean changed = false; 7462 7463 Layout layout = isShowingHint() ? mHintLayout: mLayout; 7464 7465 if (layout == null) return changed; 7466 7467 int line = layout.getLineForOffset(offset); 7468 7469 int grav; 7470 7471 switch (layout.getParagraphAlignment(line)) { 7472 case ALIGN_LEFT: 7473 grav = 1; 7474 break; 7475 case ALIGN_RIGHT: 7476 grav = -1; 7477 break; 7478 case ALIGN_NORMAL: 7479 grav = layout.getParagraphDirection(line); 7480 break; 7481 case ALIGN_OPPOSITE: 7482 grav = -layout.getParagraphDirection(line); 7483 break; 7484 case ALIGN_CENTER: 7485 default: 7486 grav = 0; 7487 break; 7488 } 7489 7490 // We only want to clamp the cursor to fit within the layout width 7491 // in left-to-right modes, because in a right to left alignment, 7492 // we want to scroll to keep the line-right on the screen, as other 7493 // lines are likely to have text flush with the right margin, which 7494 // we want to keep visible. 7495 // A better long-term solution would probably be to measure both 7496 // the full line and a blank-trimmed version, and, for example, use 7497 // the latter measurement for centering and right alignment, but for 7498 // the time being we only implement the cursor clamping in left to 7499 // right where it is most likely to be annoying. 7500 final boolean clamped = grav > 0; 7501 // FIXME: Is it okay to truncate this, or should we round? 7502 final int x = (int)layout.getPrimaryHorizontal(offset, clamped); 7503 final int top = layout.getLineTop(line); 7504 final int bottom = layout.getLineTop(line + 1); 7505 7506 int left = (int) Math.floor(layout.getLineLeft(line)); 7507 int right = (int) Math.ceil(layout.getLineRight(line)); 7508 int ht = layout.getHeight(); 7509 7510 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7511 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7512 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 7513 // If cursor has been clamped, make sure we don't scroll. 7514 right = Math.max(x, left + hspace); 7515 } 7516 7517 int hslack = (bottom - top) / 2; 7518 int vslack = hslack; 7519 7520 if (vslack > vspace / 4) 7521 vslack = vspace / 4; 7522 if (hslack > hspace / 4) 7523 hslack = hspace / 4; 7524 7525 int hs = mScrollX; 7526 int vs = mScrollY; 7527 7528 if (top - vs < vslack) 7529 vs = top - vslack; 7530 if (bottom - vs > vspace - vslack) 7531 vs = bottom - (vspace - vslack); 7532 if (ht - vs < vspace) 7533 vs = ht - vspace; 7534 if (0 - vs > 0) 7535 vs = 0; 7536 7537 if (grav != 0) { 7538 if (x - hs < hslack) { 7539 hs = x - hslack; 7540 } 7541 if (x - hs > hspace - hslack) { 7542 hs = x - (hspace - hslack); 7543 } 7544 } 7545 7546 if (grav < 0) { 7547 if (left - hs > 0) 7548 hs = left; 7549 if (right - hs < hspace) 7550 hs = right - hspace; 7551 } else if (grav > 0) { 7552 if (right - hs < hspace) 7553 hs = right - hspace; 7554 if (left - hs > 0) 7555 hs = left; 7556 } else /* grav == 0 */ { 7557 if (right - left <= hspace) { 7558 /* 7559 * If the entire text fits, center it exactly. 7560 */ 7561 hs = left - (hspace - (right - left)) / 2; 7562 } else if (x > right - hslack) { 7563 /* 7564 * If we are near the right edge, keep the right edge 7565 * at the edge of the view. 7566 */ 7567 hs = right - hspace; 7568 } else if (x < left + hslack) { 7569 /* 7570 * If we are near the left edge, keep the left edge 7571 * at the edge of the view. 7572 */ 7573 hs = left; 7574 } else if (left > hs) { 7575 /* 7576 * Is there whitespace visible at the left? Fix it if so. 7577 */ 7578 hs = left; 7579 } else if (right < hs + hspace) { 7580 /* 7581 * Is there whitespace visible at the right? Fix it if so. 7582 */ 7583 hs = right - hspace; 7584 } else { 7585 /* 7586 * Otherwise, float as needed. 7587 */ 7588 if (x - hs < hslack) { 7589 hs = x - hslack; 7590 } 7591 if (x - hs > hspace - hslack) { 7592 hs = x - (hspace - hslack); 7593 } 7594 } 7595 } 7596 7597 if (hs != mScrollX || vs != mScrollY) { 7598 if (mScroller == null) { 7599 scrollTo(hs, vs); 7600 } else { 7601 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 7602 int dx = hs - mScrollX; 7603 int dy = vs - mScrollY; 7604 7605 if (duration > ANIMATED_SCROLL_GAP) { 7606 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 7607 awakenScrollBars(mScroller.getDuration()); 7608 invalidate(); 7609 } else { 7610 if (!mScroller.isFinished()) { 7611 mScroller.abortAnimation(); 7612 } 7613 7614 scrollBy(dx, dy); 7615 } 7616 7617 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 7618 } 7619 7620 changed = true; 7621 } 7622 7623 if (isFocused()) { 7624 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 7625 // requestRectangleOnScreen() is in terms of content coordinates. 7626 7627 // The offsets here are to ensure the rectangle we are using is 7628 // within our view bounds, in case the cursor is on the far left 7629 // or right. If it isn't withing the bounds, then this request 7630 // will be ignored. 7631 if (mTempRect == null) mTempRect = new Rect(); 7632 mTempRect.set(x - 2, top, x + 2, bottom); 7633 getInterestingRect(mTempRect, line); 7634 mTempRect.offset(mScrollX, mScrollY); 7635 7636 if (requestRectangleOnScreen(mTempRect)) { 7637 changed = true; 7638 } 7639 } 7640 7641 return changed; 7642 } 7643 7644 /** 7645 * Move the cursor, if needed, so that it is at an offset that is visible 7646 * to the user. This will not move the cursor if it represents more than 7647 * one character (a selection range). This will only work if the 7648 * TextView contains spannable text; otherwise it will do nothing. 7649 * 7650 * @return True if the cursor was actually moved, false otherwise. 7651 */ moveCursorToVisibleOffset()7652 public boolean moveCursorToVisibleOffset() { 7653 if (!(mText instanceof Spannable)) { 7654 return false; 7655 } 7656 int start = getSelectionStart(); 7657 int end = getSelectionEnd(); 7658 if (start != end) { 7659 return false; 7660 } 7661 7662 // First: make sure the line is visible on screen: 7663 7664 int line = mLayout.getLineForOffset(start); 7665 7666 final int top = mLayout.getLineTop(line); 7667 final int bottom = mLayout.getLineTop(line + 1); 7668 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7669 int vslack = (bottom - top) / 2; 7670 if (vslack > vspace / 4) 7671 vslack = vspace / 4; 7672 final int vs = mScrollY; 7673 7674 if (top < (vs+vslack)) { 7675 line = mLayout.getLineForVertical(vs+vslack+(bottom-top)); 7676 } else if (bottom > (vspace+vs-vslack)) { 7677 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top)); 7678 } 7679 7680 // Next: make sure the character is visible on screen: 7681 7682 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7683 final int hs = mScrollX; 7684 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 7685 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs); 7686 7687 // line might contain bidirectional text 7688 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 7689 final int highChar = leftChar > rightChar ? leftChar : rightChar; 7690 7691 int newStart = start; 7692 if (newStart < lowChar) { 7693 newStart = lowChar; 7694 } else if (newStart > highChar) { 7695 newStart = highChar; 7696 } 7697 7698 if (newStart != start) { 7699 Selection.setSelection((Spannable)mText, newStart); 7700 return true; 7701 } 7702 7703 return false; 7704 } 7705 7706 @Override computeScroll()7707 public void computeScroll() { 7708 if (mScroller != null) { 7709 if (mScroller.computeScrollOffset()) { 7710 mScrollX = mScroller.getCurrX(); 7711 mScrollY = mScroller.getCurrY(); 7712 invalidateParentCaches(); 7713 postInvalidate(); // So we draw again 7714 } 7715 } 7716 } 7717 getInterestingRect(Rect r, int line)7718 private void getInterestingRect(Rect r, int line) { 7719 convertFromViewportToContentCoordinates(r); 7720 7721 // Rectangle can can be expanded on first and last line to take 7722 // padding into account. 7723 // TODO Take left/right padding into account too? 7724 if (line == 0) r.top -= getExtendedPaddingTop(); 7725 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 7726 } 7727 convertFromViewportToContentCoordinates(Rect r)7728 private void convertFromViewportToContentCoordinates(Rect r) { 7729 final int horizontalOffset = viewportToContentHorizontalOffset(); 7730 r.left += horizontalOffset; 7731 r.right += horizontalOffset; 7732 7733 final int verticalOffset = viewportToContentVerticalOffset(); 7734 r.top += verticalOffset; 7735 r.bottom += verticalOffset; 7736 } 7737 viewportToContentHorizontalOffset()7738 int viewportToContentHorizontalOffset() { 7739 return getCompoundPaddingLeft() - mScrollX; 7740 } 7741 viewportToContentVerticalOffset()7742 int viewportToContentVerticalOffset() { 7743 int offset = getExtendedPaddingTop() - mScrollY; 7744 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7745 offset += getVerticalOffset(false); 7746 } 7747 return offset; 7748 } 7749 7750 @Override debug(int depth)7751 public void debug(int depth) { 7752 super.debug(depth); 7753 7754 String output = debugIndent(depth); 7755 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 7756 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 7757 + "} "; 7758 7759 if (mText != null) { 7760 7761 output += "mText=\"" + mText + "\" "; 7762 if (mLayout != null) { 7763 output += "mLayout width=" + mLayout.getWidth() 7764 + " height=" + mLayout.getHeight(); 7765 } 7766 } else { 7767 output += "mText=NULL"; 7768 } 7769 Log.d(VIEW_LOG_TAG, output); 7770 } 7771 7772 /** 7773 * Convenience for {@link Selection#getSelectionStart}. 7774 */ 7775 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()7776 public int getSelectionStart() { 7777 return Selection.getSelectionStart(getText()); 7778 } 7779 7780 /** 7781 * Convenience for {@link Selection#getSelectionEnd}. 7782 */ 7783 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()7784 public int getSelectionEnd() { 7785 return Selection.getSelectionEnd(getText()); 7786 } 7787 7788 /** 7789 * Return true iff there is a selection inside this text view. 7790 */ hasSelection()7791 public boolean hasSelection() { 7792 final int selectionStart = getSelectionStart(); 7793 final int selectionEnd = getSelectionEnd(); 7794 7795 return selectionStart >= 0 && selectionStart != selectionEnd; 7796 } 7797 getSelectedText()7798 String getSelectedText() { 7799 if (!hasSelection()) { 7800 return null; 7801 } 7802 7803 final int start = getSelectionStart(); 7804 final int end = getSelectionEnd(); 7805 return String.valueOf( 7806 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 7807 } 7808 7809 /** 7810 * Sets the properties of this field (lines, horizontally scrolling, 7811 * transformation method) to be for a single-line input. 7812 * 7813 * @attr ref android.R.styleable#TextView_singleLine 7814 */ setSingleLine()7815 public void setSingleLine() { 7816 setSingleLine(true); 7817 } 7818 7819 /** 7820 * Sets the properties of this field to transform input to ALL CAPS 7821 * display. This may use a "small caps" formatting if available. 7822 * This setting will be ignored if this field is editable or selectable. 7823 * 7824 * This call replaces the current transformation method. Disabling this 7825 * will not necessarily restore the previous behavior from before this 7826 * was enabled. 7827 * 7828 * @see #setTransformationMethod(TransformationMethod) 7829 * @attr ref android.R.styleable#TextView_textAllCaps 7830 */ setAllCaps(boolean allCaps)7831 public void setAllCaps(boolean allCaps) { 7832 if (allCaps) { 7833 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 7834 } else { 7835 setTransformationMethod(null); 7836 } 7837 } 7838 7839 /** 7840 * If true, sets the properties of this field (number of lines, horizontally scrolling, 7841 * transformation method) to be for a single-line input; if false, restores these to the default 7842 * conditions. 7843 * 7844 * Note that the default conditions are not necessarily those that were in effect prior this 7845 * method, and you may want to reset these properties to your custom values. 7846 * 7847 * @attr ref android.R.styleable#TextView_singleLine 7848 */ 7849 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)7850 public void setSingleLine(boolean singleLine) { 7851 // Could be used, but may break backward compatibility. 7852 // if (mSingleLine == singleLine) return; 7853 setInputTypeSingleLine(singleLine); 7854 applySingleLine(singleLine, true, true); 7855 } 7856 7857 /** 7858 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 7859 * @param singleLine 7860 */ setInputTypeSingleLine(boolean singleLine)7861 private void setInputTypeSingleLine(boolean singleLine) { 7862 if (mEditor != null && 7863 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 7864 if (singleLine) { 7865 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7866 } else { 7867 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7868 } 7869 } 7870 } 7871 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)7872 private void applySingleLine(boolean singleLine, boolean applyTransformation, 7873 boolean changeMaxLines) { 7874 mSingleLine = singleLine; 7875 if (singleLine) { 7876 setLines(1); 7877 setHorizontallyScrolling(true); 7878 if (applyTransformation) { 7879 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 7880 } 7881 } else { 7882 if (changeMaxLines) { 7883 setMaxLines(Integer.MAX_VALUE); 7884 } 7885 setHorizontallyScrolling(false); 7886 if (applyTransformation) { 7887 setTransformationMethod(null); 7888 } 7889 } 7890 } 7891 7892 /** 7893 * Causes words in the text that are longer than the view's width 7894 * to be ellipsized instead of broken in the middle. You may also 7895 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 7896 * to constrain the text to a single line. Use <code>null</code> 7897 * to turn off ellipsizing. 7898 * 7899 * If {@link #setMaxLines} has been used to set two or more lines, 7900 * only {@link android.text.TextUtils.TruncateAt#END} and 7901 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 7902 * (other ellipsizing types will not do anything). 7903 * 7904 * @attr ref android.R.styleable#TextView_ellipsize 7905 */ setEllipsize(TextUtils.TruncateAt where)7906 public void setEllipsize(TextUtils.TruncateAt where) { 7907 // TruncateAt is an enum. != comparison is ok between these singleton objects. 7908 if (mEllipsize != where) { 7909 mEllipsize = where; 7910 7911 if (mLayout != null) { 7912 nullLayouts(); 7913 requestLayout(); 7914 invalidate(); 7915 } 7916 } 7917 } 7918 7919 /** 7920 * Sets how many times to repeat the marquee animation. Only applied if the 7921 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 7922 * 7923 * @see #getMarqueeRepeatLimit() 7924 * 7925 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7926 */ setMarqueeRepeatLimit(int marqueeLimit)7927 public void setMarqueeRepeatLimit(int marqueeLimit) { 7928 mMarqueeRepeatLimit = marqueeLimit; 7929 } 7930 7931 /** 7932 * Gets the number of times the marquee animation is repeated. Only meaningful if the 7933 * TextView has marquee enabled. 7934 * 7935 * @return the number of times the marquee animation is repeated. -1 if the animation 7936 * repeats indefinitely 7937 * 7938 * @see #setMarqueeRepeatLimit(int) 7939 * 7940 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7941 */ getMarqueeRepeatLimit()7942 public int getMarqueeRepeatLimit() { 7943 return mMarqueeRepeatLimit; 7944 } 7945 7946 /** 7947 * Returns where, if anywhere, words that are longer than the view 7948 * is wide should be ellipsized. 7949 */ 7950 @ViewDebug.ExportedProperty getEllipsize()7951 public TextUtils.TruncateAt getEllipsize() { 7952 return mEllipsize; 7953 } 7954 7955 /** 7956 * Set the TextView so that when it takes focus, all the text is 7957 * selected. 7958 * 7959 * @attr ref android.R.styleable#TextView_selectAllOnFocus 7960 */ 7961 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)7962 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 7963 createEditorIfNeeded(); 7964 mEditor.mSelectAllOnFocus = selectAllOnFocus; 7965 7966 if (selectAllOnFocus && !(mText instanceof Spannable)) { 7967 setText(mText, BufferType.SPANNABLE); 7968 } 7969 } 7970 7971 /** 7972 * Set whether the cursor is visible. The default is true. Note that this property only 7973 * makes sense for editable TextView. 7974 * 7975 * @see #isCursorVisible() 7976 * 7977 * @attr ref android.R.styleable#TextView_cursorVisible 7978 */ 7979 @android.view.RemotableViewMethod setCursorVisible(boolean visible)7980 public void setCursorVisible(boolean visible) { 7981 if (visible && mEditor == null) return; // visible is the default value with no edit data 7982 createEditorIfNeeded(); 7983 if (mEditor.mCursorVisible != visible) { 7984 mEditor.mCursorVisible = visible; 7985 invalidate(); 7986 7987 mEditor.makeBlink(); 7988 7989 // InsertionPointCursorController depends on mCursorVisible 7990 mEditor.prepareCursorControllers(); 7991 } 7992 } 7993 7994 /** 7995 * @return whether or not the cursor is visible (assuming this TextView is editable) 7996 * 7997 * @see #setCursorVisible(boolean) 7998 * 7999 * @attr ref android.R.styleable#TextView_cursorVisible 8000 */ isCursorVisible()8001 public boolean isCursorVisible() { 8002 // true is the default value 8003 return mEditor == null ? true : mEditor.mCursorVisible; 8004 } 8005 canMarquee()8006 private boolean canMarquee() { 8007 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 8008 return width > 0 && (mLayout.getLineWidth(0) > width || 8009 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null && 8010 mSavedMarqueeModeLayout.getLineWidth(0) > width)); 8011 } 8012 startMarquee()8013 private void startMarquee() { 8014 // Do not ellipsize EditText 8015 if (getKeyListener() != null) return; 8016 8017 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 8018 return; 8019 } 8020 8021 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 8022 getLineCount() == 1 && canMarquee()) { 8023 8024 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8025 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 8026 final Layout tmp = mLayout; 8027 mLayout = mSavedMarqueeModeLayout; 8028 mSavedMarqueeModeLayout = tmp; 8029 setHorizontalFadingEdgeEnabled(true); 8030 requestLayout(); 8031 invalidate(); 8032 } 8033 8034 if (mMarquee == null) mMarquee = new Marquee(this); 8035 mMarquee.start(mMarqueeRepeatLimit); 8036 } 8037 } 8038 stopMarquee()8039 private void stopMarquee() { 8040 if (mMarquee != null && !mMarquee.isStopped()) { 8041 mMarquee.stop(); 8042 } 8043 8044 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 8045 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 8046 final Layout tmp = mSavedMarqueeModeLayout; 8047 mSavedMarqueeModeLayout = mLayout; 8048 mLayout = tmp; 8049 setHorizontalFadingEdgeEnabled(false); 8050 requestLayout(); 8051 invalidate(); 8052 } 8053 } 8054 startStopMarquee(boolean start)8055 private void startStopMarquee(boolean start) { 8056 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8057 if (start) { 8058 startMarquee(); 8059 } else { 8060 stopMarquee(); 8061 } 8062 } 8063 } 8064 8065 /** 8066 * This method is called when the text is changed, in case any subclasses 8067 * would like to know. 8068 * 8069 * Within <code>text</code>, the <code>lengthAfter</code> characters 8070 * beginning at <code>start</code> have just replaced old text that had 8071 * length <code>lengthBefore</code>. It is an error to attempt to make 8072 * changes to <code>text</code> from this callback. 8073 * 8074 * @param text The text the TextView is displaying 8075 * @param start The offset of the start of the range of the text that was 8076 * modified 8077 * @param lengthBefore The length of the former text that has been replaced 8078 * @param lengthAfter The length of the replacement modified text 8079 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)8080 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 8081 // intentionally empty, template pattern method can be overridden by subclasses 8082 } 8083 8084 /** 8085 * This method is called when the selection has changed, in case any 8086 * subclasses would like to know. 8087 * 8088 * @param selStart The new selection start location. 8089 * @param selEnd The new selection end location. 8090 */ onSelectionChanged(int selStart, int selEnd)8091 protected void onSelectionChanged(int selStart, int selEnd) { 8092 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 8093 } 8094 8095 /** 8096 * Adds a TextWatcher to the list of those whose methods are called 8097 * whenever this TextView's text changes. 8098 * <p> 8099 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 8100 * not called after {@link #setText} calls. Now, doing {@link #setText} 8101 * if there are any text changed listeners forces the buffer type to 8102 * Editable if it would not otherwise be and does call this method. 8103 */ addTextChangedListener(TextWatcher watcher)8104 public void addTextChangedListener(TextWatcher watcher) { 8105 if (mListeners == null) { 8106 mListeners = new ArrayList<TextWatcher>(); 8107 } 8108 8109 mListeners.add(watcher); 8110 } 8111 8112 /** 8113 * Removes the specified TextWatcher from the list of those whose 8114 * methods are called 8115 * whenever this TextView's text changes. 8116 */ removeTextChangedListener(TextWatcher watcher)8117 public void removeTextChangedListener(TextWatcher watcher) { 8118 if (mListeners != null) { 8119 int i = mListeners.indexOf(watcher); 8120 8121 if (i >= 0) { 8122 mListeners.remove(i); 8123 } 8124 } 8125 } 8126 sendBeforeTextChanged(CharSequence text, int start, int before, int after)8127 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 8128 if (mListeners != null) { 8129 final ArrayList<TextWatcher> list = mListeners; 8130 final int count = list.size(); 8131 for (int i = 0; i < count; i++) { 8132 list.get(i).beforeTextChanged(text, start, before, after); 8133 } 8134 } 8135 8136 // The spans that are inside or intersect the modified region no longer make sense 8137 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 8138 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 8139 } 8140 8141 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)8142 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 8143 if (!(mText instanceof Editable)) return; 8144 Editable text = (Editable) mText; 8145 8146 T[] spans = text.getSpans(start, end, type); 8147 final int length = spans.length; 8148 for (int i = 0; i < length; i++) { 8149 final int spanStart = text.getSpanStart(spans[i]); 8150 final int spanEnd = text.getSpanEnd(spans[i]); 8151 if (spanEnd == start || spanStart == end) break; 8152 text.removeSpan(spans[i]); 8153 } 8154 } 8155 removeAdjacentSuggestionSpans(final int pos)8156 void removeAdjacentSuggestionSpans(final int pos) { 8157 if (!(mText instanceof Editable)) return; 8158 final Editable text = (Editable) mText; 8159 8160 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 8161 final int length = spans.length; 8162 for (int i = 0; i < length; i++) { 8163 final int spanStart = text.getSpanStart(spans[i]); 8164 final int spanEnd = text.getSpanEnd(spans[i]); 8165 if (spanEnd == pos || spanStart == pos) { 8166 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 8167 text.removeSpan(spans[i]); 8168 } 8169 } 8170 } 8171 } 8172 8173 /** 8174 * Not private so it can be called from an inner class without going 8175 * through a thunk. 8176 */ sendOnTextChanged(CharSequence text, int start, int before, int after)8177 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 8178 if (mListeners != null) { 8179 final ArrayList<TextWatcher> list = mListeners; 8180 final int count = list.size(); 8181 for (int i = 0; i < count; i++) { 8182 list.get(i).onTextChanged(text, start, before, after); 8183 } 8184 } 8185 8186 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 8187 } 8188 8189 /** 8190 * Not private so it can be called from an inner class without going 8191 * through a thunk. 8192 */ sendAfterTextChanged(Editable text)8193 void sendAfterTextChanged(Editable text) { 8194 if (mListeners != null) { 8195 final ArrayList<TextWatcher> list = mListeners; 8196 final int count = list.size(); 8197 for (int i = 0; i < count; i++) { 8198 list.get(i).afterTextChanged(text); 8199 } 8200 } 8201 hideErrorIfUnchanged(); 8202 } 8203 updateAfterEdit()8204 void updateAfterEdit() { 8205 invalidate(); 8206 int curs = getSelectionStart(); 8207 8208 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8209 registerForPreDraw(); 8210 } 8211 8212 checkForResize(); 8213 8214 if (curs >= 0) { 8215 mHighlightPathBogus = true; 8216 if (mEditor != null) mEditor.makeBlink(); 8217 bringPointIntoView(curs); 8218 } 8219 } 8220 8221 /** 8222 * Not private so it can be called from an inner class without going 8223 * through a thunk. 8224 */ handleTextChanged(CharSequence buffer, int start, int before, int after)8225 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 8226 sLastCutCopyOrTextChangedTime = 0; 8227 8228 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 8229 if (ims == null || ims.mBatchEditNesting == 0) { 8230 updateAfterEdit(); 8231 } 8232 if (ims != null) { 8233 ims.mContentChanged = true; 8234 if (ims.mChangedStart < 0) { 8235 ims.mChangedStart = start; 8236 ims.mChangedEnd = start+before; 8237 } else { 8238 ims.mChangedStart = Math.min(ims.mChangedStart, start); 8239 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 8240 } 8241 ims.mChangedDelta += after-before; 8242 } 8243 resetErrorChangedFlag(); 8244 sendOnTextChanged(buffer, start, before, after); 8245 onTextChanged(buffer, start, before, after); 8246 } 8247 8248 /** 8249 * Not private so it can be called from an inner class without going 8250 * through a thunk. 8251 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)8252 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 8253 // XXX Make the start and end move together if this ends up 8254 // spending too much time invalidating. 8255 8256 boolean selChanged = false; 8257 int newSelStart=-1, newSelEnd=-1; 8258 8259 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 8260 8261 if (what == Selection.SELECTION_END) { 8262 selChanged = true; 8263 newSelEnd = newStart; 8264 8265 if (oldStart >= 0 || newStart >= 0) { 8266 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 8267 checkForResize(); 8268 registerForPreDraw(); 8269 if (mEditor != null) mEditor.makeBlink(); 8270 } 8271 } 8272 8273 if (what == Selection.SELECTION_START) { 8274 selChanged = true; 8275 newSelStart = newStart; 8276 8277 if (oldStart >= 0 || newStart >= 0) { 8278 int end = Selection.getSelectionEnd(buf); 8279 invalidateCursor(end, oldStart, newStart); 8280 } 8281 } 8282 8283 if (selChanged) { 8284 mHighlightPathBogus = true; 8285 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 8286 8287 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) { 8288 if (newSelStart < 0) { 8289 newSelStart = Selection.getSelectionStart(buf); 8290 } 8291 if (newSelEnd < 0) { 8292 newSelEnd = Selection.getSelectionEnd(buf); 8293 } 8294 8295 if (mEditor != null) { 8296 mEditor.refreshTextActionMode(); 8297 if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) { 8298 // User generated selection has been removed. 8299 setHasTransientState(false); 8300 } 8301 } 8302 onSelectionChanged(newSelStart, newSelEnd); 8303 } 8304 } 8305 8306 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle || 8307 what instanceof CharacterStyle) { 8308 if (ims == null || ims.mBatchEditNesting == 0) { 8309 invalidate(); 8310 mHighlightPathBogus = true; 8311 checkForResize(); 8312 } else { 8313 ims.mContentChanged = true; 8314 } 8315 if (mEditor != null) { 8316 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 8317 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 8318 mEditor.invalidateHandlesAndActionMode(); 8319 } 8320 } 8321 8322 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 8323 mHighlightPathBogus = true; 8324 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 8325 ims.mSelectionModeChanged = true; 8326 } 8327 8328 if (Selection.getSelectionStart(buf) >= 0) { 8329 if (ims == null || ims.mBatchEditNesting == 0) { 8330 invalidateCursor(); 8331 } else { 8332 ims.mCursorChanged = true; 8333 } 8334 } 8335 } 8336 8337 if (what instanceof ParcelableSpan) { 8338 // If this is a span that can be sent to a remote process, 8339 // the current extract editor would be interested in it. 8340 if (ims != null && ims.mExtractedTextRequest != null) { 8341 if (ims.mBatchEditNesting != 0) { 8342 if (oldStart >= 0) { 8343 if (ims.mChangedStart > oldStart) { 8344 ims.mChangedStart = oldStart; 8345 } 8346 if (ims.mChangedStart > oldEnd) { 8347 ims.mChangedStart = oldEnd; 8348 } 8349 } 8350 if (newStart >= 0) { 8351 if (ims.mChangedStart > newStart) { 8352 ims.mChangedStart = newStart; 8353 } 8354 if (ims.mChangedStart > newEnd) { 8355 ims.mChangedStart = newEnd; 8356 } 8357 } 8358 } else { 8359 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: " 8360 + oldStart + "-" + oldEnd + "," 8361 + newStart + "-" + newEnd + " " + what); 8362 ims.mContentChanged = true; 8363 } 8364 } 8365 } 8366 8367 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 && 8368 what instanceof SpellCheckSpan) { 8369 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 8370 } 8371 } 8372 8373 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)8374 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 8375 if (isTemporarilyDetached()) { 8376 // If we are temporarily in the detach state, then do nothing. 8377 super.onFocusChanged(focused, direction, previouslyFocusedRect); 8378 return; 8379 } 8380 8381 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 8382 8383 if (focused) { 8384 if (mText instanceof Spannable) { 8385 Spannable sp = (Spannable) mText; 8386 MetaKeyKeyListener.resetMetaState(sp); 8387 } 8388 } 8389 8390 startStopMarquee(focused); 8391 8392 if (mTransformation != null) { 8393 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 8394 } 8395 8396 super.onFocusChanged(focused, direction, previouslyFocusedRect); 8397 } 8398 8399 @Override onWindowFocusChanged(boolean hasWindowFocus)8400 public void onWindowFocusChanged(boolean hasWindowFocus) { 8401 super.onWindowFocusChanged(hasWindowFocus); 8402 8403 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 8404 8405 startStopMarquee(hasWindowFocus); 8406 } 8407 8408 @Override onVisibilityChanged(View changedView, int visibility)8409 protected void onVisibilityChanged(View changedView, int visibility) { 8410 super.onVisibilityChanged(changedView, visibility); 8411 if (mEditor != null && visibility != VISIBLE) { 8412 mEditor.hideCursorAndSpanControllers(); 8413 stopTextActionMode(); 8414 } 8415 } 8416 8417 /** 8418 * Use {@link BaseInputConnection#removeComposingSpans 8419 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 8420 * state from this text view. 8421 */ clearComposingText()8422 public void clearComposingText() { 8423 if (mText instanceof Spannable) { 8424 BaseInputConnection.removeComposingSpans((Spannable)mText); 8425 } 8426 } 8427 8428 @Override setSelected(boolean selected)8429 public void setSelected(boolean selected) { 8430 boolean wasSelected = isSelected(); 8431 8432 super.setSelected(selected); 8433 8434 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8435 if (selected) { 8436 startMarquee(); 8437 } else { 8438 stopMarquee(); 8439 } 8440 } 8441 } 8442 8443 @Override onTouchEvent(MotionEvent event)8444 public boolean onTouchEvent(MotionEvent event) { 8445 final int action = event.getActionMasked(); 8446 if (mEditor != null) { 8447 mEditor.onTouchEvent(event); 8448 8449 if (mEditor.mSelectionModifierCursorController != null && 8450 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 8451 return true; 8452 } 8453 } 8454 8455 final boolean superResult = super.onTouchEvent(event); 8456 8457 /* 8458 * Don't handle the release after a long press, because it will move the selection away from 8459 * whatever the menu action was trying to affect. If the long press should have triggered an 8460 * insertion action mode, we can now actually show it. 8461 */ 8462 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 8463 mEditor.mDiscardNextActionUp = false; 8464 8465 if (mEditor.mIsInsertionActionModeStartPending) { 8466 mEditor.startInsertionActionMode(); 8467 mEditor.mIsInsertionActionModeStartPending = false; 8468 } 8469 return superResult; 8470 } 8471 8472 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && 8473 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 8474 8475 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 8476 && mText instanceof Spannable && mLayout != null) { 8477 boolean handled = false; 8478 8479 if (mMovement != null) { 8480 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 8481 } 8482 8483 final boolean textIsSelectable = isTextSelectable(); 8484 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 8485 // The LinkMovementMethod which should handle taps on links has not been installed 8486 // on non editable text that support text selection. 8487 // We reproduce its behavior here to open links for these. 8488 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 8489 getSelectionEnd(), ClickableSpan.class); 8490 8491 if (links.length > 0) { 8492 links[0].onClick(this); 8493 handled = true; 8494 } 8495 } 8496 8497 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 8498 // Show the IME, except when selecting in read-only text. 8499 final InputMethodManager imm = InputMethodManager.peekInstance(); 8500 viewClicked(imm); 8501 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) { 8502 handled |= imm != null && imm.showSoftInput(this, 0); 8503 } 8504 8505 // The above condition ensures that the mEditor is not null 8506 mEditor.onTouchUpEvent(event); 8507 8508 handled = true; 8509 } 8510 8511 if (handled) { 8512 return true; 8513 } 8514 } 8515 8516 return superResult; 8517 } 8518 8519 @Override onGenericMotionEvent(MotionEvent event)8520 public boolean onGenericMotionEvent(MotionEvent event) { 8521 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8522 try { 8523 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 8524 return true; 8525 } 8526 } catch (AbstractMethodError ex) { 8527 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 8528 // Ignore its absence in case third party applications implemented the 8529 // interface directly. 8530 } 8531 } 8532 return super.onGenericMotionEvent(event); 8533 } 8534 8535 @Override onCreateContextMenu(ContextMenu menu)8536 protected void onCreateContextMenu(ContextMenu menu) { 8537 if (mEditor != null) { 8538 mEditor.onCreateContextMenu(menu); 8539 } 8540 } 8541 8542 @Override showContextMenu()8543 public boolean showContextMenu() { 8544 if (mEditor != null) { 8545 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 8546 } 8547 return super.showContextMenu(); 8548 } 8549 8550 @Override showContextMenu(float x, float y)8551 public boolean showContextMenu(float x, float y) { 8552 if (mEditor != null) { 8553 mEditor.setContextMenuAnchor(x, y); 8554 } 8555 return super.showContextMenu(x, y); 8556 } 8557 8558 /** 8559 * @return True iff this TextView contains a text that can be edited, or if this is 8560 * a selectable TextView. 8561 */ isTextEditable()8562 boolean isTextEditable() { 8563 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 8564 } 8565 8566 /** 8567 * Returns true, only while processing a touch gesture, if the initial 8568 * touch down event caused focus to move to the text view and as a result 8569 * its selection changed. Only valid while processing the touch gesture 8570 * of interest, in an editable text view. 8571 */ didTouchFocusSelect()8572 public boolean didTouchFocusSelect() { 8573 return mEditor != null && mEditor.mTouchFocusSelected; 8574 } 8575 8576 @Override cancelLongPress()8577 public void cancelLongPress() { 8578 super.cancelLongPress(); 8579 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 8580 } 8581 8582 @Override onTrackballEvent(MotionEvent event)8583 public boolean onTrackballEvent(MotionEvent event) { 8584 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8585 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 8586 return true; 8587 } 8588 } 8589 8590 return super.onTrackballEvent(event); 8591 } 8592 setScroller(Scroller s)8593 public void setScroller(Scroller s) { 8594 mScroller = s; 8595 } 8596 8597 @Override getLeftFadingEdgeStrength()8598 protected float getLeftFadingEdgeStrength() { 8599 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 8600 final Marquee marquee = mMarquee; 8601 if (marquee.shouldDrawLeftFade()) { 8602 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 8603 } else { 8604 return 0.0f; 8605 } 8606 } else if (getLineCount() == 1) { 8607 final float lineLeft = getLayout().getLineLeft(0); 8608 if(lineLeft > mScrollX) return 0.0f; 8609 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 8610 } 8611 return super.getLeftFadingEdgeStrength(); 8612 } 8613 8614 @Override getRightFadingEdgeStrength()8615 protected float getRightFadingEdgeStrength() { 8616 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 8617 final Marquee marquee = mMarquee; 8618 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 8619 } else if (getLineCount() == 1) { 8620 final float rightEdge = mScrollX + (getWidth() - getCompoundPaddingLeft() - 8621 getCompoundPaddingRight()); 8622 final float lineRight = getLayout().getLineRight(0); 8623 if(lineRight < rightEdge) return 0.0f; 8624 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 8625 } 8626 return super.getRightFadingEdgeStrength(); 8627 } 8628 8629 /** 8630 * Calculates the fading edge strength as the ratio of the distance between two 8631 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 8632 * value for the distance calculation. 8633 * 8634 * @param position1 A horizontal position. 8635 * @param position2 A horizontal position. 8636 * @return Fading edge strength between [0.0f, 1.0f]. 8637 */ 8638 @FloatRange(from=0.0, to=1.0) getHorizontalFadingEdgeStrength(float position1, float position2)8639 private final float getHorizontalFadingEdgeStrength(float position1, float position2) { 8640 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 8641 if(horizontalFadingEdgeLength == 0) return 0.0f; 8642 final float diff = Math.abs(position1 - position2); 8643 if(diff > horizontalFadingEdgeLength) return 1.0f; 8644 return diff / horizontalFadingEdgeLength; 8645 } 8646 isMarqueeFadeEnabled()8647 private final boolean isMarqueeFadeEnabled() { 8648 return mEllipsize == TextUtils.TruncateAt.MARQUEE && 8649 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 8650 } 8651 8652 @Override computeHorizontalScrollRange()8653 protected int computeHorizontalScrollRange() { 8654 if (mLayout != null) { 8655 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ? 8656 (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 8657 } 8658 8659 return super.computeHorizontalScrollRange(); 8660 } 8661 8662 @Override computeVerticalScrollRange()8663 protected int computeVerticalScrollRange() { 8664 if (mLayout != null) 8665 return mLayout.getHeight(); 8666 8667 return super.computeVerticalScrollRange(); 8668 } 8669 8670 @Override computeVerticalScrollExtent()8671 protected int computeVerticalScrollExtent() { 8672 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 8673 } 8674 8675 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)8676 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 8677 super.findViewsWithText(outViews, searched, flags); 8678 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 8679 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 8680 String searchedLowerCase = searched.toString().toLowerCase(); 8681 String textLowerCase = mText.toString().toLowerCase(); 8682 if (textLowerCase.contains(searchedLowerCase)) { 8683 outViews.add(this); 8684 } 8685 } 8686 } 8687 8688 public enum BufferType { 8689 NORMAL, SPANNABLE, EDITABLE, 8690 } 8691 8692 /** 8693 * Returns the TextView_textColor attribute from the TypedArray, if set, or 8694 * the TextAppearance_textColor from the TextView_textAppearance attribute, 8695 * if TextView_textColor was not set directly. 8696 * 8697 * @removed 8698 */ getTextColors(Context context, TypedArray attrs)8699 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 8700 if (attrs == null) { 8701 // Preserve behavior prior to removal of this API. 8702 throw new NullPointerException(); 8703 } 8704 8705 // It's not safe to use this method from apps. The parameter 'attrs' 8706 // must have been obtained using the TextView filter array which is not 8707 // available to the SDK. As such, we grab a default TypedArray with the 8708 // right filter instead here. 8709 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 8710 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 8711 if (colors == null) { 8712 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 8713 if (ap != 0) { 8714 final TypedArray appearance = context.obtainStyledAttributes( 8715 ap, R.styleable.TextAppearance); 8716 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 8717 appearance.recycle(); 8718 } 8719 } 8720 a.recycle(); 8721 8722 return colors; 8723 } 8724 8725 /** 8726 * Returns the default color from the TextView_textColor attribute from the 8727 * AttributeSet, if set, or the default color from the 8728 * TextAppearance_textColor from the TextView_textAppearance attribute, if 8729 * TextView_textColor was not set directly. 8730 * 8731 * @removed 8732 */ getTextColor(Context context, TypedArray attrs, int def)8733 public static int getTextColor(Context context, TypedArray attrs, int def) { 8734 final ColorStateList colors = getTextColors(context, attrs); 8735 if (colors == null) { 8736 return def; 8737 } else { 8738 return colors.getDefaultColor(); 8739 } 8740 } 8741 8742 @Override onKeyShortcut(int keyCode, KeyEvent event)8743 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 8744 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 8745 // Handle Ctrl-only shortcuts. 8746 switch (keyCode) { 8747 case KeyEvent.KEYCODE_A: 8748 if (canSelectText()) { 8749 return onTextContextMenuItem(ID_SELECT_ALL); 8750 } 8751 break; 8752 case KeyEvent.KEYCODE_Z: 8753 if (canUndo()) { 8754 return onTextContextMenuItem(ID_UNDO); 8755 } 8756 break; 8757 case KeyEvent.KEYCODE_X: 8758 if (canCut()) { 8759 return onTextContextMenuItem(ID_CUT); 8760 } 8761 break; 8762 case KeyEvent.KEYCODE_C: 8763 if (canCopy()) { 8764 return onTextContextMenuItem(ID_COPY); 8765 } 8766 break; 8767 case KeyEvent.KEYCODE_V: 8768 if (canPaste()) { 8769 return onTextContextMenuItem(ID_PASTE); 8770 } 8771 break; 8772 } 8773 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 8774 // Handle Ctrl-Shift shortcuts. 8775 switch (keyCode) { 8776 case KeyEvent.KEYCODE_Z: 8777 if (canRedo()) { 8778 return onTextContextMenuItem(ID_REDO); 8779 } 8780 break; 8781 case KeyEvent.KEYCODE_V: 8782 if (canPaste()) { 8783 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 8784 } 8785 } 8786 } 8787 return super.onKeyShortcut(keyCode, event); 8788 } 8789 8790 /** 8791 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 8792 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 8793 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 8794 * sufficient. 8795 */ canSelectText()8796 boolean canSelectText() { 8797 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 8798 } 8799 8800 /** 8801 * Test based on the <i>intrinsic</i> charateristics of the TextView. 8802 * The text must be spannable and the movement method must allow for arbitary selection. 8803 * 8804 * See also {@link #canSelectText()}. 8805 */ textCanBeSelected()8806 boolean textCanBeSelected() { 8807 // prepareCursorController() relies on this method. 8808 // If you change this condition, make sure prepareCursorController is called anywhere 8809 // the value of this condition might be changed. 8810 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 8811 return isTextEditable() || 8812 (isTextSelectable() && mText instanceof Spannable && isEnabled()); 8813 } 8814 getTextServicesLocale(boolean allowNullLocale)8815 private Locale getTextServicesLocale(boolean allowNullLocale) { 8816 // Start fetching the text services locale asynchronously. 8817 updateTextServicesLocaleAsync(); 8818 // If !allowNullLocale and there is no cached text services locale, just return the default 8819 // locale. 8820 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 8821 : mCurrentSpellCheckerLocaleCache; 8822 } 8823 8824 /** 8825 * This is a temporary method. Future versions may support multi-locale text. 8826 * Caveat: This method may not return the latest text services locale, but this should be 8827 * acceptable and it's more important to make this method asynchronous. 8828 * 8829 * @return The locale that should be used for a word iterator 8830 * in this TextView, based on the current spell checker settings, 8831 * the current IME's locale, or the system default locale. 8832 * Please note that a word iterator in this TextView is different from another word iterator 8833 * used by SpellChecker.java of TextView. This method should be used for the former. 8834 * @hide 8835 */ 8836 // TODO: Support multi-locale 8837 // TODO: Update the text services locale immediately after the keyboard locale is switched 8838 // by catching intent of keyboard switch event getTextServicesLocale()8839 public Locale getTextServicesLocale() { 8840 return getTextServicesLocale(false /* allowNullLocale */); 8841 } 8842 8843 /** 8844 * @return true if this TextView is specialized for showing and interacting with the extracted 8845 * text in a full-screen input method. 8846 * @hide 8847 */ isInExtractedMode()8848 public boolean isInExtractedMode() { 8849 return false; 8850 } 8851 8852 /** 8853 * This is a temporary method. Future versions may support multi-locale text. 8854 * Caveat: This method may not return the latest spell checker locale, but this should be 8855 * acceptable and it's more important to make this method asynchronous. 8856 * 8857 * @return The locale that should be used for a spell checker in this TextView, 8858 * based on the current spell checker settings, the current IME's locale, or the system default 8859 * locale. 8860 * @hide 8861 */ getSpellCheckerLocale()8862 public Locale getSpellCheckerLocale() { 8863 return getTextServicesLocale(true /* allowNullLocale */); 8864 } 8865 updateTextServicesLocaleAsync()8866 private void updateTextServicesLocaleAsync() { 8867 // AsyncTask.execute() uses a serial executor which means we don't have 8868 // to lock around updateTextServicesLocaleLocked() to prevent it from 8869 // being executed n times in parallel. 8870 AsyncTask.execute(new Runnable() { 8871 @Override 8872 public void run() { 8873 updateTextServicesLocaleLocked(); 8874 } 8875 }); 8876 } 8877 updateTextServicesLocaleLocked()8878 private void updateTextServicesLocaleLocked() { 8879 final TextServicesManager textServicesManager = (TextServicesManager) 8880 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 8881 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 8882 final Locale locale; 8883 if (subtype != null) { 8884 locale = subtype.getLocaleObject(); 8885 } else { 8886 locale = null; 8887 } 8888 mCurrentSpellCheckerLocaleCache = locale; 8889 } 8890 onLocaleChanged()8891 void onLocaleChanged() { 8892 mEditor.onLocaleChanged(); 8893 } 8894 8895 /** 8896 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 8897 * Made available to achieve a consistent behavior. 8898 * @hide 8899 */ getWordIterator()8900 public WordIterator getWordIterator() { 8901 if (mEditor != null) { 8902 return mEditor.getWordIterator(); 8903 } else { 8904 return null; 8905 } 8906 } 8907 8908 /** @hide */ 8909 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)8910 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 8911 super.onPopulateAccessibilityEventInternal(event); 8912 8913 final CharSequence text = getTextForAccessibility(); 8914 if (!TextUtils.isEmpty(text)) { 8915 event.getText().add(text); 8916 } 8917 } 8918 8919 /** 8920 * @return true if the user has explicitly allowed accessibility services 8921 * to speak passwords. 8922 */ shouldSpeakPasswordsForAccessibility()8923 private boolean shouldSpeakPasswordsForAccessibility() { 8924 return (Settings.Secure.getIntForUser(mContext.getContentResolver(), 8925 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, 8926 UserHandle.USER_CURRENT_OR_SELF) == 1); 8927 } 8928 8929 @Override getAccessibilityClassName()8930 public CharSequence getAccessibilityClassName() { 8931 return TextView.class.getName(); 8932 } 8933 8934 @Override onProvideStructure(ViewStructure structure)8935 public void onProvideStructure(ViewStructure structure) { 8936 super.onProvideStructure(structure); 8937 final boolean isPassword = hasPasswordTransformationMethod() 8938 || isPasswordInputType(getInputType()); 8939 if (!isPassword) { 8940 if (mLayout == null) { 8941 assumeLayout(); 8942 } 8943 Layout layout = mLayout; 8944 final int lineCount = layout.getLineCount(); 8945 if (lineCount <= 1) { 8946 // Simple case: this is a single line. 8947 structure.setText(getText(), getSelectionStart(), getSelectionEnd()); 8948 } else { 8949 // Complex case: multi-line, could be scrolled or within a scroll container 8950 // so some lines are not visible. 8951 final int[] tmpCords = new int[2]; 8952 getLocationInWindow(tmpCords); 8953 final int topWindowLocation = tmpCords[1]; 8954 View root = this; 8955 ViewParent viewParent = getParent(); 8956 while (viewParent instanceof View) { 8957 root = (View) viewParent; 8958 viewParent = root.getParent(); 8959 } 8960 final int windowHeight = root.getHeight(); 8961 final int topLine; 8962 final int bottomLine; 8963 if (topWindowLocation >= 0) { 8964 // The top of the view is fully within its window; start text at line 0. 8965 topLine = getLineAtCoordinateUnclamped(0); 8966 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1); 8967 } else { 8968 // The top of hte window has scrolled off the top of the window; figure out 8969 // the starting line for this. 8970 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 8971 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation); 8972 } 8973 // We want to return some contextual lines above/below the lines that are 8974 // actually visible. 8975 int expandedTopLine = topLine - (bottomLine-topLine)/2; 8976 if (expandedTopLine < 0) { 8977 expandedTopLine = 0; 8978 } 8979 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2; 8980 if (expandedBottomLine >= lineCount) { 8981 expandedBottomLine = lineCount-1; 8982 } 8983 // Convert lines into character offsets. 8984 int expandedTopChar = layout.getLineStart(expandedTopLine); 8985 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 8986 // Take into account selection -- if there is a selection, we need to expand 8987 // the text we are returning to include that selection. 8988 final int selStart = getSelectionStart(); 8989 final int selEnd = getSelectionEnd(); 8990 if (selStart < selEnd) { 8991 if (selStart < expandedTopChar) { 8992 expandedTopChar = selStart; 8993 } 8994 if (selEnd > expandedBottomChar) { 8995 expandedBottomChar = selEnd; 8996 } 8997 } 8998 // Get the text and trim it to the range we are reporting. 8999 CharSequence text = getText(); 9000 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 9001 text = text.subSequence(expandedTopChar, expandedBottomChar); 9002 } 9003 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar); 9004 final int[] lineOffsets = new int[bottomLine-topLine+1]; 9005 final int[] lineBaselines = new int[bottomLine-topLine+1]; 9006 final int baselineOffset = getBaselineOffset(); 9007 for (int i=topLine; i<=bottomLine; i++) { 9008 lineOffsets[i-topLine] = layout.getLineStart(i); 9009 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset; 9010 } 9011 structure.setTextLines(lineOffsets, lineBaselines); 9012 } 9013 9014 // Extract style information that applies to the TextView as a whole. 9015 int style = 0; 9016 int typefaceStyle = getTypefaceStyle(); 9017 if ((typefaceStyle & Typeface.BOLD) != 0) { 9018 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 9019 } 9020 if ((typefaceStyle & Typeface.ITALIC) != 0) { 9021 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 9022 } 9023 9024 // Global styles can also be set via TextView.setPaintFlags(). 9025 int paintFlags = mTextPaint.getFlags(); 9026 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 9027 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 9028 } 9029 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 9030 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 9031 } 9032 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 9033 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 9034 } 9035 9036 // TextView does not have its own text background color. A background is either part 9037 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 9038 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 9039 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 9040 } 9041 structure.setHint(getHint()); 9042 } 9043 9044 /** @hide */ 9045 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)9046 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 9047 super.onInitializeAccessibilityEventInternal(event); 9048 9049 final boolean isPassword = hasPasswordTransformationMethod(); 9050 event.setPassword(isPassword); 9051 9052 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 9053 event.setFromIndex(Selection.getSelectionStart(mText)); 9054 event.setToIndex(Selection.getSelectionEnd(mText)); 9055 event.setItemCount(mText.length()); 9056 } 9057 } 9058 9059 /** @hide */ 9060 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)9061 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 9062 super.onInitializeAccessibilityNodeInfoInternal(info); 9063 9064 final boolean isPassword = hasPasswordTransformationMethod(); 9065 info.setPassword(isPassword); 9066 info.setText(getTextForAccessibility()); 9067 9068 if (mBufferType == BufferType.EDITABLE) { 9069 info.setEditable(true); 9070 if (isEnabled()) { 9071 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 9072 } 9073 } 9074 9075 if (mEditor != null) { 9076 info.setInputType(mEditor.mInputType); 9077 9078 if (mEditor.mError != null) { 9079 info.setContentInvalid(true); 9080 info.setError(mEditor.mError); 9081 } 9082 } 9083 9084 if (!TextUtils.isEmpty(mText)) { 9085 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 9086 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 9087 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 9088 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 9089 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 9090 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 9091 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 9092 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 9093 } 9094 9095 if (isFocused()) { 9096 if (canCopy()) { 9097 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 9098 } 9099 if (canPaste()) { 9100 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 9101 } 9102 if (canCut()) { 9103 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 9104 } 9105 if (canShare()) { 9106 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 9107 ACCESSIBILITY_ACTION_SHARE, 9108 getResources().getString(com.android.internal.R.string.share))); 9109 } 9110 if (canProcessText()) { // also implies mEditor is not null. 9111 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 9112 } 9113 } 9114 9115 // Check for known input filter types. 9116 final int numFilters = mFilters.length; 9117 for (int i = 0; i < numFilters; i++) { 9118 final InputFilter filter = mFilters[i]; 9119 if (filter instanceof InputFilter.LengthFilter) { 9120 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 9121 } 9122 } 9123 9124 if (!isSingleLine()) { 9125 info.setMultiLine(true); 9126 } 9127 } 9128 9129 /** 9130 * Performs an accessibility action after it has been offered to the 9131 * delegate. 9132 * 9133 * @hide 9134 */ 9135 @Override performAccessibilityActionInternal(int action, Bundle arguments)9136 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 9137 if (mEditor != null 9138 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 9139 return true; 9140 } 9141 switch (action) { 9142 case AccessibilityNodeInfo.ACTION_CLICK: { 9143 return performAccessibilityActionClick(arguments); 9144 } 9145 case AccessibilityNodeInfo.ACTION_COPY: { 9146 if (isFocused() && canCopy()) { 9147 if (onTextContextMenuItem(ID_COPY)) { 9148 return true; 9149 } 9150 } 9151 } return false; 9152 case AccessibilityNodeInfo.ACTION_PASTE: { 9153 if (isFocused() && canPaste()) { 9154 if (onTextContextMenuItem(ID_PASTE)) { 9155 return true; 9156 } 9157 } 9158 } return false; 9159 case AccessibilityNodeInfo.ACTION_CUT: { 9160 if (isFocused() && canCut()) { 9161 if (onTextContextMenuItem(ID_CUT)) { 9162 return true; 9163 } 9164 } 9165 } return false; 9166 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 9167 ensureIterableTextForAccessibilitySelectable(); 9168 CharSequence text = getIterableTextForAccessibility(); 9169 if (text == null) { 9170 return false; 9171 } 9172 final int start = (arguments != null) ? arguments.getInt( 9173 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 9174 final int end = (arguments != null) ? arguments.getInt( 9175 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 9176 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 9177 // No arguments clears the selection. 9178 if (start == end && end == -1) { 9179 Selection.removeSelection((Spannable) text); 9180 return true; 9181 } 9182 if (start >= 0 && start <= end && end <= text.length()) { 9183 Selection.setSelection((Spannable) text, start, end); 9184 // Make sure selection mode is engaged. 9185 if (mEditor != null) { 9186 mEditor.startSelectionActionMode(); 9187 } 9188 return true; 9189 } 9190 } 9191 } return false; 9192 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 9193 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 9194 ensureIterableTextForAccessibilitySelectable(); 9195 return super.performAccessibilityActionInternal(action, arguments); 9196 } 9197 case ACCESSIBILITY_ACTION_SHARE: { 9198 if (isFocused() && canShare()) { 9199 if (onTextContextMenuItem(ID_SHARE)) { 9200 return true; 9201 } 9202 } 9203 } return false; 9204 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 9205 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 9206 return false; 9207 } 9208 CharSequence text = (arguments != null) ? arguments.getCharSequence( 9209 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 9210 setText(text); 9211 if (mText != null) { 9212 int updatedTextLength = mText.length(); 9213 if (updatedTextLength > 0) { 9214 Selection.setSelection((Spannable) mText, updatedTextLength); 9215 } 9216 } 9217 } return true; 9218 default: { 9219 return super.performAccessibilityActionInternal(action, arguments); 9220 } 9221 } 9222 } 9223 performAccessibilityActionClick(Bundle arguments)9224 private boolean performAccessibilityActionClick(Bundle arguments) { 9225 boolean handled = false; 9226 9227 if (!isEnabled()) { 9228 return false; 9229 } 9230 9231 if (isClickable() || isLongClickable()) { 9232 // Simulate View.onTouchEvent for an ACTION_UP event 9233 if (isFocusable() && !isFocused()) { 9234 requestFocus(); 9235 } 9236 9237 performClick(); 9238 handled = true; 9239 } 9240 9241 // Show the IME, except when selecting in read-only text. 9242 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 9243 && (isTextEditable() || isTextSelectable()) && isFocused()) { 9244 final InputMethodManager imm = InputMethodManager.peekInstance(); 9245 viewClicked(imm); 9246 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 9247 handled |= imm.showSoftInput(this, 0); 9248 } 9249 } 9250 9251 return handled; 9252 } 9253 hasSpannableText()9254 private boolean hasSpannableText() { 9255 return mText != null && mText instanceof Spannable; 9256 } 9257 9258 /** @hide */ 9259 @Override sendAccessibilityEventInternal(int eventType)9260 public void sendAccessibilityEventInternal(int eventType) { 9261 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 9262 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 9263 } 9264 9265 // Do not send scroll events since first they are not interesting for 9266 // accessibility and second such events a generated too frequently. 9267 // For details see the implementation of bringTextIntoView(). 9268 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 9269 return; 9270 } 9271 super.sendAccessibilityEventInternal(eventType); 9272 } 9273 9274 /** 9275 * Returns the text that should be exposed to accessibility services. 9276 * <p> 9277 * This approximates what is displayed visually. If the user has specified 9278 * that accessibility services should speak passwords, this method will 9279 * bypass any password transformation method and return unobscured text. 9280 * 9281 * @return the text that should be exposed to accessibility services, may 9282 * be {@code null} if no text is set 9283 */ 9284 @Nullable getTextForAccessibility()9285 private CharSequence getTextForAccessibility() { 9286 // If the text is empty, we must be showing the hint text. 9287 if (TextUtils.isEmpty(mText)) { 9288 return mHint; 9289 } 9290 9291 // Check whether we need to bypass the transformation 9292 // method and expose unobscured text. 9293 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) { 9294 return mText; 9295 } 9296 9297 // Otherwise, speak whatever text is being displayed. 9298 return mTransformed; 9299 } 9300 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)9301 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 9302 int fromIndex, int removedCount, int addedCount) { 9303 AccessibilityEvent event = 9304 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 9305 event.setFromIndex(fromIndex); 9306 event.setRemovedCount(removedCount); 9307 event.setAddedCount(addedCount); 9308 event.setBeforeText(beforeText); 9309 sendAccessibilityEventUnchecked(event); 9310 } 9311 9312 /** 9313 * Returns whether this text view is a current input method target. The 9314 * default implementation just checks with {@link InputMethodManager}. 9315 */ isInputMethodTarget()9316 public boolean isInputMethodTarget() { 9317 InputMethodManager imm = InputMethodManager.peekInstance(); 9318 return imm != null && imm.isActive(this); 9319 } 9320 9321 static final int ID_SELECT_ALL = android.R.id.selectAll; 9322 static final int ID_UNDO = android.R.id.undo; 9323 static final int ID_REDO = android.R.id.redo; 9324 static final int ID_CUT = android.R.id.cut; 9325 static final int ID_COPY = android.R.id.copy; 9326 static final int ID_PASTE = android.R.id.paste; 9327 static final int ID_SHARE = android.R.id.shareText; 9328 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 9329 static final int ID_REPLACE = android.R.id.replaceText; 9330 9331 /** 9332 * Called when a context menu option for the text view is selected. Currently 9333 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 9334 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 9335 * 9336 * @return true if the context menu item action was performed. 9337 */ onTextContextMenuItem(int id)9338 public boolean onTextContextMenuItem(int id) { 9339 int min = 0; 9340 int max = mText.length(); 9341 9342 if (isFocused()) { 9343 final int selStart = getSelectionStart(); 9344 final int selEnd = getSelectionEnd(); 9345 9346 min = Math.max(0, Math.min(selStart, selEnd)); 9347 max = Math.max(0, Math.max(selStart, selEnd)); 9348 } 9349 9350 switch (id) { 9351 case ID_SELECT_ALL: 9352 selectAllText(); 9353 return true; 9354 9355 case ID_UNDO: 9356 if (mEditor != null) { 9357 mEditor.undo(); 9358 } 9359 return true; // Returns true even if nothing was undone. 9360 9361 case ID_REDO: 9362 if (mEditor != null) { 9363 mEditor.redo(); 9364 } 9365 return true; // Returns true even if nothing was undone. 9366 9367 case ID_PASTE: 9368 paste(min, max, true /* withFormatting */); 9369 return true; 9370 9371 case ID_PASTE_AS_PLAIN_TEXT: 9372 paste(min, max, false /* withFormatting */); 9373 return true; 9374 9375 case ID_CUT: 9376 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 9377 deleteText_internal(min, max); 9378 return true; 9379 9380 case ID_COPY: 9381 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 9382 stopTextActionMode(); 9383 return true; 9384 9385 case ID_REPLACE: 9386 if (mEditor != null) { 9387 mEditor.replace(); 9388 } 9389 return true; 9390 9391 case ID_SHARE: 9392 shareSelectedText(); 9393 return true; 9394 } 9395 return false; 9396 } 9397 getTransformedText(int start, int end)9398 CharSequence getTransformedText(int start, int end) { 9399 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 9400 } 9401 9402 @Override performLongClick()9403 public boolean performLongClick() { 9404 boolean handled = false; 9405 9406 if (mEditor != null) { 9407 mEditor.mIsBeingLongClicked = true; 9408 } 9409 9410 if (super.performLongClick()) { 9411 handled = true; 9412 } 9413 9414 if (mEditor != null) { 9415 handled |= mEditor.performLongClick(handled); 9416 mEditor.mIsBeingLongClicked = false; 9417 } 9418 9419 if (handled) { 9420 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 9421 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 9422 } 9423 9424 return handled; 9425 } 9426 9427 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)9428 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 9429 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 9430 if (mEditor != null) { 9431 mEditor.onScrollChanged(); 9432 } 9433 } 9434 9435 /** 9436 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 9437 * by the IME or by the spell checker as the user types. This is done by adding 9438 * {@link SuggestionSpan}s to the text. 9439 * 9440 * When suggestions are enabled (default), this list of suggestions will be displayed when the 9441 * user asks for them on these parts of the text. This value depends on the inputType of this 9442 * TextView. 9443 * 9444 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 9445 * 9446 * In addition, the type variation must be one of 9447 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 9448 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 9449 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 9450 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 9451 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 9452 * 9453 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 9454 * 9455 * @return true if the suggestions popup window is enabled, based on the inputType. 9456 */ isSuggestionsEnabled()9457 public boolean isSuggestionsEnabled() { 9458 if (mEditor == null) return false; 9459 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 9460 return false; 9461 } 9462 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 9463 9464 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 9465 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || 9466 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || 9467 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || 9468 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || 9469 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 9470 } 9471 9472 /** 9473 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 9474 * selection is initiated in this View. 9475 * 9476 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 9477 * Paste, Replace and Share actions, depending on what this View supports. 9478 * 9479 * <p>A custom implementation can add new entries in the default menu in its 9480 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 9481 * method. The default actions can also be removed from the menu using 9482 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 9483 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 9484 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 9485 * 9486 * <p>Returning false from 9487 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 9488 * will prevent the action mode from being started. 9489 * 9490 * <p>Action click events should be handled by the custom implementation of 9491 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 9492 * android.view.MenuItem)}. 9493 * 9494 * <p>Note that text selection mode is not started when a TextView receives focus and the 9495 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 9496 * that case, to allow for quick replacement. 9497 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)9498 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 9499 createEditorIfNeeded(); 9500 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 9501 } 9502 9503 /** 9504 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 9505 * 9506 * @return The current custom selection callback. 9507 */ getCustomSelectionActionModeCallback()9508 public ActionMode.Callback getCustomSelectionActionModeCallback() { 9509 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 9510 } 9511 9512 /** 9513 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 9514 * insertion is initiated in this View. 9515 * The standard implementation populates the menu with a subset of Select All, 9516 * Paste and Replace actions, depending on what this View supports. 9517 * 9518 * <p>A custom implementation can add new entries in the default menu in its 9519 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 9520 * android.view.Menu)} method. The default actions can also be removed from the menu using 9521 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 9522 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 9523 * 9524 * <p>Returning false from 9525 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 9526 * android.view.Menu)} will prevent the action mode from being started.</p> 9527 * 9528 * <p>Action click events should be handled by the custom implementation of 9529 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 9530 * android.view.MenuItem)}.</p> 9531 * 9532 * <p>Note that text insertion mode is not started when a TextView receives focus and the 9533 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 9534 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)9535 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 9536 createEditorIfNeeded(); 9537 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 9538 } 9539 9540 /** 9541 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 9542 * 9543 * @return The current custom insertion callback. 9544 */ getCustomInsertionActionModeCallback()9545 public ActionMode.Callback getCustomInsertionActionModeCallback() { 9546 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 9547 } 9548 9549 /** 9550 * @hide 9551 */ stopTextActionMode()9552 protected void stopTextActionMode() { 9553 if (mEditor != null) { 9554 mEditor.stopTextActionMode(); 9555 } 9556 } 9557 canUndo()9558 boolean canUndo() { 9559 return mEditor != null && mEditor.canUndo(); 9560 } 9561 canRedo()9562 boolean canRedo() { 9563 return mEditor != null && mEditor.canRedo(); 9564 } 9565 canCut()9566 boolean canCut() { 9567 if (hasPasswordTransformationMethod()) { 9568 return false; 9569 } 9570 9571 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && 9572 mEditor.mKeyListener != null) { 9573 return true; 9574 } 9575 9576 return false; 9577 } 9578 canCopy()9579 boolean canCopy() { 9580 if (hasPasswordTransformationMethod()) { 9581 return false; 9582 } 9583 9584 if (mText.length() > 0 && hasSelection() && mEditor != null) { 9585 return true; 9586 } 9587 9588 return false; 9589 } 9590 canShare()9591 boolean canShare() { 9592 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 9593 return false; 9594 } 9595 return canCopy(); 9596 } 9597 isDeviceProvisioned()9598 boolean isDeviceProvisioned() { 9599 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 9600 mDeviceProvisionedState = Settings.Global.getInt( 9601 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 9602 ? DEVICE_PROVISIONED_YES 9603 : DEVICE_PROVISIONED_NO; 9604 } 9605 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 9606 } 9607 canPaste()9608 boolean canPaste() { 9609 return (mText instanceof Editable && 9610 mEditor != null && mEditor.mKeyListener != null && 9611 getSelectionStart() >= 0 && 9612 getSelectionEnd() >= 0 && 9613 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). 9614 hasPrimaryClip()); 9615 } 9616 canProcessText()9617 boolean canProcessText() { 9618 if (getId() == View.NO_ID) { 9619 return false; 9620 } 9621 return canShare(); 9622 } 9623 canSelectAllText()9624 boolean canSelectAllText() { 9625 return canSelectText() && !hasPasswordTransformationMethod() 9626 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 9627 } 9628 selectAllText()9629 boolean selectAllText() { 9630 final int length = mText.length(); 9631 Selection.setSelection((Spannable) mText, 0, length); 9632 return length > 0; 9633 } 9634 replaceSelectionWithText(CharSequence text)9635 void replaceSelectionWithText(CharSequence text) { 9636 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 9637 } 9638 9639 /** 9640 * Paste clipboard content between min and max positions. 9641 */ paste(int min, int max, boolean withFormatting)9642 private void paste(int min, int max, boolean withFormatting) { 9643 ClipboardManager clipboard = 9644 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 9645 ClipData clip = clipboard.getPrimaryClip(); 9646 if (clip != null) { 9647 boolean didFirst = false; 9648 for (int i=0; i<clip.getItemCount(); i++) { 9649 final CharSequence paste; 9650 if (withFormatting) { 9651 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 9652 } else { 9653 // Get an item as text and remove all spans by toString(). 9654 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 9655 paste = (text instanceof Spanned) ? text.toString() : text; 9656 } 9657 if (paste != null) { 9658 if (!didFirst) { 9659 Selection.setSelection((Spannable) mText, max); 9660 ((Editable) mText).replace(min, max, paste); 9661 didFirst = true; 9662 } else { 9663 ((Editable) mText).insert(getSelectionEnd(), "\n"); 9664 ((Editable) mText).insert(getSelectionEnd(), paste); 9665 } 9666 } 9667 } 9668 sLastCutCopyOrTextChangedTime = 0; 9669 } 9670 } 9671 shareSelectedText()9672 private void shareSelectedText() { 9673 String selectedText = getSelectedText(); 9674 if (selectedText != null && !selectedText.isEmpty()) { 9675 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 9676 sharingIntent.setType("text/plain"); 9677 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 9678 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 9679 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 9680 Selection.setSelection((Spannable) mText, getSelectionEnd()); 9681 } 9682 } 9683 setPrimaryClip(ClipData clip)9684 private void setPrimaryClip(ClipData clip) { 9685 ClipboardManager clipboard = (ClipboardManager) getContext(). 9686 getSystemService(Context.CLIPBOARD_SERVICE); 9687 clipboard.setPrimaryClip(clip); 9688 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 9689 } 9690 9691 /** 9692 * Get the character offset closest to the specified absolute position. A typical use case is to 9693 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 9694 * 9695 * @param x The horizontal absolute position of a point on screen 9696 * @param y The vertical absolute position of a point on screen 9697 * @return the character offset for the character whose position is closest to the specified 9698 * position. Returns -1 if there is no layout. 9699 */ getOffsetForPosition(float x, float y)9700 public int getOffsetForPosition(float x, float y) { 9701 if (getLayout() == null) return -1; 9702 final int line = getLineAtCoordinate(y); 9703 final int offset = getOffsetAtCoordinate(line, x); 9704 return offset; 9705 } 9706 convertToLocalHorizontalCoordinate(float x)9707 float convertToLocalHorizontalCoordinate(float x) { 9708 x -= getTotalPaddingLeft(); 9709 // Clamp the position to inside of the view. 9710 x = Math.max(0.0f, x); 9711 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 9712 x += getScrollX(); 9713 return x; 9714 } 9715 getLineAtCoordinate(float y)9716 int getLineAtCoordinate(float y) { 9717 y -= getTotalPaddingTop(); 9718 // Clamp the position to inside of the view. 9719 y = Math.max(0.0f, y); 9720 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 9721 y += getScrollY(); 9722 return getLayout().getLineForVertical((int) y); 9723 } 9724 getLineAtCoordinateUnclamped(float y)9725 int getLineAtCoordinateUnclamped(float y) { 9726 y -= getTotalPaddingTop(); 9727 y += getScrollY(); 9728 return getLayout().getLineForVertical((int) y); 9729 } 9730 getOffsetAtCoordinate(int line, float x)9731 int getOffsetAtCoordinate(int line, float x) { 9732 x = convertToLocalHorizontalCoordinate(x); 9733 return getLayout().getOffsetForHorizontal(line, x); 9734 } 9735 9736 @Override onDragEvent(DragEvent event)9737 public boolean onDragEvent(DragEvent event) { 9738 switch (event.getAction()) { 9739 case DragEvent.ACTION_DRAG_STARTED: 9740 return mEditor != null && mEditor.hasInsertionController(); 9741 9742 case DragEvent.ACTION_DRAG_ENTERED: 9743 TextView.this.requestFocus(); 9744 return true; 9745 9746 case DragEvent.ACTION_DRAG_LOCATION: 9747 final int offset = getOffsetForPosition(event.getX(), event.getY()); 9748 Selection.setSelection((Spannable)mText, offset); 9749 return true; 9750 9751 case DragEvent.ACTION_DROP: 9752 if (mEditor != null) mEditor.onDrop(event); 9753 return true; 9754 9755 case DragEvent.ACTION_DRAG_ENDED: 9756 case DragEvent.ACTION_DRAG_EXITED: 9757 default: 9758 return true; 9759 } 9760 } 9761 isInBatchEditMode()9762 boolean isInBatchEditMode() { 9763 if (mEditor == null) return false; 9764 final Editor.InputMethodState ims = mEditor.mInputMethodState; 9765 if (ims != null) { 9766 return ims.mBatchEditNesting > 0; 9767 } 9768 return mEditor.mInBatchEditControllers; 9769 } 9770 9771 @Override onRtlPropertiesChanged(int layoutDirection)9772 public void onRtlPropertiesChanged(int layoutDirection) { 9773 super.onRtlPropertiesChanged(layoutDirection); 9774 9775 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 9776 if (mTextDir != newTextDir) { 9777 mTextDir = newTextDir; 9778 if (mLayout != null) { 9779 checkForRelayout(); 9780 } 9781 } 9782 } 9783 9784 /** 9785 * @hide 9786 */ getTextDirectionHeuristic()9787 protected TextDirectionHeuristic getTextDirectionHeuristic() { 9788 if (hasPasswordTransformationMethod()) { 9789 // passwords fields should be LTR 9790 return TextDirectionHeuristics.LTR; 9791 } 9792 9793 // Always need to resolve layout direction first 9794 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 9795 9796 // Now, we can select the heuristic 9797 switch (getTextDirection()) { 9798 default: 9799 case TEXT_DIRECTION_FIRST_STRONG: 9800 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 9801 TextDirectionHeuristics.FIRSTSTRONG_LTR); 9802 case TEXT_DIRECTION_ANY_RTL: 9803 return TextDirectionHeuristics.ANYRTL_LTR; 9804 case TEXT_DIRECTION_LTR: 9805 return TextDirectionHeuristics.LTR; 9806 case TEXT_DIRECTION_RTL: 9807 return TextDirectionHeuristics.RTL; 9808 case TEXT_DIRECTION_LOCALE: 9809 return TextDirectionHeuristics.LOCALE; 9810 case TEXT_DIRECTION_FIRST_STRONG_LTR: 9811 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 9812 case TEXT_DIRECTION_FIRST_STRONG_RTL: 9813 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 9814 } 9815 } 9816 9817 /** 9818 * @hide 9819 */ 9820 @Override onResolveDrawables(int layoutDirection)9821 public void onResolveDrawables(int layoutDirection) { 9822 // No need to resolve twice 9823 if (mLastLayoutDirection == layoutDirection) { 9824 return; 9825 } 9826 mLastLayoutDirection = layoutDirection; 9827 9828 // Resolve drawables 9829 if (mDrawables != null) { 9830 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 9831 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 9832 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 9833 applyCompoundDrawableTint(); 9834 } 9835 } 9836 } 9837 9838 /** 9839 * Prepares a drawable for display by propagating layout direction and 9840 * drawable state. 9841 * 9842 * @param dr the drawable to prepare 9843 */ prepareDrawableForDisplay(@ullable Drawable dr)9844 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 9845 if (dr == null) { 9846 return; 9847 } 9848 9849 dr.setLayoutDirection(getLayoutDirection()); 9850 9851 if (dr.isStateful()) { 9852 dr.setState(getDrawableState()); 9853 dr.jumpToCurrentState(); 9854 } 9855 } 9856 9857 /** 9858 * @hide 9859 */ resetResolvedDrawables()9860 protected void resetResolvedDrawables() { 9861 super.resetResolvedDrawables(); 9862 mLastLayoutDirection = -1; 9863 } 9864 9865 /** 9866 * @hide 9867 */ viewClicked(InputMethodManager imm)9868 protected void viewClicked(InputMethodManager imm) { 9869 if (imm != null) { 9870 imm.viewClicked(this); 9871 } 9872 } 9873 9874 /** 9875 * Deletes the range of text [start, end[. 9876 * @hide 9877 */ deleteText_internal(int start, int end)9878 protected void deleteText_internal(int start, int end) { 9879 ((Editable) mText).delete(start, end); 9880 } 9881 9882 /** 9883 * Replaces the range of text [start, end[ by replacement text 9884 * @hide 9885 */ replaceText_internal(int start, int end, CharSequence text)9886 protected void replaceText_internal(int start, int end, CharSequence text) { 9887 ((Editable) mText).replace(start, end, text); 9888 } 9889 9890 /** 9891 * Sets a span on the specified range of text 9892 * @hide 9893 */ setSpan_internal(Object span, int start, int end, int flags)9894 protected void setSpan_internal(Object span, int start, int end, int flags) { 9895 ((Editable) mText).setSpan(span, start, end, flags); 9896 } 9897 9898 /** 9899 * Moves the cursor to the specified offset position in text 9900 * @hide 9901 */ setCursorPosition_internal(int start, int end)9902 protected void setCursorPosition_internal(int start, int end) { 9903 Selection.setSelection(((Editable) mText), start, end); 9904 } 9905 9906 /** 9907 * An Editor should be created as soon as any of the editable-specific fields (grouped 9908 * inside the Editor object) is assigned to a non-default value. 9909 * This method will create the Editor if needed. 9910 * 9911 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 9912 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 9913 * Editor for backward compatibility, as soon as one of these fields is assigned. 9914 * 9915 * Also note that for performance reasons, the mEditor is created when needed, but not 9916 * reset when no more edit-specific fields are needed. 9917 */ createEditorIfNeeded()9918 private void createEditorIfNeeded() { 9919 if (mEditor == null) { 9920 mEditor = new Editor(this); 9921 } 9922 } 9923 9924 /** 9925 * @hide 9926 */ 9927 @Override getIterableTextForAccessibility()9928 public CharSequence getIterableTextForAccessibility() { 9929 return mText; 9930 } 9931 ensureIterableTextForAccessibilitySelectable()9932 private void ensureIterableTextForAccessibilitySelectable() { 9933 if (!(mText instanceof Spannable)) { 9934 setText(mText, BufferType.SPANNABLE); 9935 } 9936 } 9937 9938 /** 9939 * @hide 9940 */ 9941 @Override getIteratorForGranularity(int granularity)9942 public TextSegmentIterator getIteratorForGranularity(int granularity) { 9943 switch (granularity) { 9944 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 9945 Spannable text = (Spannable) getIterableTextForAccessibility(); 9946 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9947 AccessibilityIterators.LineTextSegmentIterator iterator = 9948 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 9949 iterator.initialize(text, getLayout()); 9950 return iterator; 9951 } 9952 } break; 9953 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 9954 Spannable text = (Spannable) getIterableTextForAccessibility(); 9955 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9956 AccessibilityIterators.PageTextSegmentIterator iterator = 9957 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 9958 iterator.initialize(this); 9959 return iterator; 9960 } 9961 } break; 9962 } 9963 return super.getIteratorForGranularity(granularity); 9964 } 9965 9966 /** 9967 * @hide 9968 */ 9969 @Override getAccessibilitySelectionStart()9970 public int getAccessibilitySelectionStart() { 9971 return getSelectionStart(); 9972 } 9973 9974 /** 9975 * @hide 9976 */ isAccessibilitySelectionExtendable()9977 public boolean isAccessibilitySelectionExtendable() { 9978 return true; 9979 } 9980 9981 /** 9982 * @hide 9983 */ 9984 @Override getAccessibilitySelectionEnd()9985 public int getAccessibilitySelectionEnd() { 9986 return getSelectionEnd(); 9987 } 9988 9989 /** 9990 * @hide 9991 */ 9992 @Override setAccessibilitySelection(int start, int end)9993 public void setAccessibilitySelection(int start, int end) { 9994 if (getAccessibilitySelectionStart() == start 9995 && getAccessibilitySelectionEnd() == end) { 9996 return; 9997 } 9998 CharSequence text = getIterableTextForAccessibility(); 9999 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 10000 Selection.setSelection((Spannable) text, start, end); 10001 } else { 10002 Selection.removeSelection((Spannable) text); 10003 } 10004 // Hide all selection controllers used for adjusting selection 10005 // since we are doing so explicitlty by other means and these 10006 // controllers interact with how selection behaves. 10007 if (mEditor != null) { 10008 mEditor.hideCursorAndSpanControllers(); 10009 mEditor.stopTextActionMode(); 10010 } 10011 } 10012 10013 /** @hide */ 10014 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)10015 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 10016 super.encodeProperties(stream); 10017 10018 TruncateAt ellipsize = getEllipsize(); 10019 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 10020 stream.addProperty("text:textSize", getTextSize()); 10021 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 10022 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 10023 stream.addProperty("text:selectionStart", getSelectionStart()); 10024 stream.addProperty("text:selectionEnd", getSelectionEnd()); 10025 stream.addProperty("text:curTextColor", mCurTextColor); 10026 stream.addProperty("text:text", mText == null ? null : mText.toString()); 10027 stream.addProperty("text:gravity", mGravity); 10028 } 10029 10030 /** 10031 * User interface state that is stored by TextView for implementing 10032 * {@link View#onSaveInstanceState}. 10033 */ 10034 public static class SavedState extends BaseSavedState { 10035 int selStart = -1; 10036 int selEnd = -1; 10037 CharSequence text; 10038 boolean frozenWithFocus; 10039 CharSequence error; 10040 ParcelableParcel editorState; // Optional state from Editor. 10041 SavedState(Parcelable superState)10042 SavedState(Parcelable superState) { 10043 super(superState); 10044 } 10045 10046 @Override writeToParcel(Parcel out, int flags)10047 public void writeToParcel(Parcel out, int flags) { 10048 super.writeToParcel(out, flags); 10049 out.writeInt(selStart); 10050 out.writeInt(selEnd); 10051 out.writeInt(frozenWithFocus ? 1 : 0); 10052 TextUtils.writeToParcel(text, out, flags); 10053 10054 if (error == null) { 10055 out.writeInt(0); 10056 } else { 10057 out.writeInt(1); 10058 TextUtils.writeToParcel(error, out, flags); 10059 } 10060 10061 if (editorState == null) { 10062 out.writeInt(0); 10063 } else { 10064 out.writeInt(1); 10065 editorState.writeToParcel(out, flags); 10066 } 10067 } 10068 10069 @Override toString()10070 public String toString() { 10071 String str = "TextView.SavedState{" 10072 + Integer.toHexString(System.identityHashCode(this)) 10073 + " start=" + selStart + " end=" + selEnd; 10074 if (text != null) { 10075 str += " text=" + text; 10076 } 10077 return str + "}"; 10078 } 10079 10080 @SuppressWarnings("hiding") 10081 public static final Parcelable.Creator<SavedState> CREATOR 10082 = new Parcelable.Creator<SavedState>() { 10083 public SavedState createFromParcel(Parcel in) { 10084 return new SavedState(in); 10085 } 10086 10087 public SavedState[] newArray(int size) { 10088 return new SavedState[size]; 10089 } 10090 }; 10091 SavedState(Parcel in)10092 private SavedState(Parcel in) { 10093 super(in); 10094 selStart = in.readInt(); 10095 selEnd = in.readInt(); 10096 frozenWithFocus = (in.readInt() != 0); 10097 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 10098 10099 if (in.readInt() != 0) { 10100 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 10101 } 10102 10103 if (in.readInt() != 0) { 10104 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 10105 } 10106 } 10107 } 10108 10109 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 10110 private char[] mChars; 10111 private int mStart, mLength; 10112 CharWrapper(char[] chars, int start, int len)10113 public CharWrapper(char[] chars, int start, int len) { 10114 mChars = chars; 10115 mStart = start; 10116 mLength = len; 10117 } 10118 set(char[] chars, int start, int len)10119 /* package */ void set(char[] chars, int start, int len) { 10120 mChars = chars; 10121 mStart = start; 10122 mLength = len; 10123 } 10124 length()10125 public int length() { 10126 return mLength; 10127 } 10128 charAt(int off)10129 public char charAt(int off) { 10130 return mChars[off + mStart]; 10131 } 10132 10133 @Override toString()10134 public String toString() { 10135 return new String(mChars, mStart, mLength); 10136 } 10137 subSequence(int start, int end)10138 public CharSequence subSequence(int start, int end) { 10139 if (start < 0 || end < 0 || start > mLength || end > mLength) { 10140 throw new IndexOutOfBoundsException(start + ", " + end); 10141 } 10142 10143 return new String(mChars, start + mStart, end - start); 10144 } 10145 getChars(int start, int end, char[] buf, int off)10146 public void getChars(int start, int end, char[] buf, int off) { 10147 if (start < 0 || end < 0 || start > mLength || end > mLength) { 10148 throw new IndexOutOfBoundsException(start + ", " + end); 10149 } 10150 10151 System.arraycopy(mChars, start + mStart, buf, off, end - start); 10152 } 10153 drawText(Canvas c, int start, int end, float x, float y, Paint p)10154 public void drawText(Canvas c, int start, int end, 10155 float x, float y, Paint p) { 10156 c.drawText(mChars, start + mStart, end - start, x, y, p); 10157 } 10158 drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)10159 public void drawTextRun(Canvas c, int start, int end, 10160 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 10161 int count = end - start; 10162 int contextCount = contextEnd - contextStart; 10163 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 10164 contextCount, x, y, isRtl, p); 10165 } 10166 measureText(int start, int end, Paint p)10167 public float measureText(int start, int end, Paint p) { 10168 return p.measureText(mChars, start + mStart, end - start); 10169 } 10170 getTextWidths(int start, int end, float[] widths, Paint p)10171 public int getTextWidths(int start, int end, float[] widths, Paint p) { 10172 return p.getTextWidths(mChars, start + mStart, end - start, widths); 10173 } 10174 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)10175 public float getTextRunAdvances(int start, int end, int contextStart, 10176 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 10177 Paint p) { 10178 int count = end - start; 10179 int contextCount = contextEnd - contextStart; 10180 return p.getTextRunAdvances(mChars, start + mStart, count, 10181 contextStart + mStart, contextCount, isRtl, advances, 10182 advancesIndex); 10183 } 10184 getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)10185 public int getTextRunCursor(int contextStart, int contextEnd, int dir, 10186 int offset, int cursorOpt, Paint p) { 10187 int contextCount = contextEnd - contextStart; 10188 return p.getTextRunCursor(mChars, contextStart + mStart, 10189 contextCount, dir, offset + mStart, cursorOpt); 10190 } 10191 } 10192 10193 private static final class Marquee { 10194 // TODO: Add an option to configure this 10195 private static final float MARQUEE_DELTA_MAX = 0.07f; 10196 private static final int MARQUEE_DELAY = 1200; 10197 private static final int MARQUEE_DP_PER_SECOND = 30; 10198 10199 private static final byte MARQUEE_STOPPED = 0x0; 10200 private static final byte MARQUEE_STARTING = 0x1; 10201 private static final byte MARQUEE_RUNNING = 0x2; 10202 10203 private final WeakReference<TextView> mView; 10204 private final Choreographer mChoreographer; 10205 10206 private byte mStatus = MARQUEE_STOPPED; 10207 private final float mPixelsPerSecond; 10208 private float mMaxScroll; 10209 private float mMaxFadeScroll; 10210 private float mGhostStart; 10211 private float mGhostOffset; 10212 private float mFadeStop; 10213 private int mRepeatLimit; 10214 10215 private float mScroll; 10216 private long mLastAnimationMs; 10217 Marquee(TextView v)10218 Marquee(TextView v) { 10219 final float density = v.getContext().getResources().getDisplayMetrics().density; 10220 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; 10221 mView = new WeakReference<TextView>(v); 10222 mChoreographer = Choreographer.getInstance(); 10223 } 10224 10225 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 10226 @Override 10227 public void doFrame(long frameTimeNanos) { 10228 tick(); 10229 } 10230 }; 10231 10232 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 10233 @Override 10234 public void doFrame(long frameTimeNanos) { 10235 mStatus = MARQUEE_RUNNING; 10236 mLastAnimationMs = mChoreographer.getFrameTime(); 10237 tick(); 10238 } 10239 }; 10240 10241 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 10242 @Override 10243 public void doFrame(long frameTimeNanos) { 10244 if (mStatus == MARQUEE_RUNNING) { 10245 if (mRepeatLimit >= 0) { 10246 mRepeatLimit--; 10247 } 10248 start(mRepeatLimit); 10249 } 10250 } 10251 }; 10252 tick()10253 void tick() { 10254 if (mStatus != MARQUEE_RUNNING) { 10255 return; 10256 } 10257 10258 mChoreographer.removeFrameCallback(mTickCallback); 10259 10260 final TextView textView = mView.get(); 10261 if (textView != null && (textView.isFocused() || textView.isSelected())) { 10262 long currentMs = mChoreographer.getFrameTime(); 10263 long deltaMs = currentMs - mLastAnimationMs; 10264 mLastAnimationMs = currentMs; 10265 float deltaPx = deltaMs / 1000f * mPixelsPerSecond; 10266 mScroll += deltaPx; 10267 if (mScroll > mMaxScroll) { 10268 mScroll = mMaxScroll; 10269 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 10270 } else { 10271 mChoreographer.postFrameCallback(mTickCallback); 10272 } 10273 textView.invalidate(); 10274 } 10275 } 10276 stop()10277 void stop() { 10278 mStatus = MARQUEE_STOPPED; 10279 mChoreographer.removeFrameCallback(mStartCallback); 10280 mChoreographer.removeFrameCallback(mRestartCallback); 10281 mChoreographer.removeFrameCallback(mTickCallback); 10282 resetScroll(); 10283 } 10284 resetScroll()10285 private void resetScroll() { 10286 mScroll = 0.0f; 10287 final TextView textView = mView.get(); 10288 if (textView != null) textView.invalidate(); 10289 } 10290 start(int repeatLimit)10291 void start(int repeatLimit) { 10292 if (repeatLimit == 0) { 10293 stop(); 10294 return; 10295 } 10296 mRepeatLimit = repeatLimit; 10297 final TextView textView = mView.get(); 10298 if (textView != null && textView.mLayout != null) { 10299 mStatus = MARQUEE_STARTING; 10300 mScroll = 0.0f; 10301 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - 10302 textView.getCompoundPaddingRight(); 10303 final float lineWidth = textView.mLayout.getLineWidth(0); 10304 final float gap = textWidth / 3.0f; 10305 mGhostStart = lineWidth - textWidth + gap; 10306 mMaxScroll = mGhostStart + textWidth; 10307 mGhostOffset = lineWidth + gap; 10308 mFadeStop = lineWidth + textWidth / 6.0f; 10309 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 10310 10311 textView.invalidate(); 10312 mChoreographer.postFrameCallback(mStartCallback); 10313 } 10314 } 10315 getGhostOffset()10316 float getGhostOffset() { 10317 return mGhostOffset; 10318 } 10319 getScroll()10320 float getScroll() { 10321 return mScroll; 10322 } 10323 getMaxFadeScroll()10324 float getMaxFadeScroll() { 10325 return mMaxFadeScroll; 10326 } 10327 shouldDrawLeftFade()10328 boolean shouldDrawLeftFade() { 10329 return mScroll <= mFadeStop; 10330 } 10331 shouldDrawGhost()10332 boolean shouldDrawGhost() { 10333 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 10334 } 10335 isRunning()10336 boolean isRunning() { 10337 return mStatus == MARQUEE_RUNNING; 10338 } 10339 isStopped()10340 boolean isStopped() { 10341 return mStatus == MARQUEE_STOPPED; 10342 } 10343 } 10344 10345 private class ChangeWatcher implements TextWatcher, SpanWatcher { 10346 10347 private CharSequence mBeforeText; 10348 beforeTextChanged(CharSequence buffer, int start, int before, int after)10349 public void beforeTextChanged(CharSequence buffer, int start, 10350 int before, int after) { 10351 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start 10352 + " before=" + before + " after=" + after + ": " + buffer); 10353 10354 if (AccessibilityManager.getInstance(mContext).isEnabled() 10355 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) 10356 || shouldSpeakPasswordsForAccessibility())) { 10357 mBeforeText = buffer.toString(); 10358 } 10359 10360 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 10361 } 10362 onTextChanged(CharSequence buffer, int start, int before, int after)10363 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 10364 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start 10365 + " before=" + before + " after=" + after + ": " + buffer); 10366 TextView.this.handleTextChanged(buffer, start, before, after); 10367 10368 if (AccessibilityManager.getInstance(mContext).isEnabled() && 10369 (isFocused() || isSelected() && isShown())) { 10370 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 10371 mBeforeText = null; 10372 } 10373 } 10374 afterTextChanged(Editable buffer)10375 public void afterTextChanged(Editable buffer) { 10376 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer); 10377 TextView.this.sendAfterTextChanged(buffer); 10378 10379 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 10380 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 10381 } 10382 } 10383 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)10384 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 10385 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 10386 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 10387 TextView.this.spanChange(buf, what, s, st, e, en); 10388 } 10389 onSpanAdded(Spannable buf, Object what, int s, int e)10390 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 10391 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e 10392 + " what=" + what + ": " + buf); 10393 TextView.this.spanChange(buf, what, -1, s, -1, e); 10394 } 10395 onSpanRemoved(Spannable buf, Object what, int s, int e)10396 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 10397 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e 10398 + " what=" + what + ": " + buf); 10399 TextView.this.spanChange(buf, what, s, -1, e, -1); 10400 } 10401 } 10402 } 10403