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