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