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