1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 23 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 24 25 import android.R; 26 import android.annotation.CheckResult; 27 import android.annotation.ColorInt; 28 import android.annotation.DrawableRes; 29 import android.annotation.FloatRange; 30 import android.annotation.IntDef; 31 import android.annotation.IntRange; 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.annotation.Px; 35 import android.annotation.RequiresPermission; 36 import android.annotation.Size; 37 import android.annotation.StringRes; 38 import android.annotation.StyleRes; 39 import android.annotation.XmlRes; 40 import android.app.Activity; 41 import android.app.PendingIntent; 42 import android.app.assist.AssistStructure; 43 import android.compat.annotation.UnsupportedAppUsage; 44 import android.content.ClipData; 45 import android.content.ClipDescription; 46 import android.content.ClipboardManager; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.UndoManager; 50 import android.content.pm.PackageManager; 51 import android.content.res.ColorStateList; 52 import android.content.res.CompatibilityInfo; 53 import android.content.res.Configuration; 54 import android.content.res.Resources; 55 import android.content.res.TypedArray; 56 import android.content.res.XmlResourceParser; 57 import android.graphics.BaseCanvas; 58 import android.graphics.BlendMode; 59 import android.graphics.Canvas; 60 import android.graphics.Insets; 61 import android.graphics.Paint; 62 import android.graphics.Paint.FontMetricsInt; 63 import android.graphics.Path; 64 import android.graphics.PorterDuff; 65 import android.graphics.Rect; 66 import android.graphics.RectF; 67 import android.graphics.Typeface; 68 import android.graphics.drawable.Drawable; 69 import android.graphics.fonts.FontStyle; 70 import android.graphics.fonts.FontVariationAxis; 71 import android.icu.text.DecimalFormatSymbols; 72 import android.os.AsyncTask; 73 import android.os.Build; 74 import android.os.Build.VERSION_CODES; 75 import android.os.Bundle; 76 import android.os.LocaleList; 77 import android.os.Parcel; 78 import android.os.Parcelable; 79 import android.os.ParcelableParcel; 80 import android.os.Process; 81 import android.os.SystemClock; 82 import android.os.UserHandle; 83 import android.provider.Settings; 84 import android.text.BoringLayout; 85 import android.text.DynamicLayout; 86 import android.text.Editable; 87 import android.text.GetChars; 88 import android.text.GraphicsOperations; 89 import android.text.InputFilter; 90 import android.text.InputType; 91 import android.text.Layout; 92 import android.text.ParcelableSpan; 93 import android.text.PrecomputedText; 94 import android.text.Selection; 95 import android.text.SpanWatcher; 96 import android.text.Spannable; 97 import android.text.SpannableStringBuilder; 98 import android.text.Spanned; 99 import android.text.SpannedString; 100 import android.text.StaticLayout; 101 import android.text.TextDirectionHeuristic; 102 import android.text.TextDirectionHeuristics; 103 import android.text.TextPaint; 104 import android.text.TextUtils; 105 import android.text.TextUtils.TruncateAt; 106 import android.text.TextWatcher; 107 import android.text.method.AllCapsTransformationMethod; 108 import android.text.method.ArrowKeyMovementMethod; 109 import android.text.method.DateKeyListener; 110 import android.text.method.DateTimeKeyListener; 111 import android.text.method.DialerKeyListener; 112 import android.text.method.DigitsKeyListener; 113 import android.text.method.KeyListener; 114 import android.text.method.LinkMovementMethod; 115 import android.text.method.MetaKeyKeyListener; 116 import android.text.method.MovementMethod; 117 import android.text.method.PasswordTransformationMethod; 118 import android.text.method.SingleLineTransformationMethod; 119 import android.text.method.TextKeyListener; 120 import android.text.method.TimeKeyListener; 121 import android.text.method.TransformationMethod; 122 import android.text.method.TransformationMethod2; 123 import android.text.method.WordIterator; 124 import android.text.style.CharacterStyle; 125 import android.text.style.ClickableSpan; 126 import android.text.style.ParagraphStyle; 127 import android.text.style.SpellCheckSpan; 128 import android.text.style.SuggestionSpan; 129 import android.text.style.URLSpan; 130 import android.text.style.UpdateAppearance; 131 import android.text.util.Linkify; 132 import android.util.AttributeSet; 133 import android.util.DisplayMetrics; 134 import android.util.IntArray; 135 import android.util.Log; 136 import android.util.SparseIntArray; 137 import android.util.TypedValue; 138 import android.view.AccessibilityIterators.TextSegmentIterator; 139 import android.view.ActionMode; 140 import android.view.Choreographer; 141 import android.view.ContextMenu; 142 import android.view.DragEvent; 143 import android.view.Gravity; 144 import android.view.HapticFeedbackConstants; 145 import android.view.InputDevice; 146 import android.view.KeyCharacterMap; 147 import android.view.KeyEvent; 148 import android.view.MotionEvent; 149 import android.view.PointerIcon; 150 import android.view.View; 151 import android.view.ViewConfiguration; 152 import android.view.ViewDebug; 153 import android.view.ViewGroup.LayoutParams; 154 import android.view.ViewHierarchyEncoder; 155 import android.view.ViewParent; 156 import android.view.ViewRootImpl; 157 import android.view.ViewStructure; 158 import android.view.ViewTreeObserver; 159 import android.view.accessibility.AccessibilityEvent; 160 import android.view.accessibility.AccessibilityManager; 161 import android.view.accessibility.AccessibilityNodeInfo; 162 import android.view.animation.AnimationUtils; 163 import android.view.autofill.AutofillManager; 164 import android.view.autofill.AutofillValue; 165 import android.view.contentcapture.ContentCaptureManager; 166 import android.view.contentcapture.ContentCaptureSession; 167 import android.view.inputmethod.BaseInputConnection; 168 import android.view.inputmethod.CompletionInfo; 169 import android.view.inputmethod.CorrectionInfo; 170 import android.view.inputmethod.CursorAnchorInfo; 171 import android.view.inputmethod.EditorInfo; 172 import android.view.inputmethod.ExtractedText; 173 import android.view.inputmethod.ExtractedTextRequest; 174 import android.view.inputmethod.InputConnection; 175 import android.view.inputmethod.InputMethodManager; 176 import android.view.inspector.InspectableProperty; 177 import android.view.inspector.InspectableProperty.EnumEntry; 178 import android.view.inspector.InspectableProperty.FlagEntry; 179 import android.view.textclassifier.TextClassification; 180 import android.view.textclassifier.TextClassificationContext; 181 import android.view.textclassifier.TextClassificationManager; 182 import android.view.textclassifier.TextClassifier; 183 import android.view.textclassifier.TextLinks; 184 import android.view.textservice.SpellCheckerSubtype; 185 import android.view.textservice.TextServicesManager; 186 import android.widget.RemoteViews.RemoteView; 187 188 import com.android.internal.annotations.VisibleForTesting; 189 import com.android.internal.logging.MetricsLogger; 190 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 191 import com.android.internal.util.FastMath; 192 import com.android.internal.util.Preconditions; 193 import com.android.internal.widget.EditableInputConnection; 194 195 import libcore.util.EmptyArray; 196 197 import org.xmlpull.v1.XmlPullParserException; 198 199 import java.io.IOException; 200 import java.lang.annotation.Retention; 201 import java.lang.annotation.RetentionPolicy; 202 import java.lang.ref.WeakReference; 203 import java.util.ArrayList; 204 import java.util.Arrays; 205 import java.util.Locale; 206 import java.util.Objects; 207 import java.util.concurrent.CompletableFuture; 208 import java.util.concurrent.TimeUnit; 209 import java.util.function.Consumer; 210 import java.util.function.Supplier; 211 212 /** 213 * A user interface element that displays text to the user. 214 * To provide user-editable text, see {@link EditText}. 215 * <p> 216 * The following code sample shows a typical use, with an XML layout 217 * and code to modify the contents of the text view: 218 * </p> 219 220 * <pre> 221 * <LinearLayout 222 xmlns:android="http://schemas.android.com/apk/res/android" 223 android:layout_width="match_parent" 224 android:layout_height="match_parent"> 225 * <TextView 226 * android:id="@+id/text_view_id" 227 * android:layout_height="wrap_content" 228 * android:layout_width="wrap_content" 229 * android:text="@string/hello" /> 230 * </LinearLayout> 231 * </pre> 232 * <p> 233 * This code sample demonstrates how to modify the contents of the text view 234 * defined in the previous XML layout: 235 * </p> 236 * <pre> 237 * public class MainActivity extends Activity { 238 * 239 * protected void onCreate(Bundle savedInstanceState) { 240 * super.onCreate(savedInstanceState); 241 * setContentView(R.layout.activity_main); 242 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 243 * helloTextView.setText(R.string.user_greeting); 244 * } 245 * } 246 * </pre> 247 * <p> 248 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 249 * </p> 250 * <p> 251 * <b>XML attributes</b> 252 * <p> 253 * See {@link android.R.styleable#TextView TextView Attributes}, 254 * {@link android.R.styleable#View View Attributes} 255 * 256 * @attr ref android.R.styleable#TextView_text 257 * @attr ref android.R.styleable#TextView_bufferType 258 * @attr ref android.R.styleable#TextView_hint 259 * @attr ref android.R.styleable#TextView_textColor 260 * @attr ref android.R.styleable#TextView_textColorHighlight 261 * @attr ref android.R.styleable#TextView_textColorHint 262 * @attr ref android.R.styleable#TextView_textAppearance 263 * @attr ref android.R.styleable#TextView_textColorLink 264 * @attr ref android.R.styleable#TextView_textFontWeight 265 * @attr ref android.R.styleable#TextView_textSize 266 * @attr ref android.R.styleable#TextView_textScaleX 267 * @attr ref android.R.styleable#TextView_fontFamily 268 * @attr ref android.R.styleable#TextView_typeface 269 * @attr ref android.R.styleable#TextView_textStyle 270 * @attr ref android.R.styleable#TextView_cursorVisible 271 * @attr ref android.R.styleable#TextView_maxLines 272 * @attr ref android.R.styleable#TextView_maxHeight 273 * @attr ref android.R.styleable#TextView_lines 274 * @attr ref android.R.styleable#TextView_height 275 * @attr ref android.R.styleable#TextView_minLines 276 * @attr ref android.R.styleable#TextView_minHeight 277 * @attr ref android.R.styleable#TextView_maxEms 278 * @attr ref android.R.styleable#TextView_maxWidth 279 * @attr ref android.R.styleable#TextView_ems 280 * @attr ref android.R.styleable#TextView_width 281 * @attr ref android.R.styleable#TextView_minEms 282 * @attr ref android.R.styleable#TextView_minWidth 283 * @attr ref android.R.styleable#TextView_gravity 284 * @attr ref android.R.styleable#TextView_scrollHorizontally 285 * @attr ref android.R.styleable#TextView_password 286 * @attr ref android.R.styleable#TextView_singleLine 287 * @attr ref android.R.styleable#TextView_selectAllOnFocus 288 * @attr ref android.R.styleable#TextView_includeFontPadding 289 * @attr ref android.R.styleable#TextView_maxLength 290 * @attr ref android.R.styleable#TextView_shadowColor 291 * @attr ref android.R.styleable#TextView_shadowDx 292 * @attr ref android.R.styleable#TextView_shadowDy 293 * @attr ref android.R.styleable#TextView_shadowRadius 294 * @attr ref android.R.styleable#TextView_autoLink 295 * @attr ref android.R.styleable#TextView_linksClickable 296 * @attr ref android.R.styleable#TextView_numeric 297 * @attr ref android.R.styleable#TextView_digits 298 * @attr ref android.R.styleable#TextView_phoneNumber 299 * @attr ref android.R.styleable#TextView_inputMethod 300 * @attr ref android.R.styleable#TextView_capitalize 301 * @attr ref android.R.styleable#TextView_autoText 302 * @attr ref android.R.styleable#TextView_editable 303 * @attr ref android.R.styleable#TextView_freezesText 304 * @attr ref android.R.styleable#TextView_ellipsize 305 * @attr ref android.R.styleable#TextView_drawableTop 306 * @attr ref android.R.styleable#TextView_drawableBottom 307 * @attr ref android.R.styleable#TextView_drawableRight 308 * @attr ref android.R.styleable#TextView_drawableLeft 309 * @attr ref android.R.styleable#TextView_drawableStart 310 * @attr ref android.R.styleable#TextView_drawableEnd 311 * @attr ref android.R.styleable#TextView_drawablePadding 312 * @attr ref android.R.styleable#TextView_drawableTint 313 * @attr ref android.R.styleable#TextView_drawableTintMode 314 * @attr ref android.R.styleable#TextView_lineSpacingExtra 315 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 316 * @attr ref android.R.styleable#TextView_justificationMode 317 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 318 * @attr ref android.R.styleable#TextView_inputType 319 * @attr ref android.R.styleable#TextView_imeOptions 320 * @attr ref android.R.styleable#TextView_privateImeOptions 321 * @attr ref android.R.styleable#TextView_imeActionLabel 322 * @attr ref android.R.styleable#TextView_imeActionId 323 * @attr ref android.R.styleable#TextView_editorExtras 324 * @attr ref android.R.styleable#TextView_elegantTextHeight 325 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 326 * @attr ref android.R.styleable#TextView_letterSpacing 327 * @attr ref android.R.styleable#TextView_fontFeatureSettings 328 * @attr ref android.R.styleable#TextView_fontVariationSettings 329 * @attr ref android.R.styleable#TextView_breakStrategy 330 * @attr ref android.R.styleable#TextView_hyphenationFrequency 331 * @attr ref android.R.styleable#TextView_autoSizeTextType 332 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 333 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 334 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 335 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 336 * @attr ref android.R.styleable#TextView_textCursorDrawable 337 * @attr ref android.R.styleable#TextView_textSelectHandle 338 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 339 * @attr ref android.R.styleable#TextView_textSelectHandleRight 340 * @attr ref android.R.styleable#TextView_allowUndo 341 * @attr ref android.R.styleable#TextView_enabled 342 */ 343 @RemoteView 344 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 345 static final String LOG_TAG = "TextView"; 346 static final boolean DEBUG_EXTRACT = false; 347 private static final float[] TEMP_POSITION = new float[2]; 348 349 // Enum for the "typeface" XML parameter. 350 // TODO: How can we get this from the XML instead of hardcoding it here? 351 /** @hide */ 352 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 353 @Retention(RetentionPolicy.SOURCE) 354 public @interface XMLTypefaceAttr{} 355 private static final int DEFAULT_TYPEFACE = -1; 356 private static final int SANS = 1; 357 private static final int SERIF = 2; 358 private static final int MONOSPACE = 3; 359 360 // Enum for the "ellipsize" XML parameter. 361 private static final int ELLIPSIZE_NOT_SET = -1; 362 private static final int ELLIPSIZE_NONE = 0; 363 private static final int ELLIPSIZE_START = 1; 364 private static final int ELLIPSIZE_MIDDLE = 2; 365 private static final int ELLIPSIZE_END = 3; 366 private static final int ELLIPSIZE_MARQUEE = 4; 367 368 // Bitfield for the "numeric" XML parameter. 369 // TODO: How can we get this from the XML instead of hardcoding it here? 370 private static final int SIGNED = 2; 371 private static final int DECIMAL = 4; 372 373 /** 374 * Draw marquee text with fading edges as usual 375 */ 376 private static final int MARQUEE_FADE_NORMAL = 0; 377 378 /** 379 * Draw marquee text as ellipsize end while inactive instead of with the fade. 380 * (Useful for devices where the fade can be expensive if overdone) 381 */ 382 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 383 384 /** 385 * Draw marquee text with fading edges because it is currently active/animating. 386 */ 387 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 388 389 @UnsupportedAppUsage 390 private static final int LINES = 1; 391 private static final int EMS = LINES; 392 private static final int PIXELS = 2; 393 394 private static final RectF TEMP_RECTF = new RectF(); 395 396 /** @hide */ 397 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 398 private static final int ANIMATED_SCROLL_GAP = 250; 399 400 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 401 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 402 403 private static final int CHANGE_WATCHER_PRIORITY = 100; 404 405 // New state used to change background based on whether this TextView is multiline. 406 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 407 408 // Accessibility action to share selected text. 409 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 410 411 /** 412 * @hide 413 */ 414 // Accessibility action start id for "process text" actions. 415 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 416 417 /** 418 * @hide 419 */ 420 static final int PROCESS_TEXT_REQUEST_CODE = 100; 421 422 /** 423 * Return code of {@link #doKeyDown}. 424 */ 425 private static final int KEY_EVENT_NOT_HANDLED = 0; 426 private static final int KEY_EVENT_HANDLED = -1; 427 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 428 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 429 430 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 431 432 // System wide time for last cut, copy or text changed action. 433 static long sLastCutCopyOrTextChangedTime; 434 435 private ColorStateList mTextColor; 436 private ColorStateList mHintTextColor; 437 private ColorStateList mLinkTextColor; 438 @ViewDebug.ExportedProperty(category = "text") 439 440 /** 441 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 442 */ 443 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 444 private int mCurTextColor; 445 446 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 447 private int mCurHintTextColor; 448 private boolean mFreezesText; 449 450 @UnsupportedAppUsage 451 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 452 @UnsupportedAppUsage 453 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 454 455 @UnsupportedAppUsage 456 private float mShadowRadius; 457 @UnsupportedAppUsage 458 private float mShadowDx; 459 @UnsupportedAppUsage 460 private float mShadowDy; 461 private int mShadowColor; 462 463 private boolean mPreDrawRegistered; 464 private boolean mPreDrawListenerDetached; 465 466 private TextClassifier mTextClassifier; 467 private TextClassifier mTextClassificationSession; 468 private TextClassificationContext mTextClassificationContext; 469 470 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 471 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 472 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 473 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 474 // into by the user holding the movement key down) then we shouldn't prevent the focus from 475 // changing. 476 private boolean mPreventDefaultMovement; 477 478 private TextUtils.TruncateAt mEllipsize; 479 480 static class Drawables { 481 static final int LEFT = 0; 482 static final int TOP = 1; 483 static final int RIGHT = 2; 484 static final int BOTTOM = 3; 485 486 static final int DRAWABLE_NONE = -1; 487 static final int DRAWABLE_RIGHT = 0; 488 static final int DRAWABLE_LEFT = 1; 489 490 final Rect mCompoundRect = new Rect(); 491 492 final Drawable[] mShowing = new Drawable[4]; 493 494 ColorStateList mTintList; 495 BlendMode mBlendMode; 496 boolean mHasTint; 497 boolean mHasTintMode; 498 499 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 500 Drawable mDrawableLeftInitial, mDrawableRightInitial; 501 502 boolean mIsRtlCompatibilityMode; 503 boolean mOverride; 504 505 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 506 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 507 508 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 509 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 510 511 int mDrawablePadding; 512 513 int mDrawableSaved = DRAWABLE_NONE; 514 Drawables(Context context)515 public Drawables(Context context) { 516 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 517 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 518 || !context.getApplicationInfo().hasRtlSupport(); 519 mOverride = false; 520 } 521 522 /** 523 * @return {@code true} if this object contains metadata that needs to 524 * be retained, {@code false} otherwise 525 */ 526 public boolean hasMetadata() { 527 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 528 } 529 530 /** 531 * Updates the list of displayed drawables to account for the current 532 * layout direction. 533 * 534 * @param layoutDirection the current layout direction 535 * @return {@code true} if the displayed drawables changed 536 */ 537 public boolean resolveWithLayoutDirection(int layoutDirection) { 538 final Drawable previousLeft = mShowing[Drawables.LEFT]; 539 final Drawable previousRight = mShowing[Drawables.RIGHT]; 540 541 // First reset "left" and "right" drawables to their initial values 542 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 543 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 544 545 if (mIsRtlCompatibilityMode) { 546 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 547 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 548 mShowing[Drawables.LEFT] = mDrawableStart; 549 mDrawableSizeLeft = mDrawableSizeStart; 550 mDrawableHeightLeft = mDrawableHeightStart; 551 } 552 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 553 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 554 mShowing[Drawables.RIGHT] = mDrawableEnd; 555 mDrawableSizeRight = mDrawableSizeEnd; 556 mDrawableHeightRight = mDrawableHeightEnd; 557 } 558 } else { 559 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 560 // drawable if and only if they have been defined 561 switch(layoutDirection) { 562 case LAYOUT_DIRECTION_RTL: 563 if (mOverride) { 564 mShowing[Drawables.RIGHT] = mDrawableStart; 565 mDrawableSizeRight = mDrawableSizeStart; 566 mDrawableHeightRight = mDrawableHeightStart; 567 568 mShowing[Drawables.LEFT] = mDrawableEnd; 569 mDrawableSizeLeft = mDrawableSizeEnd; 570 mDrawableHeightLeft = mDrawableHeightEnd; 571 } 572 break; 573 574 case LAYOUT_DIRECTION_LTR: 575 default: 576 if (mOverride) { 577 mShowing[Drawables.LEFT] = mDrawableStart; 578 mDrawableSizeLeft = mDrawableSizeStart; 579 mDrawableHeightLeft = mDrawableHeightStart; 580 581 mShowing[Drawables.RIGHT] = mDrawableEnd; 582 mDrawableSizeRight = mDrawableSizeEnd; 583 mDrawableHeightRight = mDrawableHeightEnd; 584 } 585 break; 586 } 587 } 588 589 applyErrorDrawableIfNeeded(layoutDirection); 590 591 return mShowing[Drawables.LEFT] != previousLeft 592 || mShowing[Drawables.RIGHT] != previousRight; 593 } 594 595 public void setErrorDrawable(Drawable dr, TextView tv) { 596 if (mDrawableError != dr && mDrawableError != null) { 597 mDrawableError.setCallback(null); 598 } 599 mDrawableError = dr; 600 601 if (mDrawableError != null) { 602 final Rect compoundRect = mCompoundRect; 603 final int[] state = tv.getDrawableState(); 604 605 mDrawableError.setState(state); 606 mDrawableError.copyBounds(compoundRect); 607 mDrawableError.setCallback(tv); 608 mDrawableSizeError = compoundRect.width(); 609 mDrawableHeightError = compoundRect.height(); 610 } else { 611 mDrawableSizeError = mDrawableHeightError = 0; 612 } 613 } 614 615 private void applyErrorDrawableIfNeeded(int layoutDirection) { 616 // first restore the initial state if needed 617 switch (mDrawableSaved) { 618 case DRAWABLE_LEFT: 619 mShowing[Drawables.LEFT] = mDrawableTemp; 620 mDrawableSizeLeft = mDrawableSizeTemp; 621 mDrawableHeightLeft = mDrawableHeightTemp; 622 break; 623 case DRAWABLE_RIGHT: 624 mShowing[Drawables.RIGHT] = mDrawableTemp; 625 mDrawableSizeRight = mDrawableSizeTemp; 626 mDrawableHeightRight = mDrawableHeightTemp; 627 break; 628 case DRAWABLE_NONE: 629 default: 630 } 631 // then, if needed, assign the Error drawable to the correct location 632 if (mDrawableError != null) { 633 switch(layoutDirection) { 634 case LAYOUT_DIRECTION_RTL: 635 mDrawableSaved = DRAWABLE_LEFT; 636 637 mDrawableTemp = mShowing[Drawables.LEFT]; 638 mDrawableSizeTemp = mDrawableSizeLeft; 639 mDrawableHeightTemp = mDrawableHeightLeft; 640 641 mShowing[Drawables.LEFT] = mDrawableError; 642 mDrawableSizeLeft = mDrawableSizeError; 643 mDrawableHeightLeft = mDrawableHeightError; 644 break; 645 case LAYOUT_DIRECTION_LTR: 646 default: 647 mDrawableSaved = DRAWABLE_RIGHT; 648 649 mDrawableTemp = mShowing[Drawables.RIGHT]; 650 mDrawableSizeTemp = mDrawableSizeRight; 651 mDrawableHeightTemp = mDrawableHeightRight; 652 653 mShowing[Drawables.RIGHT] = mDrawableError; 654 mDrawableSizeRight = mDrawableSizeError; 655 mDrawableHeightRight = mDrawableHeightError; 656 break; 657 } 658 } 659 } 660 } 661 662 @UnsupportedAppUsage 663 Drawables mDrawables; 664 665 @UnsupportedAppUsage 666 private CharWrapper mCharWrapper; 667 668 @UnsupportedAppUsage(trackingBug = 124050217) 669 private Marquee mMarquee; 670 @UnsupportedAppUsage 671 private boolean mRestartMarquee; 672 673 private int mMarqueeRepeatLimit = 3; 674 675 private int mLastLayoutDirection = -1; 676 677 /** 678 * On some devices the fading edges add a performance penalty if used 679 * extensively in the same layout. This mode indicates how the marquee 680 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 681 */ 682 @UnsupportedAppUsage 683 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 684 685 /** 686 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 687 * the layout that should be used when the mode switches. 688 */ 689 @UnsupportedAppUsage 690 private Layout mSavedMarqueeModeLayout; 691 692 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 693 @ViewDebug.ExportedProperty(category = "text") 694 @UnsupportedAppUsage 695 private @Nullable CharSequence mText; 696 private @Nullable Spannable mSpannable; 697 private @Nullable PrecomputedText mPrecomputed; 698 699 @UnsupportedAppUsage 700 private CharSequence mTransformed; 701 @UnsupportedAppUsage 702 private BufferType mBufferType = BufferType.NORMAL; 703 704 private CharSequence mHint; 705 @UnsupportedAppUsage 706 private Layout mHintLayout; 707 708 private MovementMethod mMovement; 709 710 private TransformationMethod mTransformation; 711 @UnsupportedAppUsage 712 private boolean mAllowTransformationLengthChange; 713 @UnsupportedAppUsage 714 private ChangeWatcher mChangeWatcher; 715 716 @UnsupportedAppUsage(trackingBug = 123769451) 717 private ArrayList<TextWatcher> mListeners; 718 719 // display attributes 720 @UnsupportedAppUsage 721 private final TextPaint mTextPaint; 722 @UnsupportedAppUsage 723 private boolean mUserSetTextScaleX; 724 @UnsupportedAppUsage 725 private Layout mLayout; 726 private boolean mLocalesChanged = false; 727 728 // True if setKeyListener() has been explicitly called 729 private boolean mListenerChanged = false; 730 // True if internationalized input should be used for numbers and date and time. 731 private final boolean mUseInternationalizedInput; 732 // True if fallback fonts that end up getting used should be allowed to affect line spacing. 733 /* package */ boolean mUseFallbackLineSpacing; 734 735 @ViewDebug.ExportedProperty(category = "text") 736 @UnsupportedAppUsage 737 private int mGravity = Gravity.TOP | Gravity.START; 738 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 739 private boolean mHorizontallyScrolling; 740 741 private int mAutoLinkMask; 742 private boolean mLinksClickable = true; 743 744 @UnsupportedAppUsage 745 private float mSpacingMult = 1.0f; 746 @UnsupportedAppUsage 747 private float mSpacingAdd = 0.0f; 748 749 private int mBreakStrategy; 750 private int mHyphenationFrequency; 751 private int mJustificationMode; 752 753 @UnsupportedAppUsage 754 private int mMaximum = Integer.MAX_VALUE; 755 @UnsupportedAppUsage 756 private int mMaxMode = LINES; 757 @UnsupportedAppUsage 758 private int mMinimum = 0; 759 @UnsupportedAppUsage 760 private int mMinMode = LINES; 761 762 @UnsupportedAppUsage 763 private int mOldMaximum = mMaximum; 764 @UnsupportedAppUsage 765 private int mOldMaxMode = mMaxMode; 766 767 @UnsupportedAppUsage 768 private int mMaxWidth = Integer.MAX_VALUE; 769 @UnsupportedAppUsage 770 private int mMaxWidthMode = PIXELS; 771 @UnsupportedAppUsage 772 private int mMinWidth = 0; 773 @UnsupportedAppUsage 774 private int mMinWidthMode = PIXELS; 775 776 @UnsupportedAppUsage 777 private boolean mSingleLine; 778 @UnsupportedAppUsage 779 private int mDesiredHeightAtMeasure = -1; 780 @UnsupportedAppUsage 781 private boolean mIncludePad = true; 782 private int mDeferScroll = -1; 783 784 // tmp primitives, so we don't alloc them on each draw 785 private Rect mTempRect; 786 private long mLastScroll; 787 private Scroller mScroller; 788 private TextPaint mTempTextPaint; 789 790 @UnsupportedAppUsage 791 private BoringLayout.Metrics mBoring; 792 @UnsupportedAppUsage 793 private BoringLayout.Metrics mHintBoring; 794 @UnsupportedAppUsage 795 private BoringLayout mSavedLayout; 796 @UnsupportedAppUsage 797 private BoringLayout mSavedHintLayout; 798 799 @UnsupportedAppUsage 800 private TextDirectionHeuristic mTextDir; 801 802 private InputFilter[] mFilters = NO_FILTERS; 803 804 /** 805 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 806 * the same as {@link Process#myUserHandle()}. 807 * 808 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 809 * other apps may need to set this so that the system can use right user's resources and 810 * services such as input methods and spell checkers.</p> 811 * 812 * @see #setTextOperationUser(UserHandle) 813 */ 814 @Nullable 815 private UserHandle mTextOperationUser; 816 817 private volatile Locale mCurrentSpellCheckerLocaleCache; 818 819 // It is possible to have a selection even when mEditor is null (programmatically set, like when 820 // a link is pressed). These highlight-related fields do not go in mEditor. 821 @UnsupportedAppUsage 822 int mHighlightColor = 0x6633B5E5; 823 private Path mHighlightPath; 824 @UnsupportedAppUsage 825 private final Paint mHighlightPaint; 826 @UnsupportedAppUsage 827 private boolean mHighlightPathBogus = true; 828 829 // Although these fields are specific to editable text, they are not added to Editor because 830 // they are defined by the TextView's style and are theme-dependent. 831 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 832 int mCursorDrawableRes; 833 private Drawable mCursorDrawable; 834 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 835 // by removing it, but we would break apps targeting <= P that use it by reflection. 836 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 837 int mTextSelectHandleLeftRes; 838 private Drawable mTextSelectHandleLeft; 839 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 840 // by removing it, but we would break apps targeting <= P that use it by reflection. 841 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 842 int mTextSelectHandleRightRes; 843 private Drawable mTextSelectHandleRight; 844 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 845 // by removing it, but we would break apps targeting <= P that use it by reflection. 846 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 847 int mTextSelectHandleRes; 848 private Drawable mTextSelectHandle; 849 int mTextEditSuggestionItemLayout; 850 int mTextEditSuggestionContainerLayout; 851 int mTextEditSuggestionHighlightStyle; 852 853 /** 854 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 855 * See {@link #createEditorIfNeeded()}. 856 */ 857 @UnsupportedAppUsage 858 private Editor mEditor; 859 860 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 861 private static final int DEVICE_PROVISIONED_NO = 1; 862 private static final int DEVICE_PROVISIONED_YES = 2; 863 864 /** 865 * Some special options such as sharing selected text should only be shown if the device 866 * is provisioned. Only check the provisioned state once for a given view instance. 867 */ 868 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 869 870 /** 871 * The TextView does not auto-size text (default). 872 */ 873 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 874 875 /** 876 * The TextView scales text size both horizontally and vertically to fit within the 877 * container. 878 */ 879 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 880 881 /** @hide */ 882 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 883 AUTO_SIZE_TEXT_TYPE_NONE, 884 AUTO_SIZE_TEXT_TYPE_UNIFORM 885 }) 886 @Retention(RetentionPolicy.SOURCE) 887 public @interface AutoSizeTextType {} 888 // Default minimum size for auto-sizing text in scaled pixels. 889 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 890 // Default maximum size for auto-sizing text in scaled pixels. 891 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 892 // Default value for the step size in pixels. 893 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 894 // Use this to specify that any of the auto-size configuration int values have not been set. 895 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 896 // Auto-size text type. 897 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 898 // Specify if auto-size text is needed. 899 private boolean mNeedsAutoSizeText = false; 900 // Step size for auto-sizing in pixels. 901 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 902 // Minimum text size for auto-sizing in pixels. 903 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 904 // Maximum text size for auto-sizing in pixels. 905 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 906 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 907 // when auto-sizing text. 908 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 909 // Specifies whether auto-size should use the provided auto size steps set or if it should 910 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 911 // mAutoSizeStepGranularityInPx. 912 private boolean mHasPresetAutoSizeValues = false; 913 914 // Autofill-related attributes 915 // 916 // Indicates whether the text was set statically or dynamically, so it can be used to 917 // sanitize autofill requests. 918 private boolean mTextSetFromXmlOrResourceId = false; 919 // Resource id used to set the text. 920 private @StringRes int mTextId = Resources.ID_NULL; 921 // 922 // End of autofill-related attributes 923 924 /** 925 * Kick-start the font cache for the zygote process (to pay the cost of 926 * initializing freetype for our default font only once). 927 * @hide 928 */ 929 public static void preloadFontCache() { 930 Paint p = new Paint(); 931 p.setAntiAlias(true); 932 // Ensure that the Typeface is loaded here. 933 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 934 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 935 // since Paint.measureText can not be called without Typeface static initializer. 936 p.setTypeface(Typeface.DEFAULT); 937 // We don't care about the result, just the side-effect of measuring. 938 p.measureText("H"); 939 } 940 941 /** 942 * Interface definition for a callback to be invoked when an action is 943 * performed on the editor. 944 */ 945 public interface OnEditorActionListener { 946 /** 947 * Called when an action is being performed. 948 * 949 * @param v The view that was clicked. 950 * @param actionId Identifier of the action. This will be either the 951 * identifier you supplied, or {@link EditorInfo#IME_NULL 952 * EditorInfo.IME_NULL} if being called due to the enter key 953 * being pressed. 954 * @param event If triggered by an enter key, this is the event; 955 * otherwise, this is null. 956 * @return Return true if you have consumed the action, else false. 957 */ 958 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 959 } 960 961 public TextView(Context context) { 962 this(context, null); 963 } 964 965 public TextView(Context context, @Nullable AttributeSet attrs) { 966 this(context, attrs, com.android.internal.R.attr.textViewStyle); 967 } 968 969 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 970 this(context, attrs, defStyleAttr, 0); 971 } 972 973 @SuppressWarnings("deprecation") 974 public TextView( 975 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 976 super(context, attrs, defStyleAttr, defStyleRes); 977 978 // TextView is important by default, unless app developer overrode attribute. 979 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 980 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 981 } 982 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 983 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 984 } 985 986 setTextInternal(""); 987 988 final Resources res = getResources(); 989 final CompatibilityInfo compat = res.getCompatibilityInfo(); 990 991 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 992 mTextPaint.density = res.getDisplayMetrics().density; 993 mTextPaint.setCompatibilityScaling(compat.applicationScale); 994 995 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 996 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 997 998 mMovement = getDefaultMovementMethod(); 999 1000 mTransformation = null; 1001 1002 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1003 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1004 attributes.mTextSize = 15; 1005 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1006 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1007 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1008 1009 final Resources.Theme theme = context.getTheme(); 1010 1011 /* 1012 * Look the appearance up without checking first if it exists because 1013 * almost every TextView has one and it greatly simplifies the logic 1014 * to be able to parse the appearance first and then let specific tags 1015 * for this View override it. 1016 */ 1017 TypedArray a = theme.obtainStyledAttributes(attrs, 1018 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1019 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1020 attrs, a, defStyleAttr, defStyleRes); 1021 TypedArray appearance = null; 1022 int ap = a.getResourceId( 1023 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1024 a.recycle(); 1025 if (ap != -1) { 1026 appearance = theme.obtainStyledAttributes( 1027 ap, com.android.internal.R.styleable.TextAppearance); 1028 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1029 null, appearance, 0, ap); 1030 } 1031 if (appearance != null) { 1032 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1033 attributes.mFontFamilyExplicit = false; 1034 appearance.recycle(); 1035 } 1036 1037 boolean editable = getDefaultEditable(); 1038 CharSequence inputMethod = null; 1039 int numeric = 0; 1040 CharSequence digits = null; 1041 boolean phone = false; 1042 boolean autotext = false; 1043 int autocap = -1; 1044 int buffertype = 0; 1045 boolean selectallonfocus = false; 1046 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1047 drawableBottom = null, drawableStart = null, drawableEnd = null; 1048 ColorStateList drawableTint = null; 1049 BlendMode drawableTintMode = null; 1050 int drawablePadding = 0; 1051 int ellipsize = ELLIPSIZE_NOT_SET; 1052 boolean singleLine = false; 1053 int maxlength = -1; 1054 CharSequence text = ""; 1055 CharSequence hint = null; 1056 boolean password = false; 1057 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1058 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1059 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1060 int inputType = EditorInfo.TYPE_NULL; 1061 a = theme.obtainStyledAttributes( 1062 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1063 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1064 defStyleAttr, defStyleRes); 1065 int firstBaselineToTopHeight = -1; 1066 int lastBaselineToBottomHeight = -1; 1067 int lineHeight = -1; 1068 1069 readTextAppearance(context, a, attributes, true /* styleArray */); 1070 1071 int n = a.getIndexCount(); 1072 1073 // Must set id in a temporary variable because it will be reset by setText() 1074 boolean textIsSetFromXml = false; 1075 for (int i = 0; i < n; i++) { 1076 int attr = a.getIndex(i); 1077 1078 switch (attr) { 1079 case com.android.internal.R.styleable.TextView_editable: 1080 editable = a.getBoolean(attr, editable); 1081 break; 1082 1083 case com.android.internal.R.styleable.TextView_inputMethod: 1084 inputMethod = a.getText(attr); 1085 break; 1086 1087 case com.android.internal.R.styleable.TextView_numeric: 1088 numeric = a.getInt(attr, numeric); 1089 break; 1090 1091 case com.android.internal.R.styleable.TextView_digits: 1092 digits = a.getText(attr); 1093 break; 1094 1095 case com.android.internal.R.styleable.TextView_phoneNumber: 1096 phone = a.getBoolean(attr, phone); 1097 break; 1098 1099 case com.android.internal.R.styleable.TextView_autoText: 1100 autotext = a.getBoolean(attr, autotext); 1101 break; 1102 1103 case com.android.internal.R.styleable.TextView_capitalize: 1104 autocap = a.getInt(attr, autocap); 1105 break; 1106 1107 case com.android.internal.R.styleable.TextView_bufferType: 1108 buffertype = a.getInt(attr, buffertype); 1109 break; 1110 1111 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1112 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1113 break; 1114 1115 case com.android.internal.R.styleable.TextView_autoLink: 1116 mAutoLinkMask = a.getInt(attr, 0); 1117 break; 1118 1119 case com.android.internal.R.styleable.TextView_linksClickable: 1120 mLinksClickable = a.getBoolean(attr, true); 1121 break; 1122 1123 case com.android.internal.R.styleable.TextView_drawableLeft: 1124 drawableLeft = a.getDrawable(attr); 1125 break; 1126 1127 case com.android.internal.R.styleable.TextView_drawableTop: 1128 drawableTop = a.getDrawable(attr); 1129 break; 1130 1131 case com.android.internal.R.styleable.TextView_drawableRight: 1132 drawableRight = a.getDrawable(attr); 1133 break; 1134 1135 case com.android.internal.R.styleable.TextView_drawableBottom: 1136 drawableBottom = a.getDrawable(attr); 1137 break; 1138 1139 case com.android.internal.R.styleable.TextView_drawableStart: 1140 drawableStart = a.getDrawable(attr); 1141 break; 1142 1143 case com.android.internal.R.styleable.TextView_drawableEnd: 1144 drawableEnd = a.getDrawable(attr); 1145 break; 1146 1147 case com.android.internal.R.styleable.TextView_drawableTint: 1148 drawableTint = a.getColorStateList(attr); 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_drawableTintMode: 1152 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1153 drawableTintMode); 1154 break; 1155 1156 case com.android.internal.R.styleable.TextView_drawablePadding: 1157 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1158 break; 1159 1160 case com.android.internal.R.styleable.TextView_maxLines: 1161 setMaxLines(a.getInt(attr, -1)); 1162 break; 1163 1164 case com.android.internal.R.styleable.TextView_maxHeight: 1165 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1166 break; 1167 1168 case com.android.internal.R.styleable.TextView_lines: 1169 setLines(a.getInt(attr, -1)); 1170 break; 1171 1172 case com.android.internal.R.styleable.TextView_height: 1173 setHeight(a.getDimensionPixelSize(attr, -1)); 1174 break; 1175 1176 case com.android.internal.R.styleable.TextView_minLines: 1177 setMinLines(a.getInt(attr, -1)); 1178 break; 1179 1180 case com.android.internal.R.styleable.TextView_minHeight: 1181 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1182 break; 1183 1184 case com.android.internal.R.styleable.TextView_maxEms: 1185 setMaxEms(a.getInt(attr, -1)); 1186 break; 1187 1188 case com.android.internal.R.styleable.TextView_maxWidth: 1189 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1190 break; 1191 1192 case com.android.internal.R.styleable.TextView_ems: 1193 setEms(a.getInt(attr, -1)); 1194 break; 1195 1196 case com.android.internal.R.styleable.TextView_width: 1197 setWidth(a.getDimensionPixelSize(attr, -1)); 1198 break; 1199 1200 case com.android.internal.R.styleable.TextView_minEms: 1201 setMinEms(a.getInt(attr, -1)); 1202 break; 1203 1204 case com.android.internal.R.styleable.TextView_minWidth: 1205 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1206 break; 1207 1208 case com.android.internal.R.styleable.TextView_gravity: 1209 setGravity(a.getInt(attr, -1)); 1210 break; 1211 1212 case com.android.internal.R.styleable.TextView_hint: 1213 hint = a.getText(attr); 1214 break; 1215 1216 case com.android.internal.R.styleable.TextView_text: 1217 textIsSetFromXml = true; 1218 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1219 text = a.getText(attr); 1220 break; 1221 1222 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1223 if (a.getBoolean(attr, false)) { 1224 setHorizontallyScrolling(true); 1225 } 1226 break; 1227 1228 case com.android.internal.R.styleable.TextView_singleLine: 1229 singleLine = a.getBoolean(attr, singleLine); 1230 break; 1231 1232 case com.android.internal.R.styleable.TextView_ellipsize: 1233 ellipsize = a.getInt(attr, ellipsize); 1234 break; 1235 1236 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1237 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1238 break; 1239 1240 case com.android.internal.R.styleable.TextView_includeFontPadding: 1241 if (!a.getBoolean(attr, true)) { 1242 setIncludeFontPadding(false); 1243 } 1244 break; 1245 1246 case com.android.internal.R.styleable.TextView_cursorVisible: 1247 if (!a.getBoolean(attr, true)) { 1248 setCursorVisible(false); 1249 } 1250 break; 1251 1252 case com.android.internal.R.styleable.TextView_maxLength: 1253 maxlength = a.getInt(attr, -1); 1254 break; 1255 1256 case com.android.internal.R.styleable.TextView_textScaleX: 1257 setTextScaleX(a.getFloat(attr, 1.0f)); 1258 break; 1259 1260 case com.android.internal.R.styleable.TextView_freezesText: 1261 mFreezesText = a.getBoolean(attr, false); 1262 break; 1263 1264 case com.android.internal.R.styleable.TextView_enabled: 1265 setEnabled(a.getBoolean(attr, isEnabled())); 1266 break; 1267 1268 case com.android.internal.R.styleable.TextView_password: 1269 password = a.getBoolean(attr, password); 1270 break; 1271 1272 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1273 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1274 break; 1275 1276 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1277 mSpacingMult = a.getFloat(attr, mSpacingMult); 1278 break; 1279 1280 case com.android.internal.R.styleable.TextView_inputType: 1281 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1282 break; 1283 1284 case com.android.internal.R.styleable.TextView_allowUndo: 1285 createEditorIfNeeded(); 1286 mEditor.mAllowUndo = a.getBoolean(attr, true); 1287 break; 1288 1289 case com.android.internal.R.styleable.TextView_imeOptions: 1290 createEditorIfNeeded(); 1291 mEditor.createInputContentTypeIfNeeded(); 1292 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1293 mEditor.mInputContentType.imeOptions); 1294 break; 1295 1296 case com.android.internal.R.styleable.TextView_imeActionLabel: 1297 createEditorIfNeeded(); 1298 mEditor.createInputContentTypeIfNeeded(); 1299 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1300 break; 1301 1302 case com.android.internal.R.styleable.TextView_imeActionId: 1303 createEditorIfNeeded(); 1304 mEditor.createInputContentTypeIfNeeded(); 1305 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1306 mEditor.mInputContentType.imeActionId); 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_privateImeOptions: 1310 setPrivateImeOptions(a.getString(attr)); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_editorExtras: 1314 try { 1315 setInputExtras(a.getResourceId(attr, 0)); 1316 } catch (XmlPullParserException e) { 1317 Log.w(LOG_TAG, "Failure reading input extras", e); 1318 } catch (IOException e) { 1319 Log.w(LOG_TAG, "Failure reading input extras", e); 1320 } 1321 break; 1322 1323 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1324 mCursorDrawableRes = a.getResourceId(attr, 0); 1325 break; 1326 1327 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1328 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1329 break; 1330 1331 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1332 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1333 break; 1334 1335 case com.android.internal.R.styleable.TextView_textSelectHandle: 1336 mTextSelectHandleRes = a.getResourceId(attr, 0); 1337 break; 1338 1339 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1340 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1341 break; 1342 1343 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1344 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1345 break; 1346 1347 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1348 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1349 break; 1350 1351 case com.android.internal.R.styleable.TextView_textIsSelectable: 1352 setTextIsSelectable(a.getBoolean(attr, false)); 1353 break; 1354 1355 case com.android.internal.R.styleable.TextView_breakStrategy: 1356 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1357 break; 1358 1359 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1360 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1361 break; 1362 1363 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1364 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1365 break; 1366 1367 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1368 autoSizeStepGranularityInPx = a.getDimension(attr, 1369 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1370 break; 1371 1372 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1373 autoSizeMinTextSizeInPx = a.getDimension(attr, 1374 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1375 break; 1376 1377 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1378 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1379 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1380 break; 1381 1382 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1383 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1384 if (autoSizeStepSizeArrayResId > 0) { 1385 final TypedArray autoSizePresetTextSizes = a.getResources() 1386 .obtainTypedArray(autoSizeStepSizeArrayResId); 1387 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1388 autoSizePresetTextSizes.recycle(); 1389 } 1390 break; 1391 case com.android.internal.R.styleable.TextView_justificationMode: 1392 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1393 break; 1394 1395 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1396 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1397 break; 1398 1399 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1400 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1401 break; 1402 1403 case com.android.internal.R.styleable.TextView_lineHeight: 1404 lineHeight = a.getDimensionPixelSize(attr, -1); 1405 break; 1406 } 1407 } 1408 1409 a.recycle(); 1410 1411 BufferType bufferType = BufferType.EDITABLE; 1412 1413 final int variation = 1414 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1415 final boolean passwordInputType = variation 1416 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1417 final boolean webPasswordInputType = variation 1418 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1419 final boolean numberPasswordInputType = variation 1420 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1421 1422 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1423 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1424 mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; 1425 1426 if (inputMethod != null) { 1427 Class<?> c; 1428 1429 try { 1430 c = Class.forName(inputMethod.toString()); 1431 } catch (ClassNotFoundException ex) { 1432 throw new RuntimeException(ex); 1433 } 1434 1435 try { 1436 createEditorIfNeeded(); 1437 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1438 } catch (InstantiationException ex) { 1439 throw new RuntimeException(ex); 1440 } catch (IllegalAccessException ex) { 1441 throw new RuntimeException(ex); 1442 } 1443 try { 1444 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1445 ? inputType 1446 : mEditor.mKeyListener.getInputType(); 1447 } catch (IncompatibleClassChangeError e) { 1448 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1449 } 1450 } else if (digits != null) { 1451 createEditorIfNeeded(); 1452 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1453 // If no input type was specified, we will default to generic 1454 // text, since we can't tell the IME about the set of digits 1455 // that was selected. 1456 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1457 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1458 } else if (inputType != EditorInfo.TYPE_NULL) { 1459 setInputType(inputType, true); 1460 // If set, the input type overrides what was set using the deprecated singleLine flag. 1461 singleLine = !isMultilineInputType(inputType); 1462 } else if (phone) { 1463 createEditorIfNeeded(); 1464 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1465 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1466 } else if (numeric != 0) { 1467 createEditorIfNeeded(); 1468 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1469 null, // locale 1470 (numeric & SIGNED) != 0, 1471 (numeric & DECIMAL) != 0); 1472 inputType = mEditor.mKeyListener.getInputType(); 1473 mEditor.mInputType = inputType; 1474 } else if (autotext || autocap != -1) { 1475 TextKeyListener.Capitalize cap; 1476 1477 inputType = EditorInfo.TYPE_CLASS_TEXT; 1478 1479 switch (autocap) { 1480 case 1: 1481 cap = TextKeyListener.Capitalize.SENTENCES; 1482 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1483 break; 1484 1485 case 2: 1486 cap = TextKeyListener.Capitalize.WORDS; 1487 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1488 break; 1489 1490 case 3: 1491 cap = TextKeyListener.Capitalize.CHARACTERS; 1492 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1493 break; 1494 1495 default: 1496 cap = TextKeyListener.Capitalize.NONE; 1497 break; 1498 } 1499 1500 createEditorIfNeeded(); 1501 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1502 mEditor.mInputType = inputType; 1503 } else if (editable) { 1504 createEditorIfNeeded(); 1505 mEditor.mKeyListener = TextKeyListener.getInstance(); 1506 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1507 } else if (isTextSelectable()) { 1508 // Prevent text changes from keyboard. 1509 if (mEditor != null) { 1510 mEditor.mKeyListener = null; 1511 mEditor.mInputType = EditorInfo.TYPE_NULL; 1512 } 1513 bufferType = BufferType.SPANNABLE; 1514 // So that selection can be changed using arrow keys and touch is handled. 1515 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1516 } else { 1517 if (mEditor != null) mEditor.mKeyListener = null; 1518 1519 switch (buffertype) { 1520 case 0: 1521 bufferType = BufferType.NORMAL; 1522 break; 1523 case 1: 1524 bufferType = BufferType.SPANNABLE; 1525 break; 1526 case 2: 1527 bufferType = BufferType.EDITABLE; 1528 break; 1529 } 1530 } 1531 1532 if (mEditor != null) { 1533 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1534 numberPasswordInputType); 1535 } 1536 1537 if (selectallonfocus) { 1538 createEditorIfNeeded(); 1539 mEditor.mSelectAllOnFocus = true; 1540 1541 if (bufferType == BufferType.NORMAL) { 1542 bufferType = BufferType.SPANNABLE; 1543 } 1544 } 1545 1546 // Set up the tint (if needed) before setting the drawables so that it 1547 // gets applied correctly. 1548 if (drawableTint != null || drawableTintMode != null) { 1549 if (mDrawables == null) { 1550 mDrawables = new Drawables(context); 1551 } 1552 if (drawableTint != null) { 1553 mDrawables.mTintList = drawableTint; 1554 mDrawables.mHasTint = true; 1555 } 1556 if (drawableTintMode != null) { 1557 mDrawables.mBlendMode = drawableTintMode; 1558 mDrawables.mHasTintMode = true; 1559 } 1560 } 1561 1562 // This call will save the initial left/right drawables 1563 setCompoundDrawablesWithIntrinsicBounds( 1564 drawableLeft, drawableTop, drawableRight, drawableBottom); 1565 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1566 setCompoundDrawablePadding(drawablePadding); 1567 1568 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1569 // of lines of height are unchanged for multi-line TextViews. 1570 setInputTypeSingleLine(singleLine); 1571 applySingleLine(singleLine, singleLine, singleLine); 1572 1573 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1574 ellipsize = ELLIPSIZE_END; 1575 } 1576 1577 switch (ellipsize) { 1578 case ELLIPSIZE_START: 1579 setEllipsize(TextUtils.TruncateAt.START); 1580 break; 1581 case ELLIPSIZE_MIDDLE: 1582 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1583 break; 1584 case ELLIPSIZE_END: 1585 setEllipsize(TextUtils.TruncateAt.END); 1586 break; 1587 case ELLIPSIZE_MARQUEE: 1588 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1589 setHorizontalFadingEdgeEnabled(true); 1590 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1591 } else { 1592 setHorizontalFadingEdgeEnabled(false); 1593 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1594 } 1595 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1596 break; 1597 } 1598 1599 final boolean isPassword = password || passwordInputType || webPasswordInputType 1600 || numberPasswordInputType; 1601 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1602 && (mEditor.mInputType 1603 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1604 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1605 if (isMonospaceEnforced) { 1606 attributes.mTypefaceIndex = MONOSPACE; 1607 } 1608 1609 applyTextAppearance(attributes); 1610 1611 if (isPassword) { 1612 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1613 } 1614 1615 if (maxlength >= 0) { 1616 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1617 } else { 1618 setFilters(NO_FILTERS); 1619 } 1620 1621 setText(text, bufferType); 1622 if (mText == null) { 1623 mText = ""; 1624 } 1625 if (mTransformed == null) { 1626 mTransformed = ""; 1627 } 1628 1629 if (textIsSetFromXml) { 1630 mTextSetFromXmlOrResourceId = true; 1631 } 1632 1633 if (hint != null) setHint(hint); 1634 1635 /* 1636 * Views are not normally clickable unless specified to be. 1637 * However, TextViews that have input or movement methods *are* 1638 * clickable by default. By setting clickable here, we implicitly set focusable as well 1639 * if not overridden by the developer. 1640 */ 1641 a = context.obtainStyledAttributes( 1642 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1643 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1644 boolean clickable = canInputOrMove || isClickable(); 1645 boolean longClickable = canInputOrMove || isLongClickable(); 1646 int focusable = getFocusable(); 1647 1648 n = a.getIndexCount(); 1649 for (int i = 0; i < n; i++) { 1650 int attr = a.getIndex(i); 1651 1652 switch (attr) { 1653 case com.android.internal.R.styleable.View_focusable: 1654 TypedValue val = new TypedValue(); 1655 if (a.getValue(attr, val)) { 1656 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1657 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1658 : val.data; 1659 } 1660 break; 1661 1662 case com.android.internal.R.styleable.View_clickable: 1663 clickable = a.getBoolean(attr, clickable); 1664 break; 1665 1666 case com.android.internal.R.styleable.View_longClickable: 1667 longClickable = a.getBoolean(attr, longClickable); 1668 break; 1669 } 1670 } 1671 a.recycle(); 1672 1673 // Some apps were relying on the undefined behavior of focusable winning over 1674 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1675 // when starting with EditText and setting only focusable=false). To keep those apps from 1676 // breaking, re-apply the focusable attribute here. 1677 if (focusable != getFocusable()) { 1678 setFocusable(focusable); 1679 } 1680 setClickable(clickable); 1681 setLongClickable(longClickable); 1682 1683 if (mEditor != null) mEditor.prepareCursorControllers(); 1684 1685 // If not explicitly specified this view is important for accessibility. 1686 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1687 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1688 } 1689 1690 if (supportsAutoSizeText()) { 1691 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1692 // If uniform auto-size has been specified but preset values have not been set then 1693 // replace the auto-size configuration values that have not been specified with the 1694 // defaults. 1695 if (!mHasPresetAutoSizeValues) { 1696 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1697 1698 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1699 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1700 TypedValue.COMPLEX_UNIT_SP, 1701 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1702 displayMetrics); 1703 } 1704 1705 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1706 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1707 TypedValue.COMPLEX_UNIT_SP, 1708 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1709 displayMetrics); 1710 } 1711 1712 if (autoSizeStepGranularityInPx 1713 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1714 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1715 } 1716 1717 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1718 autoSizeMaxTextSizeInPx, 1719 autoSizeStepGranularityInPx); 1720 } 1721 1722 setupAutoSizeText(); 1723 } 1724 } else { 1725 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1726 } 1727 1728 if (firstBaselineToTopHeight >= 0) { 1729 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1730 } 1731 if (lastBaselineToBottomHeight >= 0) { 1732 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1733 } 1734 if (lineHeight >= 0) { 1735 setLineHeight(lineHeight); 1736 } 1737 } 1738 1739 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1740 private void setTextInternal(@Nullable CharSequence text) { 1741 mText = text; 1742 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1743 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1744 } 1745 1746 /** 1747 * Specify whether this widget should automatically scale the text to try to perfectly fit 1748 * within the layout bounds by using the default auto-size configuration. 1749 * 1750 * @param autoSizeTextType the type of auto-size. Must be one of 1751 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1752 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1753 * 1754 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1755 * 1756 * @attr ref android.R.styleable#TextView_autoSizeTextType 1757 * 1758 * @see #getAutoSizeTextType() 1759 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1760 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1761 if (supportsAutoSizeText()) { 1762 switch (autoSizeTextType) { 1763 case AUTO_SIZE_TEXT_TYPE_NONE: 1764 clearAutoSizeConfiguration(); 1765 break; 1766 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1767 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1768 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1769 TypedValue.COMPLEX_UNIT_SP, 1770 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1771 displayMetrics); 1772 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1773 TypedValue.COMPLEX_UNIT_SP, 1774 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1775 displayMetrics); 1776 1777 validateAndSetAutoSizeTextTypeUniformConfiguration( 1778 autoSizeMinTextSizeInPx, 1779 autoSizeMaxTextSizeInPx, 1780 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1781 if (setupAutoSizeText()) { 1782 autoSizeText(); 1783 invalidate(); 1784 } 1785 break; 1786 default: 1787 throw new IllegalArgumentException( 1788 "Unknown auto-size text type: " + autoSizeTextType); 1789 } 1790 } 1791 } 1792 1793 /** 1794 * Specify whether this widget should automatically scale the text to try to perfectly fit 1795 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1796 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1797 * 1798 * @param autoSizeMinTextSize the minimum text size available for auto-size 1799 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1800 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1801 * the minimum and maximum text size in order to build the set of 1802 * text sizes the system uses to choose from when auto-sizing 1803 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1804 * possible dimension units 1805 * 1806 * @throws IllegalArgumentException if any of the configuration params are invalid. 1807 * 1808 * @attr ref android.R.styleable#TextView_autoSizeTextType 1809 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1810 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1811 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1812 * 1813 * @see #setAutoSizeTextTypeWithDefaults(int) 1814 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1815 * @see #getAutoSizeMinTextSize() 1816 * @see #getAutoSizeMaxTextSize() 1817 * @see #getAutoSizeStepGranularity() 1818 * @see #getAutoSizeTextAvailableSizes() 1819 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1820 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1821 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1822 if (supportsAutoSizeText()) { 1823 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1824 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1825 unit, autoSizeMinTextSize, displayMetrics); 1826 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1827 unit, autoSizeMaxTextSize, displayMetrics); 1828 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1829 unit, autoSizeStepGranularity, displayMetrics); 1830 1831 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1832 autoSizeMaxTextSizeInPx, 1833 autoSizeStepGranularityInPx); 1834 1835 if (setupAutoSizeText()) { 1836 autoSizeText(); 1837 invalidate(); 1838 } 1839 } 1840 } 1841 1842 /** 1843 * Specify whether this widget should automatically scale the text to try to perfectly fit 1844 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1845 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1846 * 1847 * @param presetSizes an {@code int} array of sizes in pixels 1848 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1849 * the possible dimension units 1850 * 1851 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1852 * 1853 * @attr ref android.R.styleable#TextView_autoSizeTextType 1854 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1855 * 1856 * @see #setAutoSizeTextTypeWithDefaults(int) 1857 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1858 * @see #getAutoSizeMinTextSize() 1859 * @see #getAutoSizeMaxTextSize() 1860 * @see #getAutoSizeTextAvailableSizes() 1861 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1862 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1863 if (supportsAutoSizeText()) { 1864 final int presetSizesLength = presetSizes.length; 1865 if (presetSizesLength > 0) { 1866 int[] presetSizesInPx = new int[presetSizesLength]; 1867 1868 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1869 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1870 } else { 1871 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1872 // Convert all to sizes to pixels. 1873 for (int i = 0; i < presetSizesLength; i++) { 1874 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 1875 presetSizes[i], displayMetrics)); 1876 } 1877 } 1878 1879 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1880 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1881 throw new IllegalArgumentException("None of the preset sizes is valid: " 1882 + Arrays.toString(presetSizes)); 1883 } 1884 } else { 1885 mHasPresetAutoSizeValues = false; 1886 } 1887 1888 if (setupAutoSizeText()) { 1889 autoSizeText(); 1890 invalidate(); 1891 } 1892 } 1893 } 1894 1895 /** 1896 * Returns the type of auto-size set for this widget. 1897 * 1898 * @return an {@code int} corresponding to one of the auto-size types: 1899 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1900 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1901 * 1902 * @attr ref android.R.styleable#TextView_autoSizeTextType 1903 * 1904 * @see #setAutoSizeTextTypeWithDefaults(int) 1905 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1906 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1907 */ 1908 @InspectableProperty(enumMapping = { 1909 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 1910 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 1911 }) 1912 @AutoSizeTextType getAutoSizeTextType()1913 public int getAutoSizeTextType() { 1914 return mAutoSizeTextType; 1915 } 1916 1917 /** 1918 * @return the current auto-size step granularity in pixels. 1919 * 1920 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1921 * 1922 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1923 */ 1924 @InspectableProperty getAutoSizeStepGranularity()1925 public int getAutoSizeStepGranularity() { 1926 return Math.round(mAutoSizeStepGranularityInPx); 1927 } 1928 1929 /** 1930 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 1931 * if auto-size has not been configured this function returns {@code -1}. 1932 * 1933 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1934 * 1935 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1936 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1937 */ 1938 @InspectableProperty getAutoSizeMinTextSize()1939 public int getAutoSizeMinTextSize() { 1940 return Math.round(mAutoSizeMinTextSizeInPx); 1941 } 1942 1943 /** 1944 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 1945 * if auto-size has not been configured this function returns {@code -1}. 1946 * 1947 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1948 * 1949 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1950 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1951 */ 1952 @InspectableProperty getAutoSizeMaxTextSize()1953 public int getAutoSizeMaxTextSize() { 1954 return Math.round(mAutoSizeMaxTextSizeInPx); 1955 } 1956 1957 /** 1958 * @return the current auto-size {@code int} sizes array (in pixels). 1959 * 1960 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1961 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1962 */ getAutoSizeTextAvailableSizes()1963 public int[] getAutoSizeTextAvailableSizes() { 1964 return mAutoSizeTextSizesInPx; 1965 } 1966 setupAutoSizeUniformPresetSizes(TypedArray textSizes)1967 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 1968 final int textSizesLength = textSizes.length(); 1969 final int[] parsedSizes = new int[textSizesLength]; 1970 1971 if (textSizesLength > 0) { 1972 for (int i = 0; i < textSizesLength; i++) { 1973 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 1974 } 1975 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 1976 setupAutoSizeUniformPresetSizesConfiguration(); 1977 } 1978 } 1979 setupAutoSizeUniformPresetSizesConfiguration()1980 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 1981 final int sizesLength = mAutoSizeTextSizesInPx.length; 1982 mHasPresetAutoSizeValues = sizesLength > 0; 1983 if (mHasPresetAutoSizeValues) { 1984 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 1985 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 1986 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 1987 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1988 } 1989 return mHasPresetAutoSizeValues; 1990 } 1991 1992 /** 1993 * If all params are valid then save the auto-size configuration. 1994 * 1995 * @throws IllegalArgumentException if any of the params are invalid 1996 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)1997 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 1998 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 1999 // First validate. 2000 if (autoSizeMinTextSizeInPx <= 0) { 2001 throw new IllegalArgumentException("Minimum auto-size text size (" 2002 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2003 } 2004 2005 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2006 throw new IllegalArgumentException("Maximum auto-size text size (" 2007 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2008 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2009 } 2010 2011 if (autoSizeStepGranularityInPx <= 0) { 2012 throw new IllegalArgumentException("The auto-size step granularity (" 2013 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2014 } 2015 2016 // All good, persist the configuration. 2017 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2018 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2019 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2020 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2021 mHasPresetAutoSizeValues = false; 2022 } 2023 clearAutoSizeConfiguration()2024 private void clearAutoSizeConfiguration() { 2025 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2026 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2027 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2028 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2029 mAutoSizeTextSizesInPx = EmptyArray.INT; 2030 mNeedsAutoSizeText = false; 2031 } 2032 2033 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2034 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2035 final int presetValuesLength = presetValues.length; 2036 if (presetValuesLength == 0) { 2037 return presetValues; 2038 } 2039 Arrays.sort(presetValues); 2040 2041 final IntArray uniqueValidSizes = new IntArray(); 2042 for (int i = 0; i < presetValuesLength; i++) { 2043 final int currentPresetValue = presetValues[i]; 2044 2045 if (currentPresetValue > 0 2046 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2047 uniqueValidSizes.add(currentPresetValue); 2048 } 2049 } 2050 2051 return presetValuesLength == uniqueValidSizes.size() 2052 ? presetValues 2053 : uniqueValidSizes.toArray(); 2054 } 2055 setupAutoSizeText()2056 private boolean setupAutoSizeText() { 2057 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2058 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2059 // not have a predefined set of sizes or if the current sizes array is empty. 2060 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2061 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2062 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2063 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2064 for (int i = 0; i < autoSizeValuesLength; i++) { 2065 autoSizeTextSizesInPx[i] = Math.round( 2066 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2067 } 2068 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2069 } 2070 2071 mNeedsAutoSizeText = true; 2072 } else { 2073 mNeedsAutoSizeText = false; 2074 } 2075 2076 return mNeedsAutoSizeText; 2077 } 2078 parseDimensionArray(TypedArray dimens)2079 private int[] parseDimensionArray(TypedArray dimens) { 2080 if (dimens == null) { 2081 return null; 2082 } 2083 int[] result = new int[dimens.length()]; 2084 for (int i = 0; i < result.length; i++) { 2085 result[i] = dimens.getDimensionPixelSize(i, 0); 2086 } 2087 return result; 2088 } 2089 2090 /** 2091 * @hide 2092 */ 2093 @Override onActivityResult(int requestCode, int resultCode, Intent data)2094 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2095 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2096 if (resultCode == Activity.RESULT_OK && data != null) { 2097 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2098 if (result != null) { 2099 if (isTextEditable()) { 2100 replaceSelectionWithText(result); 2101 if (mEditor != null) { 2102 mEditor.refreshTextActionMode(); 2103 } 2104 } else { 2105 if (result.length() > 0) { 2106 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2107 .show(); 2108 } 2109 } 2110 } 2111 } else if (mSpannable != null) { 2112 // Reset the selection. 2113 Selection.setSelection(mSpannable, getSelectionEnd()); 2114 } 2115 } 2116 } 2117 2118 /** 2119 * Sets the Typeface taking into account the given attributes. 2120 * 2121 * @param typeface a typeface 2122 * @param familyName family name string, e.g. "serif" 2123 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2124 * @param style a typeface style 2125 * @param weight a weight value for the Typeface or -1 if not specified. 2126 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2127 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2128 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2129 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2130 if (typeface == null && familyName != null) { 2131 // Lookup normal Typeface from system font map. 2132 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2133 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2134 } else if (typeface != null) { 2135 resolveStyleAndSetTypeface(typeface, style, weight); 2136 } else { // both typeface and familyName is null. 2137 switch (typefaceIndex) { 2138 case SANS: 2139 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2140 break; 2141 case SERIF: 2142 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2143 break; 2144 case MONOSPACE: 2145 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2146 break; 2147 case DEFAULT_TYPEFACE: 2148 default: 2149 resolveStyleAndSetTypeface(null, style, weight); 2150 break; 2151 } 2152 } 2153 } 2154 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2155 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2156 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2157 if (weight >= 0) { 2158 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2159 final boolean italic = (style & Typeface.ITALIC) != 0; 2160 setTypeface(Typeface.create(typeface, weight, italic)); 2161 } else { 2162 setTypeface(typeface, style); 2163 } 2164 } 2165 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2166 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2167 boolean hasRelativeDrawables = (start != null) || (end != null); 2168 if (hasRelativeDrawables) { 2169 Drawables dr = mDrawables; 2170 if (dr == null) { 2171 mDrawables = dr = new Drawables(getContext()); 2172 } 2173 mDrawables.mOverride = true; 2174 final Rect compoundRect = dr.mCompoundRect; 2175 int[] state = getDrawableState(); 2176 if (start != null) { 2177 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2178 start.setState(state); 2179 start.copyBounds(compoundRect); 2180 start.setCallback(this); 2181 2182 dr.mDrawableStart = start; 2183 dr.mDrawableSizeStart = compoundRect.width(); 2184 dr.mDrawableHeightStart = compoundRect.height(); 2185 } else { 2186 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2187 } 2188 if (end != null) { 2189 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2190 end.setState(state); 2191 end.copyBounds(compoundRect); 2192 end.setCallback(this); 2193 2194 dr.mDrawableEnd = end; 2195 dr.mDrawableSizeEnd = compoundRect.width(); 2196 dr.mDrawableHeightEnd = compoundRect.height(); 2197 } else { 2198 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2199 } 2200 resetResolvedDrawables(); 2201 resolveDrawables(); 2202 applyCompoundDrawableTint(); 2203 } 2204 } 2205 2206 @android.view.RemotableViewMethod 2207 @Override setEnabled(boolean enabled)2208 public void setEnabled(boolean enabled) { 2209 if (enabled == isEnabled()) { 2210 return; 2211 } 2212 2213 if (!enabled) { 2214 // Hide the soft input if the currently active TextView is disabled 2215 InputMethodManager imm = getInputMethodManager(); 2216 if (imm != null && imm.isActive(this)) { 2217 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2218 } 2219 } 2220 2221 super.setEnabled(enabled); 2222 2223 if (enabled) { 2224 // Make sure IME is updated with current editor info. 2225 InputMethodManager imm = getInputMethodManager(); 2226 if (imm != null) imm.restartInput(this); 2227 } 2228 2229 // Will change text color 2230 if (mEditor != null) { 2231 mEditor.invalidateTextDisplayList(); 2232 mEditor.prepareCursorControllers(); 2233 2234 // start or stop the cursor blinking as appropriate 2235 mEditor.makeBlink(); 2236 } 2237 } 2238 2239 /** 2240 * Sets the typeface and style in which the text should be displayed, 2241 * and turns on the fake bold and italic bits in the Paint if the 2242 * Typeface that you provided does not have all the bits in the 2243 * style that you specified. 2244 * 2245 * @attr ref android.R.styleable#TextView_typeface 2246 * @attr ref android.R.styleable#TextView_textStyle 2247 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2248 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2249 if (style > 0) { 2250 if (tf == null) { 2251 tf = Typeface.defaultFromStyle(style); 2252 } else { 2253 tf = Typeface.create(tf, style); 2254 } 2255 2256 setTypeface(tf); 2257 // now compute what (if any) algorithmic styling is needed 2258 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2259 int need = style & ~typefaceStyle; 2260 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2261 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2262 } else { 2263 mTextPaint.setFakeBoldText(false); 2264 mTextPaint.setTextSkewX(0); 2265 setTypeface(tf); 2266 } 2267 } 2268 2269 /** 2270 * Subclasses override this to specify that they have a KeyListener 2271 * by default even if not specifically called for in the XML options. 2272 */ getDefaultEditable()2273 protected boolean getDefaultEditable() { 2274 return false; 2275 } 2276 2277 /** 2278 * Subclasses override this to specify a default movement method. 2279 */ getDefaultMovementMethod()2280 protected MovementMethod getDefaultMovementMethod() { 2281 return null; 2282 } 2283 2284 /** 2285 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2286 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2287 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2288 * the return value from this method to Spannable or Editable, respectively. 2289 * 2290 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2291 * should make your own copy first.</p> 2292 * 2293 * @return The text displayed by the text view. 2294 * @attr ref android.R.styleable#TextView_text 2295 */ 2296 @ViewDebug.CapturedViewProperty 2297 @InspectableProperty getText()2298 public CharSequence getText() { 2299 return mText; 2300 } 2301 2302 /** 2303 * Returns the length, in characters, of the text managed by this TextView 2304 * @return The length of the text managed by the TextView in characters. 2305 */ length()2306 public int length() { 2307 return mText.length(); 2308 } 2309 2310 /** 2311 * Return the text that TextView is displaying as an Editable object. If the text is not 2312 * editable, null is returned. 2313 * 2314 * @see #getText 2315 */ getEditableText()2316 public Editable getEditableText() { 2317 return (mText instanceof Editable) ? (Editable) mText : null; 2318 } 2319 2320 /** 2321 * @hide 2322 */ 2323 @VisibleForTesting getTransformed()2324 public CharSequence getTransformed() { 2325 return mTransformed; 2326 } 2327 2328 /** 2329 * Gets the vertical distance between lines of text, in pixels. 2330 * Note that markup within the text can cause individual lines 2331 * to be taller or shorter than this height, and the layout may 2332 * contain additional first-or last-line padding. 2333 * @return The height of one standard line in pixels. 2334 */ 2335 @InspectableProperty getLineHeight()2336 public int getLineHeight() { 2337 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2338 } 2339 2340 /** 2341 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2342 * This value can be null if the text or width has recently changed. 2343 * @return The Layout that is currently being used to display the text. 2344 */ getLayout()2345 public final Layout getLayout() { 2346 return mLayout; 2347 } 2348 2349 /** 2350 * @return the {@link android.text.Layout} that is currently being used to 2351 * display the hint text. This can be null. 2352 */ 2353 @UnsupportedAppUsage getHintLayout()2354 final Layout getHintLayout() { 2355 return mHintLayout; 2356 } 2357 2358 /** 2359 * Retrieve the {@link android.content.UndoManager} that is currently associated 2360 * with this TextView. By default there is no associated UndoManager, so null 2361 * is returned. One can be associated with the TextView through 2362 * {@link #setUndoManager(android.content.UndoManager, String)} 2363 * 2364 * @hide 2365 */ getUndoManager()2366 public final UndoManager getUndoManager() { 2367 // TODO: Consider supporting a global undo manager. 2368 throw new UnsupportedOperationException("not implemented"); 2369 } 2370 2371 2372 /** 2373 * @hide 2374 */ 2375 @VisibleForTesting getEditorForTesting()2376 public final Editor getEditorForTesting() { 2377 return mEditor; 2378 } 2379 2380 /** 2381 * Associate an {@link android.content.UndoManager} with this TextView. Once 2382 * done, all edit operations on the TextView will result in appropriate 2383 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2384 * stack. 2385 * 2386 * @param undoManager The {@link android.content.UndoManager} to associate with 2387 * this TextView, or null to clear any existing association. 2388 * @param tag String tag identifying this particular TextView owner in the 2389 * UndoManager. This is used to keep the correct association with the 2390 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2391 * 2392 * @hide 2393 */ setUndoManager(UndoManager undoManager, String tag)2394 public final void setUndoManager(UndoManager undoManager, String tag) { 2395 // TODO: Consider supporting a global undo manager. An implementation will need to: 2396 // * createEditorIfNeeded() 2397 // * Promote to BufferType.EDITABLE if needed. 2398 // * Update the UndoManager and UndoOwner. 2399 // Likewise it will need to be able to restore the default UndoManager. 2400 throw new UnsupportedOperationException("not implemented"); 2401 } 2402 2403 /** 2404 * Gets the current {@link KeyListener} for the TextView. 2405 * This will frequently be null for non-EditText TextViews. 2406 * @return the current key listener for this TextView. 2407 * 2408 * @attr ref android.R.styleable#TextView_numeric 2409 * @attr ref android.R.styleable#TextView_digits 2410 * @attr ref android.R.styleable#TextView_phoneNumber 2411 * @attr ref android.R.styleable#TextView_inputMethod 2412 * @attr ref android.R.styleable#TextView_capitalize 2413 * @attr ref android.R.styleable#TextView_autoText 2414 */ getKeyListener()2415 public final KeyListener getKeyListener() { 2416 return mEditor == null ? null : mEditor.mKeyListener; 2417 } 2418 2419 /** 2420 * Sets the key listener to be used with this TextView. This can be null 2421 * to disallow user input. Note that this method has significant and 2422 * subtle interactions with soft keyboards and other input method: 2423 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2424 * for important details. Calling this method will replace the current 2425 * content type of the text view with the content type returned by the 2426 * key listener. 2427 * <p> 2428 * Be warned that if you want a TextView with a key listener or movement 2429 * method not to be focusable, or if you want a TextView without a 2430 * key listener or movement method to be focusable, you must call 2431 * {@link #setFocusable} again after calling this to get the focusability 2432 * back the way you want it. 2433 * 2434 * @attr ref android.R.styleable#TextView_numeric 2435 * @attr ref android.R.styleable#TextView_digits 2436 * @attr ref android.R.styleable#TextView_phoneNumber 2437 * @attr ref android.R.styleable#TextView_inputMethod 2438 * @attr ref android.R.styleable#TextView_capitalize 2439 * @attr ref android.R.styleable#TextView_autoText 2440 */ setKeyListener(KeyListener input)2441 public void setKeyListener(KeyListener input) { 2442 mListenerChanged = true; 2443 setKeyListenerOnly(input); 2444 fixFocusableAndClickableSettings(); 2445 2446 if (input != null) { 2447 createEditorIfNeeded(); 2448 setInputTypeFromEditor(); 2449 } else { 2450 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2451 } 2452 2453 InputMethodManager imm = getInputMethodManager(); 2454 if (imm != null) imm.restartInput(this); 2455 } 2456 setInputTypeFromEditor()2457 private void setInputTypeFromEditor() { 2458 try { 2459 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2460 } catch (IncompatibleClassChangeError e) { 2461 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2462 } 2463 // Change inputType, without affecting transformation. 2464 // No need to applySingleLine since mSingleLine is unchanged. 2465 setInputTypeSingleLine(mSingleLine); 2466 } 2467 setKeyListenerOnly(KeyListener input)2468 private void setKeyListenerOnly(KeyListener input) { 2469 if (mEditor == null && input == null) return; // null is the default value 2470 2471 createEditorIfNeeded(); 2472 if (mEditor.mKeyListener != input) { 2473 mEditor.mKeyListener = input; 2474 if (input != null && !(mText instanceof Editable)) { 2475 setText(mText); 2476 } 2477 2478 setFilters((Editable) mText, mFilters); 2479 } 2480 } 2481 2482 /** 2483 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2484 * which provides positioning, scrolling, and text selection functionality. 2485 * This will frequently be null for non-EditText TextViews. 2486 * @return the movement method being used for this TextView. 2487 * @see android.text.method.MovementMethod 2488 */ getMovementMethod()2489 public final MovementMethod getMovementMethod() { 2490 return mMovement; 2491 } 2492 2493 /** 2494 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2495 * for this TextView. This can be null to disallow using the arrow keys to move the 2496 * cursor or scroll the view. 2497 * <p> 2498 * Be warned that if you want a TextView with a key listener or movement 2499 * method not to be focusable, or if you want a TextView without a 2500 * key listener or movement method to be focusable, you must call 2501 * {@link #setFocusable} again after calling this to get the focusability 2502 * back the way you want it. 2503 */ setMovementMethod(MovementMethod movement)2504 public final void setMovementMethod(MovementMethod movement) { 2505 if (mMovement != movement) { 2506 mMovement = movement; 2507 2508 if (movement != null && mSpannable == null) { 2509 setText(mText); 2510 } 2511 2512 fixFocusableAndClickableSettings(); 2513 2514 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2515 // mMovement 2516 if (mEditor != null) mEditor.prepareCursorControllers(); 2517 } 2518 } 2519 fixFocusableAndClickableSettings()2520 private void fixFocusableAndClickableSettings() { 2521 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2522 setFocusable(FOCUSABLE); 2523 setClickable(true); 2524 setLongClickable(true); 2525 } else { 2526 setFocusable(FOCUSABLE_AUTO); 2527 setClickable(false); 2528 setLongClickable(false); 2529 } 2530 } 2531 2532 /** 2533 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2534 * This is frequently null, except for single-line and password fields. 2535 * @return the current transformation method for this TextView. 2536 * 2537 * @attr ref android.R.styleable#TextView_password 2538 * @attr ref android.R.styleable#TextView_singleLine 2539 */ getTransformationMethod()2540 public final TransformationMethod getTransformationMethod() { 2541 return mTransformation; 2542 } 2543 2544 /** 2545 * Sets the transformation that is applied to the text that this 2546 * TextView is displaying. 2547 * 2548 * @attr ref android.R.styleable#TextView_password 2549 * @attr ref android.R.styleable#TextView_singleLine 2550 */ setTransformationMethod(TransformationMethod method)2551 public final void setTransformationMethod(TransformationMethod method) { 2552 if (method == mTransformation) { 2553 // Avoid the setText() below if the transformation is 2554 // the same. 2555 return; 2556 } 2557 if (mTransformation != null) { 2558 if (mSpannable != null) { 2559 mSpannable.removeSpan(mTransformation); 2560 } 2561 } 2562 2563 mTransformation = method; 2564 2565 if (method instanceof TransformationMethod2) { 2566 TransformationMethod2 method2 = (TransformationMethod2) method; 2567 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2568 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2569 } else { 2570 mAllowTransformationLengthChange = false; 2571 } 2572 2573 setText(mText); 2574 2575 if (hasPasswordTransformationMethod()) { 2576 notifyViewAccessibilityStateChangedIfNeeded( 2577 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2578 } 2579 2580 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2581 // getTextDirectionHeuristic, needs reset 2582 mTextDir = getTextDirectionHeuristic(); 2583 } 2584 2585 /** 2586 * Returns the top padding of the view, plus space for the top 2587 * Drawable if any. 2588 */ getCompoundPaddingTop()2589 public int getCompoundPaddingTop() { 2590 final Drawables dr = mDrawables; 2591 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2592 return mPaddingTop; 2593 } else { 2594 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2595 } 2596 } 2597 2598 /** 2599 * Returns the bottom padding of the view, plus space for the bottom 2600 * Drawable if any. 2601 */ getCompoundPaddingBottom()2602 public int getCompoundPaddingBottom() { 2603 final Drawables dr = mDrawables; 2604 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2605 return mPaddingBottom; 2606 } else { 2607 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2608 } 2609 } 2610 2611 /** 2612 * Returns the left padding of the view, plus space for the left 2613 * Drawable if any. 2614 */ getCompoundPaddingLeft()2615 public int getCompoundPaddingLeft() { 2616 final Drawables dr = mDrawables; 2617 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2618 return mPaddingLeft; 2619 } else { 2620 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2621 } 2622 } 2623 2624 /** 2625 * Returns the right padding of the view, plus space for the right 2626 * Drawable if any. 2627 */ getCompoundPaddingRight()2628 public int getCompoundPaddingRight() { 2629 final Drawables dr = mDrawables; 2630 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2631 return mPaddingRight; 2632 } else { 2633 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2634 } 2635 } 2636 2637 /** 2638 * Returns the start padding of the view, plus space for the start 2639 * Drawable if any. 2640 */ getCompoundPaddingStart()2641 public int getCompoundPaddingStart() { 2642 resolveDrawables(); 2643 switch(getLayoutDirection()) { 2644 default: 2645 case LAYOUT_DIRECTION_LTR: 2646 return getCompoundPaddingLeft(); 2647 case LAYOUT_DIRECTION_RTL: 2648 return getCompoundPaddingRight(); 2649 } 2650 } 2651 2652 /** 2653 * Returns the end padding of the view, plus space for the end 2654 * Drawable if any. 2655 */ getCompoundPaddingEnd()2656 public int getCompoundPaddingEnd() { 2657 resolveDrawables(); 2658 switch(getLayoutDirection()) { 2659 default: 2660 case LAYOUT_DIRECTION_LTR: 2661 return getCompoundPaddingRight(); 2662 case LAYOUT_DIRECTION_RTL: 2663 return getCompoundPaddingLeft(); 2664 } 2665 } 2666 2667 /** 2668 * Returns the extended top padding of the view, including both the 2669 * top Drawable if any and any extra space to keep more than maxLines 2670 * of text from showing. It is only valid to call this after measuring. 2671 */ getExtendedPaddingTop()2672 public int getExtendedPaddingTop() { 2673 if (mMaxMode != LINES) { 2674 return getCompoundPaddingTop(); 2675 } 2676 2677 if (mLayout == null) { 2678 assumeLayout(); 2679 } 2680 2681 if (mLayout.getLineCount() <= mMaximum) { 2682 return getCompoundPaddingTop(); 2683 } 2684 2685 int top = getCompoundPaddingTop(); 2686 int bottom = getCompoundPaddingBottom(); 2687 int viewht = getHeight() - top - bottom; 2688 int layoutht = mLayout.getLineTop(mMaximum); 2689 2690 if (layoutht >= viewht) { 2691 return top; 2692 } 2693 2694 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2695 if (gravity == Gravity.TOP) { 2696 return top; 2697 } else if (gravity == Gravity.BOTTOM) { 2698 return top + viewht - layoutht; 2699 } else { // (gravity == Gravity.CENTER_VERTICAL) 2700 return top + (viewht - layoutht) / 2; 2701 } 2702 } 2703 2704 /** 2705 * Returns the extended bottom padding of the view, including both the 2706 * bottom Drawable if any and any extra space to keep more than maxLines 2707 * of text from showing. It is only valid to call this after measuring. 2708 */ getExtendedPaddingBottom()2709 public int getExtendedPaddingBottom() { 2710 if (mMaxMode != LINES) { 2711 return getCompoundPaddingBottom(); 2712 } 2713 2714 if (mLayout == null) { 2715 assumeLayout(); 2716 } 2717 2718 if (mLayout.getLineCount() <= mMaximum) { 2719 return getCompoundPaddingBottom(); 2720 } 2721 2722 int top = getCompoundPaddingTop(); 2723 int bottom = getCompoundPaddingBottom(); 2724 int viewht = getHeight() - top - bottom; 2725 int layoutht = mLayout.getLineTop(mMaximum); 2726 2727 if (layoutht >= viewht) { 2728 return bottom; 2729 } 2730 2731 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2732 if (gravity == Gravity.TOP) { 2733 return bottom + viewht - layoutht; 2734 } else if (gravity == Gravity.BOTTOM) { 2735 return bottom; 2736 } else { // (gravity == Gravity.CENTER_VERTICAL) 2737 return bottom + (viewht - layoutht) / 2; 2738 } 2739 } 2740 2741 /** 2742 * Returns the total left padding of the view, including the left 2743 * Drawable if any. 2744 */ getTotalPaddingLeft()2745 public int getTotalPaddingLeft() { 2746 return getCompoundPaddingLeft(); 2747 } 2748 2749 /** 2750 * Returns the total right padding of the view, including the right 2751 * Drawable if any. 2752 */ getTotalPaddingRight()2753 public int getTotalPaddingRight() { 2754 return getCompoundPaddingRight(); 2755 } 2756 2757 /** 2758 * Returns the total start padding of the view, including the start 2759 * Drawable if any. 2760 */ getTotalPaddingStart()2761 public int getTotalPaddingStart() { 2762 return getCompoundPaddingStart(); 2763 } 2764 2765 /** 2766 * Returns the total end padding of the view, including the end 2767 * Drawable if any. 2768 */ getTotalPaddingEnd()2769 public int getTotalPaddingEnd() { 2770 return getCompoundPaddingEnd(); 2771 } 2772 2773 /** 2774 * Returns the total top padding of the view, including the top 2775 * Drawable if any, the extra space to keep more than maxLines 2776 * from showing, and the vertical offset for gravity, if any. 2777 */ getTotalPaddingTop()2778 public int getTotalPaddingTop() { 2779 return getExtendedPaddingTop() + getVerticalOffset(true); 2780 } 2781 2782 /** 2783 * Returns the total bottom padding of the view, including the bottom 2784 * Drawable if any, the extra space to keep more than maxLines 2785 * from showing, and the vertical offset for gravity, if any. 2786 */ getTotalPaddingBottom()2787 public int getTotalPaddingBottom() { 2788 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2789 } 2790 2791 /** 2792 * Sets the Drawables (if any) to appear to the left of, above, to the 2793 * right of, and below the text. Use {@code null} if you do not want a 2794 * Drawable there. The Drawables must already have had 2795 * {@link Drawable#setBounds} called. 2796 * <p> 2797 * Calling this method will overwrite any Drawables previously set using 2798 * {@link #setCompoundDrawablesRelative} or related methods. 2799 * 2800 * @attr ref android.R.styleable#TextView_drawableLeft 2801 * @attr ref android.R.styleable#TextView_drawableTop 2802 * @attr ref android.R.styleable#TextView_drawableRight 2803 * @attr ref android.R.styleable#TextView_drawableBottom 2804 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2805 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2806 @Nullable Drawable right, @Nullable Drawable bottom) { 2807 Drawables dr = mDrawables; 2808 2809 // We're switching to absolute, discard relative. 2810 if (dr != null) { 2811 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2812 dr.mDrawableStart = null; 2813 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2814 dr.mDrawableEnd = null; 2815 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2816 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2817 } 2818 2819 final boolean drawables = left != null || top != null || right != null || bottom != null; 2820 if (!drawables) { 2821 // Clearing drawables... can we free the data structure? 2822 if (dr != null) { 2823 if (!dr.hasMetadata()) { 2824 mDrawables = null; 2825 } else { 2826 // We need to retain the last set padding, so just clear 2827 // out all of the fields in the existing structure. 2828 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2829 if (dr.mShowing[i] != null) { 2830 dr.mShowing[i].setCallback(null); 2831 } 2832 dr.mShowing[i] = null; 2833 } 2834 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2835 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2836 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2837 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2838 } 2839 } 2840 } else { 2841 if (dr == null) { 2842 mDrawables = dr = new Drawables(getContext()); 2843 } 2844 2845 mDrawables.mOverride = false; 2846 2847 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2848 dr.mShowing[Drawables.LEFT].setCallback(null); 2849 } 2850 dr.mShowing[Drawables.LEFT] = left; 2851 2852 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2853 dr.mShowing[Drawables.TOP].setCallback(null); 2854 } 2855 dr.mShowing[Drawables.TOP] = top; 2856 2857 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2858 dr.mShowing[Drawables.RIGHT].setCallback(null); 2859 } 2860 dr.mShowing[Drawables.RIGHT] = right; 2861 2862 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2863 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2864 } 2865 dr.mShowing[Drawables.BOTTOM] = bottom; 2866 2867 final Rect compoundRect = dr.mCompoundRect; 2868 int[] state; 2869 2870 state = getDrawableState(); 2871 2872 if (left != null) { 2873 left.setState(state); 2874 left.copyBounds(compoundRect); 2875 left.setCallback(this); 2876 dr.mDrawableSizeLeft = compoundRect.width(); 2877 dr.mDrawableHeightLeft = compoundRect.height(); 2878 } else { 2879 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2880 } 2881 2882 if (right != null) { 2883 right.setState(state); 2884 right.copyBounds(compoundRect); 2885 right.setCallback(this); 2886 dr.mDrawableSizeRight = compoundRect.width(); 2887 dr.mDrawableHeightRight = compoundRect.height(); 2888 } else { 2889 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2890 } 2891 2892 if (top != null) { 2893 top.setState(state); 2894 top.copyBounds(compoundRect); 2895 top.setCallback(this); 2896 dr.mDrawableSizeTop = compoundRect.height(); 2897 dr.mDrawableWidthTop = compoundRect.width(); 2898 } else { 2899 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2900 } 2901 2902 if (bottom != null) { 2903 bottom.setState(state); 2904 bottom.copyBounds(compoundRect); 2905 bottom.setCallback(this); 2906 dr.mDrawableSizeBottom = compoundRect.height(); 2907 dr.mDrawableWidthBottom = compoundRect.width(); 2908 } else { 2909 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2910 } 2911 } 2912 2913 // Save initial left/right drawables 2914 if (dr != null) { 2915 dr.mDrawableLeftInitial = left; 2916 dr.mDrawableRightInitial = right; 2917 } 2918 2919 resetResolvedDrawables(); 2920 resolveDrawables(); 2921 applyCompoundDrawableTint(); 2922 invalidate(); 2923 requestLayout(); 2924 } 2925 2926 /** 2927 * Sets the Drawables (if any) to appear to the left of, above, to the 2928 * right of, and below the text. Use 0 if you do not want a Drawable there. 2929 * The Drawables' bounds will be set to their intrinsic bounds. 2930 * <p> 2931 * Calling this method will overwrite any Drawables previously set using 2932 * {@link #setCompoundDrawablesRelative} or related methods. 2933 * 2934 * @param left Resource identifier of the left Drawable. 2935 * @param top Resource identifier of the top Drawable. 2936 * @param right Resource identifier of the right Drawable. 2937 * @param bottom Resource identifier of the bottom Drawable. 2938 * 2939 * @attr ref android.R.styleable#TextView_drawableLeft 2940 * @attr ref android.R.styleable#TextView_drawableTop 2941 * @attr ref android.R.styleable#TextView_drawableRight 2942 * @attr ref android.R.styleable#TextView_drawableBottom 2943 */ 2944 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2945 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 2946 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 2947 final Context context = getContext(); 2948 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2949 top != 0 ? context.getDrawable(top) : null, 2950 right != 0 ? context.getDrawable(right) : null, 2951 bottom != 0 ? context.getDrawable(bottom) : null); 2952 } 2953 2954 /** 2955 * Sets the Drawables (if any) to appear to the left of, above, to the 2956 * right of, and below the text. Use {@code null} if you do not want a 2957 * Drawable there. The Drawables' bounds will be set to their intrinsic 2958 * bounds. 2959 * <p> 2960 * Calling this method will overwrite any Drawables previously set using 2961 * {@link #setCompoundDrawablesRelative} or related methods. 2962 * 2963 * @attr ref android.R.styleable#TextView_drawableLeft 2964 * @attr ref android.R.styleable#TextView_drawableTop 2965 * @attr ref android.R.styleable#TextView_drawableRight 2966 * @attr ref android.R.styleable#TextView_drawableBottom 2967 */ 2968 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2969 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 2970 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 2971 2972 if (left != null) { 2973 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 2974 } 2975 if (right != null) { 2976 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 2977 } 2978 if (top != null) { 2979 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2980 } 2981 if (bottom != null) { 2982 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2983 } 2984 setCompoundDrawables(left, top, right, bottom); 2985 } 2986 2987 /** 2988 * Sets the Drawables (if any) to appear to the start of, above, to the end 2989 * of, and below the text. Use {@code null} if you do not want a Drawable 2990 * there. The Drawables must already have had {@link Drawable#setBounds} 2991 * called. 2992 * <p> 2993 * Calling this method will overwrite any Drawables previously set using 2994 * {@link #setCompoundDrawables} or related methods. 2995 * 2996 * @attr ref android.R.styleable#TextView_drawableStart 2997 * @attr ref android.R.styleable#TextView_drawableTop 2998 * @attr ref android.R.styleable#TextView_drawableEnd 2999 * @attr ref android.R.styleable#TextView_drawableBottom 3000 */ 3001 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3002 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3003 @Nullable Drawable end, @Nullable Drawable bottom) { 3004 Drawables dr = mDrawables; 3005 3006 // We're switching to relative, discard absolute. 3007 if (dr != null) { 3008 if (dr.mShowing[Drawables.LEFT] != null) { 3009 dr.mShowing[Drawables.LEFT].setCallback(null); 3010 } 3011 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3012 if (dr.mShowing[Drawables.RIGHT] != null) { 3013 dr.mShowing[Drawables.RIGHT].setCallback(null); 3014 } 3015 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3016 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3017 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3018 } 3019 3020 final boolean drawables = start != null || top != null 3021 || end != null || bottom != null; 3022 3023 if (!drawables) { 3024 // Clearing drawables... can we free the data structure? 3025 if (dr != null) { 3026 if (!dr.hasMetadata()) { 3027 mDrawables = null; 3028 } else { 3029 // We need to retain the last set padding, so just clear 3030 // out all of the fields in the existing structure. 3031 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3032 dr.mDrawableStart = null; 3033 if (dr.mShowing[Drawables.TOP] != null) { 3034 dr.mShowing[Drawables.TOP].setCallback(null); 3035 } 3036 dr.mShowing[Drawables.TOP] = null; 3037 if (dr.mDrawableEnd != null) { 3038 dr.mDrawableEnd.setCallback(null); 3039 } 3040 dr.mDrawableEnd = null; 3041 if (dr.mShowing[Drawables.BOTTOM] != null) { 3042 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3043 } 3044 dr.mShowing[Drawables.BOTTOM] = null; 3045 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3046 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3047 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3048 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3049 } 3050 } 3051 } else { 3052 if (dr == null) { 3053 mDrawables = dr = new Drawables(getContext()); 3054 } 3055 3056 mDrawables.mOverride = true; 3057 3058 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3059 dr.mDrawableStart.setCallback(null); 3060 } 3061 dr.mDrawableStart = start; 3062 3063 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3064 dr.mShowing[Drawables.TOP].setCallback(null); 3065 } 3066 dr.mShowing[Drawables.TOP] = top; 3067 3068 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3069 dr.mDrawableEnd.setCallback(null); 3070 } 3071 dr.mDrawableEnd = end; 3072 3073 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3074 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3075 } 3076 dr.mShowing[Drawables.BOTTOM] = bottom; 3077 3078 final Rect compoundRect = dr.mCompoundRect; 3079 int[] state; 3080 3081 state = getDrawableState(); 3082 3083 if (start != null) { 3084 start.setState(state); 3085 start.copyBounds(compoundRect); 3086 start.setCallback(this); 3087 dr.mDrawableSizeStart = compoundRect.width(); 3088 dr.mDrawableHeightStart = compoundRect.height(); 3089 } else { 3090 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3091 } 3092 3093 if (end != null) { 3094 end.setState(state); 3095 end.copyBounds(compoundRect); 3096 end.setCallback(this); 3097 dr.mDrawableSizeEnd = compoundRect.width(); 3098 dr.mDrawableHeightEnd = compoundRect.height(); 3099 } else { 3100 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3101 } 3102 3103 if (top != null) { 3104 top.setState(state); 3105 top.copyBounds(compoundRect); 3106 top.setCallback(this); 3107 dr.mDrawableSizeTop = compoundRect.height(); 3108 dr.mDrawableWidthTop = compoundRect.width(); 3109 } else { 3110 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3111 } 3112 3113 if (bottom != null) { 3114 bottom.setState(state); 3115 bottom.copyBounds(compoundRect); 3116 bottom.setCallback(this); 3117 dr.mDrawableSizeBottom = compoundRect.height(); 3118 dr.mDrawableWidthBottom = compoundRect.width(); 3119 } else { 3120 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3121 } 3122 } 3123 3124 resetResolvedDrawables(); 3125 resolveDrawables(); 3126 invalidate(); 3127 requestLayout(); 3128 } 3129 3130 /** 3131 * Sets the Drawables (if any) to appear to the start of, above, to the end 3132 * of, and below the text. Use 0 if you do not want a Drawable there. The 3133 * Drawables' bounds will be set to their intrinsic bounds. 3134 * <p> 3135 * Calling this method will overwrite any Drawables previously set using 3136 * {@link #setCompoundDrawables} or related methods. 3137 * 3138 * @param start Resource identifier of the start Drawable. 3139 * @param top Resource identifier of the top Drawable. 3140 * @param end Resource identifier of the end Drawable. 3141 * @param bottom Resource identifier of the bottom Drawable. 3142 * 3143 * @attr ref android.R.styleable#TextView_drawableStart 3144 * @attr ref android.R.styleable#TextView_drawableTop 3145 * @attr ref android.R.styleable#TextView_drawableEnd 3146 * @attr ref android.R.styleable#TextView_drawableBottom 3147 */ 3148 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3149 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3150 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3151 final Context context = getContext(); 3152 setCompoundDrawablesRelativeWithIntrinsicBounds( 3153 start != 0 ? context.getDrawable(start) : null, 3154 top != 0 ? context.getDrawable(top) : null, 3155 end != 0 ? context.getDrawable(end) : null, 3156 bottom != 0 ? context.getDrawable(bottom) : null); 3157 } 3158 3159 /** 3160 * Sets the Drawables (if any) to appear to the start of, above, to the end 3161 * of, and below the text. Use {@code null} if you do not want a Drawable 3162 * there. The Drawables' bounds will be set to their intrinsic bounds. 3163 * <p> 3164 * Calling this method will overwrite any Drawables previously set using 3165 * {@link #setCompoundDrawables} or related methods. 3166 * 3167 * @attr ref android.R.styleable#TextView_drawableStart 3168 * @attr ref android.R.styleable#TextView_drawableTop 3169 * @attr ref android.R.styleable#TextView_drawableEnd 3170 * @attr ref android.R.styleable#TextView_drawableBottom 3171 */ 3172 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3173 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3174 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3175 3176 if (start != null) { 3177 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3178 } 3179 if (end != null) { 3180 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3181 } 3182 if (top != null) { 3183 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3184 } 3185 if (bottom != null) { 3186 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3187 } 3188 setCompoundDrawablesRelative(start, top, end, bottom); 3189 } 3190 3191 /** 3192 * Returns drawables for the left, top, right, and bottom borders. 3193 * 3194 * @attr ref android.R.styleable#TextView_drawableLeft 3195 * @attr ref android.R.styleable#TextView_drawableTop 3196 * @attr ref android.R.styleable#TextView_drawableRight 3197 * @attr ref android.R.styleable#TextView_drawableBottom 3198 */ 3199 @NonNull getCompoundDrawables()3200 public Drawable[] getCompoundDrawables() { 3201 final Drawables dr = mDrawables; 3202 if (dr != null) { 3203 return dr.mShowing.clone(); 3204 } else { 3205 return new Drawable[] { null, null, null, null }; 3206 } 3207 } 3208 3209 /** 3210 * Returns drawables for the start, top, end, and bottom borders. 3211 * 3212 * @attr ref android.R.styleable#TextView_drawableStart 3213 * @attr ref android.R.styleable#TextView_drawableTop 3214 * @attr ref android.R.styleable#TextView_drawableEnd 3215 * @attr ref android.R.styleable#TextView_drawableBottom 3216 */ 3217 @NonNull getCompoundDrawablesRelative()3218 public Drawable[] getCompoundDrawablesRelative() { 3219 final Drawables dr = mDrawables; 3220 if (dr != null) { 3221 return new Drawable[] { 3222 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3223 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3224 }; 3225 } else { 3226 return new Drawable[] { null, null, null, null }; 3227 } 3228 } 3229 3230 /** 3231 * Sets the size of the padding between the compound drawables and 3232 * the text. 3233 * 3234 * @attr ref android.R.styleable#TextView_drawablePadding 3235 */ 3236 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3237 public void setCompoundDrawablePadding(int pad) { 3238 Drawables dr = mDrawables; 3239 if (pad == 0) { 3240 if (dr != null) { 3241 dr.mDrawablePadding = pad; 3242 } 3243 } else { 3244 if (dr == null) { 3245 mDrawables = dr = new Drawables(getContext()); 3246 } 3247 dr.mDrawablePadding = pad; 3248 } 3249 3250 invalidate(); 3251 requestLayout(); 3252 } 3253 3254 /** 3255 * Returns the padding between the compound drawables and the text. 3256 * 3257 * @attr ref android.R.styleable#TextView_drawablePadding 3258 */ 3259 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3260 public int getCompoundDrawablePadding() { 3261 final Drawables dr = mDrawables; 3262 return dr != null ? dr.mDrawablePadding : 0; 3263 } 3264 3265 /** 3266 * Applies a tint to the compound drawables. Does not modify the 3267 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 3268 * <p> 3269 * Subsequent calls to 3270 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3271 * and related methods will automatically mutate the drawables and apply 3272 * the specified tint and tint mode using 3273 * {@link Drawable#setTintList(ColorStateList)}. 3274 * 3275 * @param tint the tint to apply, may be {@code null} to clear tint 3276 * 3277 * @attr ref android.R.styleable#TextView_drawableTint 3278 * @see #getCompoundDrawableTintList() 3279 * @see Drawable#setTintList(ColorStateList) 3280 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3281 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3282 if (mDrawables == null) { 3283 mDrawables = new Drawables(getContext()); 3284 } 3285 mDrawables.mTintList = tint; 3286 mDrawables.mHasTint = true; 3287 3288 applyCompoundDrawableTint(); 3289 } 3290 3291 /** 3292 * @return the tint applied to the compound drawables 3293 * @attr ref android.R.styleable#TextView_drawableTint 3294 * @see #setCompoundDrawableTintList(ColorStateList) 3295 */ 3296 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3297 public ColorStateList getCompoundDrawableTintList() { 3298 return mDrawables != null ? mDrawables.mTintList : null; 3299 } 3300 3301 /** 3302 * Specifies the blending mode used to apply the tint specified by 3303 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3304 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3305 * 3306 * @param tintMode the blending mode used to apply the tint, may be 3307 * {@code null} to clear tint 3308 * @attr ref android.R.styleable#TextView_drawableTintMode 3309 * @see #setCompoundDrawableTintList(ColorStateList) 3310 * @see Drawable#setTintMode(PorterDuff.Mode) 3311 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3312 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3313 setCompoundDrawableTintBlendMode(tintMode != null 3314 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3315 } 3316 3317 /** 3318 * Specifies the blending mode used to apply the tint specified by 3319 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3320 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3321 * 3322 * @param blendMode the blending mode used to apply the tint, may be 3323 * {@code null} to clear tint 3324 * @attr ref android.R.styleable#TextView_drawableTintMode 3325 * @see #setCompoundDrawableTintList(ColorStateList) 3326 * @see Drawable#setTintBlendMode(BlendMode) 3327 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3328 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3329 if (mDrawables == null) { 3330 mDrawables = new Drawables(getContext()); 3331 } 3332 mDrawables.mBlendMode = blendMode; 3333 mDrawables.mHasTintMode = true; 3334 3335 applyCompoundDrawableTint(); 3336 } 3337 3338 /** 3339 * Returns the blending mode used to apply the tint to the compound 3340 * drawables, if specified. 3341 * 3342 * @return the blending mode used to apply the tint to the compound 3343 * drawables 3344 * @attr ref android.R.styleable#TextView_drawableTintMode 3345 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3346 * 3347 */ 3348 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3349 public PorterDuff.Mode getCompoundDrawableTintMode() { 3350 BlendMode mode = getCompoundDrawableTintBlendMode(); 3351 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3352 } 3353 3354 /** 3355 * Returns the blending mode used to apply the tint to the compound 3356 * drawables, if specified. 3357 * 3358 * @return the blending mode used to apply the tint to the compound 3359 * drawables 3360 * @attr ref android.R.styleable#TextView_drawableTintMode 3361 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3362 */ 3363 @InspectableProperty(name = "drawableBlendMode", 3364 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3365 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3366 return mDrawables != null ? mDrawables.mBlendMode : null; 3367 } 3368 applyCompoundDrawableTint()3369 private void applyCompoundDrawableTint() { 3370 if (mDrawables == null) { 3371 return; 3372 } 3373 3374 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3375 final ColorStateList tintList = mDrawables.mTintList; 3376 final BlendMode blendMode = mDrawables.mBlendMode; 3377 final boolean hasTint = mDrawables.mHasTint; 3378 final boolean hasTintMode = mDrawables.mHasTintMode; 3379 final int[] state = getDrawableState(); 3380 3381 for (Drawable dr : mDrawables.mShowing) { 3382 if (dr == null) { 3383 continue; 3384 } 3385 3386 if (dr == mDrawables.mDrawableError) { 3387 // From a developer's perspective, the error drawable isn't 3388 // a compound drawable. Don't apply the generic compound 3389 // drawable tint to it. 3390 continue; 3391 } 3392 3393 dr.mutate(); 3394 3395 if (hasTint) { 3396 dr.setTintList(tintList); 3397 } 3398 3399 if (hasTintMode) { 3400 dr.setTintBlendMode(blendMode); 3401 } 3402 3403 // The drawable (or one of its children) may not have been 3404 // stateful before applying the tint, so let's try again. 3405 if (dr.isStateful()) { 3406 dr.setState(state); 3407 } 3408 } 3409 } 3410 } 3411 3412 /** 3413 * @inheritDoc 3414 * 3415 * @see #setFirstBaselineToTopHeight(int) 3416 * @see #setLastBaselineToBottomHeight(int) 3417 */ 3418 @Override setPadding(int left, int top, int right, int bottom)3419 public void setPadding(int left, int top, int right, int bottom) { 3420 if (left != mPaddingLeft 3421 || right != mPaddingRight 3422 || top != mPaddingTop 3423 || bottom != mPaddingBottom) { 3424 nullLayouts(); 3425 } 3426 3427 // the super call will requestLayout() 3428 super.setPadding(left, top, right, bottom); 3429 invalidate(); 3430 } 3431 3432 /** 3433 * @inheritDoc 3434 * 3435 * @see #setFirstBaselineToTopHeight(int) 3436 * @see #setLastBaselineToBottomHeight(int) 3437 */ 3438 @Override setPaddingRelative(int start, int top, int end, int bottom)3439 public void setPaddingRelative(int start, int top, int end, int bottom) { 3440 if (start != getPaddingStart() 3441 || end != getPaddingEnd() 3442 || top != mPaddingTop 3443 || bottom != mPaddingBottom) { 3444 nullLayouts(); 3445 } 3446 3447 // the super call will requestLayout() 3448 super.setPaddingRelative(start, top, end, bottom); 3449 invalidate(); 3450 } 3451 3452 /** 3453 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3454 * the distance between the top of the TextView and first line's baseline. 3455 * <p> 3456 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3457 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3458 * 3459 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3460 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3461 * Moreover since this function sets the top padding, if the height of the TextView is less than 3462 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3463 * down and bottom will be clipped. 3464 * 3465 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3466 * in pixels 3467 * 3468 * @see #getFirstBaselineToTopHeight() 3469 * @see #setLastBaselineToBottomHeight(int) 3470 * @see #setPadding(int, int, int, int) 3471 * @see #setPaddingRelative(int, int, int, int) 3472 * 3473 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3474 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3475 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3476 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3477 3478 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3479 final int fontMetricsTop; 3480 if (getIncludeFontPadding()) { 3481 fontMetricsTop = fontMetrics.top; 3482 } else { 3483 fontMetricsTop = fontMetrics.ascent; 3484 } 3485 3486 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3487 // in settings). At the moment, we don't. 3488 3489 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3490 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3491 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3492 } 3493 } 3494 3495 /** 3496 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3497 * the distance between the bottom of the TextView and the last line's baseline. 3498 * <p> 3499 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3500 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3501 * 3502 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3503 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3504 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3505 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3506 * clipped. 3507 * 3508 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3509 * in pixels 3510 * 3511 * @see #getLastBaselineToBottomHeight() 3512 * @see #setFirstBaselineToTopHeight(int) 3513 * @see #setPadding(int, int, int, int) 3514 * @see #setPaddingRelative(int, int, int, int) 3515 * 3516 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3517 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3518 public void setLastBaselineToBottomHeight( 3519 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3520 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3521 3522 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3523 final int fontMetricsBottom; 3524 if (getIncludeFontPadding()) { 3525 fontMetricsBottom = fontMetrics.bottom; 3526 } else { 3527 fontMetricsBottom = fontMetrics.descent; 3528 } 3529 3530 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3531 // in settings). At the moment, we don't. 3532 3533 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3534 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3535 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3536 } 3537 } 3538 3539 /** 3540 * Returns the distance between the first text baseline and the top of this TextView. 3541 * 3542 * @see #setFirstBaselineToTopHeight(int) 3543 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3544 */ 3545 @InspectableProperty getFirstBaselineToTopHeight()3546 public int getFirstBaselineToTopHeight() { 3547 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3548 } 3549 3550 /** 3551 * Returns the distance between the last text baseline and the bottom of this TextView. 3552 * 3553 * @see #setLastBaselineToBottomHeight(int) 3554 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3555 */ 3556 @InspectableProperty getLastBaselineToBottomHeight()3557 public int getLastBaselineToBottomHeight() { 3558 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3559 } 3560 3561 /** 3562 * Gets the autolink mask of the text. 3563 * 3564 * See {@link Linkify#ALL} and peers for possible values. 3565 * 3566 * @attr ref android.R.styleable#TextView_autoLink 3567 */ 3568 @InspectableProperty(name = "autoLink", flagMapping = { 3569 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3570 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3571 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3572 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3573 }) getAutoLinkMask()3574 public final int getAutoLinkMask() { 3575 return mAutoLinkMask; 3576 } 3577 3578 /** 3579 * Sets the Drawable corresponding to the selection handle used for 3580 * positioning the cursor within text. The Drawable defaults to the value 3581 * of the textSelectHandle attribute. 3582 * Note that any change applied to the handle Drawable will not be visible 3583 * until the handle is hidden and then drawn again. 3584 * 3585 * @see #setTextSelectHandle(int) 3586 * @attr ref android.R.styleable#TextView_textSelectHandle 3587 */ 3588 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3589 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3590 Preconditions.checkNotNull(textSelectHandle, 3591 "The text select handle should not be null."); 3592 mTextSelectHandle = textSelectHandle; 3593 mTextSelectHandleRes = 0; 3594 if (mEditor != null) { 3595 mEditor.loadHandleDrawables(true /* overwrite */); 3596 } 3597 } 3598 3599 /** 3600 * Sets the Drawable corresponding to the selection handle used for 3601 * positioning the cursor within text. The Drawable defaults to the value 3602 * of the textSelectHandle attribute. 3603 * Note that any change applied to the handle Drawable will not be visible 3604 * until the handle is hidden and then drawn again. 3605 * 3606 * @see #setTextSelectHandle(Drawable) 3607 * @attr ref android.R.styleable#TextView_textSelectHandle 3608 */ 3609 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3610 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3611 Preconditions.checkArgument(textSelectHandle != 0, 3612 "The text select handle should be a valid drawable resource id."); 3613 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3614 } 3615 3616 /** 3617 * Returns the Drawable corresponding to the selection handle used 3618 * for positioning the cursor within text. 3619 * Note that any change applied to the handle Drawable will not be visible 3620 * until the handle is hidden and then drawn again. 3621 * 3622 * @return the text select handle drawable 3623 * 3624 * @see #setTextSelectHandle(Drawable) 3625 * @see #setTextSelectHandle(int) 3626 * @attr ref android.R.styleable#TextView_textSelectHandle 3627 */ getTextSelectHandle()3628 @Nullable public Drawable getTextSelectHandle() { 3629 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3630 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3631 } 3632 return mTextSelectHandle; 3633 } 3634 3635 /** 3636 * Sets the Drawable corresponding to the left handle used 3637 * for selecting text. The Drawable defaults to the value of the 3638 * textSelectHandleLeft attribute. 3639 * Note that any change applied to the handle Drawable will not be visible 3640 * until the handle is hidden and then drawn again. 3641 * 3642 * @see #setTextSelectHandleLeft(int) 3643 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3644 */ 3645 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3646 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3647 Preconditions.checkNotNull(textSelectHandleLeft, 3648 "The left text select handle should not be null."); 3649 mTextSelectHandleLeft = textSelectHandleLeft; 3650 mTextSelectHandleLeftRes = 0; 3651 if (mEditor != null) { 3652 mEditor.loadHandleDrawables(true /* overwrite */); 3653 } 3654 } 3655 3656 /** 3657 * Sets the Drawable corresponding to the left handle used 3658 * for selecting text. The Drawable defaults to the value of the 3659 * textSelectHandleLeft attribute. 3660 * Note that any change applied to the handle Drawable will not be visible 3661 * until the handle is hidden and then drawn again. 3662 * 3663 * @see #setTextSelectHandleLeft(Drawable) 3664 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3665 */ 3666 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3667 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3668 Preconditions.checkArgument(textSelectHandleLeft != 0, 3669 "The text select left handle should be a valid drawable resource id."); 3670 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3671 } 3672 3673 /** 3674 * Returns the Drawable corresponding to the left handle used 3675 * for selecting text. 3676 * Note that any change applied to the handle Drawable will not be visible 3677 * until the handle is hidden and then drawn again. 3678 * 3679 * @return the left text selection handle drawable 3680 * 3681 * @see #setTextSelectHandleLeft(Drawable) 3682 * @see #setTextSelectHandleLeft(int) 3683 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3684 */ getTextSelectHandleLeft()3685 @Nullable public Drawable getTextSelectHandleLeft() { 3686 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3687 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3688 } 3689 return mTextSelectHandleLeft; 3690 } 3691 3692 /** 3693 * Sets the Drawable corresponding to the right handle used 3694 * for selecting text. The Drawable defaults to the value of the 3695 * textSelectHandleRight attribute. 3696 * Note that any change applied to the handle Drawable will not be visible 3697 * until the handle is hidden and then drawn again. 3698 * 3699 * @see #setTextSelectHandleRight(int) 3700 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3701 */ 3702 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3703 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3704 Preconditions.checkNotNull(textSelectHandleRight, 3705 "The right text select handle should not be null."); 3706 mTextSelectHandleRight = textSelectHandleRight; 3707 mTextSelectHandleRightRes = 0; 3708 if (mEditor != null) { 3709 mEditor.loadHandleDrawables(true /* overwrite */); 3710 } 3711 } 3712 3713 /** 3714 * Sets the Drawable corresponding to the right handle used 3715 * for selecting text. The Drawable defaults to the value of the 3716 * textSelectHandleRight attribute. 3717 * Note that any change applied to the handle Drawable will not be visible 3718 * until the handle is hidden and then drawn again. 3719 * 3720 * @see #setTextSelectHandleRight(Drawable) 3721 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3722 */ 3723 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3724 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3725 Preconditions.checkArgument(textSelectHandleRight != 0, 3726 "The text select right handle should be a valid drawable resource id."); 3727 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3728 } 3729 3730 /** 3731 * Returns the Drawable corresponding to the right handle used 3732 * for selecting text. 3733 * Note that any change applied to the handle Drawable will not be visible 3734 * until the handle is hidden and then drawn again. 3735 * 3736 * @return the right text selection handle drawable 3737 * 3738 * @see #setTextSelectHandleRight(Drawable) 3739 * @see #setTextSelectHandleRight(int) 3740 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3741 */ getTextSelectHandleRight()3742 @Nullable public Drawable getTextSelectHandleRight() { 3743 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3744 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3745 } 3746 return mTextSelectHandleRight; 3747 } 3748 3749 /** 3750 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3751 * value of the textCursorDrawable attribute. 3752 * Note that any change applied to the cursor Drawable will not be visible 3753 * until the cursor is hidden and then drawn again. 3754 * 3755 * @see #setTextCursorDrawable(int) 3756 * @attr ref android.R.styleable#TextView_textCursorDrawable 3757 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3758 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3759 mCursorDrawable = textCursorDrawable; 3760 mCursorDrawableRes = 0; 3761 if (mEditor != null) { 3762 mEditor.loadCursorDrawable(); 3763 } 3764 } 3765 3766 /** 3767 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3768 * value of the textCursorDrawable attribute. 3769 * Note that any change applied to the cursor Drawable will not be visible 3770 * until the cursor is hidden and then drawn again. 3771 * 3772 * @see #setTextCursorDrawable(Drawable) 3773 * @attr ref android.R.styleable#TextView_textCursorDrawable 3774 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3775 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3776 setTextCursorDrawable( 3777 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3778 } 3779 3780 /** 3781 * Returns the Drawable corresponding to the text cursor. 3782 * Note that any change applied to the cursor Drawable will not be visible 3783 * until the cursor is hidden and then drawn again. 3784 * 3785 * @return the text cursor drawable 3786 * 3787 * @see #setTextCursorDrawable(Drawable) 3788 * @see #setTextCursorDrawable(int) 3789 * @attr ref android.R.styleable#TextView_textCursorDrawable 3790 */ getTextCursorDrawable()3791 @Nullable public Drawable getTextCursorDrawable() { 3792 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3793 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3794 } 3795 return mCursorDrawable; 3796 } 3797 3798 /** 3799 * Sets the text appearance from the specified style resource. 3800 * <p> 3801 * Use a framework-defined {@code TextAppearance} style like 3802 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3803 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3804 * set of attributes that can be used in a custom style. 3805 * 3806 * @param resId the resource identifier of the style to apply 3807 * @attr ref android.R.styleable#TextView_textAppearance 3808 */ 3809 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3810 public void setTextAppearance(@StyleRes int resId) { 3811 setTextAppearance(mContext, resId); 3812 } 3813 3814 /** 3815 * Sets the text color, size, style, hint color, and highlight color 3816 * from the specified TextAppearance resource. 3817 * 3818 * @deprecated Use {@link #setTextAppearance(int)} instead. 3819 */ 3820 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3821 public void setTextAppearance(Context context, @StyleRes int resId) { 3822 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3823 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3824 readTextAppearance(context, ta, attributes, false /* styleArray */); 3825 ta.recycle(); 3826 applyTextAppearance(attributes); 3827 } 3828 3829 /** 3830 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3831 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3832 */ 3833 private static class TextAppearanceAttributes { 3834 int mTextColorHighlight = 0; 3835 ColorStateList mTextColor = null; 3836 ColorStateList mTextColorHint = null; 3837 ColorStateList mTextColorLink = null; 3838 int mTextSize = -1; 3839 LocaleList mTextLocales = null; 3840 String mFontFamily = null; 3841 Typeface mFontTypeface = null; 3842 boolean mFontFamilyExplicit = false; 3843 int mTypefaceIndex = -1; 3844 int mTextStyle = 0; 3845 int mFontWeight = -1; 3846 boolean mAllCaps = false; 3847 int mShadowColor = 0; 3848 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3849 boolean mHasElegant = false; 3850 boolean mElegant = false; 3851 boolean mHasFallbackLineSpacing = false; 3852 boolean mFallbackLineSpacing = false; 3853 boolean mHasLetterSpacing = false; 3854 float mLetterSpacing = 0; 3855 String mFontFeatureSettings = null; 3856 String mFontVariationSettings = null; 3857 3858 @Override toString()3859 public String toString() { 3860 return "TextAppearanceAttributes {\n" 3861 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 3862 + " mTextColor:" + mTextColor + "\n" 3863 + " mTextColorHint:" + mTextColorHint + "\n" 3864 + " mTextColorLink:" + mTextColorLink + "\n" 3865 + " mTextSize:" + mTextSize + "\n" 3866 + " mTextLocales:" + mTextLocales + "\n" 3867 + " mFontFamily:" + mFontFamily + "\n" 3868 + " mFontTypeface:" + mFontTypeface + "\n" 3869 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 3870 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 3871 + " mTextStyle:" + mTextStyle + "\n" 3872 + " mFontWeight:" + mFontWeight + "\n" 3873 + " mAllCaps:" + mAllCaps + "\n" 3874 + " mShadowColor:" + mShadowColor + "\n" 3875 + " mShadowDx:" + mShadowDx + "\n" 3876 + " mShadowDy:" + mShadowDy + "\n" 3877 + " mShadowRadius:" + mShadowRadius + "\n" 3878 + " mHasElegant:" + mHasElegant + "\n" 3879 + " mElegant:" + mElegant + "\n" 3880 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 3881 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 3882 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 3883 + " mLetterSpacing:" + mLetterSpacing + "\n" 3884 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 3885 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 3886 + "}"; 3887 } 3888 } 3889 3890 // Maps styleable attributes that exist both in TextView style and TextAppearance. 3891 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 3892 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3893 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 3894 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3895 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 3896 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3897 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 3898 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3899 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 3900 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3901 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 3902 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3903 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 3904 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3905 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 3906 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3907 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 3908 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3909 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 3910 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3911 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 3912 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3913 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 3914 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3915 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 3916 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3917 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 3918 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3919 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 3920 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3921 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 3922 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3923 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 3924 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3925 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 3926 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3927 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 3928 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3929 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 3930 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3931 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 3932 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 3933 } 3934 3935 /** 3936 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 3937 * set. If the TypedArray contains a value that was already set in the given attributes, that 3938 * will be overridden. 3939 * 3940 * @param context The Context to be used 3941 * @param appearance The TypedArray to read properties from 3942 * @param attributes the TextAppearanceAttributes to fill in 3943 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 3944 * what attribute indexes will be used to read the properties. 3945 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3946 private void readTextAppearance(Context context, TypedArray appearance, 3947 TextAppearanceAttributes attributes, boolean styleArray) { 3948 final int n = appearance.getIndexCount(); 3949 for (int i = 0; i < n; i++) { 3950 final int attr = appearance.getIndex(i); 3951 int index = attr; 3952 // Translate style array index ids to TextAppearance ids. 3953 if (styleArray) { 3954 index = sAppearanceValues.get(attr, -1); 3955 if (index == -1) { 3956 // This value is not part of a Text Appearance and should be ignored. 3957 continue; 3958 } 3959 } 3960 switch (index) { 3961 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 3962 attributes.mTextColorHighlight = 3963 appearance.getColor(attr, attributes.mTextColorHighlight); 3964 break; 3965 case com.android.internal.R.styleable.TextAppearance_textColor: 3966 attributes.mTextColor = appearance.getColorStateList(attr); 3967 break; 3968 case com.android.internal.R.styleable.TextAppearance_textColorHint: 3969 attributes.mTextColorHint = appearance.getColorStateList(attr); 3970 break; 3971 case com.android.internal.R.styleable.TextAppearance_textColorLink: 3972 attributes.mTextColorLink = appearance.getColorStateList(attr); 3973 break; 3974 case com.android.internal.R.styleable.TextAppearance_textSize: 3975 attributes.mTextSize = 3976 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 3977 break; 3978 case com.android.internal.R.styleable.TextAppearance_textLocale: 3979 final String localeString = appearance.getString(attr); 3980 if (localeString != null) { 3981 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 3982 if (!localeList.isEmpty()) { 3983 attributes.mTextLocales = localeList; 3984 } 3985 } 3986 break; 3987 case com.android.internal.R.styleable.TextAppearance_typeface: 3988 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 3989 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 3990 attributes.mFontFamily = null; 3991 } 3992 break; 3993 case com.android.internal.R.styleable.TextAppearance_fontFamily: 3994 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 3995 try { 3996 attributes.mFontTypeface = appearance.getFont(attr); 3997 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 3998 // Expected if it is not a font resource. 3999 } 4000 } 4001 if (attributes.mFontTypeface == null) { 4002 attributes.mFontFamily = appearance.getString(attr); 4003 } 4004 attributes.mFontFamilyExplicit = true; 4005 break; 4006 case com.android.internal.R.styleable.TextAppearance_textStyle: 4007 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4008 break; 4009 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4010 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4011 break; 4012 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4013 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4014 break; 4015 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4016 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4017 break; 4018 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4019 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4020 break; 4021 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4022 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4023 break; 4024 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4025 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4026 break; 4027 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4028 attributes.mHasElegant = true; 4029 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4030 break; 4031 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4032 attributes.mHasFallbackLineSpacing = true; 4033 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4034 attributes.mFallbackLineSpacing); 4035 break; 4036 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4037 attributes.mHasLetterSpacing = true; 4038 attributes.mLetterSpacing = 4039 appearance.getFloat(attr, attributes.mLetterSpacing); 4040 break; 4041 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4042 attributes.mFontFeatureSettings = appearance.getString(attr); 4043 break; 4044 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4045 attributes.mFontVariationSettings = appearance.getString(attr); 4046 break; 4047 default: 4048 } 4049 } 4050 } 4051 applyTextAppearance(TextAppearanceAttributes attributes)4052 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4053 if (attributes.mTextColor != null) { 4054 setTextColor(attributes.mTextColor); 4055 } 4056 4057 if (attributes.mTextColorHint != null) { 4058 setHintTextColor(attributes.mTextColorHint); 4059 } 4060 4061 if (attributes.mTextColorLink != null) { 4062 setLinkTextColor(attributes.mTextColorLink); 4063 } 4064 4065 if (attributes.mTextColorHighlight != 0) { 4066 setHighlightColor(attributes.mTextColorHighlight); 4067 } 4068 4069 if (attributes.mTextSize != -1) { 4070 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4071 } 4072 4073 if (attributes.mTextLocales != null) { 4074 setTextLocales(attributes.mTextLocales); 4075 } 4076 4077 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4078 attributes.mFontFamily = null; 4079 } 4080 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4081 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4082 4083 if (attributes.mShadowColor != 0) { 4084 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4085 attributes.mShadowColor); 4086 } 4087 4088 if (attributes.mAllCaps) { 4089 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4090 } 4091 4092 if (attributes.mHasElegant) { 4093 setElegantTextHeight(attributes.mElegant); 4094 } 4095 4096 if (attributes.mHasFallbackLineSpacing) { 4097 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4098 } 4099 4100 if (attributes.mHasLetterSpacing) { 4101 setLetterSpacing(attributes.mLetterSpacing); 4102 } 4103 4104 if (attributes.mFontFeatureSettings != null) { 4105 setFontFeatureSettings(attributes.mFontFeatureSettings); 4106 } 4107 4108 if (attributes.mFontVariationSettings != null) { 4109 setFontVariationSettings(attributes.mFontVariationSettings); 4110 } 4111 } 4112 4113 /** 4114 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4115 * the first member of {@link #getTextLocales()}. 4116 * @return the default primary {@link Locale} of the text in this TextView. 4117 */ 4118 @NonNull getTextLocale()4119 public Locale getTextLocale() { 4120 return mTextPaint.getTextLocale(); 4121 } 4122 4123 /** 4124 * Get the default {@link LocaleList} of the text in this TextView. 4125 * @return the default {@link LocaleList} of the text in this TextView. 4126 */ 4127 @NonNull @Size(min = 1) getTextLocales()4128 public LocaleList getTextLocales() { 4129 return mTextPaint.getTextLocales(); 4130 } 4131 changeListenerLocaleTo(@ullable Locale locale)4132 private void changeListenerLocaleTo(@Nullable Locale locale) { 4133 if (mListenerChanged) { 4134 // If a listener has been explicitly set, don't change it. We may break something. 4135 return; 4136 } 4137 // The following null check is not absolutely necessary since all calling points of 4138 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4139 // here in case others would want to call this method in the future. 4140 if (mEditor != null) { 4141 KeyListener listener = mEditor.mKeyListener; 4142 if (listener instanceof DigitsKeyListener) { 4143 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4144 } else if (listener instanceof DateKeyListener) { 4145 listener = DateKeyListener.getInstance(locale); 4146 } else if (listener instanceof TimeKeyListener) { 4147 listener = TimeKeyListener.getInstance(locale); 4148 } else if (listener instanceof DateTimeKeyListener) { 4149 listener = DateTimeKeyListener.getInstance(locale); 4150 } else { 4151 return; 4152 } 4153 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4154 setKeyListenerOnly(listener); 4155 setInputTypeFromEditor(); 4156 if (wasPasswordType) { 4157 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4158 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4159 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4160 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4161 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4162 } 4163 } 4164 } 4165 } 4166 4167 /** 4168 * Set the default {@link Locale} of the text in this TextView to a one-member 4169 * {@link LocaleList} containing just the given Locale. 4170 * 4171 * @param locale the {@link Locale} for drawing text, must not be null. 4172 * 4173 * @see #setTextLocales 4174 */ setTextLocale(@onNull Locale locale)4175 public void setTextLocale(@NonNull Locale locale) { 4176 mLocalesChanged = true; 4177 mTextPaint.setTextLocale(locale); 4178 if (mLayout != null) { 4179 nullLayouts(); 4180 requestLayout(); 4181 invalidate(); 4182 } 4183 } 4184 4185 /** 4186 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4187 * 4188 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4189 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4190 * other aspects of text display, including line breaking. 4191 * 4192 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4193 * 4194 * @see Paint#setTextLocales 4195 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4196 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4197 mLocalesChanged = true; 4198 mTextPaint.setTextLocales(locales); 4199 if (mLayout != null) { 4200 nullLayouts(); 4201 requestLayout(); 4202 invalidate(); 4203 } 4204 } 4205 4206 @Override onConfigurationChanged(Configuration newConfig)4207 protected void onConfigurationChanged(Configuration newConfig) { 4208 super.onConfigurationChanged(newConfig); 4209 if (!mLocalesChanged) { 4210 mTextPaint.setTextLocales(LocaleList.getDefault()); 4211 if (mLayout != null) { 4212 nullLayouts(); 4213 requestLayout(); 4214 invalidate(); 4215 } 4216 } 4217 } 4218 4219 /** 4220 * @return the size (in pixels) of the default text size in this TextView. 4221 */ 4222 @InspectableProperty 4223 @ViewDebug.ExportedProperty(category = "text") getTextSize()4224 public float getTextSize() { 4225 return mTextPaint.getTextSize(); 4226 } 4227 4228 /** 4229 * @return the size (in scaled pixels) of the default text size in this TextView. 4230 * @hide 4231 */ 4232 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4233 public float getScaledTextSize() { 4234 return mTextPaint.getTextSize() / mTextPaint.density; 4235 } 4236 4237 /** @hide */ 4238 @ViewDebug.ExportedProperty(category = "text", mapping = { 4239 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4240 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4241 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4242 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4243 }) getTypefaceStyle()4244 public int getTypefaceStyle() { 4245 Typeface typeface = mTextPaint.getTypeface(); 4246 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4247 } 4248 4249 /** 4250 * Set the default text size to the given value, interpreted as "scaled 4251 * pixel" units. This size is adjusted based on the current density and 4252 * user font size preference. 4253 * 4254 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4255 * 4256 * @param size The scaled pixel size. 4257 * 4258 * @attr ref android.R.styleable#TextView_textSize 4259 */ 4260 @android.view.RemotableViewMethod setTextSize(float size)4261 public void setTextSize(float size) { 4262 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4263 } 4264 4265 /** 4266 * Set the default text size to a given unit and value. See {@link 4267 * TypedValue} for the possible dimension units. 4268 * 4269 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4270 * 4271 * @param unit The desired dimension unit. 4272 * @param size The desired size in the given units. 4273 * 4274 * @attr ref android.R.styleable#TextView_textSize 4275 */ setTextSize(int unit, float size)4276 public void setTextSize(int unit, float size) { 4277 if (!isAutoSizeEnabled()) { 4278 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4279 } 4280 } 4281 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4282 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4283 Context c = getContext(); 4284 Resources r; 4285 4286 if (c == null) { 4287 r = Resources.getSystem(); 4288 } else { 4289 r = c.getResources(); 4290 } 4291 4292 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4293 shouldRequestLayout); 4294 } 4295 4296 @UnsupportedAppUsage setRawTextSize(float size, boolean shouldRequestLayout)4297 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4298 if (size != mTextPaint.getTextSize()) { 4299 mTextPaint.setTextSize(size); 4300 4301 if (shouldRequestLayout && mLayout != null) { 4302 // Do not auto-size right after setting the text size. 4303 mNeedsAutoSizeText = false; 4304 nullLayouts(); 4305 requestLayout(); 4306 invalidate(); 4307 } 4308 } 4309 } 4310 4311 /** 4312 * Gets the extent by which text should be stretched horizontally. 4313 * This will usually be 1.0. 4314 * @return The horizontal scale factor. 4315 */ 4316 @InspectableProperty getTextScaleX()4317 public float getTextScaleX() { 4318 return mTextPaint.getTextScaleX(); 4319 } 4320 4321 /** 4322 * Sets the horizontal scale factor for text. The default value 4323 * is 1.0. Values greater than 1.0 stretch the text wider. 4324 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4325 * @param size The horizontal scale factor. 4326 * @attr ref android.R.styleable#TextView_textScaleX 4327 */ 4328 @android.view.RemotableViewMethod setTextScaleX(float size)4329 public void setTextScaleX(float size) { 4330 if (size != mTextPaint.getTextScaleX()) { 4331 mUserSetTextScaleX = true; 4332 mTextPaint.setTextScaleX(size); 4333 4334 if (mLayout != null) { 4335 nullLayouts(); 4336 requestLayout(); 4337 invalidate(); 4338 } 4339 } 4340 } 4341 4342 /** 4343 * Sets the typeface and style in which the text should be displayed. 4344 * Note that not all Typeface families actually have bold and italic 4345 * variants, so you may need to use 4346 * {@link #setTypeface(Typeface, int)} to get the appearance 4347 * that you actually want. 4348 * 4349 * @see #getTypeface() 4350 * 4351 * @attr ref android.R.styleable#TextView_fontFamily 4352 * @attr ref android.R.styleable#TextView_typeface 4353 * @attr ref android.R.styleable#TextView_textStyle 4354 */ setTypeface(@ullable Typeface tf)4355 public void setTypeface(@Nullable Typeface tf) { 4356 if (mTextPaint.getTypeface() != tf) { 4357 mTextPaint.setTypeface(tf); 4358 4359 if (mLayout != null) { 4360 nullLayouts(); 4361 requestLayout(); 4362 invalidate(); 4363 } 4364 } 4365 } 4366 4367 /** 4368 * Gets the current {@link Typeface} that is used to style the text. 4369 * @return The current Typeface. 4370 * 4371 * @see #setTypeface(Typeface) 4372 * 4373 * @attr ref android.R.styleable#TextView_fontFamily 4374 * @attr ref android.R.styleable#TextView_typeface 4375 * @attr ref android.R.styleable#TextView_textStyle 4376 */ 4377 @InspectableProperty getTypeface()4378 public Typeface getTypeface() { 4379 return mTextPaint.getTypeface(); 4380 } 4381 4382 /** 4383 * Set the TextView's elegant height metrics flag. This setting selects font 4384 * variants that have not been compacted to fit Latin-based vertical 4385 * metrics, and also increases top and bottom bounds to provide more space. 4386 * 4387 * @param elegant set the paint's elegant metrics flag. 4388 * 4389 * @see #isElegantTextHeight() 4390 * @see Paint#isElegantTextHeight() 4391 * 4392 * @attr ref android.R.styleable#TextView_elegantTextHeight 4393 */ setElegantTextHeight(boolean elegant)4394 public void setElegantTextHeight(boolean elegant) { 4395 if (elegant != mTextPaint.isElegantTextHeight()) { 4396 mTextPaint.setElegantTextHeight(elegant); 4397 if (mLayout != null) { 4398 nullLayouts(); 4399 requestLayout(); 4400 invalidate(); 4401 } 4402 } 4403 } 4404 4405 /** 4406 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4407 * displaying the text (which is needed to avoid text from consecutive lines running into 4408 * each other). If set, fallback fonts that end up getting used can increase the ascent 4409 * and descent of the lines that they are used on. 4410 * <p/> 4411 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4412 * is typically much taller or deeper than Latin text. 4413 * 4414 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4415 * 4416 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4417 * 4418 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4419 */ setFallbackLineSpacing(boolean enabled)4420 public void setFallbackLineSpacing(boolean enabled) { 4421 if (mUseFallbackLineSpacing != enabled) { 4422 mUseFallbackLineSpacing = enabled; 4423 if (mLayout != null) { 4424 nullLayouts(); 4425 requestLayout(); 4426 invalidate(); 4427 } 4428 } 4429 } 4430 4431 /** 4432 * @return whether fallback line spacing is enabled, {@code true} by default 4433 * 4434 * @see #setFallbackLineSpacing(boolean) 4435 * 4436 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4437 */ 4438 @InspectableProperty isFallbackLineSpacing()4439 public boolean isFallbackLineSpacing() { 4440 return mUseFallbackLineSpacing; 4441 } 4442 4443 /** 4444 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4445 * variants that have not been compacted to fit Latin-based vertical 4446 * metrics, and also increases top and bottom bounds to provide more space. 4447 * @return {@code true} if the elegant height metrics flag is set. 4448 * 4449 * @see #setElegantTextHeight(boolean) 4450 * @see Paint#setElegantTextHeight(boolean) 4451 */ 4452 @InspectableProperty isElegantTextHeight()4453 public boolean isElegantTextHeight() { 4454 return mTextPaint.isElegantTextHeight(); 4455 } 4456 4457 /** 4458 * Gets the text letter-space value, which determines the spacing between characters. 4459 * The value returned is in ems. Normally, this value is 0.0. 4460 * @return The text letter-space value in ems. 4461 * 4462 * @see #setLetterSpacing(float) 4463 * @see Paint#setLetterSpacing 4464 */ 4465 @InspectableProperty getLetterSpacing()4466 public float getLetterSpacing() { 4467 return mTextPaint.getLetterSpacing(); 4468 } 4469 4470 /** 4471 * Sets text letter-spacing in em units. Typical values 4472 * for slight expansion will be around 0.05. Negative values tighten text. 4473 * 4474 * @see #getLetterSpacing() 4475 * @see Paint#getLetterSpacing 4476 * 4477 * @param letterSpacing A text letter-space value in ems. 4478 * @attr ref android.R.styleable#TextView_letterSpacing 4479 */ 4480 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4481 public void setLetterSpacing(float letterSpacing) { 4482 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4483 mTextPaint.setLetterSpacing(letterSpacing); 4484 4485 if (mLayout != null) { 4486 nullLayouts(); 4487 requestLayout(); 4488 invalidate(); 4489 } 4490 } 4491 } 4492 4493 /** 4494 * Returns the font feature settings. The format is the same as the CSS 4495 * font-feature-settings attribute: 4496 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4497 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4498 * 4499 * @return the currently set font feature settings. Default is null. 4500 * 4501 * @see #setFontFeatureSettings(String) 4502 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4503 */ 4504 @InspectableProperty 4505 @Nullable getFontFeatureSettings()4506 public String getFontFeatureSettings() { 4507 return mTextPaint.getFontFeatureSettings(); 4508 } 4509 4510 /** 4511 * Returns the font variation settings. 4512 * 4513 * @return the currently set font variation settings. Returns null if no variation is 4514 * specified. 4515 * 4516 * @see #setFontVariationSettings(String) 4517 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4518 */ 4519 @Nullable getFontVariationSettings()4520 public String getFontVariationSettings() { 4521 return mTextPaint.getFontVariationSettings(); 4522 } 4523 4524 /** 4525 * Sets the break strategy for breaking paragraphs into lines. The default value for 4526 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4527 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4528 * text "dancing" when being edited. 4529 * <p/> 4530 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4531 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4532 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4533 * improves the structure of text layout however has performance impact and requires more time 4534 * to do the text layout. 4535 * 4536 * @attr ref android.R.styleable#TextView_breakStrategy 4537 * @see #getBreakStrategy() 4538 * @see #setHyphenationFrequency(int) 4539 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4540 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4541 mBreakStrategy = breakStrategy; 4542 if (mLayout != null) { 4543 nullLayouts(); 4544 requestLayout(); 4545 invalidate(); 4546 } 4547 } 4548 4549 /** 4550 * Gets the current strategy for breaking paragraphs into lines. 4551 * @return the current strategy for breaking paragraphs into lines. 4552 * 4553 * @attr ref android.R.styleable#TextView_breakStrategy 4554 * @see #setBreakStrategy(int) 4555 */ 4556 @InspectableProperty(enumMapping = { 4557 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4558 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4559 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4560 }) 4561 @Layout.BreakStrategy getBreakStrategy()4562 public int getBreakStrategy() { 4563 return mBreakStrategy; 4564 } 4565 4566 /** 4567 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4568 * The default value for both TextView and {@link EditText} is 4569 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4570 * is set from the theme. 4571 * <p/> 4572 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4573 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4574 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4575 * improves the structure of text layout however has performance impact and requires more time 4576 * to do the text layout. 4577 * <p/> 4578 * Note: Before Android Q, in the theme hyphenation frequency is set to 4579 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4580 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4581 * 4582 * @param hyphenationFrequency the hyphenation frequency to use, one of 4583 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4584 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4585 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4586 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4587 * @see #getHyphenationFrequency() 4588 * @see #getBreakStrategy() 4589 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4590 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4591 mHyphenationFrequency = hyphenationFrequency; 4592 if (mLayout != null) { 4593 nullLayouts(); 4594 requestLayout(); 4595 invalidate(); 4596 } 4597 } 4598 4599 /** 4600 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4601 * @return the current frequency of automatic hyphenation to be used when determining word 4602 * breaks. 4603 * 4604 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4605 * @see #setHyphenationFrequency(int) 4606 */ 4607 @InspectableProperty(enumMapping = { 4608 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4609 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4610 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4611 }) 4612 @Layout.HyphenationFrequency getHyphenationFrequency()4613 public int getHyphenationFrequency() { 4614 return mHyphenationFrequency; 4615 } 4616 4617 /** 4618 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4619 * 4620 * @return a current {@link PrecomputedText.Params} 4621 * @see PrecomputedText 4622 */ getTextMetricsParams()4623 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4624 return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), 4625 mBreakStrategy, mHyphenationFrequency); 4626 } 4627 4628 /** 4629 * Apply the text layout parameter. 4630 * 4631 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4632 * @see PrecomputedText 4633 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4634 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4635 mTextPaint.set(params.getTextPaint()); 4636 mUserSetTextScaleX = true; 4637 mTextDir = params.getTextDirection(); 4638 mBreakStrategy = params.getBreakStrategy(); 4639 mHyphenationFrequency = params.getHyphenationFrequency(); 4640 if (mLayout != null) { 4641 nullLayouts(); 4642 requestLayout(); 4643 invalidate(); 4644 } 4645 } 4646 4647 /** 4648 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4649 * last line is too short for justification, the last line will be displayed with the 4650 * alignment set by {@link android.view.View#setTextAlignment}. 4651 * 4652 * @see #getJustificationMode() 4653 */ 4654 @Layout.JustificationMode setJustificationMode(@ayout.JustificationMode int justificationMode)4655 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 4656 mJustificationMode = justificationMode; 4657 if (mLayout != null) { 4658 nullLayouts(); 4659 requestLayout(); 4660 invalidate(); 4661 } 4662 } 4663 4664 /** 4665 * @return true if currently paragraph justification mode. 4666 * 4667 * @see #setJustificationMode(int) 4668 */ 4669 @InspectableProperty(enumMapping = { 4670 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 4671 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 4672 }) getJustificationMode()4673 public @Layout.JustificationMode int getJustificationMode() { 4674 return mJustificationMode; 4675 } 4676 4677 /** 4678 * Sets font feature settings. The format is the same as the CSS 4679 * font-feature-settings attribute: 4680 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4681 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4682 * 4683 * @param fontFeatureSettings font feature settings represented as CSS compatible string 4684 * 4685 * @see #getFontFeatureSettings() 4686 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 4687 * 4688 * @attr ref android.R.styleable#TextView_fontFeatureSettings 4689 */ 4690 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)4691 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 4692 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 4693 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 4694 4695 if (mLayout != null) { 4696 nullLayouts(); 4697 requestLayout(); 4698 invalidate(); 4699 } 4700 } 4701 } 4702 4703 4704 /** 4705 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 4706 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 4707 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 4708 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 4709 * are invalid. If a specified axis name is not defined in the font, the settings will be 4710 * ignored. 4711 * 4712 * <p> 4713 * Examples, 4714 * <ul> 4715 * <li>Set font width to 150. 4716 * <pre> 4717 * <code> 4718 * TextView textView = (TextView) findViewById(R.id.textView); 4719 * textView.setFontVariationSettings("'wdth' 150"); 4720 * </code> 4721 * </pre> 4722 * </li> 4723 * 4724 * <li>Set the font slant to 20 degrees and ask for italic style. 4725 * <pre> 4726 * <code> 4727 * TextView textView = (TextView) findViewById(R.id.textView); 4728 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 4729 * </code> 4730 * </pre> 4731 * </p> 4732 * </li> 4733 * </ul> 4734 * 4735 * @param fontVariationSettings font variation settings. You can pass null or empty string as 4736 * no variation settings. 4737 * @return true if the given settings is effective to at least one font file underlying this 4738 * TextView. This function also returns true for empty settings string. Otherwise 4739 * returns false. 4740 * 4741 * @throws IllegalArgumentException If given string is not a valid font variation settings 4742 * format. 4743 * 4744 * @see #getFontVariationSettings() 4745 * @see FontVariationAxis 4746 * 4747 * @attr ref android.R.styleable#TextView_fontVariationSettings 4748 */ setFontVariationSettings(@ullable String fontVariationSettings)4749 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 4750 final String existingSettings = mTextPaint.getFontVariationSettings(); 4751 if (fontVariationSettings == existingSettings 4752 || (fontVariationSettings != null 4753 && fontVariationSettings.equals(existingSettings))) { 4754 return true; 4755 } 4756 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 4757 4758 if (effective && mLayout != null) { 4759 nullLayouts(); 4760 requestLayout(); 4761 invalidate(); 4762 } 4763 return effective; 4764 } 4765 4766 /** 4767 * Sets the text color for all the states (normal, selected, 4768 * focused) to be this color. 4769 * 4770 * @param color A color value in the form 0xAARRGGBB. 4771 * Do not pass a resource ID. To get a color value from a resource ID, call 4772 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 4773 * 4774 * @see #setTextColor(ColorStateList) 4775 * @see #getTextColors() 4776 * 4777 * @attr ref android.R.styleable#TextView_textColor 4778 */ 4779 @android.view.RemotableViewMethod setTextColor(@olorInt int color)4780 public void setTextColor(@ColorInt int color) { 4781 mTextColor = ColorStateList.valueOf(color); 4782 updateTextColors(); 4783 } 4784 4785 /** 4786 * Sets the text color. 4787 * 4788 * @see #setTextColor(int) 4789 * @see #getTextColors() 4790 * @see #setHintTextColor(ColorStateList) 4791 * @see #setLinkTextColor(ColorStateList) 4792 * 4793 * @attr ref android.R.styleable#TextView_textColor 4794 */ 4795 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)4796 public void setTextColor(ColorStateList colors) { 4797 if (colors == null) { 4798 throw new NullPointerException(); 4799 } 4800 4801 mTextColor = colors; 4802 updateTextColors(); 4803 } 4804 4805 /** 4806 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4807 * 4808 * @see #setTextColor(ColorStateList) 4809 * @see #setTextColor(int) 4810 * 4811 * @attr ref android.R.styleable#TextView_textColor 4812 */ 4813 @InspectableProperty(name = "textColor") getTextColors()4814 public final ColorStateList getTextColors() { 4815 return mTextColor; 4816 } 4817 4818 /** 4819 * Return the current color selected for normal text. 4820 * 4821 * @return Returns the current text color. 4822 */ 4823 @ColorInt getCurrentTextColor()4824 public final int getCurrentTextColor() { 4825 return mCurTextColor; 4826 } 4827 4828 /** 4829 * Sets the color used to display the selection highlight. 4830 * 4831 * @attr ref android.R.styleable#TextView_textColorHighlight 4832 */ 4833 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)4834 public void setHighlightColor(@ColorInt int color) { 4835 if (mHighlightColor != color) { 4836 mHighlightColor = color; 4837 invalidate(); 4838 } 4839 } 4840 4841 /** 4842 * @return the color used to display the selection highlight 4843 * 4844 * @see #setHighlightColor(int) 4845 * 4846 * @attr ref android.R.styleable#TextView_textColorHighlight 4847 */ 4848 @InspectableProperty(name = "textColorHighlight") 4849 @ColorInt getHighlightColor()4850 public int getHighlightColor() { 4851 return mHighlightColor; 4852 } 4853 4854 /** 4855 * Sets whether the soft input method will be made visible when this 4856 * TextView gets focused. The default is true. 4857 */ 4858 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)4859 public final void setShowSoftInputOnFocus(boolean show) { 4860 createEditorIfNeeded(); 4861 mEditor.mShowSoftInputOnFocus = show; 4862 } 4863 4864 /** 4865 * Returns whether the soft input method will be made visible when this 4866 * TextView gets focused. The default is true. 4867 */ getShowSoftInputOnFocus()4868 public final boolean getShowSoftInputOnFocus() { 4869 // When there is no Editor, return default true value 4870 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4871 } 4872 4873 /** 4874 * Gives the text a shadow of the specified blur radius and color, the specified 4875 * distance from its drawn position. 4876 * <p> 4877 * The text shadow produced does not interact with the properties on view 4878 * that are responsible for real time shadows, 4879 * {@link View#getElevation() elevation} and 4880 * {@link View#getTranslationZ() translationZ}. 4881 * 4882 * @see Paint#setShadowLayer(float, float, float, int) 4883 * 4884 * @attr ref android.R.styleable#TextView_shadowColor 4885 * @attr ref android.R.styleable#TextView_shadowDx 4886 * @attr ref android.R.styleable#TextView_shadowDy 4887 * @attr ref android.R.styleable#TextView_shadowRadius 4888 */ setShadowLayer(float radius, float dx, float dy, int color)4889 public void setShadowLayer(float radius, float dx, float dy, int color) { 4890 mTextPaint.setShadowLayer(radius, dx, dy, color); 4891 4892 mShadowRadius = radius; 4893 mShadowDx = dx; 4894 mShadowDy = dy; 4895 mShadowColor = color; 4896 4897 // Will change text clip region 4898 if (mEditor != null) { 4899 mEditor.invalidateTextDisplayList(); 4900 mEditor.invalidateHandlesAndActionMode(); 4901 } 4902 invalidate(); 4903 } 4904 4905 /** 4906 * Gets the radius of the shadow layer. 4907 * 4908 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 4909 * 4910 * @see #setShadowLayer(float, float, float, int) 4911 * 4912 * @attr ref android.R.styleable#TextView_shadowRadius 4913 */ 4914 @InspectableProperty getShadowRadius()4915 public float getShadowRadius() { 4916 return mShadowRadius; 4917 } 4918 4919 /** 4920 * @return the horizontal offset of the shadow layer 4921 * 4922 * @see #setShadowLayer(float, float, float, int) 4923 * 4924 * @attr ref android.R.styleable#TextView_shadowDx 4925 */ 4926 @InspectableProperty getShadowDx()4927 public float getShadowDx() { 4928 return mShadowDx; 4929 } 4930 4931 /** 4932 * Gets the vertical offset of the shadow layer. 4933 * @return The vertical offset of the shadow layer. 4934 * 4935 * @see #setShadowLayer(float, float, float, int) 4936 * 4937 * @attr ref android.R.styleable#TextView_shadowDy 4938 */ 4939 @InspectableProperty getShadowDy()4940 public float getShadowDy() { 4941 return mShadowDy; 4942 } 4943 4944 /** 4945 * Gets the color of the shadow layer. 4946 * @return the color of the shadow layer 4947 * 4948 * @see #setShadowLayer(float, float, float, int) 4949 * 4950 * @attr ref android.R.styleable#TextView_shadowColor 4951 */ 4952 @InspectableProperty 4953 @ColorInt getShadowColor()4954 public int getShadowColor() { 4955 return mShadowColor; 4956 } 4957 4958 /** 4959 * Gets the {@link TextPaint} used for the text. 4960 * Use this only to consult the Paint's properties and not to change them. 4961 * @return The base paint used for the text. 4962 */ getPaint()4963 public TextPaint getPaint() { 4964 return mTextPaint; 4965 } 4966 4967 /** 4968 * Sets the autolink mask of the text. See {@link 4969 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 4970 * possible values. 4971 * 4972 * <p class="note"><b>Note:</b> 4973 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 4974 * is deprecated and should be avoided; see its documentation. 4975 * 4976 * @attr ref android.R.styleable#TextView_autoLink 4977 */ 4978 @android.view.RemotableViewMethod setAutoLinkMask(int mask)4979 public final void setAutoLinkMask(int mask) { 4980 mAutoLinkMask = mask; 4981 } 4982 4983 /** 4984 * Sets whether the movement method will automatically be set to 4985 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4986 * set to nonzero and links are detected in {@link #setText}. 4987 * The default is true. 4988 * 4989 * @attr ref android.R.styleable#TextView_linksClickable 4990 */ 4991 @android.view.RemotableViewMethod setLinksClickable(boolean whether)4992 public final void setLinksClickable(boolean whether) { 4993 mLinksClickable = whether; 4994 } 4995 4996 /** 4997 * Returns whether the movement method will automatically be set to 4998 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4999 * set to nonzero and links are detected in {@link #setText}. 5000 * The default is true. 5001 * 5002 * @attr ref android.R.styleable#TextView_linksClickable 5003 */ 5004 @InspectableProperty getLinksClickable()5005 public final boolean getLinksClickable() { 5006 return mLinksClickable; 5007 } 5008 5009 /** 5010 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5011 * (by {@link Linkify} or otherwise) if any. You can call 5012 * {@link URLSpan#getURL} on them to find where they link to 5013 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5014 * to find the region of the text they are attached to. 5015 */ getUrls()5016 public URLSpan[] getUrls() { 5017 if (mText instanceof Spanned) { 5018 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5019 } else { 5020 return new URLSpan[0]; 5021 } 5022 } 5023 5024 /** 5025 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5026 * TextView. 5027 * 5028 * @see #setHintTextColor(ColorStateList) 5029 * @see #getHintTextColors() 5030 * @see #setTextColor(int) 5031 * 5032 * @attr ref android.R.styleable#TextView_textColorHint 5033 */ 5034 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5035 public final void setHintTextColor(@ColorInt int color) { 5036 mHintTextColor = ColorStateList.valueOf(color); 5037 updateTextColors(); 5038 } 5039 5040 /** 5041 * Sets the color of the hint text. 5042 * 5043 * @see #getHintTextColors() 5044 * @see #setHintTextColor(int) 5045 * @see #setTextColor(ColorStateList) 5046 * @see #setLinkTextColor(ColorStateList) 5047 * 5048 * @attr ref android.R.styleable#TextView_textColorHint 5049 */ setHintTextColor(ColorStateList colors)5050 public final void setHintTextColor(ColorStateList colors) { 5051 mHintTextColor = colors; 5052 updateTextColors(); 5053 } 5054 5055 /** 5056 * @return the color of the hint text, for the different states of this TextView. 5057 * 5058 * @see #setHintTextColor(ColorStateList) 5059 * @see #setHintTextColor(int) 5060 * @see #setTextColor(ColorStateList) 5061 * @see #setLinkTextColor(ColorStateList) 5062 * 5063 * @attr ref android.R.styleable#TextView_textColorHint 5064 */ 5065 @InspectableProperty(name = "textColorHint") getHintTextColors()5066 public final ColorStateList getHintTextColors() { 5067 return mHintTextColor; 5068 } 5069 5070 /** 5071 * <p>Return the current color selected to paint the hint text.</p> 5072 * 5073 * @return Returns the current hint text color. 5074 */ 5075 @ColorInt getCurrentHintTextColor()5076 public final int getCurrentHintTextColor() { 5077 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5078 } 5079 5080 /** 5081 * Sets the color of links in the text. 5082 * 5083 * @see #setLinkTextColor(ColorStateList) 5084 * @see #getLinkTextColors() 5085 * 5086 * @attr ref android.R.styleable#TextView_textColorLink 5087 */ 5088 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5089 public final void setLinkTextColor(@ColorInt int color) { 5090 mLinkTextColor = ColorStateList.valueOf(color); 5091 updateTextColors(); 5092 } 5093 5094 /** 5095 * Sets the color of links in the text. 5096 * 5097 * @see #setLinkTextColor(int) 5098 * @see #getLinkTextColors() 5099 * @see #setTextColor(ColorStateList) 5100 * @see #setHintTextColor(ColorStateList) 5101 * 5102 * @attr ref android.R.styleable#TextView_textColorLink 5103 */ setLinkTextColor(ColorStateList colors)5104 public final void setLinkTextColor(ColorStateList colors) { 5105 mLinkTextColor = colors; 5106 updateTextColors(); 5107 } 5108 5109 /** 5110 * @return the list of colors used to paint the links in the text, for the different states of 5111 * this TextView 5112 * 5113 * @see #setLinkTextColor(ColorStateList) 5114 * @see #setLinkTextColor(int) 5115 * 5116 * @attr ref android.R.styleable#TextView_textColorLink 5117 */ 5118 @InspectableProperty(name = "textColorLink") getLinkTextColors()5119 public final ColorStateList getLinkTextColors() { 5120 return mLinkTextColor; 5121 } 5122 5123 /** 5124 * Sets the horizontal alignment of the text and the 5125 * vertical gravity that will be used when there is extra space 5126 * in the TextView beyond what is required for the text itself. 5127 * 5128 * @see android.view.Gravity 5129 * @attr ref android.R.styleable#TextView_gravity 5130 */ setGravity(int gravity)5131 public void setGravity(int gravity) { 5132 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5133 gravity |= Gravity.START; 5134 } 5135 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5136 gravity |= Gravity.TOP; 5137 } 5138 5139 boolean newLayout = false; 5140 5141 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5142 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5143 newLayout = true; 5144 } 5145 5146 if (gravity != mGravity) { 5147 invalidate(); 5148 } 5149 5150 mGravity = gravity; 5151 5152 if (mLayout != null && newLayout) { 5153 // XXX this is heavy-handed because no actual content changes. 5154 int want = mLayout.getWidth(); 5155 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5156 5157 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5158 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5159 } 5160 } 5161 5162 /** 5163 * Returns the horizontal and vertical alignment of this TextView. 5164 * 5165 * @see android.view.Gravity 5166 * @attr ref android.R.styleable#TextView_gravity 5167 */ 5168 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5169 public int getGravity() { 5170 return mGravity; 5171 } 5172 5173 /** 5174 * Gets the flags on the Paint being used to display the text. 5175 * @return The flags on the Paint being used to display the text. 5176 * @see Paint#getFlags 5177 */ getPaintFlags()5178 public int getPaintFlags() { 5179 return mTextPaint.getFlags(); 5180 } 5181 5182 /** 5183 * Sets flags on the Paint being used to display the text and 5184 * reflows the text if they are different from the old flags. 5185 * @see Paint#setFlags 5186 */ 5187 @android.view.RemotableViewMethod setPaintFlags(int flags)5188 public void setPaintFlags(int flags) { 5189 if (mTextPaint.getFlags() != flags) { 5190 mTextPaint.setFlags(flags); 5191 5192 if (mLayout != null) { 5193 nullLayouts(); 5194 requestLayout(); 5195 invalidate(); 5196 } 5197 } 5198 } 5199 5200 /** 5201 * Sets whether the text should be allowed to be wider than the 5202 * View is. If false, it will be wrapped to the width of the View. 5203 * 5204 * @attr ref android.R.styleable#TextView_scrollHorizontally 5205 */ setHorizontallyScrolling(boolean whether)5206 public void setHorizontallyScrolling(boolean whether) { 5207 if (mHorizontallyScrolling != whether) { 5208 mHorizontallyScrolling = whether; 5209 5210 if (mLayout != null) { 5211 nullLayouts(); 5212 requestLayout(); 5213 invalidate(); 5214 } 5215 } 5216 } 5217 5218 /** 5219 * Returns whether the text is allowed to be wider than the View. 5220 * If false, the text will be wrapped to the width of the View. 5221 * 5222 * @attr ref android.R.styleable#TextView_scrollHorizontally 5223 * @see #setHorizontallyScrolling(boolean) 5224 */ 5225 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5226 public final boolean isHorizontallyScrollable() { 5227 return mHorizontallyScrolling; 5228 } 5229 5230 /** 5231 * Returns whether the text is allowed to be wider than the View. 5232 * If false, the text will be wrapped to the width of the View. 5233 * 5234 * @attr ref android.R.styleable#TextView_scrollHorizontally 5235 * @hide 5236 */ 5237 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5238 public boolean getHorizontallyScrolling() { 5239 return mHorizontallyScrolling; 5240 } 5241 5242 /** 5243 * Sets the height of the TextView to be at least {@code minLines} tall. 5244 * <p> 5245 * This value is used for height calculation if LayoutParams does not force TextView to have an 5246 * exact height. Setting this value overrides other previous minimum height configurations such 5247 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5248 * this value to 1. 5249 * 5250 * @param minLines the minimum height of TextView in terms of number of lines 5251 * 5252 * @see #getMinLines() 5253 * @see #setLines(int) 5254 * 5255 * @attr ref android.R.styleable#TextView_minLines 5256 */ 5257 @android.view.RemotableViewMethod setMinLines(int minLines)5258 public void setMinLines(int minLines) { 5259 mMinimum = minLines; 5260 mMinMode = LINES; 5261 5262 requestLayout(); 5263 invalidate(); 5264 } 5265 5266 /** 5267 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5268 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5269 * 5270 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5271 * height is not defined in lines 5272 * 5273 * @see #setMinLines(int) 5274 * @see #setLines(int) 5275 * 5276 * @attr ref android.R.styleable#TextView_minLines 5277 */ 5278 @InspectableProperty getMinLines()5279 public int getMinLines() { 5280 return mMinMode == LINES ? mMinimum : -1; 5281 } 5282 5283 /** 5284 * Sets the height of the TextView to be at least {@code minPixels} tall. 5285 * <p> 5286 * This value is used for height calculation if LayoutParams does not force TextView to have an 5287 * exact height. Setting this value overrides previous minimum height configurations such as 5288 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5289 * <p> 5290 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5291 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5292 * used to decide the final height. 5293 * 5294 * @param minPixels the minimum height of TextView in terms of pixels 5295 * 5296 * @see #getMinHeight() 5297 * @see #setHeight(int) 5298 * 5299 * @attr ref android.R.styleable#TextView_minHeight 5300 */ 5301 @android.view.RemotableViewMethod setMinHeight(int minPixels)5302 public void setMinHeight(int minPixels) { 5303 mMinimum = minPixels; 5304 mMinMode = PIXELS; 5305 5306 requestLayout(); 5307 invalidate(); 5308 } 5309 5310 /** 5311 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5312 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5313 * 5314 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5315 * defined in pixels 5316 * 5317 * @see #setMinHeight(int) 5318 * @see #setHeight(int) 5319 * 5320 * @attr ref android.R.styleable#TextView_minHeight 5321 */ getMinHeight()5322 public int getMinHeight() { 5323 return mMinMode == PIXELS ? mMinimum : -1; 5324 } 5325 5326 /** 5327 * Sets the height of the TextView to be at most {@code maxLines} tall. 5328 * <p> 5329 * This value is used for height calculation if LayoutParams does not force TextView to have an 5330 * exact height. Setting this value overrides previous maximum height configurations such as 5331 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5332 * 5333 * @param maxLines the maximum height of TextView in terms of number of lines 5334 * 5335 * @see #getMaxLines() 5336 * @see #setLines(int) 5337 * 5338 * @attr ref android.R.styleable#TextView_maxLines 5339 */ 5340 @android.view.RemotableViewMethod setMaxLines(int maxLines)5341 public void setMaxLines(int maxLines) { 5342 mMaximum = maxLines; 5343 mMaxMode = LINES; 5344 5345 requestLayout(); 5346 invalidate(); 5347 } 5348 5349 /** 5350 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5351 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5352 * 5353 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5354 * is not defined in lines. 5355 * 5356 * @see #setMaxLines(int) 5357 * @see #setLines(int) 5358 * 5359 * @attr ref android.R.styleable#TextView_maxLines 5360 */ 5361 @InspectableProperty getMaxLines()5362 public int getMaxLines() { 5363 return mMaxMode == LINES ? mMaximum : -1; 5364 } 5365 5366 /** 5367 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5368 * <p> 5369 * This value is used for height calculation if LayoutParams does not force TextView to have an 5370 * exact height. Setting this value overrides previous maximum height configurations such as 5371 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5372 * 5373 * @param maxPixels the maximum height of TextView in terms of pixels 5374 * 5375 * @see #getMaxHeight() 5376 * @see #setHeight(int) 5377 * 5378 * @attr ref android.R.styleable#TextView_maxHeight 5379 */ 5380 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5381 public void setMaxHeight(int maxPixels) { 5382 mMaximum = maxPixels; 5383 mMaxMode = PIXELS; 5384 5385 requestLayout(); 5386 invalidate(); 5387 } 5388 5389 /** 5390 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5391 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5392 * 5393 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5394 * is not defined in pixels 5395 * 5396 * @see #setMaxHeight(int) 5397 * @see #setHeight(int) 5398 * 5399 * @attr ref android.R.styleable#TextView_maxHeight 5400 */ 5401 @InspectableProperty getMaxHeight()5402 public int getMaxHeight() { 5403 return mMaxMode == PIXELS ? mMaximum : -1; 5404 } 5405 5406 /** 5407 * Sets the height of the TextView to be exactly {@code lines} tall. 5408 * <p> 5409 * This value is used for height calculation if LayoutParams does not force TextView to have an 5410 * exact height. Setting this value overrides previous minimum/maximum height configurations 5411 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5412 * set this value to 1. 5413 * 5414 * @param lines the exact height of the TextView in terms of lines 5415 * 5416 * @see #setHeight(int) 5417 * 5418 * @attr ref android.R.styleable#TextView_lines 5419 */ 5420 @android.view.RemotableViewMethod setLines(int lines)5421 public void setLines(int lines) { 5422 mMaximum = mMinimum = lines; 5423 mMaxMode = mMinMode = LINES; 5424 5425 requestLayout(); 5426 invalidate(); 5427 } 5428 5429 /** 5430 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5431 * <p> 5432 * This value is used for height calculation if LayoutParams does not force TextView to have an 5433 * exact height. Setting this value overrides previous minimum/maximum height configurations 5434 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5435 * 5436 * @param pixels the exact height of the TextView in terms of pixels 5437 * 5438 * @see #setLines(int) 5439 * 5440 * @attr ref android.R.styleable#TextView_height 5441 */ 5442 @android.view.RemotableViewMethod setHeight(int pixels)5443 public void setHeight(int pixels) { 5444 mMaximum = mMinimum = pixels; 5445 mMaxMode = mMinMode = PIXELS; 5446 5447 requestLayout(); 5448 invalidate(); 5449 } 5450 5451 /** 5452 * Sets the width of the TextView to be at least {@code minEms} wide. 5453 * <p> 5454 * This value is used for width calculation if LayoutParams does not force TextView to have an 5455 * exact width. Setting this value overrides previous minimum width configurations such as 5456 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5457 * 5458 * @param minEms the minimum width of TextView in terms of ems 5459 * 5460 * @see #getMinEms() 5461 * @see #setEms(int) 5462 * 5463 * @attr ref android.R.styleable#TextView_minEms 5464 */ 5465 @android.view.RemotableViewMethod setMinEms(int minEms)5466 public void setMinEms(int minEms) { 5467 mMinWidth = minEms; 5468 mMinWidthMode = EMS; 5469 5470 requestLayout(); 5471 invalidate(); 5472 } 5473 5474 /** 5475 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5476 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5477 * 5478 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5479 * defined in ems 5480 * 5481 * @see #setMinEms(int) 5482 * @see #setEms(int) 5483 * 5484 * @attr ref android.R.styleable#TextView_minEms 5485 */ 5486 @InspectableProperty getMinEms()5487 public int getMinEms() { 5488 return mMinWidthMode == EMS ? mMinWidth : -1; 5489 } 5490 5491 /** 5492 * Sets the width of the TextView to be at least {@code minPixels} wide. 5493 * <p> 5494 * This value is used for width calculation if LayoutParams does not force TextView to have an 5495 * exact width. Setting this value overrides previous minimum width configurations such as 5496 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5497 * <p> 5498 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5499 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5500 * to decide the final width. 5501 * 5502 * @param minPixels the minimum width of TextView in terms of pixels 5503 * 5504 * @see #getMinWidth() 5505 * @see #setWidth(int) 5506 * 5507 * @attr ref android.R.styleable#TextView_minWidth 5508 */ 5509 @android.view.RemotableViewMethod setMinWidth(int minPixels)5510 public void setMinWidth(int minPixels) { 5511 mMinWidth = minPixels; 5512 mMinWidthMode = PIXELS; 5513 5514 requestLayout(); 5515 invalidate(); 5516 } 5517 5518 /** 5519 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5520 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5521 * 5522 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5523 * defined in pixels 5524 * 5525 * @see #setMinWidth(int) 5526 * @see #setWidth(int) 5527 * 5528 * @attr ref android.R.styleable#TextView_minWidth 5529 */ 5530 @InspectableProperty getMinWidth()5531 public int getMinWidth() { 5532 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5533 } 5534 5535 /** 5536 * Sets the width of the TextView to be at most {@code maxEms} wide. 5537 * <p> 5538 * This value is used for width calculation if LayoutParams does not force TextView to have an 5539 * exact width. Setting this value overrides previous maximum width configurations such as 5540 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5541 * 5542 * @param maxEms the maximum width of TextView in terms of ems 5543 * 5544 * @see #getMaxEms() 5545 * @see #setEms(int) 5546 * 5547 * @attr ref android.R.styleable#TextView_maxEms 5548 */ 5549 @android.view.RemotableViewMethod setMaxEms(int maxEms)5550 public void setMaxEms(int maxEms) { 5551 mMaxWidth = maxEms; 5552 mMaxWidthMode = EMS; 5553 5554 requestLayout(); 5555 invalidate(); 5556 } 5557 5558 /** 5559 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5560 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5561 * 5562 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5563 * defined in ems 5564 * 5565 * @see #setMaxEms(int) 5566 * @see #setEms(int) 5567 * 5568 * @attr ref android.R.styleable#TextView_maxEms 5569 */ 5570 @InspectableProperty getMaxEms()5571 public int getMaxEms() { 5572 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5573 } 5574 5575 /** 5576 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5577 * <p> 5578 * This value is used for width calculation if LayoutParams does not force TextView to have an 5579 * exact width. Setting this value overrides previous maximum width configurations such as 5580 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5581 * 5582 * @param maxPixels the maximum width of TextView in terms of pixels 5583 * 5584 * @see #getMaxWidth() 5585 * @see #setWidth(int) 5586 * 5587 * @attr ref android.R.styleable#TextView_maxWidth 5588 */ 5589 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5590 public void setMaxWidth(int maxPixels) { 5591 mMaxWidth = maxPixels; 5592 mMaxWidthMode = PIXELS; 5593 5594 requestLayout(); 5595 invalidate(); 5596 } 5597 5598 /** 5599 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5600 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5601 * 5602 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5603 * defined in pixels 5604 * 5605 * @see #setMaxWidth(int) 5606 * @see #setWidth(int) 5607 * 5608 * @attr ref android.R.styleable#TextView_maxWidth 5609 */ 5610 @InspectableProperty getMaxWidth()5611 public int getMaxWidth() { 5612 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5613 } 5614 5615 /** 5616 * Sets the width of the TextView to be exactly {@code ems} wide. 5617 * 5618 * This value is used for width calculation if LayoutParams does not force TextView to have an 5619 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5620 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5621 * 5622 * @param ems the exact width of the TextView in terms of ems 5623 * 5624 * @see #setWidth(int) 5625 * 5626 * @attr ref android.R.styleable#TextView_ems 5627 */ 5628 @android.view.RemotableViewMethod setEms(int ems)5629 public void setEms(int ems) { 5630 mMaxWidth = mMinWidth = ems; 5631 mMaxWidthMode = mMinWidthMode = EMS; 5632 5633 requestLayout(); 5634 invalidate(); 5635 } 5636 5637 /** 5638 * Sets the width of the TextView to be exactly {@code pixels} wide. 5639 * <p> 5640 * This value is used for width calculation if LayoutParams does not force TextView to have an 5641 * exact width. Setting this value overrides previous minimum/maximum width configurations 5642 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5643 * 5644 * @param pixels the exact width of the TextView in terms of pixels 5645 * 5646 * @see #setEms(int) 5647 * 5648 * @attr ref android.R.styleable#TextView_width 5649 */ 5650 @android.view.RemotableViewMethod setWidth(int pixels)5651 public void setWidth(int pixels) { 5652 mMaxWidth = mMinWidth = pixels; 5653 mMaxWidthMode = mMinWidthMode = PIXELS; 5654 5655 requestLayout(); 5656 invalidate(); 5657 } 5658 5659 /** 5660 * Sets line spacing for this TextView. Each line other than the last line will have its height 5661 * multiplied by {@code mult} and have {@code add} added to it. 5662 * 5663 * @param add The value in pixels that should be added to each line other than the last line. 5664 * This will be applied after the multiplier 5665 * @param mult The value by which each line height other than the last line will be multiplied 5666 * by 5667 * 5668 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5669 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5670 */ setLineSpacing(float add, float mult)5671 public void setLineSpacing(float add, float mult) { 5672 if (mSpacingAdd != add || mSpacingMult != mult) { 5673 mSpacingAdd = add; 5674 mSpacingMult = mult; 5675 5676 if (mLayout != null) { 5677 nullLayouts(); 5678 requestLayout(); 5679 invalidate(); 5680 } 5681 } 5682 } 5683 5684 /** 5685 * Gets the line spacing multiplier 5686 * 5687 * @return the value by which each line's height is multiplied to get its actual height. 5688 * 5689 * @see #setLineSpacing(float, float) 5690 * @see #getLineSpacingExtra() 5691 * 5692 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5693 */ 5694 @InspectableProperty getLineSpacingMultiplier()5695 public float getLineSpacingMultiplier() { 5696 return mSpacingMult; 5697 } 5698 5699 /** 5700 * Gets the line spacing extra space 5701 * 5702 * @return the extra space that is added to the height of each lines of this TextView. 5703 * 5704 * @see #setLineSpacing(float, float) 5705 * @see #getLineSpacingMultiplier() 5706 * 5707 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5708 */ 5709 @InspectableProperty getLineSpacingExtra()5710 public float getLineSpacingExtra() { 5711 return mSpacingAdd; 5712 } 5713 5714 /** 5715 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 5716 * between subsequent baselines in the TextView. 5717 * 5718 * @param lineHeight the line height in pixels 5719 * 5720 * @see #setLineSpacing(float, float) 5721 * @see #getLineSpacingExtra() 5722 * 5723 * @attr ref android.R.styleable#TextView_lineHeight 5724 */ setLineHeight(@x @ntRangefrom = 0) int lineHeight)5725 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 5726 Preconditions.checkArgumentNonnegative(lineHeight); 5727 5728 final int fontHeight = getPaint().getFontMetricsInt(null); 5729 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 5730 if (lineHeight != fontHeight) { 5731 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 5732 setLineSpacing(lineHeight - fontHeight, 1f); 5733 } 5734 } 5735 5736 /** 5737 * Convenience method to append the specified text to the TextView's 5738 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5739 * if it was not already editable. 5740 * 5741 * @param text text to be appended to the already displayed text 5742 */ append(CharSequence text)5743 public final void append(CharSequence text) { 5744 append(text, 0, text.length()); 5745 } 5746 5747 /** 5748 * Convenience method to append the specified text slice to the TextView's 5749 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5750 * if it was not already editable. 5751 * 5752 * @param text text to be appended to the already displayed text 5753 * @param start the index of the first character in the {@code text} 5754 * @param end the index of the character following the last character in the {@code text} 5755 * 5756 * @see Appendable#append(CharSequence, int, int) 5757 */ append(CharSequence text, int start, int end)5758 public void append(CharSequence text, int start, int end) { 5759 if (!(mText instanceof Editable)) { 5760 setText(mText, BufferType.EDITABLE); 5761 } 5762 5763 ((Editable) mText).append(text, start, end); 5764 5765 if (mAutoLinkMask != 0) { 5766 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 5767 // Do not change the movement method for text that support text selection as it 5768 // would prevent an arbitrary cursor displacement. 5769 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 5770 setMovementMethod(LinkMovementMethod.getInstance()); 5771 } 5772 } 5773 } 5774 updateTextColors()5775 private void updateTextColors() { 5776 boolean inval = false; 5777 final int[] drawableState = getDrawableState(); 5778 int color = mTextColor.getColorForState(drawableState, 0); 5779 if (color != mCurTextColor) { 5780 mCurTextColor = color; 5781 inval = true; 5782 } 5783 if (mLinkTextColor != null) { 5784 color = mLinkTextColor.getColorForState(drawableState, 0); 5785 if (color != mTextPaint.linkColor) { 5786 mTextPaint.linkColor = color; 5787 inval = true; 5788 } 5789 } 5790 if (mHintTextColor != null) { 5791 color = mHintTextColor.getColorForState(drawableState, 0); 5792 if (color != mCurHintTextColor) { 5793 mCurHintTextColor = color; 5794 if (mText.length() == 0) { 5795 inval = true; 5796 } 5797 } 5798 } 5799 if (inval) { 5800 // Text needs to be redrawn with the new color 5801 if (mEditor != null) mEditor.invalidateTextDisplayList(); 5802 invalidate(); 5803 } 5804 } 5805 5806 @Override drawableStateChanged()5807 protected void drawableStateChanged() { 5808 super.drawableStateChanged(); 5809 5810 if (mTextColor != null && mTextColor.isStateful() 5811 || (mHintTextColor != null && mHintTextColor.isStateful()) 5812 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 5813 updateTextColors(); 5814 } 5815 5816 if (mDrawables != null) { 5817 final int[] state = getDrawableState(); 5818 for (Drawable dr : mDrawables.mShowing) { 5819 if (dr != null && dr.isStateful() && dr.setState(state)) { 5820 invalidateDrawable(dr); 5821 } 5822 } 5823 } 5824 } 5825 5826 @Override drawableHotspotChanged(float x, float y)5827 public void drawableHotspotChanged(float x, float y) { 5828 super.drawableHotspotChanged(x, y); 5829 5830 if (mDrawables != null) { 5831 for (Drawable dr : mDrawables.mShowing) { 5832 if (dr != null) { 5833 dr.setHotspot(x, y); 5834 } 5835 } 5836 } 5837 } 5838 5839 @Override onSaveInstanceState()5840 public Parcelable onSaveInstanceState() { 5841 Parcelable superState = super.onSaveInstanceState(); 5842 5843 // Save state if we are forced to 5844 final boolean freezesText = getFreezesText(); 5845 boolean hasSelection = false; 5846 int start = -1; 5847 int end = -1; 5848 5849 if (mText != null) { 5850 start = getSelectionStart(); 5851 end = getSelectionEnd(); 5852 if (start >= 0 || end >= 0) { 5853 // Or save state if there is a selection 5854 hasSelection = true; 5855 } 5856 } 5857 5858 if (freezesText || hasSelection) { 5859 SavedState ss = new SavedState(superState); 5860 5861 if (freezesText) { 5862 if (mText instanceof Spanned) { 5863 final Spannable sp = new SpannableStringBuilder(mText); 5864 5865 if (mEditor != null) { 5866 removeMisspelledSpans(sp); 5867 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5868 } 5869 5870 ss.text = sp; 5871 } else { 5872 ss.text = mText.toString(); 5873 } 5874 } 5875 5876 if (hasSelection) { 5877 // XXX Should also save the current scroll position! 5878 ss.selStart = start; 5879 ss.selEnd = end; 5880 } 5881 5882 if (isFocused() && start >= 0 && end >= 0) { 5883 ss.frozenWithFocus = true; 5884 } 5885 5886 ss.error = getError(); 5887 5888 if (mEditor != null) { 5889 ss.editorState = mEditor.saveInstanceState(); 5890 } 5891 return ss; 5892 } 5893 5894 return superState; 5895 } 5896 removeMisspelledSpans(Spannable spannable)5897 void removeMisspelledSpans(Spannable spannable) { 5898 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 5899 SuggestionSpan.class); 5900 for (int i = 0; i < suggestionSpans.length; i++) { 5901 int flags = suggestionSpans[i].getFlags(); 5902 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 5903 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 5904 spannable.removeSpan(suggestionSpans[i]); 5905 } 5906 } 5907 } 5908 5909 @Override onRestoreInstanceState(Parcelable state)5910 public void onRestoreInstanceState(Parcelable state) { 5911 if (!(state instanceof SavedState)) { 5912 super.onRestoreInstanceState(state); 5913 return; 5914 } 5915 5916 SavedState ss = (SavedState) state; 5917 super.onRestoreInstanceState(ss.getSuperState()); 5918 5919 // XXX restore buffer type too, as well as lots of other stuff 5920 if (ss.text != null) { 5921 setText(ss.text); 5922 } 5923 5924 if (ss.selStart >= 0 && ss.selEnd >= 0) { 5925 if (mSpannable != null) { 5926 int len = mText.length(); 5927 5928 if (ss.selStart > len || ss.selEnd > len) { 5929 String restored = ""; 5930 5931 if (ss.text != null) { 5932 restored = "(restored) "; 5933 } 5934 5935 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 5936 + " out of range for " + restored + "text " + mText); 5937 } else { 5938 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 5939 5940 if (ss.frozenWithFocus) { 5941 createEditorIfNeeded(); 5942 mEditor.mFrozenWithFocus = true; 5943 } 5944 } 5945 } 5946 } 5947 5948 if (ss.error != null) { 5949 final CharSequence error = ss.error; 5950 // Display the error later, after the first layout pass 5951 post(new Runnable() { 5952 public void run() { 5953 if (mEditor == null || !mEditor.mErrorWasChanged) { 5954 setError(error); 5955 } 5956 } 5957 }); 5958 } 5959 5960 if (ss.editorState != null) { 5961 createEditorIfNeeded(); 5962 mEditor.restoreInstanceState(ss.editorState); 5963 } 5964 } 5965 5966 /** 5967 * Control whether this text view saves its entire text contents when 5968 * freezing to an icicle, in addition to dynamic state such as cursor 5969 * position. By default this is false, not saving the text. Set to true 5970 * if the text in the text view is not being saved somewhere else in 5971 * persistent storage (such as in a content provider) so that if the 5972 * view is later thawed the user will not lose their data. For 5973 * {@link android.widget.EditText} it is always enabled, regardless of 5974 * the value of the attribute. 5975 * 5976 * @param freezesText Controls whether a frozen icicle should include the 5977 * entire text data: true to include it, false to not. 5978 * 5979 * @attr ref android.R.styleable#TextView_freezesText 5980 */ 5981 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)5982 public void setFreezesText(boolean freezesText) { 5983 mFreezesText = freezesText; 5984 } 5985 5986 /** 5987 * Return whether this text view is including its entire text contents 5988 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 5989 * 5990 * @return Returns true if text is included, false if it isn't. 5991 * 5992 * @see #setFreezesText 5993 */ 5994 @InspectableProperty getFreezesText()5995 public boolean getFreezesText() { 5996 return mFreezesText; 5997 } 5998 5999 /////////////////////////////////////////////////////////////////////////// 6000 6001 /** 6002 * Sets the Factory used to create new {@link Editable Editables}. 6003 * 6004 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6005 * 6006 * @see android.text.Editable.Factory 6007 * @see android.widget.TextView.BufferType#EDITABLE 6008 */ setEditableFactory(Editable.Factory factory)6009 public final void setEditableFactory(Editable.Factory factory) { 6010 mEditableFactory = factory; 6011 setText(mText); 6012 } 6013 6014 /** 6015 * Sets the Factory used to create new {@link Spannable Spannables}. 6016 * 6017 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6018 * 6019 * @see android.text.Spannable.Factory 6020 * @see android.widget.TextView.BufferType#SPANNABLE 6021 */ setSpannableFactory(Spannable.Factory factory)6022 public final void setSpannableFactory(Spannable.Factory factory) { 6023 mSpannableFactory = factory; 6024 setText(mText); 6025 } 6026 6027 /** 6028 * Sets the text to be displayed. TextView <em>does not</em> accept 6029 * HTML-like formatting, which you can do with text strings in XML resource files. 6030 * To style your strings, attach android.text.style.* objects to a 6031 * {@link android.text.SpannableString}, or see the 6032 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6033 * Available Resource Types</a> documentation for an example of setting 6034 * formatted text in the XML resource file. 6035 * <p/> 6036 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6037 * intermediate {@link Spannable Spannables}. Likewise it will use 6038 * {@link android.text.Editable.Factory} to create final or intermediate 6039 * {@link Editable Editables}. 6040 * 6041 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6042 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6043 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6044 * 6045 * @param text text to be displayed 6046 * 6047 * @attr ref android.R.styleable#TextView_text 6048 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6049 * parameters used to create the PrecomputedText mismatches 6050 * with this TextView. 6051 */ 6052 @android.view.RemotableViewMethod setText(CharSequence text)6053 public final void setText(CharSequence text) { 6054 setText(text, mBufferType); 6055 } 6056 6057 /** 6058 * Sets the text to be displayed but retains the cursor position. Same as 6059 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6060 * new text. 6061 * <p/> 6062 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6063 * intermediate {@link Spannable Spannables}. Likewise it will use 6064 * {@link android.text.Editable.Factory} to create final or intermediate 6065 * {@link Editable Editables}. 6066 * 6067 * @param text text to be displayed 6068 * 6069 * @see #setText(CharSequence) 6070 */ 6071 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6072 public final void setTextKeepState(CharSequence text) { 6073 setTextKeepState(text, mBufferType); 6074 } 6075 6076 /** 6077 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6078 * <p/> 6079 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6080 * intermediate {@link Spannable Spannables}. Likewise it will use 6081 * {@link android.text.Editable.Factory} to create final or intermediate 6082 * {@link Editable Editables}. 6083 * 6084 * Subclasses overriding this method should ensure that the following post condition holds, 6085 * in order to guarantee the safety of the view's measurement and layout operations: 6086 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6087 * will be different from {@code null}. 6088 * 6089 * @param text text to be displayed 6090 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6091 * stored as a static text, styleable/spannable text, or editable text 6092 * 6093 * @see #setText(CharSequence) 6094 * @see android.widget.TextView.BufferType 6095 * @see #setSpannableFactory(Spannable.Factory) 6096 * @see #setEditableFactory(Editable.Factory) 6097 * 6098 * @attr ref android.R.styleable#TextView_text 6099 * @attr ref android.R.styleable#TextView_bufferType 6100 */ setText(CharSequence text, BufferType type)6101 public void setText(CharSequence text, BufferType type) { 6102 setText(text, type, true, 0); 6103 6104 if (mCharWrapper != null) { 6105 mCharWrapper.mChars = null; 6106 } 6107 } 6108 6109 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6110 private void setText(CharSequence text, BufferType type, 6111 boolean notifyBefore, int oldlen) { 6112 mTextSetFromXmlOrResourceId = false; 6113 if (text == null) { 6114 text = ""; 6115 } 6116 6117 // If suggestions are not enabled, remove the suggestion spans from the text 6118 if (!isSuggestionsEnabled()) { 6119 text = removeSuggestionSpans(text); 6120 } 6121 6122 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6123 6124 if (text instanceof Spanned 6125 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6126 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6127 setHorizontalFadingEdgeEnabled(true); 6128 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6129 } else { 6130 setHorizontalFadingEdgeEnabled(false); 6131 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6132 } 6133 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6134 } 6135 6136 int n = mFilters.length; 6137 for (int i = 0; i < n; i++) { 6138 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6139 if (out != null) { 6140 text = out; 6141 } 6142 } 6143 6144 if (notifyBefore) { 6145 if (mText != null) { 6146 oldlen = mText.length(); 6147 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6148 } else { 6149 sendBeforeTextChanged("", 0, 0, text.length()); 6150 } 6151 } 6152 6153 boolean needEditableForNotification = false; 6154 6155 if (mListeners != null && mListeners.size() != 0) { 6156 needEditableForNotification = true; 6157 } 6158 6159 PrecomputedText precomputed = 6160 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6161 if (type == BufferType.EDITABLE || getKeyListener() != null 6162 || needEditableForNotification) { 6163 createEditorIfNeeded(); 6164 mEditor.forgetUndoRedo(); 6165 Editable t = mEditableFactory.newEditable(text); 6166 text = t; 6167 setFilters(t, mFilters); 6168 InputMethodManager imm = getInputMethodManager(); 6169 if (imm != null) imm.restartInput(this); 6170 } else if (precomputed != null) { 6171 if (mTextDir == null) { 6172 mTextDir = getTextDirectionHeuristic(); 6173 } 6174 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6175 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6176 mHyphenationFrequency); 6177 switch (checkResult) { 6178 case PrecomputedText.Params.UNUSABLE: 6179 throw new IllegalArgumentException( 6180 "PrecomputedText's Parameters don't match the parameters of this TextView." 6181 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6182 + "to override the settings of this TextView: " 6183 + "PrecomputedText: " + precomputed.getParams() 6184 + "TextView: " + getTextMetricsParams()); 6185 case PrecomputedText.Params.NEED_RECOMPUTE: 6186 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6187 break; 6188 case PrecomputedText.Params.USABLE: 6189 // pass through 6190 } 6191 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6192 text = mSpannableFactory.newSpannable(text); 6193 } else if (!(text instanceof CharWrapper)) { 6194 text = TextUtils.stringOrSpannedString(text); 6195 } 6196 6197 if (mAutoLinkMask != 0) { 6198 Spannable s2; 6199 6200 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6201 s2 = (Spannable) text; 6202 } else { 6203 s2 = mSpannableFactory.newSpannable(text); 6204 } 6205 6206 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6207 text = s2; 6208 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6209 6210 /* 6211 * We must go ahead and set the text before changing the 6212 * movement method, because setMovementMethod() may call 6213 * setText() again to try to upgrade the buffer type. 6214 */ 6215 setTextInternal(text); 6216 6217 // Do not change the movement method for text that support text selection as it 6218 // would prevent an arbitrary cursor displacement. 6219 if (mLinksClickable && !textCanBeSelected()) { 6220 setMovementMethod(LinkMovementMethod.getInstance()); 6221 } 6222 } 6223 } 6224 6225 mBufferType = type; 6226 setTextInternal(text); 6227 6228 if (mTransformation == null) { 6229 mTransformed = text; 6230 } else { 6231 mTransformed = mTransformation.getTransformation(text, this); 6232 } 6233 if (mTransformed == null) { 6234 // Should not happen if the transformation method follows the non-null postcondition. 6235 mTransformed = ""; 6236 } 6237 6238 final int textLength = text.length(); 6239 6240 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6241 Spannable sp = (Spannable) text; 6242 6243 // Remove any ChangeWatchers that might have come from other TextViews. 6244 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6245 final int count = watchers.length; 6246 for (int i = 0; i < count; i++) { 6247 sp.removeSpan(watchers[i]); 6248 } 6249 6250 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6251 6252 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6253 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6254 6255 if (mEditor != null) mEditor.addSpanWatchers(sp); 6256 6257 if (mTransformation != null) { 6258 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6259 } 6260 6261 if (mMovement != null) { 6262 mMovement.initialize(this, (Spannable) text); 6263 6264 /* 6265 * Initializing the movement method will have set the 6266 * selection, so reset mSelectionMoved to keep that from 6267 * interfering with the normal on-focus selection-setting. 6268 */ 6269 if (mEditor != null) mEditor.mSelectionMoved = false; 6270 } 6271 } 6272 6273 if (mLayout != null) { 6274 checkForRelayout(); 6275 } 6276 6277 sendOnTextChanged(text, 0, oldlen, textLength); 6278 onTextChanged(text, 0, oldlen, textLength); 6279 6280 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6281 6282 if (needEditableForNotification) { 6283 sendAfterTextChanged((Editable) text); 6284 } else { 6285 notifyListeningManagersAfterTextChanged(); 6286 } 6287 6288 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6289 if (mEditor != null) mEditor.prepareCursorControllers(); 6290 } 6291 6292 /** 6293 * Sets the TextView to display the specified slice of the specified 6294 * char array. You must promise that you will not change the contents 6295 * of the array except for right before another call to setText(), 6296 * since the TextView has no way to know that the text 6297 * has changed and that it needs to invalidate and re-layout. 6298 * 6299 * @param text char array to be displayed 6300 * @param start start index in the char array 6301 * @param len length of char count after {@code start} 6302 */ setText(char[] text, int start, int len)6303 public final void setText(char[] text, int start, int len) { 6304 int oldlen = 0; 6305 6306 if (start < 0 || len < 0 || start + len > text.length) { 6307 throw new IndexOutOfBoundsException(start + ", " + len); 6308 } 6309 6310 /* 6311 * We must do the before-notification here ourselves because if 6312 * the old text is a CharWrapper we destroy it before calling 6313 * into the normal path. 6314 */ 6315 if (mText != null) { 6316 oldlen = mText.length(); 6317 sendBeforeTextChanged(mText, 0, oldlen, len); 6318 } else { 6319 sendBeforeTextChanged("", 0, 0, len); 6320 } 6321 6322 if (mCharWrapper == null) { 6323 mCharWrapper = new CharWrapper(text, start, len); 6324 } else { 6325 mCharWrapper.set(text, start, len); 6326 } 6327 6328 setText(mCharWrapper, mBufferType, false, oldlen); 6329 } 6330 6331 /** 6332 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6333 * the cursor position. Same as 6334 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6335 * position (if any) is retained in the new text. 6336 * <p/> 6337 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6338 * intermediate {@link Spannable Spannables}. Likewise it will use 6339 * {@link android.text.Editable.Factory} to create final or intermediate 6340 * {@link Editable Editables}. 6341 * 6342 * @param text text to be displayed 6343 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6344 * stored as a static text, styleable/spannable text, or editable text 6345 * 6346 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6347 */ setTextKeepState(CharSequence text, BufferType type)6348 public final void setTextKeepState(CharSequence text, BufferType type) { 6349 int start = getSelectionStart(); 6350 int end = getSelectionEnd(); 6351 int len = text.length(); 6352 6353 setText(text, type); 6354 6355 if (start >= 0 || end >= 0) { 6356 if (mSpannable != null) { 6357 Selection.setSelection(mSpannable, 6358 Math.max(0, Math.min(start, len)), 6359 Math.max(0, Math.min(end, len))); 6360 } 6361 } 6362 } 6363 6364 /** 6365 * Sets the text to be displayed using a string resource identifier. 6366 * 6367 * @param resid the resource identifier of the string resource to be displayed 6368 * 6369 * @see #setText(CharSequence) 6370 * 6371 * @attr ref android.R.styleable#TextView_text 6372 */ 6373 @android.view.RemotableViewMethod setText(@tringRes int resid)6374 public final void setText(@StringRes int resid) { 6375 setText(getContext().getResources().getText(resid)); 6376 mTextSetFromXmlOrResourceId = true; 6377 mTextId = resid; 6378 } 6379 6380 /** 6381 * Sets the text to be displayed using a string resource identifier and the 6382 * {@link android.widget.TextView.BufferType}. 6383 * <p/> 6384 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6385 * intermediate {@link Spannable Spannables}. Likewise it will use 6386 * {@link android.text.Editable.Factory} to create final or intermediate 6387 * {@link Editable Editables}. 6388 * 6389 * @param resid the resource identifier of the string resource to be displayed 6390 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6391 * stored as a static text, styleable/spannable text, or editable text 6392 * 6393 * @see #setText(int) 6394 * @see #setText(CharSequence) 6395 * @see android.widget.TextView.BufferType 6396 * @see #setSpannableFactory(Spannable.Factory) 6397 * @see #setEditableFactory(Editable.Factory) 6398 * 6399 * @attr ref android.R.styleable#TextView_text 6400 * @attr ref android.R.styleable#TextView_bufferType 6401 */ setText(@tringRes int resid, BufferType type)6402 public final void setText(@StringRes int resid, BufferType type) { 6403 setText(getContext().getResources().getText(resid), type); 6404 mTextSetFromXmlOrResourceId = true; 6405 mTextId = resid; 6406 } 6407 6408 /** 6409 * Sets the text to be displayed when the text of the TextView is empty. 6410 * Null means to use the normal empty text. The hint does not currently 6411 * participate in determining the size of the view. 6412 * 6413 * @attr ref android.R.styleable#TextView_hint 6414 */ 6415 @android.view.RemotableViewMethod setHint(CharSequence hint)6416 public final void setHint(CharSequence hint) { 6417 setHintInternal(hint); 6418 6419 if (mEditor != null && isInputMethodTarget()) { 6420 mEditor.reportExtractedText(); 6421 } 6422 } 6423 setHintInternal(CharSequence hint)6424 private void setHintInternal(CharSequence hint) { 6425 mHint = TextUtils.stringOrSpannedString(hint); 6426 6427 if (mLayout != null) { 6428 checkForRelayout(); 6429 } 6430 6431 if (mText.length() == 0) { 6432 invalidate(); 6433 } 6434 6435 // Invalidate display list if hint is currently used 6436 if (mEditor != null && mText.length() == 0 && mHint != null) { 6437 mEditor.invalidateTextDisplayList(); 6438 } 6439 } 6440 6441 /** 6442 * Sets the text to be displayed when the text of the TextView is empty, 6443 * from a resource. 6444 * 6445 * @attr ref android.R.styleable#TextView_hint 6446 */ 6447 @android.view.RemotableViewMethod setHint(@tringRes int resid)6448 public final void setHint(@StringRes int resid) { 6449 setHint(getContext().getResources().getText(resid)); 6450 } 6451 6452 /** 6453 * Returns the hint that is displayed when the text of the TextView 6454 * is empty. 6455 * 6456 * @attr ref android.R.styleable#TextView_hint 6457 */ 6458 @InspectableProperty 6459 @ViewDebug.CapturedViewProperty getHint()6460 public CharSequence getHint() { 6461 return mHint; 6462 } 6463 6464 /** 6465 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6466 * line characters instead of letting it wrap onto multiple lines. 6467 * 6468 * @attr ref android.R.styleable#TextView_singleLine 6469 */ 6470 @InspectableProperty isSingleLine()6471 public boolean isSingleLine() { 6472 return mSingleLine; 6473 } 6474 isMultilineInputType(int type)6475 private static boolean isMultilineInputType(int type) { 6476 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6477 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6478 } 6479 6480 /** 6481 * Removes the suggestion spans. 6482 */ removeSuggestionSpans(CharSequence text)6483 CharSequence removeSuggestionSpans(CharSequence text) { 6484 if (text instanceof Spanned) { 6485 Spannable spannable; 6486 if (text instanceof Spannable) { 6487 spannable = (Spannable) text; 6488 } else { 6489 spannable = mSpannableFactory.newSpannable(text); 6490 } 6491 6492 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6493 if (spans.length == 0) { 6494 return text; 6495 } else { 6496 text = spannable; 6497 } 6498 6499 for (int i = 0; i < spans.length; i++) { 6500 spannable.removeSpan(spans[i]); 6501 } 6502 } 6503 return text; 6504 } 6505 6506 /** 6507 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6508 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6509 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6510 * then a soft keyboard will not be displayed for this text view. 6511 * 6512 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6513 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6514 * type. 6515 * 6516 * @see #getInputType() 6517 * @see #setRawInputType(int) 6518 * @see android.text.InputType 6519 * @attr ref android.R.styleable#TextView_inputType 6520 */ setInputType(int type)6521 public void setInputType(int type) { 6522 final boolean wasPassword = isPasswordInputType(getInputType()); 6523 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6524 setInputType(type, false); 6525 final boolean isPassword = isPasswordInputType(type); 6526 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6527 boolean forceUpdate = false; 6528 if (isPassword) { 6529 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6530 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6531 Typeface.NORMAL, -1 /* weight, not specifeid */); 6532 } else if (isVisiblePassword) { 6533 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6534 forceUpdate = true; 6535 } 6536 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6537 Typeface.NORMAL, -1 /* weight, not specified */); 6538 } else if (wasPassword || wasVisiblePassword) { 6539 // not in password mode, clean up typeface and transformation 6540 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6541 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6542 -1 /* weight, not specified */); 6543 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6544 forceUpdate = true; 6545 } 6546 } 6547 6548 boolean singleLine = !isMultilineInputType(type); 6549 6550 // We need to update the single line mode if it has changed or we 6551 // were previously in password mode. 6552 if (mSingleLine != singleLine || forceUpdate) { 6553 // Change single line mode, but only change the transformation if 6554 // we are not in password mode. 6555 applySingleLine(singleLine, !isPassword, true); 6556 } 6557 6558 if (!isSuggestionsEnabled()) { 6559 setTextInternal(removeSuggestionSpans(mText)); 6560 } 6561 6562 InputMethodManager imm = getInputMethodManager(); 6563 if (imm != null) imm.restartInput(this); 6564 } 6565 6566 /** 6567 * It would be better to rely on the input type for everything. A password inputType should have 6568 * a password transformation. We should hence use isPasswordInputType instead of this method. 6569 * 6570 * We should: 6571 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6572 * would install the correct transformation). 6573 * - Refuse the installation of a non-password transformation in setTransformation if the input 6574 * type is password. 6575 * 6576 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6577 * is useful since it matches what the user can see (obfuscated text or not). 6578 * 6579 * @return true if the current transformation method is of the password type. 6580 */ hasPasswordTransformationMethod()6581 boolean hasPasswordTransformationMethod() { 6582 return mTransformation instanceof PasswordTransformationMethod; 6583 } 6584 6585 /** 6586 * Returns true if the current inputType is any type of password. 6587 * 6588 * @hide 6589 */ isAnyPasswordInputType()6590 public boolean isAnyPasswordInputType() { 6591 final int inputType = getInputType(); 6592 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6593 } 6594 isPasswordInputType(int inputType)6595 static boolean isPasswordInputType(int inputType) { 6596 final int variation = 6597 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6598 return variation 6599 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6600 || variation 6601 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6602 || variation 6603 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6604 } 6605 isVisiblePasswordInputType(int inputType)6606 private static boolean isVisiblePasswordInputType(int inputType) { 6607 final int variation = 6608 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6609 return variation 6610 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6611 } 6612 6613 /** 6614 * Directly change the content type integer of the text view, without 6615 * modifying any other state. 6616 * @see #setInputType(int) 6617 * @see android.text.InputType 6618 * @attr ref android.R.styleable#TextView_inputType 6619 */ setRawInputType(int type)6620 public void setRawInputType(int type) { 6621 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6622 createEditorIfNeeded(); 6623 mEditor.mInputType = type; 6624 } 6625 6626 /** 6627 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6628 * a {@code Locale} object that can be used to customize key various listeners. 6629 * @see DateKeyListener#getInstance(Locale) 6630 * @see DateTimeKeyListener#getInstance(Locale) 6631 * @see DigitsKeyListener#getInstance(Locale) 6632 * @see TimeKeyListener#getInstance(Locale) 6633 */ 6634 @Nullable getCustomLocaleForKeyListenerOrNull()6635 private Locale getCustomLocaleForKeyListenerOrNull() { 6636 if (!mUseInternationalizedInput) { 6637 // If the application does not target O, stick to the previous behavior. 6638 return null; 6639 } 6640 final LocaleList locales = getImeHintLocales(); 6641 if (locales == null) { 6642 // If the application does not explicitly specify IME hint locale, also stick to the 6643 // previous behavior. 6644 return null; 6645 } 6646 return locales.get(0); 6647 } 6648 6649 @UnsupportedAppUsage setInputType(int type, boolean direct)6650 private void setInputType(int type, boolean direct) { 6651 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6652 KeyListener input; 6653 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6654 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6655 TextKeyListener.Capitalize cap; 6656 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6657 cap = TextKeyListener.Capitalize.CHARACTERS; 6658 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6659 cap = TextKeyListener.Capitalize.WORDS; 6660 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6661 cap = TextKeyListener.Capitalize.SENTENCES; 6662 } else { 6663 cap = TextKeyListener.Capitalize.NONE; 6664 } 6665 input = TextKeyListener.getInstance(autotext, cap); 6666 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6667 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6668 input = DigitsKeyListener.getInstance( 6669 locale, 6670 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6671 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6672 if (locale != null) { 6673 // Override type, if necessary for i18n. 6674 int newType = input.getInputType(); 6675 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6676 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6677 // The class is different from the original class. So we need to override 6678 // 'type'. But we want to keep the password flag if it's there. 6679 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6680 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6681 } 6682 type = newType; 6683 } 6684 } 6685 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6686 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6687 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6688 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6689 input = DateKeyListener.getInstance(locale); 6690 break; 6691 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6692 input = TimeKeyListener.getInstance(locale); 6693 break; 6694 default: 6695 input = DateTimeKeyListener.getInstance(locale); 6696 break; 6697 } 6698 if (mUseInternationalizedInput) { 6699 type = input.getInputType(); // Override type, if necessary for i18n. 6700 } 6701 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6702 input = DialerKeyListener.getInstance(); 6703 } else { 6704 input = TextKeyListener.getInstance(); 6705 } 6706 setRawInputType(type); 6707 mListenerChanged = false; 6708 if (direct) { 6709 createEditorIfNeeded(); 6710 mEditor.mKeyListener = input; 6711 } else { 6712 setKeyListenerOnly(input); 6713 } 6714 } 6715 6716 /** 6717 * Get the type of the editable content. 6718 * 6719 * @see #setInputType(int) 6720 * @see android.text.InputType 6721 */ 6722 @InspectableProperty(flagMapping = { 6723 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6724 @FlagEntry( 6725 name = "text", 6726 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6727 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6728 @FlagEntry( 6729 name = "textUri", 6730 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6731 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6732 @FlagEntry( 6733 name = "textEmailAddress", 6734 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6735 target = InputType.TYPE_CLASS_TEXT 6736 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6737 @FlagEntry( 6738 name = "textEmailSubject", 6739 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6740 target = InputType.TYPE_CLASS_TEXT 6741 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6742 @FlagEntry( 6743 name = "textShortMessage", 6744 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6745 target = InputType.TYPE_CLASS_TEXT 6746 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6747 @FlagEntry( 6748 name = "textLongMessage", 6749 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6750 target = InputType.TYPE_CLASS_TEXT 6751 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6752 @FlagEntry( 6753 name = "textPersonName", 6754 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6755 target = InputType.TYPE_CLASS_TEXT 6756 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6757 @FlagEntry( 6758 name = "textPostalAddress", 6759 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6760 target = InputType.TYPE_CLASS_TEXT 6761 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6762 @FlagEntry( 6763 name = "textPassword", 6764 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6765 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6766 @FlagEntry( 6767 name = "textVisiblePassword", 6768 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6769 target = InputType.TYPE_CLASS_TEXT 6770 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6771 @FlagEntry( 6772 name = "textWebEditText", 6773 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6774 target = InputType.TYPE_CLASS_TEXT 6775 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6776 @FlagEntry( 6777 name = "textFilter", 6778 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6779 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6780 @FlagEntry( 6781 name = "textPhonetic", 6782 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6783 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6784 @FlagEntry( 6785 name = "textWebEmailAddress", 6786 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6787 target = InputType.TYPE_CLASS_TEXT 6788 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6789 @FlagEntry( 6790 name = "textWebPassword", 6791 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6792 target = InputType.TYPE_CLASS_TEXT 6793 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6794 @FlagEntry( 6795 name = "number", 6796 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6797 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6798 @FlagEntry( 6799 name = "numberPassword", 6800 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6801 target = InputType.TYPE_CLASS_NUMBER 6802 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6803 @FlagEntry( 6804 name = "phone", 6805 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6806 target = InputType.TYPE_CLASS_PHONE), 6807 @FlagEntry( 6808 name = "datetime", 6809 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6810 target = InputType.TYPE_CLASS_DATETIME 6811 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6812 @FlagEntry( 6813 name = "date", 6814 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6815 target = InputType.TYPE_CLASS_DATETIME 6816 | InputType.TYPE_DATETIME_VARIATION_DATE), 6817 @FlagEntry( 6818 name = "time", 6819 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6820 target = InputType.TYPE_CLASS_DATETIME 6821 | InputType.TYPE_DATETIME_VARIATION_TIME), 6822 @FlagEntry( 6823 name = "textCapCharacters", 6824 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6825 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6826 @FlagEntry( 6827 name = "textCapWords", 6828 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6829 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6830 @FlagEntry( 6831 name = "textCapSentences", 6832 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6833 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6834 @FlagEntry( 6835 name = "textAutoCorrect", 6836 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6837 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6838 @FlagEntry( 6839 name = "textAutoComplete", 6840 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6841 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6842 @FlagEntry( 6843 name = "textMultiLine", 6844 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6845 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6846 @FlagEntry( 6847 name = "textImeMultiLine", 6848 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6849 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6850 @FlagEntry( 6851 name = "textNoSuggestions", 6852 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6853 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6854 @FlagEntry( 6855 name = "numberSigned", 6856 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6857 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6858 @FlagEntry( 6859 name = "numberDecimal", 6860 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6861 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6862 }) getInputType()6863 public int getInputType() { 6864 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6865 } 6866 6867 /** 6868 * Change the editor type integer associated with the text view, which 6869 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6870 * when it has focus. 6871 * @see #getImeOptions 6872 * @see android.view.inputmethod.EditorInfo 6873 * @attr ref android.R.styleable#TextView_imeOptions 6874 */ setImeOptions(int imeOptions)6875 public void setImeOptions(int imeOptions) { 6876 createEditorIfNeeded(); 6877 mEditor.createInputContentTypeIfNeeded(); 6878 mEditor.mInputContentType.imeOptions = imeOptions; 6879 } 6880 6881 /** 6882 * Get the type of the Input Method Editor (IME). 6883 * @return the type of the IME 6884 * @see #setImeOptions(int) 6885 * @see EditorInfo 6886 */ 6887 @InspectableProperty(flagMapping = { 6888 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 6889 @FlagEntry( 6890 name = "actionUnspecified", 6891 mask = EditorInfo.IME_MASK_ACTION, 6892 target = EditorInfo.IME_ACTION_UNSPECIFIED), 6893 @FlagEntry( 6894 name = "actionNone", 6895 mask = EditorInfo.IME_MASK_ACTION, 6896 target = EditorInfo.IME_ACTION_NONE), 6897 @FlagEntry( 6898 name = "actionGo", 6899 mask = EditorInfo.IME_MASK_ACTION, 6900 target = EditorInfo.IME_ACTION_GO), 6901 @FlagEntry( 6902 name = "actionSearch", 6903 mask = EditorInfo.IME_MASK_ACTION, 6904 target = EditorInfo.IME_ACTION_SEARCH), 6905 @FlagEntry( 6906 name = "actionSend", 6907 mask = EditorInfo.IME_MASK_ACTION, 6908 target = EditorInfo.IME_ACTION_SEND), 6909 @FlagEntry( 6910 name = "actionNext", 6911 mask = EditorInfo.IME_MASK_ACTION, 6912 target = EditorInfo.IME_ACTION_NEXT), 6913 @FlagEntry( 6914 name = "actionDone", 6915 mask = EditorInfo.IME_MASK_ACTION, 6916 target = EditorInfo.IME_ACTION_DONE), 6917 @FlagEntry( 6918 name = "actionPrevious", 6919 mask = EditorInfo.IME_MASK_ACTION, 6920 target = EditorInfo.IME_ACTION_PREVIOUS), 6921 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 6922 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 6923 @FlagEntry( 6924 name = "flagNavigatePrevious", 6925 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 6926 @FlagEntry( 6927 name = "flagNoAccessoryAction", 6928 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 6929 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 6930 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 6931 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 6932 @FlagEntry( 6933 name = "flagNoPersonalizedLearning", 6934 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 6935 }) getImeOptions()6936 public int getImeOptions() { 6937 return mEditor != null && mEditor.mInputContentType != null 6938 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 6939 } 6940 6941 /** 6942 * Change the custom IME action associated with the text view, which 6943 * will be reported to an IME with {@link EditorInfo#actionLabel} 6944 * and {@link EditorInfo#actionId} when it has focus. 6945 * @see #getImeActionLabel 6946 * @see #getImeActionId 6947 * @see android.view.inputmethod.EditorInfo 6948 * @attr ref android.R.styleable#TextView_imeActionLabel 6949 * @attr ref android.R.styleable#TextView_imeActionId 6950 */ setImeActionLabel(CharSequence label, int actionId)6951 public void setImeActionLabel(CharSequence label, int actionId) { 6952 createEditorIfNeeded(); 6953 mEditor.createInputContentTypeIfNeeded(); 6954 mEditor.mInputContentType.imeActionLabel = label; 6955 mEditor.mInputContentType.imeActionId = actionId; 6956 } 6957 6958 /** 6959 * Get the IME action label previous set with {@link #setImeActionLabel}. 6960 * 6961 * @see #setImeActionLabel 6962 * @see android.view.inputmethod.EditorInfo 6963 */ 6964 @InspectableProperty getImeActionLabel()6965 public CharSequence getImeActionLabel() { 6966 return mEditor != null && mEditor.mInputContentType != null 6967 ? mEditor.mInputContentType.imeActionLabel : null; 6968 } 6969 6970 /** 6971 * Get the IME action ID previous set with {@link #setImeActionLabel}. 6972 * 6973 * @see #setImeActionLabel 6974 * @see android.view.inputmethod.EditorInfo 6975 */ 6976 @InspectableProperty getImeActionId()6977 public int getImeActionId() { 6978 return mEditor != null && mEditor.mInputContentType != null 6979 ? mEditor.mInputContentType.imeActionId : 0; 6980 } 6981 6982 /** 6983 * Set a special listener to be called when an action is performed 6984 * on the text view. This will be called when the enter key is pressed, 6985 * or when an action supplied to the IME is selected by the user. Setting 6986 * this means that the normal hard key event will not insert a newline 6987 * into the text view, even if it is multi-line; holding down the ALT 6988 * modifier will, however, allow the user to insert a newline character. 6989 */ setOnEditorActionListener(OnEditorActionListener l)6990 public void setOnEditorActionListener(OnEditorActionListener l) { 6991 createEditorIfNeeded(); 6992 mEditor.createInputContentTypeIfNeeded(); 6993 mEditor.mInputContentType.onEditorActionListener = l; 6994 } 6995 6996 /** 6997 * Called when an attached input method calls 6998 * {@link InputConnection#performEditorAction(int) 6999 * InputConnection.performEditorAction()} 7000 * for this text view. The default implementation will call your action 7001 * listener supplied to {@link #setOnEditorActionListener}, or perform 7002 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7003 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7004 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7005 * EditorInfo.IME_ACTION_DONE}. 7006 * 7007 * <p>For backwards compatibility, if no IME options have been set and the 7008 * text view would not normally advance focus on enter, then 7009 * the NEXT and DONE actions received here will be turned into an enter 7010 * key down/up pair to go through the normal key handling. 7011 * 7012 * @param actionCode The code of the action being performed. 7013 * 7014 * @see #setOnEditorActionListener 7015 */ onEditorAction(int actionCode)7016 public void onEditorAction(int actionCode) { 7017 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7018 if (ict != null) { 7019 if (ict.onEditorActionListener != null) { 7020 if (ict.onEditorActionListener.onEditorAction(this, 7021 actionCode, null)) { 7022 return; 7023 } 7024 } 7025 7026 // This is the handling for some default action. 7027 // Note that for backwards compatibility we don't do this 7028 // default handling if explicit ime options have not been given, 7029 // instead turning this into the normal enter key codes that an 7030 // app may be expecting. 7031 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7032 View v = focusSearch(FOCUS_FORWARD); 7033 if (v != null) { 7034 if (!v.requestFocus(FOCUS_FORWARD)) { 7035 throw new IllegalStateException("focus search returned a view " 7036 + "that wasn't able to take focus!"); 7037 } 7038 } 7039 return; 7040 7041 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7042 View v = focusSearch(FOCUS_BACKWARD); 7043 if (v != null) { 7044 if (!v.requestFocus(FOCUS_BACKWARD)) { 7045 throw new IllegalStateException("focus search returned a view " 7046 + "that wasn't able to take focus!"); 7047 } 7048 } 7049 return; 7050 7051 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7052 InputMethodManager imm = getInputMethodManager(); 7053 if (imm != null && imm.isActive(this)) { 7054 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7055 } 7056 return; 7057 } 7058 } 7059 7060 ViewRootImpl viewRootImpl = getViewRootImpl(); 7061 if (viewRootImpl != null) { 7062 long eventTime = SystemClock.uptimeMillis(); 7063 viewRootImpl.dispatchKeyFromIme( 7064 new KeyEvent(eventTime, eventTime, 7065 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7066 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7067 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7068 | KeyEvent.FLAG_EDITOR_ACTION)); 7069 viewRootImpl.dispatchKeyFromIme( 7070 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7071 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7072 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7073 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7074 | KeyEvent.FLAG_EDITOR_ACTION)); 7075 } 7076 } 7077 7078 /** 7079 * Set the private content type of the text, which is the 7080 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7081 * field that will be filled in when creating an input connection. 7082 * 7083 * @see #getPrivateImeOptions() 7084 * @see EditorInfo#privateImeOptions 7085 * @attr ref android.R.styleable#TextView_privateImeOptions 7086 */ setPrivateImeOptions(String type)7087 public void setPrivateImeOptions(String type) { 7088 createEditorIfNeeded(); 7089 mEditor.createInputContentTypeIfNeeded(); 7090 mEditor.mInputContentType.privateImeOptions = type; 7091 } 7092 7093 /** 7094 * Get the private type of the content. 7095 * 7096 * @see #setPrivateImeOptions(String) 7097 * @see EditorInfo#privateImeOptions 7098 */ 7099 @InspectableProperty getPrivateImeOptions()7100 public String getPrivateImeOptions() { 7101 return mEditor != null && mEditor.mInputContentType != null 7102 ? mEditor.mInputContentType.privateImeOptions : null; 7103 } 7104 7105 /** 7106 * Set the extra input data of the text, which is the 7107 * {@link EditorInfo#extras TextBoxAttribute.extras} 7108 * Bundle that will be filled in when creating an input connection. The 7109 * given integer is the resource identifier of an XML resource holding an 7110 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7111 * 7112 * @see #getInputExtras(boolean) 7113 * @see EditorInfo#extras 7114 * @attr ref android.R.styleable#TextView_editorExtras 7115 */ setInputExtras(@mlRes int xmlResId)7116 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7117 createEditorIfNeeded(); 7118 XmlResourceParser parser = getResources().getXml(xmlResId); 7119 mEditor.createInputContentTypeIfNeeded(); 7120 mEditor.mInputContentType.extras = new Bundle(); 7121 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7122 } 7123 7124 /** 7125 * Retrieve the input extras currently associated with the text view, which 7126 * can be viewed as well as modified. 7127 * 7128 * @param create If true, the extras will be created if they don't already 7129 * exist. Otherwise, null will be returned if none have been created. 7130 * @see #setInputExtras(int) 7131 * @see EditorInfo#extras 7132 * @attr ref android.R.styleable#TextView_editorExtras 7133 */ getInputExtras(boolean create)7134 public Bundle getInputExtras(boolean create) { 7135 if (mEditor == null && !create) return null; 7136 createEditorIfNeeded(); 7137 if (mEditor.mInputContentType == null) { 7138 if (!create) return null; 7139 mEditor.createInputContentTypeIfNeeded(); 7140 } 7141 if (mEditor.mInputContentType.extras == null) { 7142 if (!create) return null; 7143 mEditor.mInputContentType.extras = new Bundle(); 7144 } 7145 return mEditor.mInputContentType.extras; 7146 } 7147 7148 /** 7149 * Change "hint" locales associated with the text view, which will be reported to an IME with 7150 * {@link EditorInfo#hintLocales} when it has focus. 7151 * 7152 * Starting with Android O, this also causes internationalized listeners to be created (or 7153 * change locale) based on the first locale in the input locale list. 7154 * 7155 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7156 * call {@link InputMethodManager#restartInput(View)}.</p> 7157 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7158 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7159 * @see #getImeHintLocales() 7160 * @see android.view.inputmethod.EditorInfo#hintLocales 7161 */ setImeHintLocales(@ullable LocaleList hintLocales)7162 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7163 createEditorIfNeeded(); 7164 mEditor.createInputContentTypeIfNeeded(); 7165 mEditor.mInputContentType.imeHintLocales = hintLocales; 7166 if (mUseInternationalizedInput) { 7167 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7168 } 7169 } 7170 7171 /** 7172 * @return The current languages list "hint". {@code null} when no "hint" is available. 7173 * @see #setImeHintLocales(LocaleList) 7174 * @see android.view.inputmethod.EditorInfo#hintLocales 7175 */ 7176 @Nullable getImeHintLocales()7177 public LocaleList getImeHintLocales() { 7178 if (mEditor == null) { 7179 return null; 7180 } 7181 if (mEditor.mInputContentType == null) { 7182 return null; 7183 } 7184 return mEditor.mInputContentType.imeHintLocales; 7185 } 7186 7187 /** 7188 * Returns the error message that was set to be displayed with 7189 * {@link #setError}, or <code>null</code> if no error was set 7190 * or if it the error was cleared by the widget after user input. 7191 */ getError()7192 public CharSequence getError() { 7193 return mEditor == null ? null : mEditor.mError; 7194 } 7195 7196 /** 7197 * Sets the right-hand compound drawable of the TextView to the "error" 7198 * icon and sets an error message that will be displayed in a popup when 7199 * the TextView has focus. The icon and error message will be reset to 7200 * null when any key events cause changes to the TextView's text. If the 7201 * <code>error</code> is <code>null</code>, the error message and icon 7202 * will be cleared. 7203 */ 7204 @android.view.RemotableViewMethod setError(CharSequence error)7205 public void setError(CharSequence error) { 7206 if (error == null) { 7207 setError(null, null); 7208 } else { 7209 Drawable dr = getContext().getDrawable( 7210 com.android.internal.R.drawable.indicator_input_error); 7211 7212 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7213 setError(error, dr); 7214 } 7215 } 7216 7217 /** 7218 * Sets the right-hand compound drawable of the TextView to the specified 7219 * icon and sets an error message that will be displayed in a popup when 7220 * the TextView has focus. The icon and error message will be reset to 7221 * null when any key events cause changes to the TextView's text. The 7222 * drawable must already have had {@link Drawable#setBounds} set on it. 7223 * If the <code>error</code> is <code>null</code>, the error message will 7224 * be cleared (and you should provide a <code>null</code> icon as well). 7225 */ setError(CharSequence error, Drawable icon)7226 public void setError(CharSequence error, Drawable icon) { 7227 createEditorIfNeeded(); 7228 mEditor.setError(error, icon); 7229 notifyViewAccessibilityStateChangedIfNeeded( 7230 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7231 } 7232 7233 @Override setFrame(int l, int t, int r, int b)7234 protected boolean setFrame(int l, int t, int r, int b) { 7235 boolean result = super.setFrame(l, t, r, b); 7236 7237 if (mEditor != null) mEditor.setFrame(); 7238 7239 restartMarqueeIfNeeded(); 7240 7241 return result; 7242 } 7243 restartMarqueeIfNeeded()7244 private void restartMarqueeIfNeeded() { 7245 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7246 mRestartMarquee = false; 7247 startMarquee(); 7248 } 7249 } 7250 7251 /** 7252 * Sets the list of input filters that will be used if the buffer is 7253 * Editable. Has no effect otherwise. 7254 * 7255 * @attr ref android.R.styleable#TextView_maxLength 7256 */ setFilters(InputFilter[] filters)7257 public void setFilters(InputFilter[] filters) { 7258 if (filters == null) { 7259 throw new IllegalArgumentException(); 7260 } 7261 7262 mFilters = filters; 7263 7264 if (mText instanceof Editable) { 7265 setFilters((Editable) mText, filters); 7266 } 7267 } 7268 7269 /** 7270 * Sets the list of input filters on the specified Editable, 7271 * and includes mInput in the list if it is an InputFilter. 7272 */ setFilters(Editable e, InputFilter[] filters)7273 private void setFilters(Editable e, InputFilter[] filters) { 7274 if (mEditor != null) { 7275 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7276 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7277 int num = 0; 7278 if (undoFilter) num++; 7279 if (keyFilter) num++; 7280 if (num > 0) { 7281 InputFilter[] nf = new InputFilter[filters.length + num]; 7282 7283 System.arraycopy(filters, 0, nf, 0, filters.length); 7284 num = 0; 7285 if (undoFilter) { 7286 nf[filters.length] = mEditor.mUndoInputFilter; 7287 num++; 7288 } 7289 if (keyFilter) { 7290 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7291 } 7292 7293 e.setFilters(nf); 7294 return; 7295 } 7296 } 7297 e.setFilters(filters); 7298 } 7299 7300 /** 7301 * Returns the current list of input filters. 7302 * 7303 * @attr ref android.R.styleable#TextView_maxLength 7304 */ getFilters()7305 public InputFilter[] getFilters() { 7306 return mFilters; 7307 } 7308 7309 ///////////////////////////////////////////////////////////////////////// 7310 getBoxHeight(Layout l)7311 private int getBoxHeight(Layout l) { 7312 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7313 int padding = (l == mHintLayout) 7314 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7315 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7316 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7317 } 7318 7319 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7320 int getVerticalOffset(boolean forceNormal) { 7321 int voffset = 0; 7322 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7323 7324 Layout l = mLayout; 7325 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7326 l = mHintLayout; 7327 } 7328 7329 if (gravity != Gravity.TOP) { 7330 int boxht = getBoxHeight(l); 7331 int textht = l.getHeight(); 7332 7333 if (textht < boxht) { 7334 if (gravity == Gravity.BOTTOM) { 7335 voffset = boxht - textht; 7336 } else { // (gravity == Gravity.CENTER_VERTICAL) 7337 voffset = (boxht - textht) >> 1; 7338 } 7339 } 7340 } 7341 return voffset; 7342 } 7343 getBottomVerticalOffset(boolean forceNormal)7344 private int getBottomVerticalOffset(boolean forceNormal) { 7345 int voffset = 0; 7346 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7347 7348 Layout l = mLayout; 7349 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7350 l = mHintLayout; 7351 } 7352 7353 if (gravity != Gravity.BOTTOM) { 7354 int boxht = getBoxHeight(l); 7355 int textht = l.getHeight(); 7356 7357 if (textht < boxht) { 7358 if (gravity == Gravity.TOP) { 7359 voffset = boxht - textht; 7360 } else { // (gravity == Gravity.CENTER_VERTICAL) 7361 voffset = (boxht - textht) >> 1; 7362 } 7363 } 7364 } 7365 return voffset; 7366 } 7367 invalidateCursorPath()7368 void invalidateCursorPath() { 7369 if (mHighlightPathBogus) { 7370 invalidateCursor(); 7371 } else { 7372 final int horizontalPadding = getCompoundPaddingLeft(); 7373 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7374 7375 if (mEditor.mDrawableForCursor == null) { 7376 synchronized (TEMP_RECTF) { 7377 /* 7378 * The reason for this concern about the thickness of the 7379 * cursor and doing the floor/ceil on the coordinates is that 7380 * some EditTexts (notably textfields in the Browser) have 7381 * anti-aliased text where not all the characters are 7382 * necessarily at integer-multiple locations. This should 7383 * make sure the entire cursor gets invalidated instead of 7384 * sometimes missing half a pixel. 7385 */ 7386 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7387 if (thick < 1.0f) { 7388 thick = 1.0f; 7389 } 7390 7391 thick /= 2.0f; 7392 7393 // mHighlightPath is guaranteed to be non null at that point. 7394 mHighlightPath.computeBounds(TEMP_RECTF, false); 7395 7396 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7397 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7398 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7399 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7400 } 7401 } else { 7402 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7403 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7404 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7405 } 7406 } 7407 } 7408 invalidateCursor()7409 void invalidateCursor() { 7410 int where = getSelectionEnd(); 7411 7412 invalidateCursor(where, where, where); 7413 } 7414 invalidateCursor(int a, int b, int c)7415 private void invalidateCursor(int a, int b, int c) { 7416 if (a >= 0 || b >= 0 || c >= 0) { 7417 int start = Math.min(Math.min(a, b), c); 7418 int end = Math.max(Math.max(a, b), c); 7419 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7420 } 7421 } 7422 7423 /** 7424 * Invalidates the region of text enclosed between the start and end text offsets. 7425 */ invalidateRegion(int start, int end, boolean invalidateCursor)7426 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7427 if (mLayout == null) { 7428 invalidate(); 7429 } else { 7430 int lineStart = mLayout.getLineForOffset(start); 7431 int top = mLayout.getLineTop(lineStart); 7432 7433 // This is ridiculous, but the descent from the line above 7434 // can hang down into the line we really want to redraw, 7435 // so we have to invalidate part of the line above to make 7436 // sure everything that needs to be redrawn really is. 7437 // (But not the whole line above, because that would cause 7438 // the same problem with the descenders on the line above it!) 7439 if (lineStart > 0) { 7440 top -= mLayout.getLineDescent(lineStart - 1); 7441 } 7442 7443 int lineEnd; 7444 7445 if (start == end) { 7446 lineEnd = lineStart; 7447 } else { 7448 lineEnd = mLayout.getLineForOffset(end); 7449 } 7450 7451 int bottom = mLayout.getLineBottom(lineEnd); 7452 7453 // mEditor can be null in case selection is set programmatically. 7454 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7455 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7456 top = Math.min(top, bounds.top); 7457 bottom = Math.max(bottom, bounds.bottom); 7458 } 7459 7460 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7461 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7462 7463 int left, right; 7464 if (lineStart == lineEnd && !invalidateCursor) { 7465 left = (int) mLayout.getPrimaryHorizontal(start); 7466 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7467 left += compoundPaddingLeft; 7468 right += compoundPaddingLeft; 7469 } else { 7470 // Rectangle bounding box when the region spans several lines 7471 left = compoundPaddingLeft; 7472 right = getWidth() - getCompoundPaddingRight(); 7473 } 7474 7475 invalidate(mScrollX + left, verticalPadding + top, 7476 mScrollX + right, verticalPadding + bottom); 7477 } 7478 } 7479 registerForPreDraw()7480 private void registerForPreDraw() { 7481 if (!mPreDrawRegistered) { 7482 getViewTreeObserver().addOnPreDrawListener(this); 7483 mPreDrawRegistered = true; 7484 } 7485 } 7486 unregisterForPreDraw()7487 private void unregisterForPreDraw() { 7488 getViewTreeObserver().removeOnPreDrawListener(this); 7489 mPreDrawRegistered = false; 7490 mPreDrawListenerDetached = false; 7491 } 7492 7493 /** 7494 * {@inheritDoc} 7495 */ 7496 @Override onPreDraw()7497 public boolean onPreDraw() { 7498 if (mLayout == null) { 7499 assumeLayout(); 7500 } 7501 7502 if (mMovement != null) { 7503 /* This code also provides auto-scrolling when a cursor is moved using a 7504 * CursorController (insertion point or selection limits). 7505 * For selection, ensure start or end is visible depending on controller's state. 7506 */ 7507 int curs = getSelectionEnd(); 7508 // Do not create the controller if it is not already created. 7509 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7510 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7511 curs = getSelectionStart(); 7512 } 7513 7514 /* 7515 * TODO: This should really only keep the end in view if 7516 * it already was before the text changed. I'm not sure 7517 * of a good way to tell from here if it was. 7518 */ 7519 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7520 curs = mText.length(); 7521 } 7522 7523 if (curs >= 0) { 7524 bringPointIntoView(curs); 7525 } 7526 } else { 7527 bringTextIntoView(); 7528 } 7529 7530 // This has to be checked here since: 7531 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7532 // a screen rotation) since layout is not yet initialized at that point. 7533 if (mEditor != null && mEditor.mCreatedWithASelection) { 7534 mEditor.refreshTextActionMode(); 7535 mEditor.mCreatedWithASelection = false; 7536 } 7537 7538 unregisterForPreDraw(); 7539 7540 return true; 7541 } 7542 7543 @Override onAttachedToWindow()7544 protected void onAttachedToWindow() { 7545 super.onAttachedToWindow(); 7546 7547 if (mEditor != null) mEditor.onAttachedToWindow(); 7548 7549 if (mPreDrawListenerDetached) { 7550 getViewTreeObserver().addOnPreDrawListener(this); 7551 mPreDrawListenerDetached = false; 7552 } 7553 } 7554 7555 /** @hide */ 7556 @Override onDetachedFromWindowInternal()7557 protected void onDetachedFromWindowInternal() { 7558 if (mPreDrawRegistered) { 7559 getViewTreeObserver().removeOnPreDrawListener(this); 7560 mPreDrawListenerDetached = true; 7561 } 7562 7563 resetResolvedDrawables(); 7564 7565 if (mEditor != null) mEditor.onDetachedFromWindow(); 7566 7567 super.onDetachedFromWindowInternal(); 7568 } 7569 7570 @Override onScreenStateChanged(int screenState)7571 public void onScreenStateChanged(int screenState) { 7572 super.onScreenStateChanged(screenState); 7573 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7574 } 7575 7576 @Override isPaddingOffsetRequired()7577 protected boolean isPaddingOffsetRequired() { 7578 return mShadowRadius != 0 || mDrawables != null; 7579 } 7580 7581 @Override getLeftPaddingOffset()7582 protected int getLeftPaddingOffset() { 7583 return getCompoundPaddingLeft() - mPaddingLeft 7584 + (int) Math.min(0, mShadowDx - mShadowRadius); 7585 } 7586 7587 @Override getTopPaddingOffset()7588 protected int getTopPaddingOffset() { 7589 return (int) Math.min(0, mShadowDy - mShadowRadius); 7590 } 7591 7592 @Override getBottomPaddingOffset()7593 protected int getBottomPaddingOffset() { 7594 return (int) Math.max(0, mShadowDy + mShadowRadius); 7595 } 7596 7597 @Override getRightPaddingOffset()7598 protected int getRightPaddingOffset() { 7599 return -(getCompoundPaddingRight() - mPaddingRight) 7600 + (int) Math.max(0, mShadowDx + mShadowRadius); 7601 } 7602 7603 @Override verifyDrawable(@onNull Drawable who)7604 protected boolean verifyDrawable(@NonNull Drawable who) { 7605 final boolean verified = super.verifyDrawable(who); 7606 if (!verified && mDrawables != null) { 7607 for (Drawable dr : mDrawables.mShowing) { 7608 if (who == dr) { 7609 return true; 7610 } 7611 } 7612 } 7613 return verified; 7614 } 7615 7616 @Override jumpDrawablesToCurrentState()7617 public void jumpDrawablesToCurrentState() { 7618 super.jumpDrawablesToCurrentState(); 7619 if (mDrawables != null) { 7620 for (Drawable dr : mDrawables.mShowing) { 7621 if (dr != null) { 7622 dr.jumpToCurrentState(); 7623 } 7624 } 7625 } 7626 } 7627 7628 @Override invalidateDrawable(@onNull Drawable drawable)7629 public void invalidateDrawable(@NonNull Drawable drawable) { 7630 boolean handled = false; 7631 7632 if (verifyDrawable(drawable)) { 7633 final Rect dirty = drawable.getBounds(); 7634 int scrollX = mScrollX; 7635 int scrollY = mScrollY; 7636 7637 // IMPORTANT: The coordinates below are based on the coordinates computed 7638 // for each compound drawable in onDraw(). Make sure to update each section 7639 // accordingly. 7640 final TextView.Drawables drawables = mDrawables; 7641 if (drawables != null) { 7642 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7643 final int compoundPaddingTop = getCompoundPaddingTop(); 7644 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7645 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7646 7647 scrollX += mPaddingLeft; 7648 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7649 handled = true; 7650 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7651 final int compoundPaddingTop = getCompoundPaddingTop(); 7652 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7653 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7654 7655 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7656 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7657 handled = true; 7658 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7659 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7660 final int compoundPaddingRight = getCompoundPaddingRight(); 7661 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7662 7663 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7664 scrollY += mPaddingTop; 7665 handled = true; 7666 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7667 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7668 final int compoundPaddingRight = getCompoundPaddingRight(); 7669 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7670 7671 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7672 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7673 handled = true; 7674 } 7675 } 7676 7677 if (handled) { 7678 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7679 dirty.right + scrollX, dirty.bottom + scrollY); 7680 } 7681 } 7682 7683 if (!handled) { 7684 super.invalidateDrawable(drawable); 7685 } 7686 } 7687 7688 @Override hasOverlappingRendering()7689 public boolean hasOverlappingRendering() { 7690 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7691 return ((getBackground() != null && getBackground().getCurrent() != null) 7692 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7693 || mShadowColor != 0); 7694 } 7695 7696 /** 7697 * 7698 * Returns the state of the {@code textIsSelectable} flag (See 7699 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7700 * to allow users to select and copy text in a non-editable TextView, the content of an 7701 * {@link EditText} can always be selected, independently of the value of this flag. 7702 * <p> 7703 * 7704 * @return True if the text displayed in this TextView can be selected by the user. 7705 * 7706 * @attr ref android.R.styleable#TextView_textIsSelectable 7707 */ 7708 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7709 public boolean isTextSelectable() { 7710 return mEditor == null ? false : mEditor.mTextIsSelectable; 7711 } 7712 7713 /** 7714 * Sets whether the content of this view is selectable by the user. The default is 7715 * {@code false}, meaning that the content is not selectable. 7716 * <p> 7717 * When you use a TextView to display a useful piece of information to the user (such as a 7718 * contact's address), make it selectable, so that the user can select and copy its 7719 * content. You can also use set the XML attribute 7720 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7721 * <p> 7722 * When you call this method to set the value of {@code textIsSelectable}, it sets 7723 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7724 * and {@code longClickable} to the same value. These flags correspond to the attributes 7725 * {@link android.R.styleable#View_focusable android:focusable}, 7726 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7727 * {@link android.R.styleable#View_clickable android:clickable}, and 7728 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7729 * flags to a state you had set previously, call one or more of the following methods: 7730 * {@link #setFocusable(boolean) setFocusable()}, 7731 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7732 * {@link #setClickable(boolean) setClickable()} or 7733 * {@link #setLongClickable(boolean) setLongClickable()}. 7734 * 7735 * @param selectable Whether the content of this TextView should be selectable. 7736 */ setTextIsSelectable(boolean selectable)7737 public void setTextIsSelectable(boolean selectable) { 7738 if (!selectable && mEditor == null) return; // false is default value with no edit data 7739 7740 createEditorIfNeeded(); 7741 if (mEditor.mTextIsSelectable == selectable) return; 7742 7743 mEditor.mTextIsSelectable = selectable; 7744 setFocusableInTouchMode(selectable); 7745 setFocusable(FOCUSABLE_AUTO); 7746 setClickable(selectable); 7747 setLongClickable(selectable); 7748 7749 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7750 7751 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7752 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7753 7754 // Called by setText above, but safer in case of future code changes 7755 mEditor.prepareCursorControllers(); 7756 } 7757 7758 @Override onCreateDrawableState(int extraSpace)7759 protected int[] onCreateDrawableState(int extraSpace) { 7760 final int[] drawableState; 7761 7762 if (mSingleLine) { 7763 drawableState = super.onCreateDrawableState(extraSpace); 7764 } else { 7765 drawableState = super.onCreateDrawableState(extraSpace + 1); 7766 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7767 } 7768 7769 if (isTextSelectable()) { 7770 // Disable pressed state, which was introduced when TextView was made clickable. 7771 // Prevents text color change. 7772 // setClickable(false) would have a similar effect, but it also disables focus changes 7773 // and long press actions, which are both needed by text selection. 7774 final int length = drawableState.length; 7775 for (int i = 0; i < length; i++) { 7776 if (drawableState[i] == R.attr.state_pressed) { 7777 final int[] nonPressedState = new int[length - 1]; 7778 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7779 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7780 return nonPressedState; 7781 } 7782 } 7783 } 7784 7785 return drawableState; 7786 } 7787 7788 @UnsupportedAppUsage getUpdatedHighlightPath()7789 private Path getUpdatedHighlightPath() { 7790 Path highlight = null; 7791 Paint highlightPaint = mHighlightPaint; 7792 7793 final int selStart = getSelectionStart(); 7794 final int selEnd = getSelectionEnd(); 7795 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7796 if (selStart == selEnd) { 7797 if (mEditor != null && mEditor.shouldRenderCursor()) { 7798 if (mHighlightPathBogus) { 7799 if (mHighlightPath == null) mHighlightPath = new Path(); 7800 mHighlightPath.reset(); 7801 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7802 mEditor.updateCursorPosition(); 7803 mHighlightPathBogus = false; 7804 } 7805 7806 // XXX should pass to skin instead of drawing directly 7807 highlightPaint.setColor(mCurTextColor); 7808 highlightPaint.setStyle(Paint.Style.STROKE); 7809 highlight = mHighlightPath; 7810 } 7811 } else { 7812 if (mHighlightPathBogus) { 7813 if (mHighlightPath == null) mHighlightPath = new Path(); 7814 mHighlightPath.reset(); 7815 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7816 mHighlightPathBogus = false; 7817 } 7818 7819 // XXX should pass to skin instead of drawing directly 7820 highlightPaint.setColor(mHighlightColor); 7821 highlightPaint.setStyle(Paint.Style.FILL); 7822 7823 highlight = mHighlightPath; 7824 } 7825 } 7826 return highlight; 7827 } 7828 7829 /** 7830 * @hide 7831 */ getHorizontalOffsetForDrawables()7832 public int getHorizontalOffsetForDrawables() { 7833 return 0; 7834 } 7835 7836 @Override onDraw(Canvas canvas)7837 protected void onDraw(Canvas canvas) { 7838 restartMarqueeIfNeeded(); 7839 7840 // Draw the background for this view 7841 super.onDraw(canvas); 7842 7843 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7844 final int compoundPaddingTop = getCompoundPaddingTop(); 7845 final int compoundPaddingRight = getCompoundPaddingRight(); 7846 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7847 final int scrollX = mScrollX; 7848 final int scrollY = mScrollY; 7849 final int right = mRight; 7850 final int left = mLeft; 7851 final int bottom = mBottom; 7852 final int top = mTop; 7853 final boolean isLayoutRtl = isLayoutRtl(); 7854 final int offset = getHorizontalOffsetForDrawables(); 7855 final int leftOffset = isLayoutRtl ? 0 : offset; 7856 final int rightOffset = isLayoutRtl ? offset : 0; 7857 7858 final Drawables dr = mDrawables; 7859 if (dr != null) { 7860 /* 7861 * Compound, not extended, because the icon is not clipped 7862 * if the text height is smaller. 7863 */ 7864 7865 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7866 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7867 7868 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7869 // Make sure to update invalidateDrawable() when changing this code. 7870 if (dr.mShowing[Drawables.LEFT] != null) { 7871 canvas.save(); 7872 canvas.translate(scrollX + mPaddingLeft + leftOffset, 7873 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 7874 dr.mShowing[Drawables.LEFT].draw(canvas); 7875 canvas.restore(); 7876 } 7877 7878 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7879 // Make sure to update invalidateDrawable() when changing this code. 7880 if (dr.mShowing[Drawables.RIGHT] != null) { 7881 canvas.save(); 7882 canvas.translate(scrollX + right - left - mPaddingRight 7883 - dr.mDrawableSizeRight - rightOffset, 7884 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 7885 dr.mShowing[Drawables.RIGHT].draw(canvas); 7886 canvas.restore(); 7887 } 7888 7889 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7890 // Make sure to update invalidateDrawable() when changing this code. 7891 if (dr.mShowing[Drawables.TOP] != null) { 7892 canvas.save(); 7893 canvas.translate(scrollX + compoundPaddingLeft 7894 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 7895 dr.mShowing[Drawables.TOP].draw(canvas); 7896 canvas.restore(); 7897 } 7898 7899 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7900 // Make sure to update invalidateDrawable() when changing this code. 7901 if (dr.mShowing[Drawables.BOTTOM] != null) { 7902 canvas.save(); 7903 canvas.translate(scrollX + compoundPaddingLeft 7904 + (hspace - dr.mDrawableWidthBottom) / 2, 7905 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 7906 dr.mShowing[Drawables.BOTTOM].draw(canvas); 7907 canvas.restore(); 7908 } 7909 } 7910 7911 int color = mCurTextColor; 7912 7913 if (mLayout == null) { 7914 assumeLayout(); 7915 } 7916 7917 Layout layout = mLayout; 7918 7919 if (mHint != null && mText.length() == 0) { 7920 if (mHintTextColor != null) { 7921 color = mCurHintTextColor; 7922 } 7923 7924 layout = mHintLayout; 7925 } 7926 7927 mTextPaint.setColor(color); 7928 mTextPaint.drawableState = getDrawableState(); 7929 7930 canvas.save(); 7931 /* Would be faster if we didn't have to do this. Can we chop the 7932 (displayable) text so that we don't need to do this ever? 7933 */ 7934 7935 int extendedPaddingTop = getExtendedPaddingTop(); 7936 int extendedPaddingBottom = getExtendedPaddingBottom(); 7937 7938 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7939 final int maxScrollY = mLayout.getHeight() - vspace; 7940 7941 float clipLeft = compoundPaddingLeft + scrollX; 7942 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 7943 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 7944 float clipBottom = bottom - top + scrollY 7945 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 7946 7947 if (mShadowRadius != 0) { 7948 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 7949 clipRight += Math.max(0, mShadowDx + mShadowRadius); 7950 7951 clipTop += Math.min(0, mShadowDy - mShadowRadius); 7952 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 7953 } 7954 7955 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 7956 7957 int voffsetText = 0; 7958 int voffsetCursor = 0; 7959 7960 // translate in by our padding 7961 /* shortcircuit calling getVerticaOffset() */ 7962 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7963 voffsetText = getVerticalOffset(false); 7964 voffsetCursor = getVerticalOffset(true); 7965 } 7966 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 7967 7968 final int layoutDirection = getLayoutDirection(); 7969 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 7970 if (isMarqueeFadeEnabled()) { 7971 if (!mSingleLine && getLineCount() == 1 && canMarquee() 7972 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 7973 final int width = mRight - mLeft; 7974 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 7975 final float dx = mLayout.getLineRight(0) - (width - padding); 7976 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7977 } 7978 7979 if (mMarquee != null && mMarquee.isRunning()) { 7980 final float dx = -mMarquee.getScroll(); 7981 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7982 } 7983 } 7984 7985 final int cursorOffsetVertical = voffsetCursor - voffsetText; 7986 7987 Path highlight = getUpdatedHighlightPath(); 7988 if (mEditor != null) { 7989 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 7990 } else { 7991 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7992 } 7993 7994 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 7995 final float dx = mMarquee.getGhostOffset(); 7996 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7997 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7998 } 7999 8000 canvas.restore(); 8001 } 8002 8003 @Override getFocusedRect(Rect r)8004 public void getFocusedRect(Rect r) { 8005 if (mLayout == null) { 8006 super.getFocusedRect(r); 8007 return; 8008 } 8009 8010 int selEnd = getSelectionEnd(); 8011 if (selEnd < 0) { 8012 super.getFocusedRect(r); 8013 return; 8014 } 8015 8016 int selStart = getSelectionStart(); 8017 if (selStart < 0 || selStart >= selEnd) { 8018 int line = mLayout.getLineForOffset(selEnd); 8019 r.top = mLayout.getLineTop(line); 8020 r.bottom = mLayout.getLineBottom(line); 8021 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8022 r.right = r.left + 4; 8023 } else { 8024 int lineStart = mLayout.getLineForOffset(selStart); 8025 int lineEnd = mLayout.getLineForOffset(selEnd); 8026 r.top = mLayout.getLineTop(lineStart); 8027 r.bottom = mLayout.getLineBottom(lineEnd); 8028 if (lineStart == lineEnd) { 8029 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8030 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8031 } else { 8032 // Selection extends across multiple lines -- make the focused 8033 // rect cover the entire width. 8034 if (mHighlightPathBogus) { 8035 if (mHighlightPath == null) mHighlightPath = new Path(); 8036 mHighlightPath.reset(); 8037 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8038 mHighlightPathBogus = false; 8039 } 8040 synchronized (TEMP_RECTF) { 8041 mHighlightPath.computeBounds(TEMP_RECTF, true); 8042 r.left = (int) TEMP_RECTF.left - 1; 8043 r.right = (int) TEMP_RECTF.right + 1; 8044 } 8045 } 8046 } 8047 8048 // Adjust for padding and gravity. 8049 int paddingLeft = getCompoundPaddingLeft(); 8050 int paddingTop = getExtendedPaddingTop(); 8051 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8052 paddingTop += getVerticalOffset(false); 8053 } 8054 r.offset(paddingLeft, paddingTop); 8055 int paddingBottom = getExtendedPaddingBottom(); 8056 r.bottom += paddingBottom; 8057 } 8058 8059 /** 8060 * Return the number of lines of text, or 0 if the internal Layout has not 8061 * been built. 8062 */ getLineCount()8063 public int getLineCount() { 8064 return mLayout != null ? mLayout.getLineCount() : 0; 8065 } 8066 8067 /** 8068 * Return the baseline for the specified line (0...getLineCount() - 1) 8069 * If bounds is not null, return the top, left, right, bottom extents 8070 * of the specified line in it. If the internal Layout has not been built, 8071 * return 0 and set bounds to (0, 0, 0, 0) 8072 * @param line which line to examine (0..getLineCount() - 1) 8073 * @param bounds Optional. If not null, it returns the extent of the line 8074 * @return the Y-coordinate of the baseline 8075 */ getLineBounds(int line, Rect bounds)8076 public int getLineBounds(int line, Rect bounds) { 8077 if (mLayout == null) { 8078 if (bounds != null) { 8079 bounds.set(0, 0, 0, 0); 8080 } 8081 return 0; 8082 } else { 8083 int baseline = mLayout.getLineBounds(line, bounds); 8084 8085 int voffset = getExtendedPaddingTop(); 8086 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8087 voffset += getVerticalOffset(true); 8088 } 8089 if (bounds != null) { 8090 bounds.offset(getCompoundPaddingLeft(), voffset); 8091 } 8092 return baseline + voffset; 8093 } 8094 } 8095 8096 @Override getBaseline()8097 public int getBaseline() { 8098 if (mLayout == null) { 8099 return super.getBaseline(); 8100 } 8101 8102 return getBaselineOffset() + mLayout.getLineBaseline(0); 8103 } 8104 getBaselineOffset()8105 int getBaselineOffset() { 8106 int voffset = 0; 8107 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8108 voffset = getVerticalOffset(true); 8109 } 8110 8111 if (isLayoutModeOptical(mParent)) { 8112 voffset -= getOpticalInsets().top; 8113 } 8114 8115 return getExtendedPaddingTop() + voffset; 8116 } 8117 8118 /** 8119 * @hide 8120 */ 8121 @Override getFadeTop(boolean offsetRequired)8122 protected int getFadeTop(boolean offsetRequired) { 8123 if (mLayout == null) return 0; 8124 8125 int voffset = 0; 8126 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8127 voffset = getVerticalOffset(true); 8128 } 8129 8130 if (offsetRequired) voffset += getTopPaddingOffset(); 8131 8132 return getExtendedPaddingTop() + voffset; 8133 } 8134 8135 /** 8136 * @hide 8137 */ 8138 @Override getFadeHeight(boolean offsetRequired)8139 protected int getFadeHeight(boolean offsetRequired) { 8140 return mLayout != null ? mLayout.getHeight() : 0; 8141 } 8142 8143 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8144 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8145 if (mSpannable != null && mLinksClickable) { 8146 final float x = event.getX(pointerIndex); 8147 final float y = event.getY(pointerIndex); 8148 final int offset = getOffsetForPosition(x, y); 8149 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8150 ClickableSpan.class); 8151 if (clickables.length > 0) { 8152 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8153 } 8154 } 8155 if (isTextSelectable() || isTextEditable()) { 8156 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8157 } 8158 return super.onResolvePointerIcon(event, pointerIndex); 8159 } 8160 8161 @Override onKeyPreIme(int keyCode, KeyEvent event)8162 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8163 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8164 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8165 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8166 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8167 return true; 8168 } 8169 return super.onKeyPreIme(keyCode, event); 8170 } 8171 8172 /** 8173 * @hide 8174 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8175 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8176 // Do nothing unless mEditor is in text action mode. 8177 if (mEditor == null || mEditor.getTextActionMode() == null) { 8178 return false; 8179 } 8180 8181 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8182 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8183 if (state != null) { 8184 state.startTracking(event, this); 8185 } 8186 return true; 8187 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8188 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8189 if (state != null) { 8190 state.handleUpEvent(event); 8191 } 8192 if (event.isTracking() && !event.isCanceled()) { 8193 stopTextActionMode(); 8194 return true; 8195 } 8196 } 8197 return false; 8198 } 8199 8200 @Override onKeyDown(int keyCode, KeyEvent event)8201 public boolean onKeyDown(int keyCode, KeyEvent event) { 8202 final int which = doKeyDown(keyCode, event, null); 8203 if (which == KEY_EVENT_NOT_HANDLED) { 8204 return super.onKeyDown(keyCode, event); 8205 } 8206 8207 return true; 8208 } 8209 8210 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8211 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8212 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8213 final int which = doKeyDown(keyCode, down, event); 8214 if (which == KEY_EVENT_NOT_HANDLED) { 8215 // Go through default dispatching. 8216 return super.onKeyMultiple(keyCode, repeatCount, event); 8217 } 8218 if (which == KEY_EVENT_HANDLED) { 8219 // Consumed the whole thing. 8220 return true; 8221 } 8222 8223 repeatCount--; 8224 8225 // We are going to dispatch the remaining events to either the input 8226 // or movement method. To do this, we will just send a repeated stream 8227 // of down and up events until we have done the complete repeatCount. 8228 // It would be nice if those interfaces had an onKeyMultiple() method, 8229 // but adding that is a more complicated change. 8230 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8231 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8232 // mEditor and mEditor.mInput are not null from doKeyDown 8233 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8234 while (--repeatCount > 0) { 8235 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8236 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8237 } 8238 hideErrorIfUnchanged(); 8239 8240 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8241 // mMovement is not null from doKeyDown 8242 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8243 while (--repeatCount > 0) { 8244 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8245 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8246 } 8247 } 8248 8249 return true; 8250 } 8251 8252 /** 8253 * Returns true if pressing ENTER in this field advances focus instead 8254 * of inserting the character. This is true mostly in single-line fields, 8255 * but also in mail addresses and subjects which will display on multiple 8256 * lines but where it doesn't make sense to insert newlines. 8257 */ shouldAdvanceFocusOnEnter()8258 private boolean shouldAdvanceFocusOnEnter() { 8259 if (getKeyListener() == null) { 8260 return false; 8261 } 8262 8263 if (mSingleLine) { 8264 return true; 8265 } 8266 8267 if (mEditor != null 8268 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8269 == EditorInfo.TYPE_CLASS_TEXT) { 8270 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8271 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8272 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8273 return true; 8274 } 8275 } 8276 8277 return false; 8278 } 8279 8280 /** 8281 * Returns true if pressing TAB in this field advances focus instead 8282 * of inserting the character. Insert tabs only in multi-line editors. 8283 */ shouldAdvanceFocusOnTab()8284 private boolean shouldAdvanceFocusOnTab() { 8285 if (getKeyListener() != null && !mSingleLine && mEditor != null 8286 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8287 == EditorInfo.TYPE_CLASS_TEXT) { 8288 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8289 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 8290 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 8291 return false; 8292 } 8293 } 8294 return true; 8295 } 8296 isDirectionalNavigationKey(int keyCode)8297 private boolean isDirectionalNavigationKey(int keyCode) { 8298 switch(keyCode) { 8299 case KeyEvent.KEYCODE_DPAD_UP: 8300 case KeyEvent.KEYCODE_DPAD_DOWN: 8301 case KeyEvent.KEYCODE_DPAD_LEFT: 8302 case KeyEvent.KEYCODE_DPAD_RIGHT: 8303 return true; 8304 } 8305 return false; 8306 } 8307 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8308 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8309 if (!isEnabled()) { 8310 return KEY_EVENT_NOT_HANDLED; 8311 } 8312 8313 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8314 // While this shouldn't be necessary because any time we're preventing default movement we 8315 // should be restricting the focus to remain within this view, thus we'll also receive 8316 // the key up event, occasionally key up events will get dropped and we don't want to 8317 // prevent the user from traversing out of this on the next key down. 8318 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8319 mPreventDefaultMovement = false; 8320 } 8321 8322 switch (keyCode) { 8323 case KeyEvent.KEYCODE_ENTER: 8324 if (event.hasNoModifiers()) { 8325 // When mInputContentType is set, we know that we are 8326 // running in a "modern" cupcake environment, so don't need 8327 // to worry about the application trying to capture 8328 // enter key events. 8329 if (mEditor != null && mEditor.mInputContentType != null) { 8330 // If there is an action listener, given them a 8331 // chance to consume the event. 8332 if (mEditor.mInputContentType.onEditorActionListener != null 8333 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8334 this, EditorInfo.IME_NULL, event)) { 8335 mEditor.mInputContentType.enterDown = true; 8336 // We are consuming the enter key for them. 8337 return KEY_EVENT_HANDLED; 8338 } 8339 } 8340 8341 // If our editor should move focus when enter is pressed, or 8342 // this is a generated event from an IME action button, then 8343 // don't let it be inserted into the text. 8344 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8345 || shouldAdvanceFocusOnEnter()) { 8346 if (hasOnClickListeners()) { 8347 return KEY_EVENT_NOT_HANDLED; 8348 } 8349 return KEY_EVENT_HANDLED; 8350 } 8351 } 8352 break; 8353 8354 case KeyEvent.KEYCODE_DPAD_CENTER: 8355 if (event.hasNoModifiers()) { 8356 if (shouldAdvanceFocusOnEnter()) { 8357 return KEY_EVENT_NOT_HANDLED; 8358 } 8359 } 8360 break; 8361 8362 case KeyEvent.KEYCODE_TAB: 8363 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8364 if (shouldAdvanceFocusOnTab()) { 8365 return KEY_EVENT_NOT_HANDLED; 8366 } 8367 } 8368 break; 8369 8370 // Has to be done on key down (and not on key up) to correctly be intercepted. 8371 case KeyEvent.KEYCODE_BACK: 8372 if (mEditor != null && mEditor.getTextActionMode() != null) { 8373 stopTextActionMode(); 8374 return KEY_EVENT_HANDLED; 8375 } 8376 break; 8377 8378 case KeyEvent.KEYCODE_CUT: 8379 if (event.hasNoModifiers() && canCut()) { 8380 if (onTextContextMenuItem(ID_CUT)) { 8381 return KEY_EVENT_HANDLED; 8382 } 8383 } 8384 break; 8385 8386 case KeyEvent.KEYCODE_COPY: 8387 if (event.hasNoModifiers() && canCopy()) { 8388 if (onTextContextMenuItem(ID_COPY)) { 8389 return KEY_EVENT_HANDLED; 8390 } 8391 } 8392 break; 8393 8394 case KeyEvent.KEYCODE_PASTE: 8395 if (event.hasNoModifiers() && canPaste()) { 8396 if (onTextContextMenuItem(ID_PASTE)) { 8397 return KEY_EVENT_HANDLED; 8398 } 8399 } 8400 break; 8401 8402 case KeyEvent.KEYCODE_FORWARD_DEL: 8403 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8404 if (onTextContextMenuItem(ID_CUT)) { 8405 return KEY_EVENT_HANDLED; 8406 } 8407 } 8408 break; 8409 8410 case KeyEvent.KEYCODE_INSERT: 8411 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8412 if (onTextContextMenuItem(ID_COPY)) { 8413 return KEY_EVENT_HANDLED; 8414 } 8415 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8416 if (onTextContextMenuItem(ID_PASTE)) { 8417 return KEY_EVENT_HANDLED; 8418 } 8419 } 8420 break; 8421 } 8422 8423 if (mEditor != null && mEditor.mKeyListener != null) { 8424 boolean doDown = true; 8425 if (otherEvent != null) { 8426 try { 8427 beginBatchEdit(); 8428 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8429 otherEvent); 8430 hideErrorIfUnchanged(); 8431 doDown = false; 8432 if (handled) { 8433 return KEY_EVENT_HANDLED; 8434 } 8435 } catch (AbstractMethodError e) { 8436 // onKeyOther was added after 1.0, so if it isn't 8437 // implemented we need to try to dispatch as a regular down. 8438 } finally { 8439 endBatchEdit(); 8440 } 8441 } 8442 8443 if (doDown) { 8444 beginBatchEdit(); 8445 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8446 keyCode, event); 8447 endBatchEdit(); 8448 hideErrorIfUnchanged(); 8449 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8450 } 8451 } 8452 8453 // bug 650865: sometimes we get a key event before a layout. 8454 // don't try to move around if we don't know the layout. 8455 8456 if (mMovement != null && mLayout != null) { 8457 boolean doDown = true; 8458 if (otherEvent != null) { 8459 try { 8460 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8461 doDown = false; 8462 if (handled) { 8463 return KEY_EVENT_HANDLED; 8464 } 8465 } catch (AbstractMethodError e) { 8466 // onKeyOther was added after 1.0, so if it isn't 8467 // implemented we need to try to dispatch as a regular down. 8468 } 8469 } 8470 if (doDown) { 8471 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8472 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8473 mPreventDefaultMovement = true; 8474 } 8475 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8476 } 8477 } 8478 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8479 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8480 // to move focus with arrows. 8481 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8482 && isDirectionalNavigationKey(keyCode)) { 8483 return KEY_EVENT_HANDLED; 8484 } 8485 } 8486 8487 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8488 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8489 } 8490 8491 /** 8492 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8493 * can be recorded. 8494 * @hide 8495 */ resetErrorChangedFlag()8496 public void resetErrorChangedFlag() { 8497 /* 8498 * Keep track of what the error was before doing the input 8499 * so that if an input filter changed the error, we leave 8500 * that error showing. Otherwise, we take down whatever 8501 * error was showing when the user types something. 8502 */ 8503 if (mEditor != null) mEditor.mErrorWasChanged = false; 8504 } 8505 8506 /** 8507 * @hide 8508 */ hideErrorIfUnchanged()8509 public void hideErrorIfUnchanged() { 8510 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8511 setError(null, null); 8512 } 8513 } 8514 8515 @Override onKeyUp(int keyCode, KeyEvent event)8516 public boolean onKeyUp(int keyCode, KeyEvent event) { 8517 if (!isEnabled()) { 8518 return super.onKeyUp(keyCode, event); 8519 } 8520 8521 if (!KeyEvent.isModifierKey(keyCode)) { 8522 mPreventDefaultMovement = false; 8523 } 8524 8525 switch (keyCode) { 8526 case KeyEvent.KEYCODE_DPAD_CENTER: 8527 if (event.hasNoModifiers()) { 8528 /* 8529 * If there is a click listener, just call through to 8530 * super, which will invoke it. 8531 * 8532 * If there isn't a click listener, try to show the soft 8533 * input method. (It will also 8534 * call performClick(), but that won't do anything in 8535 * this case.) 8536 */ 8537 if (!hasOnClickListeners()) { 8538 if (mMovement != null && mText instanceof Editable 8539 && mLayout != null && onCheckIsTextEditor()) { 8540 InputMethodManager imm = getInputMethodManager(); 8541 viewClicked(imm); 8542 if (imm != null && getShowSoftInputOnFocus()) { 8543 imm.showSoftInput(this, 0); 8544 } 8545 } 8546 } 8547 } 8548 return super.onKeyUp(keyCode, event); 8549 8550 case KeyEvent.KEYCODE_ENTER: 8551 if (event.hasNoModifiers()) { 8552 if (mEditor != null && mEditor.mInputContentType != null 8553 && mEditor.mInputContentType.onEditorActionListener != null 8554 && mEditor.mInputContentType.enterDown) { 8555 mEditor.mInputContentType.enterDown = false; 8556 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8557 this, EditorInfo.IME_NULL, event)) { 8558 return true; 8559 } 8560 } 8561 8562 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8563 || shouldAdvanceFocusOnEnter()) { 8564 /* 8565 * If there is a click listener, just call through to 8566 * super, which will invoke it. 8567 * 8568 * If there isn't a click listener, try to advance focus, 8569 * but still call through to super, which will reset the 8570 * pressed state and longpress state. (It will also 8571 * call performClick(), but that won't do anything in 8572 * this case.) 8573 */ 8574 if (!hasOnClickListeners()) { 8575 View v = focusSearch(FOCUS_DOWN); 8576 8577 if (v != null) { 8578 if (!v.requestFocus(FOCUS_DOWN)) { 8579 throw new IllegalStateException("focus search returned a view " 8580 + "that wasn't able to take focus!"); 8581 } 8582 8583 /* 8584 * Return true because we handled the key; super 8585 * will return false because there was no click 8586 * listener. 8587 */ 8588 super.onKeyUp(keyCode, event); 8589 return true; 8590 } else if ((event.getFlags() 8591 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8592 // No target for next focus, but make sure the IME 8593 // if this came from it. 8594 InputMethodManager imm = getInputMethodManager(); 8595 if (imm != null && imm.isActive(this)) { 8596 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8597 } 8598 } 8599 } 8600 } 8601 return super.onKeyUp(keyCode, event); 8602 } 8603 break; 8604 } 8605 8606 if (mEditor != null && mEditor.mKeyListener != null) { 8607 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8608 return true; 8609 } 8610 } 8611 8612 if (mMovement != null && mLayout != null) { 8613 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8614 return true; 8615 } 8616 } 8617 8618 return super.onKeyUp(keyCode, event); 8619 } 8620 8621 @Override onCheckIsTextEditor()8622 public boolean onCheckIsTextEditor() { 8623 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8624 } 8625 8626 @Override onCreateInputConnection(EditorInfo outAttrs)8627 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8628 if (onCheckIsTextEditor() && isEnabled()) { 8629 mEditor.createInputMethodStateIfNeeded(); 8630 outAttrs.inputType = getInputType(); 8631 if (mEditor.mInputContentType != null) { 8632 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8633 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8634 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8635 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8636 outAttrs.extras = mEditor.mInputContentType.extras; 8637 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8638 } else { 8639 outAttrs.imeOptions = EditorInfo.IME_NULL; 8640 outAttrs.hintLocales = null; 8641 } 8642 if (focusSearch(FOCUS_DOWN) != null) { 8643 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8644 } 8645 if (focusSearch(FOCUS_UP) != null) { 8646 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8647 } 8648 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8649 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8650 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8651 // An action has not been set, but the enter key will move to 8652 // the next focus, so set the action to that. 8653 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8654 } else { 8655 // An action has not been set, and there is no focus to move 8656 // to, so let's just supply a "done" action. 8657 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8658 } 8659 if (!shouldAdvanceFocusOnEnter()) { 8660 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8661 } 8662 } 8663 if (isMultilineInputType(outAttrs.inputType)) { 8664 // Multi-line text editors should always show an enter key. 8665 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8666 } 8667 outAttrs.hintText = mHint; 8668 outAttrs.targetInputMethodUser = mTextOperationUser; 8669 if (mText instanceof Editable) { 8670 InputConnection ic = new EditableInputConnection(this); 8671 outAttrs.initialSelStart = getSelectionStart(); 8672 outAttrs.initialSelEnd = getSelectionEnd(); 8673 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8674 return ic; 8675 } 8676 } 8677 return null; 8678 } 8679 8680 /** 8681 * If this TextView contains editable content, extract a portion of it 8682 * based on the information in <var>request</var> in to <var>outText</var>. 8683 * @return Returns true if the text was successfully extracted, else false. 8684 */ extractText(ExtractedTextRequest request, ExtractedText outText)8685 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8686 createEditorIfNeeded(); 8687 return mEditor.extractText(request, outText); 8688 } 8689 8690 /** 8691 * This is used to remove all style-impacting spans from text before new 8692 * extracted text is being replaced into it, so that we don't have any 8693 * lingering spans applied during the replace. 8694 */ removeParcelableSpans(Spannable spannable, int start, int end)8695 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8696 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8697 int i = spans.length; 8698 while (i > 0) { 8699 i--; 8700 spannable.removeSpan(spans[i]); 8701 } 8702 } 8703 8704 /** 8705 * Apply to this text view the given extracted text, as previously 8706 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8707 */ setExtractedText(ExtractedText text)8708 public void setExtractedText(ExtractedText text) { 8709 Editable content = getEditableText(); 8710 if (text.text != null) { 8711 if (content == null) { 8712 setText(text.text, TextView.BufferType.EDITABLE); 8713 } else { 8714 int start = 0; 8715 int end = content.length(); 8716 8717 if (text.partialStartOffset >= 0) { 8718 final int N = content.length(); 8719 start = text.partialStartOffset; 8720 if (start > N) start = N; 8721 end = text.partialEndOffset; 8722 if (end > N) end = N; 8723 } 8724 8725 removeParcelableSpans(content, start, end); 8726 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8727 if (text.text instanceof Spanned) { 8728 // OK to copy spans only. 8729 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8730 Object.class, content, start); 8731 } 8732 } else { 8733 content.replace(start, end, text.text); 8734 } 8735 } 8736 } 8737 8738 // Now set the selection position... make sure it is in range, to 8739 // avoid crashes. If this is a partial update, it is possible that 8740 // the underlying text may have changed, causing us problems here. 8741 // Also we just don't want to trust clients to do the right thing. 8742 Spannable sp = (Spannable) getText(); 8743 final int N = sp.length(); 8744 int start = text.selectionStart; 8745 if (start < 0) { 8746 start = 0; 8747 } else if (start > N) { 8748 start = N; 8749 } 8750 int end = text.selectionEnd; 8751 if (end < 0) { 8752 end = 0; 8753 } else if (end > N) { 8754 end = N; 8755 } 8756 Selection.setSelection(sp, start, end); 8757 8758 // Finally, update the selection mode. 8759 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8760 MetaKeyKeyListener.startSelecting(this, sp); 8761 } else { 8762 MetaKeyKeyListener.stopSelecting(this, sp); 8763 } 8764 8765 setHintInternal(text.hint); 8766 } 8767 8768 /** 8769 * @hide 8770 */ setExtracting(ExtractedTextRequest req)8771 public void setExtracting(ExtractedTextRequest req) { 8772 if (mEditor.mInputMethodState != null) { 8773 mEditor.mInputMethodState.mExtractedTextRequest = req; 8774 } 8775 // This would stop a possible selection mode, but no such mode is started in case 8776 // extracted mode will start. Some text is selected though, and will trigger an action mode 8777 // in the extracted view. 8778 mEditor.hideCursorAndSpanControllers(); 8779 stopTextActionMode(); 8780 if (mEditor.mSelectionModifierCursorController != null) { 8781 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8782 } 8783 } 8784 8785 /** 8786 * Called by the framework in response to a text completion from 8787 * the current input method, provided by it calling 8788 * {@link InputConnection#commitCompletion 8789 * InputConnection.commitCompletion()}. The default implementation does 8790 * nothing; text views that are supporting auto-completion should override 8791 * this to do their desired behavior. 8792 * 8793 * @param text The auto complete text the user has selected. 8794 */ onCommitCompletion(CompletionInfo text)8795 public void onCommitCompletion(CompletionInfo text) { 8796 // intentionally empty 8797 } 8798 8799 /** 8800 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8801 * dictionary) from the current input method, provided by it calling 8802 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8803 * The default implementation flashes the background of the corrected word to provide 8804 * feedback to the user. 8805 * 8806 * @param info The auto correct info about the text that was corrected. 8807 */ onCommitCorrection(CorrectionInfo info)8808 public void onCommitCorrection(CorrectionInfo info) { 8809 if (mEditor != null) mEditor.onCommitCorrection(info); 8810 } 8811 beginBatchEdit()8812 public void beginBatchEdit() { 8813 if (mEditor != null) mEditor.beginBatchEdit(); 8814 } 8815 endBatchEdit()8816 public void endBatchEdit() { 8817 if (mEditor != null) mEditor.endBatchEdit(); 8818 } 8819 8820 /** 8821 * Called by the framework in response to a request to begin a batch 8822 * of edit operations through a call to link {@link #beginBatchEdit()}. 8823 */ onBeginBatchEdit()8824 public void onBeginBatchEdit() { 8825 // intentionally empty 8826 } 8827 8828 /** 8829 * Called by the framework in response to a request to end a batch 8830 * of edit operations through a call to link {@link #endBatchEdit}. 8831 */ onEndBatchEdit()8832 public void onEndBatchEdit() { 8833 // intentionally empty 8834 } 8835 8836 /** 8837 * Called by the framework in response to a private command from the 8838 * current method, provided by it calling 8839 * {@link InputConnection#performPrivateCommand 8840 * InputConnection.performPrivateCommand()}. 8841 * 8842 * @param action The action name of the command. 8843 * @param data Any additional data for the command. This may be null. 8844 * @return Return true if you handled the command, else false. 8845 */ onPrivateIMECommand(String action, Bundle data)8846 public boolean onPrivateIMECommand(String action, Bundle data) { 8847 return false; 8848 } 8849 8850 /** @hide */ 8851 @VisibleForTesting 8852 @UnsupportedAppUsage nullLayouts()8853 public void nullLayouts() { 8854 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8855 mSavedLayout = (BoringLayout) mLayout; 8856 } 8857 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8858 mSavedHintLayout = (BoringLayout) mHintLayout; 8859 } 8860 8861 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8862 8863 mBoring = mHintBoring = null; 8864 8865 // Since it depends on the value of mLayout 8866 if (mEditor != null) mEditor.prepareCursorControllers(); 8867 } 8868 8869 /** 8870 * Make a new Layout based on the already-measured size of the view, 8871 * on the assumption that it was measured correctly at some point. 8872 */ 8873 @UnsupportedAppUsage assumeLayout()8874 private void assumeLayout() { 8875 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8876 8877 if (width < 1) { 8878 width = 0; 8879 } 8880 8881 int physicalWidth = width; 8882 8883 if (mHorizontallyScrolling) { 8884 width = VERY_WIDE; 8885 } 8886 8887 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 8888 physicalWidth, false); 8889 } 8890 8891 @UnsupportedAppUsage getLayoutAlignment()8892 private Layout.Alignment getLayoutAlignment() { 8893 Layout.Alignment alignment; 8894 switch (getTextAlignment()) { 8895 case TEXT_ALIGNMENT_GRAVITY: 8896 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 8897 case Gravity.START: 8898 alignment = Layout.Alignment.ALIGN_NORMAL; 8899 break; 8900 case Gravity.END: 8901 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8902 break; 8903 case Gravity.LEFT: 8904 alignment = Layout.Alignment.ALIGN_LEFT; 8905 break; 8906 case Gravity.RIGHT: 8907 alignment = Layout.Alignment.ALIGN_RIGHT; 8908 break; 8909 case Gravity.CENTER_HORIZONTAL: 8910 alignment = Layout.Alignment.ALIGN_CENTER; 8911 break; 8912 default: 8913 alignment = Layout.Alignment.ALIGN_NORMAL; 8914 break; 8915 } 8916 break; 8917 case TEXT_ALIGNMENT_TEXT_START: 8918 alignment = Layout.Alignment.ALIGN_NORMAL; 8919 break; 8920 case TEXT_ALIGNMENT_TEXT_END: 8921 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8922 break; 8923 case TEXT_ALIGNMENT_CENTER: 8924 alignment = Layout.Alignment.ALIGN_CENTER; 8925 break; 8926 case TEXT_ALIGNMENT_VIEW_START: 8927 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8928 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8929 break; 8930 case TEXT_ALIGNMENT_VIEW_END: 8931 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8932 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8933 break; 8934 case TEXT_ALIGNMENT_INHERIT: 8935 // This should never happen as we have already resolved the text alignment 8936 // but better safe than sorry so we just fall through 8937 default: 8938 alignment = Layout.Alignment.ALIGN_NORMAL; 8939 break; 8940 } 8941 return alignment; 8942 } 8943 8944 /** 8945 * The width passed in is now the desired layout width, 8946 * not the full view width with padding. 8947 * {@hide} 8948 */ 8949 @VisibleForTesting 8950 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8951 public void makeNewLayout(int wantWidth, int hintWidth, 8952 BoringLayout.Metrics boring, 8953 BoringLayout.Metrics hintBoring, 8954 int ellipsisWidth, boolean bringIntoView) { 8955 stopMarquee(); 8956 8957 // Update "old" cached values 8958 mOldMaximum = mMaximum; 8959 mOldMaxMode = mMaxMode; 8960 8961 mHighlightPathBogus = true; 8962 8963 if (wantWidth < 0) { 8964 wantWidth = 0; 8965 } 8966 if (hintWidth < 0) { 8967 hintWidth = 0; 8968 } 8969 8970 Layout.Alignment alignment = getLayoutAlignment(); 8971 final boolean testDirChange = mSingleLine && mLayout != null 8972 && (alignment == Layout.Alignment.ALIGN_NORMAL 8973 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 8974 int oldDir = 0; 8975 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 8976 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 8977 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 8978 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 8979 TruncateAt effectiveEllipsize = mEllipsize; 8980 if (mEllipsize == TruncateAt.MARQUEE 8981 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8982 effectiveEllipsize = TruncateAt.END_SMALL; 8983 } 8984 8985 if (mTextDir == null) { 8986 mTextDir = getTextDirectionHeuristic(); 8987 } 8988 8989 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 8990 effectiveEllipsize, effectiveEllipsize == mEllipsize); 8991 if (switchEllipsize) { 8992 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 8993 ? TruncateAt.END : TruncateAt.MARQUEE; 8994 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 8995 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 8996 } 8997 8998 shouldEllipsize = mEllipsize != null; 8999 mHintLayout = null; 9000 9001 if (mHint != null) { 9002 if (shouldEllipsize) hintWidth = wantWidth; 9003 9004 if (hintBoring == UNKNOWN_BORING) { 9005 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9006 mHintBoring); 9007 if (hintBoring != null) { 9008 mHintBoring = hintBoring; 9009 } 9010 } 9011 9012 if (hintBoring != null) { 9013 if (hintBoring.width <= hintWidth 9014 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9015 if (mSavedHintLayout != null) { 9016 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9017 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9018 hintBoring, mIncludePad); 9019 } else { 9020 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9021 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9022 hintBoring, mIncludePad); 9023 } 9024 9025 mSavedHintLayout = (BoringLayout) mHintLayout; 9026 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9027 if (mSavedHintLayout != null) { 9028 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9029 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9030 hintBoring, mIncludePad, mEllipsize, 9031 ellipsisWidth); 9032 } else { 9033 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9034 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9035 hintBoring, mIncludePad, mEllipsize, 9036 ellipsisWidth); 9037 } 9038 } 9039 } 9040 // TODO: code duplication with makeSingleLayout() 9041 if (mHintLayout == null) { 9042 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9043 mHint.length(), mTextPaint, hintWidth) 9044 .setAlignment(alignment) 9045 .setTextDirection(mTextDir) 9046 .setLineSpacing(mSpacingAdd, mSpacingMult) 9047 .setIncludePad(mIncludePad) 9048 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9049 .setBreakStrategy(mBreakStrategy) 9050 .setHyphenationFrequency(mHyphenationFrequency) 9051 .setJustificationMode(mJustificationMode) 9052 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9053 if (shouldEllipsize) { 9054 builder.setEllipsize(mEllipsize) 9055 .setEllipsizedWidth(ellipsisWidth); 9056 } 9057 mHintLayout = builder.build(); 9058 } 9059 } 9060 9061 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9062 registerForPreDraw(); 9063 } 9064 9065 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9066 if (!compressText(ellipsisWidth)) { 9067 final int height = mLayoutParams.height; 9068 // If the size of the view does not depend on the size of the text, try to 9069 // start the marquee immediately 9070 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9071 startMarquee(); 9072 } else { 9073 // Defer the start of the marquee until we know our width (see setFrame()) 9074 mRestartMarquee = true; 9075 } 9076 } 9077 } 9078 9079 // CursorControllers need a non-null mLayout 9080 if (mEditor != null) mEditor.prepareCursorControllers(); 9081 } 9082 9083 /** 9084 * Returns true if DynamicLayout is required 9085 * 9086 * @hide 9087 */ 9088 @VisibleForTesting useDynamicLayout()9089 public boolean useDynamicLayout() { 9090 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9091 } 9092 9093 /** 9094 * @hide 9095 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9096 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9097 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9098 boolean useSaved) { 9099 Layout result = null; 9100 if (useDynamicLayout()) { 9101 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9102 wantWidth) 9103 .setDisplayText(mTransformed) 9104 .setAlignment(alignment) 9105 .setTextDirection(mTextDir) 9106 .setLineSpacing(mSpacingAdd, mSpacingMult) 9107 .setIncludePad(mIncludePad) 9108 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9109 .setBreakStrategy(mBreakStrategy) 9110 .setHyphenationFrequency(mHyphenationFrequency) 9111 .setJustificationMode(mJustificationMode) 9112 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9113 .setEllipsizedWidth(ellipsisWidth); 9114 result = builder.build(); 9115 } else { 9116 if (boring == UNKNOWN_BORING) { 9117 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9118 if (boring != null) { 9119 mBoring = boring; 9120 } 9121 } 9122 9123 if (boring != null) { 9124 if (boring.width <= wantWidth 9125 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9126 if (useSaved && mSavedLayout != null) { 9127 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9128 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9129 boring, mIncludePad); 9130 } else { 9131 result = BoringLayout.make(mTransformed, mTextPaint, 9132 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9133 boring, mIncludePad); 9134 } 9135 9136 if (useSaved) { 9137 mSavedLayout = (BoringLayout) result; 9138 } 9139 } else if (shouldEllipsize && boring.width <= wantWidth) { 9140 if (useSaved && mSavedLayout != null) { 9141 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9142 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9143 boring, mIncludePad, effectiveEllipsize, 9144 ellipsisWidth); 9145 } else { 9146 result = BoringLayout.make(mTransformed, mTextPaint, 9147 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9148 boring, mIncludePad, effectiveEllipsize, 9149 ellipsisWidth); 9150 } 9151 } 9152 } 9153 } 9154 if (result == null) { 9155 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9156 0, mTransformed.length(), mTextPaint, wantWidth) 9157 .setAlignment(alignment) 9158 .setTextDirection(mTextDir) 9159 .setLineSpacing(mSpacingAdd, mSpacingMult) 9160 .setIncludePad(mIncludePad) 9161 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9162 .setBreakStrategy(mBreakStrategy) 9163 .setHyphenationFrequency(mHyphenationFrequency) 9164 .setJustificationMode(mJustificationMode) 9165 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9166 if (shouldEllipsize) { 9167 builder.setEllipsize(effectiveEllipsize) 9168 .setEllipsizedWidth(ellipsisWidth); 9169 } 9170 result = builder.build(); 9171 } 9172 return result; 9173 } 9174 9175 @UnsupportedAppUsage compressText(float width)9176 private boolean compressText(float width) { 9177 if (isHardwareAccelerated()) return false; 9178 9179 // Only compress the text if it hasn't been compressed by the previous pass 9180 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9181 && mTextPaint.getTextScaleX() == 1.0f) { 9182 final float textWidth = mLayout.getLineWidth(0); 9183 final float overflow = (textWidth + 1.0f - width) / width; 9184 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9185 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9186 post(new Runnable() { 9187 public void run() { 9188 requestLayout(); 9189 } 9190 }); 9191 return true; 9192 } 9193 } 9194 9195 return false; 9196 } 9197 desired(Layout layout)9198 private static int desired(Layout layout) { 9199 int n = layout.getLineCount(); 9200 CharSequence text = layout.getText(); 9201 float max = 0; 9202 9203 // if any line was wrapped, we can't use it. 9204 // but it's ok for the last line not to have a newline 9205 9206 for (int i = 0; i < n - 1; i++) { 9207 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9208 return -1; 9209 } 9210 } 9211 9212 for (int i = 0; i < n; i++) { 9213 max = Math.max(max, layout.getLineWidth(i)); 9214 } 9215 9216 return (int) Math.ceil(max); 9217 } 9218 9219 /** 9220 * Set whether the TextView includes extra top and bottom padding to make 9221 * room for accents that go above the normal ascent and descent. 9222 * The default is true. 9223 * 9224 * @see #getIncludeFontPadding() 9225 * 9226 * @attr ref android.R.styleable#TextView_includeFontPadding 9227 */ setIncludeFontPadding(boolean includepad)9228 public void setIncludeFontPadding(boolean includepad) { 9229 if (mIncludePad != includepad) { 9230 mIncludePad = includepad; 9231 9232 if (mLayout != null) { 9233 nullLayouts(); 9234 requestLayout(); 9235 invalidate(); 9236 } 9237 } 9238 } 9239 9240 /** 9241 * Gets whether the TextView includes extra top and bottom padding to make 9242 * room for accents that go above the normal ascent and descent. 9243 * 9244 * @see #setIncludeFontPadding(boolean) 9245 * 9246 * @attr ref android.R.styleable#TextView_includeFontPadding 9247 */ 9248 @InspectableProperty getIncludeFontPadding()9249 public boolean getIncludeFontPadding() { 9250 return mIncludePad; 9251 } 9252 9253 /** @hide */ 9254 @VisibleForTesting 9255 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9256 9257 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9258 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9259 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9260 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9261 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9262 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9263 9264 int width; 9265 int height; 9266 9267 BoringLayout.Metrics boring = UNKNOWN_BORING; 9268 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9269 9270 if (mTextDir == null) { 9271 mTextDir = getTextDirectionHeuristic(); 9272 } 9273 9274 int des = -1; 9275 boolean fromexisting = false; 9276 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9277 ? (float) widthSize : Float.MAX_VALUE; 9278 9279 if (widthMode == MeasureSpec.EXACTLY) { 9280 // Parent has told us how big to be. So be it. 9281 width = widthSize; 9282 } else { 9283 if (mLayout != null && mEllipsize == null) { 9284 des = desired(mLayout); 9285 } 9286 9287 if (des < 0) { 9288 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9289 if (boring != null) { 9290 mBoring = boring; 9291 } 9292 } else { 9293 fromexisting = true; 9294 } 9295 9296 if (boring == null || boring == UNKNOWN_BORING) { 9297 if (des < 0) { 9298 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9299 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9300 } 9301 width = des; 9302 } else { 9303 width = boring.width; 9304 } 9305 9306 final Drawables dr = mDrawables; 9307 if (dr != null) { 9308 width = Math.max(width, dr.mDrawableWidthTop); 9309 width = Math.max(width, dr.mDrawableWidthBottom); 9310 } 9311 9312 if (mHint != null) { 9313 int hintDes = -1; 9314 int hintWidth; 9315 9316 if (mHintLayout != null && mEllipsize == null) { 9317 hintDes = desired(mHintLayout); 9318 } 9319 9320 if (hintDes < 0) { 9321 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9322 if (hintBoring != null) { 9323 mHintBoring = hintBoring; 9324 } 9325 } 9326 9327 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9328 if (hintDes < 0) { 9329 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9330 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9331 } 9332 hintWidth = hintDes; 9333 } else { 9334 hintWidth = hintBoring.width; 9335 } 9336 9337 if (hintWidth > width) { 9338 width = hintWidth; 9339 } 9340 } 9341 9342 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9343 9344 if (mMaxWidthMode == EMS) { 9345 width = Math.min(width, mMaxWidth * getLineHeight()); 9346 } else { 9347 width = Math.min(width, mMaxWidth); 9348 } 9349 9350 if (mMinWidthMode == EMS) { 9351 width = Math.max(width, mMinWidth * getLineHeight()); 9352 } else { 9353 width = Math.max(width, mMinWidth); 9354 } 9355 9356 // Check against our minimum width 9357 width = Math.max(width, getSuggestedMinimumWidth()); 9358 9359 if (widthMode == MeasureSpec.AT_MOST) { 9360 width = Math.min(widthSize, width); 9361 } 9362 } 9363 9364 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9365 int unpaddedWidth = want; 9366 9367 if (mHorizontallyScrolling) want = VERY_WIDE; 9368 9369 int hintWant = want; 9370 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9371 9372 if (mLayout == null) { 9373 makeNewLayout(want, hintWant, boring, hintBoring, 9374 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9375 } else { 9376 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9377 || (mLayout.getEllipsizedWidth() 9378 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9379 9380 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9381 && (want > mLayout.getWidth()) 9382 && (mLayout instanceof BoringLayout 9383 || (fromexisting && des >= 0 && des <= want)); 9384 9385 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9386 9387 if (layoutChanged || maximumChanged) { 9388 if (!maximumChanged && widthChanged) { 9389 mLayout.increaseWidthTo(want); 9390 } else { 9391 makeNewLayout(want, hintWant, boring, hintBoring, 9392 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9393 } 9394 } else { 9395 // Nothing has changed 9396 } 9397 } 9398 9399 if (heightMode == MeasureSpec.EXACTLY) { 9400 // Parent has told us how big to be. So be it. 9401 height = heightSize; 9402 mDesiredHeightAtMeasure = -1; 9403 } else { 9404 int desired = getDesiredHeight(); 9405 9406 height = desired; 9407 mDesiredHeightAtMeasure = desired; 9408 9409 if (heightMode == MeasureSpec.AT_MOST) { 9410 height = Math.min(desired, heightSize); 9411 } 9412 } 9413 9414 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9415 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9416 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9417 } 9418 9419 /* 9420 * We didn't let makeNewLayout() register to bring the cursor into view, 9421 * so do it here if there is any possibility that it is needed. 9422 */ 9423 if (mMovement != null 9424 || mLayout.getWidth() > unpaddedWidth 9425 || mLayout.getHeight() > unpaddedHeight) { 9426 registerForPreDraw(); 9427 } else { 9428 scrollTo(0, 0); 9429 } 9430 9431 setMeasuredDimension(width, height); 9432 } 9433 9434 /** 9435 * Automatically computes and sets the text size. 9436 */ autoSizeText()9437 private void autoSizeText() { 9438 if (!isAutoSizeEnabled()) { 9439 return; 9440 } 9441 9442 if (mNeedsAutoSizeText) { 9443 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9444 return; 9445 } 9446 9447 final int availableWidth = mHorizontallyScrolling 9448 ? VERY_WIDE 9449 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9450 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9451 - getExtendedPaddingTop(); 9452 9453 if (availableWidth <= 0 || availableHeight <= 0) { 9454 return; 9455 } 9456 9457 synchronized (TEMP_RECTF) { 9458 TEMP_RECTF.setEmpty(); 9459 TEMP_RECTF.right = availableWidth; 9460 TEMP_RECTF.bottom = availableHeight; 9461 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9462 9463 if (optimalTextSize != getTextSize()) { 9464 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9465 false /* shouldRequestLayout */); 9466 9467 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9468 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9469 false /* bringIntoView */); 9470 } 9471 } 9472 } 9473 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9474 // after the next layout pass should set this to false. 9475 mNeedsAutoSizeText = true; 9476 } 9477 9478 /** 9479 * Performs a binary search to find the largest text size that will still fit within the size 9480 * available to this view. 9481 */ findLargestTextSizeWhichFits(RectF availableSpace)9482 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9483 final int sizesCount = mAutoSizeTextSizesInPx.length; 9484 if (sizesCount == 0) { 9485 throw new IllegalStateException("No available text sizes to choose from."); 9486 } 9487 9488 int bestSizeIndex = 0; 9489 int lowIndex = bestSizeIndex + 1; 9490 int highIndex = sizesCount - 1; 9491 int sizeToTryIndex; 9492 while (lowIndex <= highIndex) { 9493 sizeToTryIndex = (lowIndex + highIndex) / 2; 9494 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9495 bestSizeIndex = lowIndex; 9496 lowIndex = sizeToTryIndex + 1; 9497 } else { 9498 highIndex = sizeToTryIndex - 1; 9499 bestSizeIndex = highIndex; 9500 } 9501 } 9502 9503 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9504 } 9505 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9506 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9507 final CharSequence text = mTransformed != null 9508 ? mTransformed 9509 : getText(); 9510 final int maxLines = getMaxLines(); 9511 if (mTempTextPaint == null) { 9512 mTempTextPaint = new TextPaint(); 9513 } else { 9514 mTempTextPaint.reset(); 9515 } 9516 mTempTextPaint.set(getPaint()); 9517 mTempTextPaint.setTextSize(suggestedSizeInPx); 9518 9519 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9520 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9521 9522 layoutBuilder.setAlignment(getLayoutAlignment()) 9523 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9524 .setIncludePad(getIncludeFontPadding()) 9525 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9526 .setBreakStrategy(getBreakStrategy()) 9527 .setHyphenationFrequency(getHyphenationFrequency()) 9528 .setJustificationMode(getJustificationMode()) 9529 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9530 .setTextDirection(getTextDirectionHeuristic()); 9531 9532 final StaticLayout layout = layoutBuilder.build(); 9533 9534 // Lines overflow. 9535 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9536 return false; 9537 } 9538 9539 // Height overflow. 9540 if (layout.getHeight() > availableSpace.bottom) { 9541 return false; 9542 } 9543 9544 return true; 9545 } 9546 getDesiredHeight()9547 private int getDesiredHeight() { 9548 return Math.max( 9549 getDesiredHeight(mLayout, true), 9550 getDesiredHeight(mHintLayout, mEllipsize != null)); 9551 } 9552 getDesiredHeight(Layout layout, boolean cap)9553 private int getDesiredHeight(Layout layout, boolean cap) { 9554 if (layout == null) { 9555 return 0; 9556 } 9557 9558 /* 9559 * Don't cap the hint to a certain number of lines. 9560 * (Do cap it, though, if we have a maximum pixel height.) 9561 */ 9562 int desired = layout.getHeight(cap); 9563 9564 final Drawables dr = mDrawables; 9565 if (dr != null) { 9566 desired = Math.max(desired, dr.mDrawableHeightLeft); 9567 desired = Math.max(desired, dr.mDrawableHeightRight); 9568 } 9569 9570 int linecount = layout.getLineCount(); 9571 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9572 desired += padding; 9573 9574 if (mMaxMode != LINES) { 9575 desired = Math.min(desired, mMaximum); 9576 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9577 || layout instanceof BoringLayout)) { 9578 desired = layout.getLineTop(mMaximum); 9579 9580 if (dr != null) { 9581 desired = Math.max(desired, dr.mDrawableHeightLeft); 9582 desired = Math.max(desired, dr.mDrawableHeightRight); 9583 } 9584 9585 desired += padding; 9586 linecount = mMaximum; 9587 } 9588 9589 if (mMinMode == LINES) { 9590 if (linecount < mMinimum) { 9591 desired += getLineHeight() * (mMinimum - linecount); 9592 } 9593 } else { 9594 desired = Math.max(desired, mMinimum); 9595 } 9596 9597 // Check against our minimum height 9598 desired = Math.max(desired, getSuggestedMinimumHeight()); 9599 9600 return desired; 9601 } 9602 9603 /** 9604 * Check whether a change to the existing text layout requires a 9605 * new view layout. 9606 */ checkForResize()9607 private void checkForResize() { 9608 boolean sizeChanged = false; 9609 9610 if (mLayout != null) { 9611 // Check if our width changed 9612 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9613 sizeChanged = true; 9614 invalidate(); 9615 } 9616 9617 // Check if our height changed 9618 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9619 int desiredHeight = getDesiredHeight(); 9620 9621 if (desiredHeight != this.getHeight()) { 9622 sizeChanged = true; 9623 } 9624 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9625 if (mDesiredHeightAtMeasure >= 0) { 9626 int desiredHeight = getDesiredHeight(); 9627 9628 if (desiredHeight != mDesiredHeightAtMeasure) { 9629 sizeChanged = true; 9630 } 9631 } 9632 } 9633 } 9634 9635 if (sizeChanged) { 9636 requestLayout(); 9637 // caller will have already invalidated 9638 } 9639 } 9640 9641 /** 9642 * Check whether entirely new text requires a new view layout 9643 * or merely a new text layout. 9644 */ 9645 @UnsupportedAppUsage checkForRelayout()9646 private void checkForRelayout() { 9647 // If we have a fixed width, we can just swap in a new text layout 9648 // if the text height stays the same or if the view height is fixed. 9649 9650 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9651 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9652 && (mHint == null || mHintLayout != null) 9653 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9654 // Static width, so try making a new text layout. 9655 9656 int oldht = mLayout.getHeight(); 9657 int want = mLayout.getWidth(); 9658 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9659 9660 /* 9661 * No need to bring the text into view, since the size is not 9662 * changing (unless we do the requestLayout(), in which case it 9663 * will happen at measure). 9664 */ 9665 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9666 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9667 false); 9668 9669 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9670 // In a fixed-height view, so use our new text layout. 9671 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9672 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9673 autoSizeText(); 9674 invalidate(); 9675 return; 9676 } 9677 9678 // Dynamic height, but height has stayed the same, 9679 // so use our new text layout. 9680 if (mLayout.getHeight() == oldht 9681 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9682 autoSizeText(); 9683 invalidate(); 9684 return; 9685 } 9686 } 9687 9688 // We lose: the height has changed and we have a dynamic height. 9689 // Request a new view layout using our new text layout. 9690 requestLayout(); 9691 invalidate(); 9692 } else { 9693 // Dynamic width, so we have no choice but to request a new 9694 // view layout with a new text layout. 9695 nullLayouts(); 9696 requestLayout(); 9697 invalidate(); 9698 } 9699 } 9700 9701 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9702 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9703 super.onLayout(changed, left, top, right, bottom); 9704 if (mDeferScroll >= 0) { 9705 int curs = mDeferScroll; 9706 mDeferScroll = -1; 9707 bringPointIntoView(Math.min(curs, mText.length())); 9708 } 9709 // Call auto-size after the width and height have been calculated. 9710 autoSizeText(); 9711 } 9712 isShowingHint()9713 private boolean isShowingHint() { 9714 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9715 } 9716 9717 /** 9718 * Returns true if anything changed. 9719 */ 9720 @UnsupportedAppUsage bringTextIntoView()9721 private boolean bringTextIntoView() { 9722 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9723 int line = 0; 9724 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9725 line = layout.getLineCount() - 1; 9726 } 9727 9728 Layout.Alignment a = layout.getParagraphAlignment(line); 9729 int dir = layout.getParagraphDirection(line); 9730 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9731 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9732 int ht = layout.getHeight(); 9733 9734 int scrollx, scrolly; 9735 9736 // Convert to left, center, or right alignment. 9737 if (a == Layout.Alignment.ALIGN_NORMAL) { 9738 a = dir == Layout.DIR_LEFT_TO_RIGHT 9739 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9740 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9741 a = dir == Layout.DIR_LEFT_TO_RIGHT 9742 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9743 } 9744 9745 if (a == Layout.Alignment.ALIGN_CENTER) { 9746 /* 9747 * Keep centered if possible, or, if it is too wide to fit, 9748 * keep leading edge in view. 9749 */ 9750 9751 int left = (int) Math.floor(layout.getLineLeft(line)); 9752 int right = (int) Math.ceil(layout.getLineRight(line)); 9753 9754 if (right - left < hspace) { 9755 scrollx = (right + left) / 2 - hspace / 2; 9756 } else { 9757 if (dir < 0) { 9758 scrollx = right - hspace; 9759 } else { 9760 scrollx = left; 9761 } 9762 } 9763 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9764 int right = (int) Math.ceil(layout.getLineRight(line)); 9765 scrollx = right - hspace; 9766 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9767 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9768 } 9769 9770 if (ht < vspace) { 9771 scrolly = 0; 9772 } else { 9773 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9774 scrolly = ht - vspace; 9775 } else { 9776 scrolly = 0; 9777 } 9778 } 9779 9780 if (scrollx != mScrollX || scrolly != mScrollY) { 9781 scrollTo(scrollx, scrolly); 9782 return true; 9783 } else { 9784 return false; 9785 } 9786 } 9787 9788 /** 9789 * Move the point, specified by the offset, into the view if it is needed. 9790 * This has to be called after layout. Returns true if anything changed. 9791 */ bringPointIntoView(int offset)9792 public boolean bringPointIntoView(int offset) { 9793 if (isLayoutRequested()) { 9794 mDeferScroll = offset; 9795 return false; 9796 } 9797 boolean changed = false; 9798 9799 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9800 9801 if (layout == null) return changed; 9802 9803 int line = layout.getLineForOffset(offset); 9804 9805 int grav; 9806 9807 switch (layout.getParagraphAlignment(line)) { 9808 case ALIGN_LEFT: 9809 grav = 1; 9810 break; 9811 case ALIGN_RIGHT: 9812 grav = -1; 9813 break; 9814 case ALIGN_NORMAL: 9815 grav = layout.getParagraphDirection(line); 9816 break; 9817 case ALIGN_OPPOSITE: 9818 grav = -layout.getParagraphDirection(line); 9819 break; 9820 case ALIGN_CENTER: 9821 default: 9822 grav = 0; 9823 break; 9824 } 9825 9826 // We only want to clamp the cursor to fit within the layout width 9827 // in left-to-right modes, because in a right to left alignment, 9828 // we want to scroll to keep the line-right on the screen, as other 9829 // lines are likely to have text flush with the right margin, which 9830 // we want to keep visible. 9831 // A better long-term solution would probably be to measure both 9832 // the full line and a blank-trimmed version, and, for example, use 9833 // the latter measurement for centering and right alignment, but for 9834 // the time being we only implement the cursor clamping in left to 9835 // right where it is most likely to be annoying. 9836 final boolean clamped = grav > 0; 9837 // FIXME: Is it okay to truncate this, or should we round? 9838 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9839 final int top = layout.getLineTop(line); 9840 final int bottom = layout.getLineTop(line + 1); 9841 9842 int left = (int) Math.floor(layout.getLineLeft(line)); 9843 int right = (int) Math.ceil(layout.getLineRight(line)); 9844 int ht = layout.getHeight(); 9845 9846 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9847 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9848 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9849 // If cursor has been clamped, make sure we don't scroll. 9850 right = Math.max(x, left + hspace); 9851 } 9852 9853 int hslack = (bottom - top) / 2; 9854 int vslack = hslack; 9855 9856 if (vslack > vspace / 4) { 9857 vslack = vspace / 4; 9858 } 9859 if (hslack > hspace / 4) { 9860 hslack = hspace / 4; 9861 } 9862 9863 int hs = mScrollX; 9864 int vs = mScrollY; 9865 9866 if (top - vs < vslack) { 9867 vs = top - vslack; 9868 } 9869 if (bottom - vs > vspace - vslack) { 9870 vs = bottom - (vspace - vslack); 9871 } 9872 if (ht - vs < vspace) { 9873 vs = ht - vspace; 9874 } 9875 if (0 - vs > 0) { 9876 vs = 0; 9877 } 9878 9879 if (grav != 0) { 9880 if (x - hs < hslack) { 9881 hs = x - hslack; 9882 } 9883 if (x - hs > hspace - hslack) { 9884 hs = x - (hspace - hslack); 9885 } 9886 } 9887 9888 if (grav < 0) { 9889 if (left - hs > 0) { 9890 hs = left; 9891 } 9892 if (right - hs < hspace) { 9893 hs = right - hspace; 9894 } 9895 } else if (grav > 0) { 9896 if (right - hs < hspace) { 9897 hs = right - hspace; 9898 } 9899 if (left - hs > 0) { 9900 hs = left; 9901 } 9902 } else /* grav == 0 */ { 9903 if (right - left <= hspace) { 9904 /* 9905 * If the entire text fits, center it exactly. 9906 */ 9907 hs = left - (hspace - (right - left)) / 2; 9908 } else if (x > right - hslack) { 9909 /* 9910 * If we are near the right edge, keep the right edge 9911 * at the edge of the view. 9912 */ 9913 hs = right - hspace; 9914 } else if (x < left + hslack) { 9915 /* 9916 * If we are near the left edge, keep the left edge 9917 * at the edge of the view. 9918 */ 9919 hs = left; 9920 } else if (left > hs) { 9921 /* 9922 * Is there whitespace visible at the left? Fix it if so. 9923 */ 9924 hs = left; 9925 } else if (right < hs + hspace) { 9926 /* 9927 * Is there whitespace visible at the right? Fix it if so. 9928 */ 9929 hs = right - hspace; 9930 } else { 9931 /* 9932 * Otherwise, float as needed. 9933 */ 9934 if (x - hs < hslack) { 9935 hs = x - hslack; 9936 } 9937 if (x - hs > hspace - hslack) { 9938 hs = x - (hspace - hslack); 9939 } 9940 } 9941 } 9942 9943 if (hs != mScrollX || vs != mScrollY) { 9944 if (mScroller == null) { 9945 scrollTo(hs, vs); 9946 } else { 9947 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 9948 int dx = hs - mScrollX; 9949 int dy = vs - mScrollY; 9950 9951 if (duration > ANIMATED_SCROLL_GAP) { 9952 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 9953 awakenScrollBars(mScroller.getDuration()); 9954 invalidate(); 9955 } else { 9956 if (!mScroller.isFinished()) { 9957 mScroller.abortAnimation(); 9958 } 9959 9960 scrollBy(dx, dy); 9961 } 9962 9963 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 9964 } 9965 9966 changed = true; 9967 } 9968 9969 if (isFocused()) { 9970 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 9971 // requestRectangleOnScreen() is in terms of content coordinates. 9972 9973 // The offsets here are to ensure the rectangle we are using is 9974 // within our view bounds, in case the cursor is on the far left 9975 // or right. If it isn't withing the bounds, then this request 9976 // will be ignored. 9977 if (mTempRect == null) mTempRect = new Rect(); 9978 mTempRect.set(x - 2, top, x + 2, bottom); 9979 getInterestingRect(mTempRect, line); 9980 mTempRect.offset(mScrollX, mScrollY); 9981 9982 if (requestRectangleOnScreen(mTempRect)) { 9983 changed = true; 9984 } 9985 } 9986 9987 return changed; 9988 } 9989 9990 /** 9991 * Move the cursor, if needed, so that it is at an offset that is visible 9992 * to the user. This will not move the cursor if it represents more than 9993 * one character (a selection range). This will only work if the 9994 * TextView contains spannable text; otherwise it will do nothing. 9995 * 9996 * @return True if the cursor was actually moved, false otherwise. 9997 */ moveCursorToVisibleOffset()9998 public boolean moveCursorToVisibleOffset() { 9999 if (!(mText instanceof Spannable)) { 10000 return false; 10001 } 10002 int start = getSelectionStart(); 10003 int end = getSelectionEnd(); 10004 if (start != end) { 10005 return false; 10006 } 10007 10008 // First: make sure the line is visible on screen: 10009 10010 int line = mLayout.getLineForOffset(start); 10011 10012 final int top = mLayout.getLineTop(line); 10013 final int bottom = mLayout.getLineTop(line + 1); 10014 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10015 int vslack = (bottom - top) / 2; 10016 if (vslack > vspace / 4) { 10017 vslack = vspace / 4; 10018 } 10019 final int vs = mScrollY; 10020 10021 if (top < (vs + vslack)) { 10022 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10023 } else if (bottom > (vspace + vs - vslack)) { 10024 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10025 } 10026 10027 // Next: make sure the character is visible on screen: 10028 10029 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10030 final int hs = mScrollX; 10031 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10032 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10033 10034 // line might contain bidirectional text 10035 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10036 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10037 10038 int newStart = start; 10039 if (newStart < lowChar) { 10040 newStart = lowChar; 10041 } else if (newStart > highChar) { 10042 newStart = highChar; 10043 } 10044 10045 if (newStart != start) { 10046 Selection.setSelection(mSpannable, newStart); 10047 return true; 10048 } 10049 10050 return false; 10051 } 10052 10053 @Override computeScroll()10054 public void computeScroll() { 10055 if (mScroller != null) { 10056 if (mScroller.computeScrollOffset()) { 10057 mScrollX = mScroller.getCurrX(); 10058 mScrollY = mScroller.getCurrY(); 10059 invalidateParentCaches(); 10060 postInvalidate(); // So we draw again 10061 } 10062 } 10063 } 10064 getInterestingRect(Rect r, int line)10065 private void getInterestingRect(Rect r, int line) { 10066 convertFromViewportToContentCoordinates(r); 10067 10068 // Rectangle can can be expanded on first and last line to take 10069 // padding into account. 10070 // TODO Take left/right padding into account too? 10071 if (line == 0) r.top -= getExtendedPaddingTop(); 10072 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10073 } 10074 convertFromViewportToContentCoordinates(Rect r)10075 private void convertFromViewportToContentCoordinates(Rect r) { 10076 final int horizontalOffset = viewportToContentHorizontalOffset(); 10077 r.left += horizontalOffset; 10078 r.right += horizontalOffset; 10079 10080 final int verticalOffset = viewportToContentVerticalOffset(); 10081 r.top += verticalOffset; 10082 r.bottom += verticalOffset; 10083 } 10084 viewportToContentHorizontalOffset()10085 int viewportToContentHorizontalOffset() { 10086 return getCompoundPaddingLeft() - mScrollX; 10087 } 10088 10089 @UnsupportedAppUsage viewportToContentVerticalOffset()10090 int viewportToContentVerticalOffset() { 10091 int offset = getExtendedPaddingTop() - mScrollY; 10092 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10093 offset += getVerticalOffset(false); 10094 } 10095 return offset; 10096 } 10097 10098 @Override debug(int depth)10099 public void debug(int depth) { 10100 super.debug(depth); 10101 10102 String output = debugIndent(depth); 10103 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10104 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10105 + "} "; 10106 10107 if (mText != null) { 10108 10109 output += "mText=\"" + mText + "\" "; 10110 if (mLayout != null) { 10111 output += "mLayout width=" + mLayout.getWidth() 10112 + " height=" + mLayout.getHeight(); 10113 } 10114 } else { 10115 output += "mText=NULL"; 10116 } 10117 Log.d(VIEW_LOG_TAG, output); 10118 } 10119 10120 /** 10121 * Convenience for {@link Selection#getSelectionStart}. 10122 */ 10123 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10124 public int getSelectionStart() { 10125 return Selection.getSelectionStart(getText()); 10126 } 10127 10128 /** 10129 * Convenience for {@link Selection#getSelectionEnd}. 10130 */ 10131 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10132 public int getSelectionEnd() { 10133 return Selection.getSelectionEnd(getText()); 10134 } 10135 10136 /** 10137 * Return true iff there is a selection of nonzero length inside this text view. 10138 */ hasSelection()10139 public boolean hasSelection() { 10140 final int selectionStart = getSelectionStart(); 10141 final int selectionEnd = getSelectionEnd(); 10142 10143 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10144 } 10145 getSelectedText()10146 String getSelectedText() { 10147 if (!hasSelection()) { 10148 return null; 10149 } 10150 10151 final int start = getSelectionStart(); 10152 final int end = getSelectionEnd(); 10153 return String.valueOf( 10154 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10155 } 10156 10157 /** 10158 * Sets the properties of this field (lines, horizontally scrolling, 10159 * transformation method) to be for a single-line input. 10160 * 10161 * @attr ref android.R.styleable#TextView_singleLine 10162 */ setSingleLine()10163 public void setSingleLine() { 10164 setSingleLine(true); 10165 } 10166 10167 /** 10168 * Sets the properties of this field to transform input to ALL CAPS 10169 * display. This may use a "small caps" formatting if available. 10170 * This setting will be ignored if this field is editable or selectable. 10171 * 10172 * This call replaces the current transformation method. Disabling this 10173 * will not necessarily restore the previous behavior from before this 10174 * was enabled. 10175 * 10176 * @see #setTransformationMethod(TransformationMethod) 10177 * @attr ref android.R.styleable#TextView_textAllCaps 10178 */ setAllCaps(boolean allCaps)10179 public void setAllCaps(boolean allCaps) { 10180 if (allCaps) { 10181 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10182 } else { 10183 setTransformationMethod(null); 10184 } 10185 } 10186 10187 /** 10188 * 10189 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10190 * @return Whether the current transformation method is for ALL CAPS. 10191 * 10192 * @see #setAllCaps(boolean) 10193 * @see #setTransformationMethod(TransformationMethod) 10194 */ 10195 @InspectableProperty(name = "textAllCaps") isAllCaps()10196 public boolean isAllCaps() { 10197 final TransformationMethod method = getTransformationMethod(); 10198 return method != null && method instanceof AllCapsTransformationMethod; 10199 } 10200 10201 /** 10202 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10203 * transformation method) to be for a single-line input; if false, restores these to the default 10204 * conditions. 10205 * 10206 * Note that the default conditions are not necessarily those that were in effect prior this 10207 * method, and you may want to reset these properties to your custom values. 10208 * 10209 * @attr ref android.R.styleable#TextView_singleLine 10210 */ 10211 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10212 public void setSingleLine(boolean singleLine) { 10213 // Could be used, but may break backward compatibility. 10214 // if (mSingleLine == singleLine) return; 10215 setInputTypeSingleLine(singleLine); 10216 applySingleLine(singleLine, true, true); 10217 } 10218 10219 /** 10220 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10221 * @param singleLine 10222 */ setInputTypeSingleLine(boolean singleLine)10223 private void setInputTypeSingleLine(boolean singleLine) { 10224 if (mEditor != null 10225 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10226 == EditorInfo.TYPE_CLASS_TEXT) { 10227 if (singleLine) { 10228 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10229 } else { 10230 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10231 } 10232 } 10233 } 10234 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10235 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10236 boolean changeMaxLines) { 10237 mSingleLine = singleLine; 10238 if (singleLine) { 10239 setLines(1); 10240 setHorizontallyScrolling(true); 10241 if (applyTransformation) { 10242 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10243 } 10244 } else { 10245 if (changeMaxLines) { 10246 setMaxLines(Integer.MAX_VALUE); 10247 } 10248 setHorizontallyScrolling(false); 10249 if (applyTransformation) { 10250 setTransformationMethod(null); 10251 } 10252 } 10253 } 10254 10255 /** 10256 * Causes words in the text that are longer than the view's width 10257 * to be ellipsized instead of broken in the middle. You may also 10258 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10259 * to constrain the text to a single line. Use <code>null</code> 10260 * to turn off ellipsizing. 10261 * 10262 * If {@link #setMaxLines} has been used to set two or more lines, 10263 * only {@link android.text.TextUtils.TruncateAt#END} and 10264 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10265 * (other ellipsizing types will not do anything). 10266 * 10267 * @attr ref android.R.styleable#TextView_ellipsize 10268 */ setEllipsize(TextUtils.TruncateAt where)10269 public void setEllipsize(TextUtils.TruncateAt where) { 10270 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10271 if (mEllipsize != where) { 10272 mEllipsize = where; 10273 10274 if (mLayout != null) { 10275 nullLayouts(); 10276 requestLayout(); 10277 invalidate(); 10278 } 10279 } 10280 } 10281 10282 /** 10283 * Sets how many times to repeat the marquee animation. Only applied if the 10284 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10285 * 10286 * @see #getMarqueeRepeatLimit() 10287 * 10288 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10289 */ setMarqueeRepeatLimit(int marqueeLimit)10290 public void setMarqueeRepeatLimit(int marqueeLimit) { 10291 mMarqueeRepeatLimit = marqueeLimit; 10292 } 10293 10294 /** 10295 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10296 * TextView has marquee enabled. 10297 * 10298 * @return the number of times the marquee animation is repeated. -1 if the animation 10299 * repeats indefinitely 10300 * 10301 * @see #setMarqueeRepeatLimit(int) 10302 * 10303 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10304 */ 10305 @InspectableProperty getMarqueeRepeatLimit()10306 public int getMarqueeRepeatLimit() { 10307 return mMarqueeRepeatLimit; 10308 } 10309 10310 /** 10311 * Returns where, if anywhere, words that are longer than the view 10312 * is wide should be ellipsized. 10313 */ 10314 @InspectableProperty 10315 @ViewDebug.ExportedProperty getEllipsize()10316 public TextUtils.TruncateAt getEllipsize() { 10317 return mEllipsize; 10318 } 10319 10320 /** 10321 * Set the TextView so that when it takes focus, all the text is 10322 * selected. 10323 * 10324 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10325 */ 10326 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10327 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10328 createEditorIfNeeded(); 10329 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10330 10331 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10332 setText(mText, BufferType.SPANNABLE); 10333 } 10334 } 10335 10336 /** 10337 * Set whether the cursor is visible. The default is true. Note that this property only 10338 * makes sense for editable TextView. 10339 * 10340 * @see #isCursorVisible() 10341 * 10342 * @attr ref android.R.styleable#TextView_cursorVisible 10343 */ 10344 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10345 public void setCursorVisible(boolean visible) { 10346 if (visible && mEditor == null) return; // visible is the default value with no edit data 10347 createEditorIfNeeded(); 10348 if (mEditor.mCursorVisible != visible) { 10349 mEditor.mCursorVisible = visible; 10350 invalidate(); 10351 10352 mEditor.makeBlink(); 10353 10354 // InsertionPointCursorController depends on mCursorVisible 10355 mEditor.prepareCursorControllers(); 10356 } 10357 } 10358 10359 /** 10360 * @return whether or not the cursor is visible (assuming this TextView is editable) 10361 * 10362 * @see #setCursorVisible(boolean) 10363 * 10364 * @attr ref android.R.styleable#TextView_cursorVisible 10365 */ 10366 @InspectableProperty isCursorVisible()10367 public boolean isCursorVisible() { 10368 // true is the default value 10369 return mEditor == null ? true : mEditor.mCursorVisible; 10370 } 10371 canMarquee()10372 private boolean canMarquee() { 10373 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10374 return width > 0 && (mLayout.getLineWidth(0) > width 10375 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10376 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10377 } 10378 10379 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10380 private void startMarquee() { 10381 // Do not ellipsize EditText 10382 if (getKeyListener() != null) return; 10383 10384 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10385 return; 10386 } 10387 10388 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10389 && getLineCount() == 1 && canMarquee()) { 10390 10391 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10392 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10393 final Layout tmp = mLayout; 10394 mLayout = mSavedMarqueeModeLayout; 10395 mSavedMarqueeModeLayout = tmp; 10396 setHorizontalFadingEdgeEnabled(true); 10397 requestLayout(); 10398 invalidate(); 10399 } 10400 10401 if (mMarquee == null) mMarquee = new Marquee(this); 10402 mMarquee.start(mMarqueeRepeatLimit); 10403 } 10404 } 10405 stopMarquee()10406 private void stopMarquee() { 10407 if (mMarquee != null && !mMarquee.isStopped()) { 10408 mMarquee.stop(); 10409 } 10410 10411 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10412 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10413 final Layout tmp = mSavedMarqueeModeLayout; 10414 mSavedMarqueeModeLayout = mLayout; 10415 mLayout = tmp; 10416 setHorizontalFadingEdgeEnabled(false); 10417 requestLayout(); 10418 invalidate(); 10419 } 10420 } 10421 10422 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10423 private void startStopMarquee(boolean start) { 10424 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10425 if (start) { 10426 startMarquee(); 10427 } else { 10428 stopMarquee(); 10429 } 10430 } 10431 } 10432 10433 /** 10434 * This method is called when the text is changed, in case any subclasses 10435 * would like to know. 10436 * 10437 * Within <code>text</code>, the <code>lengthAfter</code> characters 10438 * beginning at <code>start</code> have just replaced old text that had 10439 * length <code>lengthBefore</code>. It is an error to attempt to make 10440 * changes to <code>text</code> from this callback. 10441 * 10442 * @param text The text the TextView is displaying 10443 * @param start The offset of the start of the range of the text that was 10444 * modified 10445 * @param lengthBefore The length of the former text that has been replaced 10446 * @param lengthAfter The length of the replacement modified text 10447 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10448 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10449 // intentionally empty, template pattern method can be overridden by subclasses 10450 } 10451 10452 /** 10453 * This method is called when the selection has changed, in case any 10454 * subclasses would like to know. 10455 * 10456 * @param selStart The new selection start location. 10457 * @param selEnd The new selection end location. 10458 */ onSelectionChanged(int selStart, int selEnd)10459 protected void onSelectionChanged(int selStart, int selEnd) { 10460 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10461 } 10462 10463 /** 10464 * Adds a TextWatcher to the list of those whose methods are called 10465 * whenever this TextView's text changes. 10466 * <p> 10467 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10468 * not called after {@link #setText} calls. Now, doing {@link #setText} 10469 * if there are any text changed listeners forces the buffer type to 10470 * Editable if it would not otherwise be and does call this method. 10471 */ addTextChangedListener(TextWatcher watcher)10472 public void addTextChangedListener(TextWatcher watcher) { 10473 if (mListeners == null) { 10474 mListeners = new ArrayList<TextWatcher>(); 10475 } 10476 10477 mListeners.add(watcher); 10478 } 10479 10480 /** 10481 * Removes the specified TextWatcher from the list of those whose 10482 * methods are called 10483 * whenever this TextView's text changes. 10484 */ removeTextChangedListener(TextWatcher watcher)10485 public void removeTextChangedListener(TextWatcher watcher) { 10486 if (mListeners != null) { 10487 int i = mListeners.indexOf(watcher); 10488 10489 if (i >= 0) { 10490 mListeners.remove(i); 10491 } 10492 } 10493 } 10494 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10495 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10496 if (mListeners != null) { 10497 final ArrayList<TextWatcher> list = mListeners; 10498 final int count = list.size(); 10499 for (int i = 0; i < count; i++) { 10500 list.get(i).beforeTextChanged(text, start, before, after); 10501 } 10502 } 10503 10504 // The spans that are inside or intersect the modified region no longer make sense 10505 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10506 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10507 } 10508 10509 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10510 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10511 if (!(mText instanceof Editable)) return; 10512 Editable text = (Editable) mText; 10513 10514 T[] spans = text.getSpans(start, end, type); 10515 final int length = spans.length; 10516 for (int i = 0; i < length; i++) { 10517 final int spanStart = text.getSpanStart(spans[i]); 10518 final int spanEnd = text.getSpanEnd(spans[i]); 10519 if (spanEnd == start || spanStart == end) break; 10520 text.removeSpan(spans[i]); 10521 } 10522 } 10523 removeAdjacentSuggestionSpans(final int pos)10524 void removeAdjacentSuggestionSpans(final int pos) { 10525 if (!(mText instanceof Editable)) return; 10526 final Editable text = (Editable) mText; 10527 10528 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10529 final int length = spans.length; 10530 for (int i = 0; i < length; i++) { 10531 final int spanStart = text.getSpanStart(spans[i]); 10532 final int spanEnd = text.getSpanEnd(spans[i]); 10533 if (spanEnd == pos || spanStart == pos) { 10534 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10535 text.removeSpan(spans[i]); 10536 } 10537 } 10538 } 10539 } 10540 10541 /** 10542 * Not private so it can be called from an inner class without going 10543 * through a thunk. 10544 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10545 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10546 if (mListeners != null) { 10547 final ArrayList<TextWatcher> list = mListeners; 10548 final int count = list.size(); 10549 for (int i = 0; i < count; i++) { 10550 list.get(i).onTextChanged(text, start, before, after); 10551 } 10552 } 10553 10554 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10555 } 10556 10557 /** 10558 * Not private so it can be called from an inner class without going 10559 * through a thunk. 10560 */ sendAfterTextChanged(Editable text)10561 void sendAfterTextChanged(Editable text) { 10562 if (mListeners != null) { 10563 final ArrayList<TextWatcher> list = mListeners; 10564 final int count = list.size(); 10565 for (int i = 0; i < count; i++) { 10566 list.get(i).afterTextChanged(text); 10567 } 10568 } 10569 10570 notifyListeningManagersAfterTextChanged(); 10571 10572 hideErrorIfUnchanged(); 10573 } 10574 10575 /** 10576 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 10577 * interested on text changes. 10578 */ notifyListeningManagersAfterTextChanged()10579 private void notifyListeningManagersAfterTextChanged() { 10580 10581 // Autofill 10582 if (isAutofillable()) { 10583 // It is important to not check whether the view is important for autofill 10584 // since the user can trigger autofill manually on not important views. 10585 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10586 if (afm != null) { 10587 if (android.view.autofill.Helper.sVerbose) { 10588 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10589 } 10590 afm.notifyValueChanged(TextView.this); 10591 } 10592 } 10593 10594 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 10595 // of using isLaidout(), so it's not called in cases where it's laid out but a 10596 // notifyAppeared was not sent. 10597 10598 // ContentCapture 10599 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 10600 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 10601 if (cm != null && cm.isContentCaptureEnabled()) { 10602 final ContentCaptureSession session = getContentCaptureSession(); 10603 if (session != null) { 10604 // TODO(b/111276913): pass flags when edited by user / add CTS test 10605 session.notifyViewTextChanged(getAutofillId(), getText()); 10606 } 10607 } 10608 } 10609 } 10610 isAutofillable()10611 private boolean isAutofillable() { 10612 // It is important to not check whether the view is important for autofill 10613 // since the user can trigger autofill manually on not important views. 10614 return getAutofillType() != AUTOFILL_TYPE_NONE; 10615 } 10616 updateAfterEdit()10617 void updateAfterEdit() { 10618 invalidate(); 10619 int curs = getSelectionStart(); 10620 10621 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10622 registerForPreDraw(); 10623 } 10624 10625 checkForResize(); 10626 10627 if (curs >= 0) { 10628 mHighlightPathBogus = true; 10629 if (mEditor != null) mEditor.makeBlink(); 10630 bringPointIntoView(curs); 10631 } 10632 } 10633 10634 /** 10635 * Not private so it can be called from an inner class without going 10636 * through a thunk. 10637 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10638 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10639 sLastCutCopyOrTextChangedTime = 0; 10640 10641 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10642 if (ims == null || ims.mBatchEditNesting == 0) { 10643 updateAfterEdit(); 10644 } 10645 if (ims != null) { 10646 ims.mContentChanged = true; 10647 if (ims.mChangedStart < 0) { 10648 ims.mChangedStart = start; 10649 ims.mChangedEnd = start + before; 10650 } else { 10651 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10652 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10653 } 10654 ims.mChangedDelta += after - before; 10655 } 10656 resetErrorChangedFlag(); 10657 sendOnTextChanged(buffer, start, before, after); 10658 onTextChanged(buffer, start, before, after); 10659 } 10660 10661 /** 10662 * Not private so it can be called from an inner class without going 10663 * through a thunk. 10664 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10665 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10666 // XXX Make the start and end move together if this ends up 10667 // spending too much time invalidating. 10668 10669 boolean selChanged = false; 10670 int newSelStart = -1, newSelEnd = -1; 10671 10672 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10673 10674 if (what == Selection.SELECTION_END) { 10675 selChanged = true; 10676 newSelEnd = newStart; 10677 10678 if (oldStart >= 0 || newStart >= 0) { 10679 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10680 checkForResize(); 10681 registerForPreDraw(); 10682 if (mEditor != null) mEditor.makeBlink(); 10683 } 10684 } 10685 10686 if (what == Selection.SELECTION_START) { 10687 selChanged = true; 10688 newSelStart = newStart; 10689 10690 if (oldStart >= 0 || newStart >= 0) { 10691 int end = Selection.getSelectionEnd(buf); 10692 invalidateCursor(end, oldStart, newStart); 10693 } 10694 } 10695 10696 if (selChanged) { 10697 mHighlightPathBogus = true; 10698 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10699 10700 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10701 if (newSelStart < 0) { 10702 newSelStart = Selection.getSelectionStart(buf); 10703 } 10704 if (newSelEnd < 0) { 10705 newSelEnd = Selection.getSelectionEnd(buf); 10706 } 10707 10708 if (mEditor != null) { 10709 mEditor.refreshTextActionMode(); 10710 if (!hasSelection() 10711 && mEditor.getTextActionMode() == null && hasTransientState()) { 10712 // User generated selection has been removed. 10713 setHasTransientState(false); 10714 } 10715 } 10716 onSelectionChanged(newSelStart, newSelEnd); 10717 } 10718 } 10719 10720 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10721 || what instanceof CharacterStyle) { 10722 if (ims == null || ims.mBatchEditNesting == 0) { 10723 invalidate(); 10724 mHighlightPathBogus = true; 10725 checkForResize(); 10726 } else { 10727 ims.mContentChanged = true; 10728 } 10729 if (mEditor != null) { 10730 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10731 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10732 mEditor.invalidateHandlesAndActionMode(); 10733 } 10734 } 10735 10736 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10737 mHighlightPathBogus = true; 10738 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10739 ims.mSelectionModeChanged = true; 10740 } 10741 10742 if (Selection.getSelectionStart(buf) >= 0) { 10743 if (ims == null || ims.mBatchEditNesting == 0) { 10744 invalidateCursor(); 10745 } else { 10746 ims.mCursorChanged = true; 10747 } 10748 } 10749 } 10750 10751 if (what instanceof ParcelableSpan) { 10752 // If this is a span that can be sent to a remote process, 10753 // the current extract editor would be interested in it. 10754 if (ims != null && ims.mExtractedTextRequest != null) { 10755 if (ims.mBatchEditNesting != 0) { 10756 if (oldStart >= 0) { 10757 if (ims.mChangedStart > oldStart) { 10758 ims.mChangedStart = oldStart; 10759 } 10760 if (ims.mChangedStart > oldEnd) { 10761 ims.mChangedStart = oldEnd; 10762 } 10763 } 10764 if (newStart >= 0) { 10765 if (ims.mChangedStart > newStart) { 10766 ims.mChangedStart = newStart; 10767 } 10768 if (ims.mChangedStart > newEnd) { 10769 ims.mChangedStart = newEnd; 10770 } 10771 } 10772 } else { 10773 if (DEBUG_EXTRACT) { 10774 Log.v(LOG_TAG, "Span change outside of batch: " 10775 + oldStart + "-" + oldEnd + "," 10776 + newStart + "-" + newEnd + " " + what); 10777 } 10778 ims.mContentChanged = true; 10779 } 10780 } 10781 } 10782 10783 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 10784 && what instanceof SpellCheckSpan) { 10785 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 10786 } 10787 } 10788 10789 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10790 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 10791 if (isTemporarilyDetached()) { 10792 // If we are temporarily in the detach state, then do nothing. 10793 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10794 return; 10795 } 10796 10797 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 10798 10799 if (focused) { 10800 if (mSpannable != null) { 10801 MetaKeyKeyListener.resetMetaState(mSpannable); 10802 } 10803 } 10804 10805 startStopMarquee(focused); 10806 10807 if (mTransformation != null) { 10808 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 10809 } 10810 10811 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10812 } 10813 10814 @Override onWindowFocusChanged(boolean hasWindowFocus)10815 public void onWindowFocusChanged(boolean hasWindowFocus) { 10816 super.onWindowFocusChanged(hasWindowFocus); 10817 10818 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 10819 10820 startStopMarquee(hasWindowFocus); 10821 } 10822 10823 @Override onVisibilityChanged(View changedView, int visibility)10824 protected void onVisibilityChanged(View changedView, int visibility) { 10825 super.onVisibilityChanged(changedView, visibility); 10826 if (mEditor != null && visibility != VISIBLE) { 10827 mEditor.hideCursorAndSpanControllers(); 10828 stopTextActionMode(); 10829 } 10830 } 10831 10832 /** 10833 * Use {@link BaseInputConnection#removeComposingSpans 10834 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 10835 * state from this text view. 10836 */ clearComposingText()10837 public void clearComposingText() { 10838 if (mText instanceof Spannable) { 10839 BaseInputConnection.removeComposingSpans(mSpannable); 10840 } 10841 } 10842 10843 @Override setSelected(boolean selected)10844 public void setSelected(boolean selected) { 10845 boolean wasSelected = isSelected(); 10846 10847 super.setSelected(selected); 10848 10849 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10850 if (selected) { 10851 startMarquee(); 10852 } else { 10853 stopMarquee(); 10854 } 10855 } 10856 } 10857 10858 @Override onTouchEvent(MotionEvent event)10859 public boolean onTouchEvent(MotionEvent event) { 10860 final int action = event.getActionMasked(); 10861 if (mEditor != null) { 10862 mEditor.onTouchEvent(event); 10863 10864 if (mEditor.mSelectionModifierCursorController != null 10865 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 10866 return true; 10867 } 10868 } 10869 10870 final boolean superResult = super.onTouchEvent(event); 10871 10872 /* 10873 * Don't handle the release after a long press, because it will move the selection away from 10874 * whatever the menu action was trying to affect. If the long press should have triggered an 10875 * insertion action mode, we can now actually show it. 10876 */ 10877 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 10878 mEditor.mDiscardNextActionUp = false; 10879 10880 if (mEditor.mIsInsertionActionModeStartPending) { 10881 mEditor.startInsertionActionMode(); 10882 mEditor.mIsInsertionActionModeStartPending = false; 10883 } 10884 return superResult; 10885 } 10886 10887 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 10888 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 10889 10890 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 10891 && mText instanceof Spannable && mLayout != null) { 10892 boolean handled = false; 10893 10894 if (mMovement != null) { 10895 handled |= mMovement.onTouchEvent(this, mSpannable, event); 10896 } 10897 10898 final boolean textIsSelectable = isTextSelectable(); 10899 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 10900 // The LinkMovementMethod which should handle taps on links has not been installed 10901 // on non editable text that support text selection. 10902 // We reproduce its behavior here to open links for these. 10903 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 10904 getSelectionEnd(), ClickableSpan.class); 10905 10906 if (links.length > 0) { 10907 links[0].onClick(this); 10908 handled = true; 10909 } 10910 } 10911 10912 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 10913 // Show the IME, except when selecting in read-only text. 10914 final InputMethodManager imm = getInputMethodManager(); 10915 viewClicked(imm); 10916 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10917 imm.showSoftInput(this, 0); 10918 } 10919 10920 // The above condition ensures that the mEditor is not null 10921 mEditor.onTouchUpEvent(event); 10922 10923 handled = true; 10924 } 10925 10926 if (handled) { 10927 return true; 10928 } 10929 } 10930 10931 return superResult; 10932 } 10933 10934 @Override onGenericMotionEvent(MotionEvent event)10935 public boolean onGenericMotionEvent(MotionEvent event) { 10936 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 10937 try { 10938 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 10939 return true; 10940 } 10941 } catch (AbstractMethodError ex) { 10942 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 10943 // Ignore its absence in case third party applications implemented the 10944 // interface directly. 10945 } 10946 } 10947 return super.onGenericMotionEvent(event); 10948 } 10949 10950 @Override onCreateContextMenu(ContextMenu menu)10951 protected void onCreateContextMenu(ContextMenu menu) { 10952 if (mEditor != null) { 10953 mEditor.onCreateContextMenu(menu); 10954 } 10955 } 10956 10957 @Override showContextMenu()10958 public boolean showContextMenu() { 10959 if (mEditor != null) { 10960 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 10961 } 10962 return super.showContextMenu(); 10963 } 10964 10965 @Override showContextMenu(float x, float y)10966 public boolean showContextMenu(float x, float y) { 10967 if (mEditor != null) { 10968 mEditor.setContextMenuAnchor(x, y); 10969 } 10970 return super.showContextMenu(x, y); 10971 } 10972 10973 /** 10974 * @return True iff this TextView contains a text that can be edited, or if this is 10975 * a selectable TextView. 10976 */ 10977 @UnsupportedAppUsage isTextEditable()10978 boolean isTextEditable() { 10979 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 10980 } 10981 10982 /** 10983 * Returns true, only while processing a touch gesture, if the initial 10984 * touch down event caused focus to move to the text view and as a result 10985 * its selection changed. Only valid while processing the touch gesture 10986 * of interest, in an editable text view. 10987 */ didTouchFocusSelect()10988 public boolean didTouchFocusSelect() { 10989 return mEditor != null && mEditor.mTouchFocusSelected; 10990 } 10991 10992 @Override cancelLongPress()10993 public void cancelLongPress() { 10994 super.cancelLongPress(); 10995 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 10996 } 10997 10998 @Override onTrackballEvent(MotionEvent event)10999 public boolean onTrackballEvent(MotionEvent event) { 11000 if (mMovement != null && mSpannable != null && mLayout != null) { 11001 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 11002 return true; 11003 } 11004 } 11005 11006 return super.onTrackballEvent(event); 11007 } 11008 11009 /** 11010 * Sets the Scroller used for producing a scrolling animation 11011 * 11012 * @param s A Scroller instance 11013 */ setScroller(Scroller s)11014 public void setScroller(Scroller s) { 11015 mScroller = s; 11016 } 11017 11018 @Override getLeftFadingEdgeStrength()11019 protected float getLeftFadingEdgeStrength() { 11020 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11021 final Marquee marquee = mMarquee; 11022 if (marquee.shouldDrawLeftFade()) { 11023 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11024 } else { 11025 return 0.0f; 11026 } 11027 } else if (getLineCount() == 1) { 11028 final float lineLeft = getLayout().getLineLeft(0); 11029 if (lineLeft > mScrollX) return 0.0f; 11030 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11031 } 11032 return super.getLeftFadingEdgeStrength(); 11033 } 11034 11035 @Override getRightFadingEdgeStrength()11036 protected float getRightFadingEdgeStrength() { 11037 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11038 final Marquee marquee = mMarquee; 11039 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11040 } else if (getLineCount() == 1) { 11041 final float rightEdge = mScrollX + 11042 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11043 final float lineRight = getLayout().getLineRight(0); 11044 if (lineRight < rightEdge) return 0.0f; 11045 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11046 } 11047 return super.getRightFadingEdgeStrength(); 11048 } 11049 11050 /** 11051 * Calculates the fading edge strength as the ratio of the distance between two 11052 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11053 * value for the distance calculation. 11054 * 11055 * @param position1 A horizontal position. 11056 * @param position2 A horizontal position. 11057 * @return Fading edge strength between [0.0f, 1.0f]. 11058 */ 11059 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11060 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11061 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11062 if (horizontalFadingEdgeLength == 0) return 0.0f; 11063 final float diff = Math.abs(position1 - position2); 11064 if (diff > horizontalFadingEdgeLength) return 1.0f; 11065 return diff / horizontalFadingEdgeLength; 11066 } 11067 isMarqueeFadeEnabled()11068 private boolean isMarqueeFadeEnabled() { 11069 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11070 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11071 } 11072 11073 @Override computeHorizontalScrollRange()11074 protected int computeHorizontalScrollRange() { 11075 if (mLayout != null) { 11076 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11077 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11078 } 11079 11080 return super.computeHorizontalScrollRange(); 11081 } 11082 11083 @Override computeVerticalScrollRange()11084 protected int computeVerticalScrollRange() { 11085 if (mLayout != null) { 11086 return mLayout.getHeight(); 11087 } 11088 return super.computeVerticalScrollRange(); 11089 } 11090 11091 @Override computeVerticalScrollExtent()11092 protected int computeVerticalScrollExtent() { 11093 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11094 } 11095 11096 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11097 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11098 super.findViewsWithText(outViews, searched, flags); 11099 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11100 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11101 String searchedLowerCase = searched.toString().toLowerCase(); 11102 String textLowerCase = mText.toString().toLowerCase(); 11103 if (textLowerCase.contains(searchedLowerCase)) { 11104 outViews.add(this); 11105 } 11106 } 11107 } 11108 11109 /** 11110 * Type of the text buffer that defines the characteristics of the text such as static, 11111 * styleable, or editable. 11112 */ 11113 public enum BufferType { 11114 NORMAL, SPANNABLE, EDITABLE 11115 } 11116 11117 /** 11118 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11119 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11120 * if TextView_textColor was not set directly. 11121 * 11122 * @removed 11123 */ getTextColors(Context context, TypedArray attrs)11124 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11125 if (attrs == null) { 11126 // Preserve behavior prior to removal of this API. 11127 throw new NullPointerException(); 11128 } 11129 11130 // It's not safe to use this method from apps. The parameter 'attrs' 11131 // must have been obtained using the TextView filter array which is not 11132 // available to the SDK. As such, we grab a default TypedArray with the 11133 // right filter instead here. 11134 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11135 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11136 if (colors == null) { 11137 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11138 if (ap != 0) { 11139 final TypedArray appearance = context.obtainStyledAttributes( 11140 ap, R.styleable.TextAppearance); 11141 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11142 appearance.recycle(); 11143 } 11144 } 11145 a.recycle(); 11146 11147 return colors; 11148 } 11149 11150 /** 11151 * Returns the default color from the TextView_textColor attribute from the 11152 * AttributeSet, if set, or the default color from the 11153 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11154 * TextView_textColor was not set directly. 11155 * 11156 * @removed 11157 */ getTextColor(Context context, TypedArray attrs, int def)11158 public static int getTextColor(Context context, TypedArray attrs, int def) { 11159 final ColorStateList colors = getTextColors(context, attrs); 11160 if (colors == null) { 11161 return def; 11162 } else { 11163 return colors.getDefaultColor(); 11164 } 11165 } 11166 11167 @Override onKeyShortcut(int keyCode, KeyEvent event)11168 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11169 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11170 // Handle Ctrl-only shortcuts. 11171 switch (keyCode) { 11172 case KeyEvent.KEYCODE_A: 11173 if (canSelectText()) { 11174 return onTextContextMenuItem(ID_SELECT_ALL); 11175 } 11176 break; 11177 case KeyEvent.KEYCODE_Z: 11178 if (canUndo()) { 11179 return onTextContextMenuItem(ID_UNDO); 11180 } 11181 break; 11182 case KeyEvent.KEYCODE_X: 11183 if (canCut()) { 11184 return onTextContextMenuItem(ID_CUT); 11185 } 11186 break; 11187 case KeyEvent.KEYCODE_C: 11188 if (canCopy()) { 11189 return onTextContextMenuItem(ID_COPY); 11190 } 11191 break; 11192 case KeyEvent.KEYCODE_V: 11193 if (canPaste()) { 11194 return onTextContextMenuItem(ID_PASTE); 11195 } 11196 break; 11197 } 11198 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11199 // Handle Ctrl-Shift shortcuts. 11200 switch (keyCode) { 11201 case KeyEvent.KEYCODE_Z: 11202 if (canRedo()) { 11203 return onTextContextMenuItem(ID_REDO); 11204 } 11205 break; 11206 case KeyEvent.KEYCODE_V: 11207 if (canPaste()) { 11208 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11209 } 11210 } 11211 } 11212 return super.onKeyShortcut(keyCode, event); 11213 } 11214 11215 /** 11216 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11217 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11218 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11219 * sufficient. 11220 */ canSelectText()11221 boolean canSelectText() { 11222 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11223 } 11224 11225 /** 11226 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11227 * The text must be spannable and the movement method must allow for arbitary selection. 11228 * 11229 * See also {@link #canSelectText()}. 11230 */ textCanBeSelected()11231 boolean textCanBeSelected() { 11232 // prepareCursorController() relies on this method. 11233 // If you change this condition, make sure prepareCursorController is called anywhere 11234 // the value of this condition might be changed. 11235 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11236 return isTextEditable() 11237 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11238 } 11239 11240 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11241 private Locale getTextServicesLocale(boolean allowNullLocale) { 11242 // Start fetching the text services locale asynchronously. 11243 updateTextServicesLocaleAsync(); 11244 // If !allowNullLocale and there is no cached text services locale, just return the default 11245 // locale. 11246 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11247 : mCurrentSpellCheckerLocaleCache; 11248 } 11249 11250 /** 11251 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11252 * this {@link TextView}. 11253 * 11254 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11255 * other apps may need to set this so that the system can user right user's resources and 11256 * services such as input methods and spell checkers.</p> 11257 * 11258 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11259 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11260 * @hide 11261 */ 11262 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11263 public final void setTextOperationUser(@Nullable UserHandle user) { 11264 if (Objects.equals(mTextOperationUser, user)) { 11265 return; 11266 } 11267 if (user != null && !Process.myUserHandle().equals(user)) { 11268 // Just for preventing people from accidentally using this hidden API without 11269 // the required permission. The same permission is also checked in the system server. 11270 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11271 != PackageManager.PERMISSION_GRANTED) { 11272 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11273 + " userId=" + user.getIdentifier() 11274 + " callingUserId" + UserHandle.myUserId()); 11275 } 11276 } 11277 mTextOperationUser = user; 11278 // Invalidate some resources 11279 mCurrentSpellCheckerLocaleCache = null; 11280 if (mEditor != null) { 11281 mEditor.onTextOperationUserChanged(); 11282 } 11283 } 11284 11285 @Nullable getTextServicesManagerForUser()11286 final TextServicesManager getTextServicesManagerForUser() { 11287 return getServiceManagerForUser("android", TextServicesManager.class); 11288 } 11289 11290 @Nullable getClipboardManagerForUser()11291 final ClipboardManager getClipboardManagerForUser() { 11292 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11293 } 11294 11295 @Nullable getTextClassificationManagerForUser()11296 final TextClassificationManager getTextClassificationManagerForUser() { 11297 return getServiceManagerForUser( 11298 getContext().getPackageName(), TextClassificationManager.class); 11299 } 11300 11301 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11302 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11303 if (mTextOperationUser == null) { 11304 return getContext().getSystemService(managerClazz); 11305 } 11306 try { 11307 Context context = getContext().createPackageContextAsUser( 11308 packageName, 0 /* flags */, mTextOperationUser); 11309 return context.getSystemService(managerClazz); 11310 } catch (PackageManager.NameNotFoundException e) { 11311 return null; 11312 } 11313 } 11314 11315 /** 11316 * Starts {@link Activity} as a text-operation user if it is specified with 11317 * {@link #setTextOperationUser(UserHandle)}. 11318 * 11319 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11320 * 11321 * @param intent The description of the activity to start. 11322 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11323 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11324 if (mTextOperationUser != null) { 11325 getContext().startActivityAsUser(intent, mTextOperationUser); 11326 } else { 11327 getContext().startActivity(intent); 11328 } 11329 } 11330 11331 /** 11332 * This is a temporary method. Future versions may support multi-locale text. 11333 * Caveat: This method may not return the latest text services locale, but this should be 11334 * acceptable and it's more important to make this method asynchronous. 11335 * 11336 * @return The locale that should be used for a word iterator 11337 * in this TextView, based on the current spell checker settings, 11338 * the current IME's locale, or the system default locale. 11339 * Please note that a word iterator in this TextView is different from another word iterator 11340 * used by SpellChecker.java of TextView. This method should be used for the former. 11341 * @hide 11342 */ 11343 // TODO: Support multi-locale 11344 // TODO: Update the text services locale immediately after the keyboard locale is switched 11345 // by catching intent of keyboard switch event getTextServicesLocale()11346 public Locale getTextServicesLocale() { 11347 return getTextServicesLocale(false /* allowNullLocale */); 11348 } 11349 11350 /** 11351 * @return {@code true} if this TextView is specialized for showing and interacting with the 11352 * extracted text in a full-screen input method. 11353 * @hide 11354 */ isInExtractedMode()11355 public boolean isInExtractedMode() { 11356 return false; 11357 } 11358 11359 /** 11360 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11361 * auto-size. 11362 */ isAutoSizeEnabled()11363 private boolean isAutoSizeEnabled() { 11364 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11365 } 11366 11367 /** 11368 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11369 * @hide 11370 */ supportsAutoSizeText()11371 protected boolean supportsAutoSizeText() { 11372 return true; 11373 } 11374 11375 /** 11376 * This is a temporary method. Future versions may support multi-locale text. 11377 * Caveat: This method may not return the latest spell checker locale, but this should be 11378 * acceptable and it's more important to make this method asynchronous. 11379 * 11380 * @return The locale that should be used for a spell checker in this TextView, 11381 * based on the current spell checker settings, the current IME's locale, or the system default 11382 * locale. 11383 * @hide 11384 */ getSpellCheckerLocale()11385 public Locale getSpellCheckerLocale() { 11386 return getTextServicesLocale(true /* allowNullLocale */); 11387 } 11388 updateTextServicesLocaleAsync()11389 private void updateTextServicesLocaleAsync() { 11390 // AsyncTask.execute() uses a serial executor which means we don't have 11391 // to lock around updateTextServicesLocaleLocked() to prevent it from 11392 // being executed n times in parallel. 11393 AsyncTask.execute(new Runnable() { 11394 @Override 11395 public void run() { 11396 updateTextServicesLocaleLocked(); 11397 } 11398 }); 11399 } 11400 11401 @UnsupportedAppUsage updateTextServicesLocaleLocked()11402 private void updateTextServicesLocaleLocked() { 11403 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11404 if (textServicesManager == null) { 11405 return; 11406 } 11407 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11408 final Locale locale; 11409 if (subtype != null) { 11410 locale = subtype.getLocaleObject(); 11411 } else { 11412 locale = null; 11413 } 11414 mCurrentSpellCheckerLocaleCache = locale; 11415 } 11416 onLocaleChanged()11417 void onLocaleChanged() { 11418 mEditor.onLocaleChanged(); 11419 } 11420 11421 /** 11422 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11423 * Made available to achieve a consistent behavior. 11424 * @hide 11425 */ getWordIterator()11426 public WordIterator getWordIterator() { 11427 if (mEditor != null) { 11428 return mEditor.getWordIterator(); 11429 } else { 11430 return null; 11431 } 11432 } 11433 11434 /** @hide */ 11435 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11436 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11437 super.onPopulateAccessibilityEventInternal(event); 11438 11439 final CharSequence text = getTextForAccessibility(); 11440 if (!TextUtils.isEmpty(text)) { 11441 event.getText().add(text); 11442 } 11443 } 11444 11445 @Override getAccessibilityClassName()11446 public CharSequence getAccessibilityClassName() { 11447 return TextView.class.getName(); 11448 } 11449 11450 /** @hide */ 11451 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11452 protected void onProvideStructure(@NonNull ViewStructure structure, 11453 @ViewStructureType int viewFor, int flags) { 11454 super.onProvideStructure(structure, viewFor, flags); 11455 11456 final boolean isPassword = hasPasswordTransformationMethod() 11457 || isPasswordInputType(getInputType()); 11458 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11459 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11460 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11461 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11462 } 11463 if (mTextId != Resources.ID_NULL) { 11464 try { 11465 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11466 } catch (Resources.NotFoundException e) { 11467 if (android.view.autofill.Helper.sVerbose) { 11468 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11469 + mTextId + ": " + e.getMessage()); 11470 } 11471 } 11472 } 11473 } 11474 11475 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11476 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11477 if (mLayout == null) { 11478 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11479 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 11480 } 11481 assumeLayout(); 11482 } 11483 Layout layout = mLayout; 11484 final int lineCount = layout.getLineCount(); 11485 if (lineCount <= 1) { 11486 // Simple case: this is a single line. 11487 final CharSequence text = getText(); 11488 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11489 structure.setText(text); 11490 } else { 11491 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11492 } 11493 } else { 11494 // Complex case: multi-line, could be scrolled or within a scroll container 11495 // so some lines are not visible. 11496 final int[] tmpCords = new int[2]; 11497 getLocationInWindow(tmpCords); 11498 final int topWindowLocation = tmpCords[1]; 11499 View root = this; 11500 ViewParent viewParent = getParent(); 11501 while (viewParent instanceof View) { 11502 root = (View) viewParent; 11503 viewParent = root.getParent(); 11504 } 11505 final int windowHeight = root.getHeight(); 11506 final int topLine; 11507 final int bottomLine; 11508 if (topWindowLocation >= 0) { 11509 // The top of the view is fully within its window; start text at line 0. 11510 topLine = getLineAtCoordinateUnclamped(0); 11511 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11512 } else { 11513 // The top of hte window has scrolled off the top of the window; figure out 11514 // the starting line for this. 11515 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11516 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11517 } 11518 // We want to return some contextual lines above/below the lines that are 11519 // actually visible. 11520 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11521 if (expandedTopLine < 0) { 11522 expandedTopLine = 0; 11523 } 11524 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11525 if (expandedBottomLine >= lineCount) { 11526 expandedBottomLine = lineCount - 1; 11527 } 11528 11529 // Convert lines into character offsets. 11530 int expandedTopChar = layout.getLineStart(expandedTopLine); 11531 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11532 11533 // Take into account selection -- if there is a selection, we need to expand 11534 // the text we are returning to include that selection. 11535 final int selStart = getSelectionStart(); 11536 final int selEnd = getSelectionEnd(); 11537 if (selStart < selEnd) { 11538 if (selStart < expandedTopChar) { 11539 expandedTopChar = selStart; 11540 } 11541 if (selEnd > expandedBottomChar) { 11542 expandedBottomChar = selEnd; 11543 } 11544 } 11545 11546 // Get the text and trim it to the range we are reporting. 11547 CharSequence text = getText(); 11548 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11549 text = text.subSequence(expandedTopChar, expandedBottomChar); 11550 } 11551 11552 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11553 structure.setText(text); 11554 } else { 11555 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 11556 11557 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11558 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11559 final int baselineOffset = getBaselineOffset(); 11560 for (int i = topLine; i <= bottomLine; i++) { 11561 lineOffsets[i - topLine] = layout.getLineStart(i); 11562 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11563 } 11564 structure.setTextLines(lineOffsets, lineBaselines); 11565 } 11566 } 11567 11568 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 11569 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11570 // Extract style information that applies to the TextView as a whole. 11571 int style = 0; 11572 int typefaceStyle = getTypefaceStyle(); 11573 if ((typefaceStyle & Typeface.BOLD) != 0) { 11574 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11575 } 11576 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11577 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11578 } 11579 11580 // Global styles can also be set via TextView.setPaintFlags(). 11581 int paintFlags = mTextPaint.getFlags(); 11582 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11583 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11584 } 11585 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11586 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11587 } 11588 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11589 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11590 } 11591 11592 // TextView does not have its own text background color. A background is either part 11593 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11594 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11595 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11596 } 11597 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11598 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11599 structure.setMinTextEms(getMinEms()); 11600 structure.setMaxTextEms(getMaxEms()); 11601 int maxLength = -1; 11602 for (InputFilter filter: getFilters()) { 11603 if (filter instanceof InputFilter.LengthFilter) { 11604 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11605 break; 11606 } 11607 } 11608 structure.setMaxTextLength(maxLength); 11609 } 11610 } 11611 structure.setHint(getHint()); 11612 structure.setInputType(getInputType()); 11613 } 11614 canRequestAutofill()11615 boolean canRequestAutofill() { 11616 if (!isAutofillable()) { 11617 return false; 11618 } 11619 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11620 if (afm != null) { 11621 return afm.isEnabled(); 11622 } 11623 return false; 11624 } 11625 requestAutofill()11626 private void requestAutofill() { 11627 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11628 if (afm != null) { 11629 afm.requestAutofill(this); 11630 } 11631 } 11632 11633 @Override autofill(AutofillValue value)11634 public void autofill(AutofillValue value) { 11635 if (!value.isText() || !isTextEditable()) { 11636 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 11637 return; 11638 } 11639 11640 final CharSequence autofilledValue = value.getTextValue(); 11641 11642 // First autofill it... 11643 setText(autofilledValue, mBufferType, true, 0); 11644 11645 // ...then move cursor to the end. 11646 final CharSequence text = getText(); 11647 if ((text instanceof Spannable)) { 11648 Selection.setSelection((Spannable) text, text.length()); 11649 } 11650 } 11651 11652 @Override getAutofillType()11653 public @AutofillType int getAutofillType() { 11654 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11655 } 11656 11657 /** 11658 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11659 * {@code char}s if longer. 11660 * 11661 * @return current text, {@code null} if the text is not editable 11662 * 11663 * @see View#getAutofillValue() 11664 */ 11665 @Override 11666 @Nullable getAutofillValue()11667 public AutofillValue getAutofillValue() { 11668 if (isTextEditable()) { 11669 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 11670 return AutofillValue.forText(text); 11671 } 11672 return null; 11673 } 11674 11675 /** @hide */ 11676 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)11677 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 11678 super.onInitializeAccessibilityEventInternal(event); 11679 11680 final boolean isPassword = hasPasswordTransformationMethod(); 11681 event.setPassword(isPassword); 11682 11683 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 11684 event.setFromIndex(Selection.getSelectionStart(mText)); 11685 event.setToIndex(Selection.getSelectionEnd(mText)); 11686 event.setItemCount(mText.length()); 11687 } 11688 } 11689 11690 /** @hide */ 11691 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11692 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 11693 super.onInitializeAccessibilityNodeInfoInternal(info); 11694 11695 final boolean isPassword = hasPasswordTransformationMethod(); 11696 info.setPassword(isPassword); 11697 info.setText(getTextForAccessibility()); 11698 info.setHintText(mHint); 11699 info.setShowingHintText(isShowingHint()); 11700 11701 if (mBufferType == BufferType.EDITABLE) { 11702 info.setEditable(true); 11703 if (isEnabled()) { 11704 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 11705 } 11706 } 11707 11708 if (mEditor != null) { 11709 info.setInputType(mEditor.mInputType); 11710 11711 if (mEditor.mError != null) { 11712 info.setContentInvalid(true); 11713 info.setError(mEditor.mError); 11714 } 11715 } 11716 11717 if (!TextUtils.isEmpty(mText)) { 11718 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 11719 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 11720 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 11721 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 11722 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 11723 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 11724 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 11725 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 11726 info.setAvailableExtraData( 11727 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); 11728 } 11729 11730 if (isFocused()) { 11731 if (canCopy()) { 11732 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 11733 } 11734 if (canPaste()) { 11735 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 11736 } 11737 if (canCut()) { 11738 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 11739 } 11740 if (canShare()) { 11741 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 11742 ACCESSIBILITY_ACTION_SHARE, 11743 getResources().getString(com.android.internal.R.string.share))); 11744 } 11745 if (canProcessText()) { // also implies mEditor is not null. 11746 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 11747 } 11748 } 11749 11750 // Check for known input filter types. 11751 final int numFilters = mFilters.length; 11752 for (int i = 0; i < numFilters; i++) { 11753 final InputFilter filter = mFilters[i]; 11754 if (filter instanceof InputFilter.LengthFilter) { 11755 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 11756 } 11757 } 11758 11759 if (!isSingleLine()) { 11760 info.setMultiLine(true); 11761 } 11762 } 11763 11764 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11765 public void addExtraDataToAccessibilityNodeInfo( 11766 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 11767 // The only extra data we support requires arguments. 11768 if (arguments == null) { 11769 return; 11770 } 11771 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 11772 int positionInfoStartIndex = arguments.getInt( 11773 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 11774 int positionInfoLength = arguments.getInt( 11775 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 11776 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 11777 || (positionInfoStartIndex >= mText.length())) { 11778 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 11779 return; 11780 } 11781 RectF[] boundingRects = new RectF[positionInfoLength]; 11782 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 11783 populateCharacterBounds(builder, positionInfoStartIndex, 11784 positionInfoStartIndex + positionInfoLength, 11785 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 11786 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 11787 for (int i = 0; i < positionInfoLength; i++) { 11788 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 11789 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 11790 RectF bounds = cursorAnchorInfo 11791 .getCharacterBounds(positionInfoStartIndex + i); 11792 if (bounds != null) { 11793 mapRectFromViewToScreenCoords(bounds, true); 11794 boundingRects[i] = bounds; 11795 } 11796 } 11797 } 11798 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 11799 } 11800 } 11801 11802 /** 11803 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 11804 * 11805 * @param builder The builder to populate 11806 * @param startIndex The starting character index to populate 11807 * @param endIndex The ending character index to populate 11808 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 11809 * content 11810 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 11811 * @hide 11812 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11813 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 11814 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 11815 float viewportToContentVerticalOffset) { 11816 final int minLine = mLayout.getLineForOffset(startIndex); 11817 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 11818 for (int line = minLine; line <= maxLine; ++line) { 11819 final int lineStart = mLayout.getLineStart(line); 11820 final int lineEnd = mLayout.getLineEnd(line); 11821 final int offsetStart = Math.max(lineStart, startIndex); 11822 final int offsetEnd = Math.min(lineEnd, endIndex); 11823 final boolean ltrLine = 11824 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 11825 final float[] widths = new float[offsetEnd - offsetStart]; 11826 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 11827 final float top = mLayout.getLineTop(line); 11828 final float bottom = mLayout.getLineBottom(line); 11829 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 11830 final float charWidth = widths[offset - offsetStart]; 11831 final boolean isRtl = mLayout.isRtlCharAt(offset); 11832 final float primary = mLayout.getPrimaryHorizontal(offset); 11833 final float secondary = mLayout.getSecondaryHorizontal(offset); 11834 // TODO: This doesn't work perfectly for text with custom styles and 11835 // TAB chars. 11836 final float left; 11837 final float right; 11838 if (ltrLine) { 11839 if (isRtl) { 11840 left = secondary - charWidth; 11841 right = secondary; 11842 } else { 11843 left = primary; 11844 right = primary + charWidth; 11845 } 11846 } else { 11847 if (!isRtl) { 11848 left = secondary; 11849 right = secondary + charWidth; 11850 } else { 11851 left = primary - charWidth; 11852 right = primary; 11853 } 11854 } 11855 // TODO: Check top-right and bottom-left as well. 11856 final float localLeft = left + viewportToContentHorizontalOffset; 11857 final float localRight = right + viewportToContentHorizontalOffset; 11858 final float localTop = top + viewportToContentVerticalOffset; 11859 final float localBottom = bottom + viewportToContentVerticalOffset; 11860 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 11861 final boolean isBottomRightVisible = 11862 isPositionVisible(localRight, localBottom); 11863 int characterBoundsFlags = 0; 11864 if (isTopLeftVisible || isBottomRightVisible) { 11865 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 11866 } 11867 if (!isTopLeftVisible || !isBottomRightVisible) { 11868 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 11869 } 11870 if (isRtl) { 11871 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 11872 } 11873 // Here offset is the index in Java chars. 11874 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 11875 localBottom, characterBoundsFlags); 11876 } 11877 } 11878 } 11879 11880 /** 11881 * @hide 11882 */ isPositionVisible(final float positionX, final float positionY)11883 public boolean isPositionVisible(final float positionX, final float positionY) { 11884 synchronized (TEMP_POSITION) { 11885 final float[] position = TEMP_POSITION; 11886 position[0] = positionX; 11887 position[1] = positionY; 11888 View view = this; 11889 11890 while (view != null) { 11891 if (view != this) { 11892 // Local scroll is already taken into account in positionX/Y 11893 position[0] -= view.getScrollX(); 11894 position[1] -= view.getScrollY(); 11895 } 11896 11897 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 11898 || position[1] > view.getHeight()) { 11899 return false; 11900 } 11901 11902 if (!view.getMatrix().isIdentity()) { 11903 view.getMatrix().mapPoints(position); 11904 } 11905 11906 position[0] += view.getLeft(); 11907 position[1] += view.getTop(); 11908 11909 final ViewParent parent = view.getParent(); 11910 if (parent instanceof View) { 11911 view = (View) parent; 11912 } else { 11913 // We've reached the ViewRoot, stop iterating 11914 view = null; 11915 } 11916 } 11917 } 11918 11919 // We've been able to walk up the view hierarchy and the position was never clipped 11920 return true; 11921 } 11922 11923 /** 11924 * Performs an accessibility action after it has been offered to the 11925 * delegate. 11926 * 11927 * @hide 11928 */ 11929 @Override performAccessibilityActionInternal(int action, Bundle arguments)11930 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 11931 if (mEditor != null 11932 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 11933 return true; 11934 } 11935 switch (action) { 11936 case AccessibilityNodeInfo.ACTION_CLICK: { 11937 return performAccessibilityActionClick(arguments); 11938 } 11939 case AccessibilityNodeInfo.ACTION_COPY: { 11940 if (isFocused() && canCopy()) { 11941 if (onTextContextMenuItem(ID_COPY)) { 11942 return true; 11943 } 11944 } 11945 } return false; 11946 case AccessibilityNodeInfo.ACTION_PASTE: { 11947 if (isFocused() && canPaste()) { 11948 if (onTextContextMenuItem(ID_PASTE)) { 11949 return true; 11950 } 11951 } 11952 } return false; 11953 case AccessibilityNodeInfo.ACTION_CUT: { 11954 if (isFocused() && canCut()) { 11955 if (onTextContextMenuItem(ID_CUT)) { 11956 return true; 11957 } 11958 } 11959 } return false; 11960 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 11961 ensureIterableTextForAccessibilitySelectable(); 11962 CharSequence text = getIterableTextForAccessibility(); 11963 if (text == null) { 11964 return false; 11965 } 11966 final int start = (arguments != null) ? arguments.getInt( 11967 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 11968 final int end = (arguments != null) ? arguments.getInt( 11969 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 11970 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 11971 // No arguments clears the selection. 11972 if (start == end && end == -1) { 11973 Selection.removeSelection((Spannable) text); 11974 return true; 11975 } 11976 if (start >= 0 && start <= end && end <= text.length()) { 11977 Selection.setSelection((Spannable) text, start, end); 11978 // Make sure selection mode is engaged. 11979 if (mEditor != null) { 11980 mEditor.startSelectionActionModeAsync(false); 11981 } 11982 return true; 11983 } 11984 } 11985 } return false; 11986 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 11987 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 11988 ensureIterableTextForAccessibilitySelectable(); 11989 return super.performAccessibilityActionInternal(action, arguments); 11990 } 11991 case ACCESSIBILITY_ACTION_SHARE: { 11992 if (isFocused() && canShare()) { 11993 if (onTextContextMenuItem(ID_SHARE)) { 11994 return true; 11995 } 11996 } 11997 } return false; 11998 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 11999 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 12000 return false; 12001 } 12002 CharSequence text = (arguments != null) ? arguments.getCharSequence( 12003 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 12004 setText(text); 12005 if (mText != null) { 12006 int updatedTextLength = mText.length(); 12007 if (updatedTextLength > 0) { 12008 Selection.setSelection(mSpannable, updatedTextLength); 12009 } 12010 } 12011 } return true; 12012 default: { 12013 return super.performAccessibilityActionInternal(action, arguments); 12014 } 12015 } 12016 } 12017 performAccessibilityActionClick(Bundle arguments)12018 private boolean performAccessibilityActionClick(Bundle arguments) { 12019 boolean handled = false; 12020 12021 if (!isEnabled()) { 12022 return false; 12023 } 12024 12025 if (isClickable() || isLongClickable()) { 12026 // Simulate View.onTouchEvent for an ACTION_UP event 12027 if (isFocusable() && !isFocused()) { 12028 requestFocus(); 12029 } 12030 12031 performClick(); 12032 handled = true; 12033 } 12034 12035 // Show the IME, except when selecting in read-only text. 12036 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12037 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12038 final InputMethodManager imm = getInputMethodManager(); 12039 viewClicked(imm); 12040 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12041 handled |= imm.showSoftInput(this, 0); 12042 } 12043 } 12044 12045 return handled; 12046 } 12047 hasSpannableText()12048 private boolean hasSpannableText() { 12049 return mText != null && mText instanceof Spannable; 12050 } 12051 12052 /** @hide */ 12053 @Override sendAccessibilityEventInternal(int eventType)12054 public void sendAccessibilityEventInternal(int eventType) { 12055 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12056 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12057 } 12058 12059 super.sendAccessibilityEventInternal(eventType); 12060 } 12061 12062 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12063 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12064 // Do not send scroll events since first they are not interesting for 12065 // accessibility and second such events a generated too frequently. 12066 // For details see the implementation of bringTextIntoView(). 12067 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12068 return; 12069 } 12070 super.sendAccessibilityEventUnchecked(event); 12071 } 12072 12073 /** 12074 * Returns the text that should be exposed to accessibility services. 12075 * <p> 12076 * This approximates what is displayed visually. If the user has specified 12077 * that accessibility services should speak passwords, this method will 12078 * bypass any password transformation method and return unobscured text. 12079 * 12080 * @return the text that should be exposed to accessibility services, may 12081 * be {@code null} if no text is set 12082 */ 12083 @Nullable 12084 @UnsupportedAppUsage getTextForAccessibility()12085 private CharSequence getTextForAccessibility() { 12086 // If the text is empty, we must be showing the hint text. 12087 if (TextUtils.isEmpty(mText)) { 12088 return mHint; 12089 } 12090 12091 // Otherwise, return whatever text is being displayed. 12092 return TextUtils.trimToParcelableSize(mTransformed); 12093 } 12094 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12095 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12096 int fromIndex, int removedCount, int addedCount) { 12097 AccessibilityEvent event = 12098 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12099 event.setFromIndex(fromIndex); 12100 event.setRemovedCount(removedCount); 12101 event.setAddedCount(addedCount); 12102 event.setBeforeText(beforeText); 12103 sendAccessibilityEventUnchecked(event); 12104 } 12105 getInputMethodManager()12106 private InputMethodManager getInputMethodManager() { 12107 return getContext().getSystemService(InputMethodManager.class); 12108 } 12109 12110 /** 12111 * Returns whether this text view is a current input method target. The 12112 * default implementation just checks with {@link InputMethodManager}. 12113 * @return True if the TextView is a current input method target; false otherwise. 12114 */ isInputMethodTarget()12115 public boolean isInputMethodTarget() { 12116 InputMethodManager imm = getInputMethodManager(); 12117 return imm != null && imm.isActive(this); 12118 } 12119 12120 static final int ID_SELECT_ALL = android.R.id.selectAll; 12121 static final int ID_UNDO = android.R.id.undo; 12122 static final int ID_REDO = android.R.id.redo; 12123 static final int ID_CUT = android.R.id.cut; 12124 static final int ID_COPY = android.R.id.copy; 12125 static final int ID_PASTE = android.R.id.paste; 12126 static final int ID_SHARE = android.R.id.shareText; 12127 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12128 static final int ID_REPLACE = android.R.id.replaceText; 12129 static final int ID_ASSIST = android.R.id.textAssist; 12130 static final int ID_AUTOFILL = android.R.id.autofill; 12131 12132 /** 12133 * Called when a context menu option for the text view is selected. Currently 12134 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12135 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12136 * 12137 * @return true if the context menu item action was performed. 12138 */ onTextContextMenuItem(int id)12139 public boolean onTextContextMenuItem(int id) { 12140 int min = 0; 12141 int max = mText.length(); 12142 12143 if (isFocused()) { 12144 final int selStart = getSelectionStart(); 12145 final int selEnd = getSelectionEnd(); 12146 12147 min = Math.max(0, Math.min(selStart, selEnd)); 12148 max = Math.max(0, Math.max(selStart, selEnd)); 12149 } 12150 12151 switch (id) { 12152 case ID_SELECT_ALL: 12153 final boolean hadSelection = hasSelection(); 12154 selectAllText(); 12155 if (mEditor != null && hadSelection) { 12156 mEditor.invalidateActionModeAsync(); 12157 } 12158 return true; 12159 12160 case ID_UNDO: 12161 if (mEditor != null) { 12162 mEditor.undo(); 12163 } 12164 return true; // Returns true even if nothing was undone. 12165 12166 case ID_REDO: 12167 if (mEditor != null) { 12168 mEditor.redo(); 12169 } 12170 return true; // Returns true even if nothing was undone. 12171 12172 case ID_PASTE: 12173 paste(min, max, true /* withFormatting */); 12174 return true; 12175 12176 case ID_PASTE_AS_PLAIN_TEXT: 12177 paste(min, max, false /* withFormatting */); 12178 return true; 12179 12180 case ID_CUT: 12181 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12182 if (setPrimaryClip(cutData)) { 12183 deleteText_internal(min, max); 12184 } else { 12185 Toast.makeText(getContext(), 12186 com.android.internal.R.string.failed_to_copy_to_clipboard, 12187 Toast.LENGTH_SHORT).show(); 12188 } 12189 return true; 12190 12191 case ID_COPY: 12192 // For link action mode in a non-selectable/non-focusable TextView, 12193 // make sure that we set the appropriate min/max. 12194 final int selStart = getSelectionStart(); 12195 final int selEnd = getSelectionEnd(); 12196 min = Math.max(0, Math.min(selStart, selEnd)); 12197 max = Math.max(0, Math.max(selStart, selEnd)); 12198 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12199 if (setPrimaryClip(copyData)) { 12200 stopTextActionMode(); 12201 } else { 12202 Toast.makeText(getContext(), 12203 com.android.internal.R.string.failed_to_copy_to_clipboard, 12204 Toast.LENGTH_SHORT).show(); 12205 } 12206 return true; 12207 12208 case ID_REPLACE: 12209 if (mEditor != null) { 12210 mEditor.replace(); 12211 } 12212 return true; 12213 12214 case ID_SHARE: 12215 shareSelectedText(); 12216 return true; 12217 12218 case ID_AUTOFILL: 12219 requestAutofill(); 12220 stopTextActionMode(); 12221 return true; 12222 } 12223 return false; 12224 } 12225 12226 @UnsupportedAppUsage getTransformedText(int start, int end)12227 CharSequence getTransformedText(int start, int end) { 12228 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12229 } 12230 12231 @Override performLongClick()12232 public boolean performLongClick() { 12233 boolean handled = false; 12234 boolean performedHapticFeedback = false; 12235 12236 if (mEditor != null) { 12237 mEditor.mIsBeingLongClicked = true; 12238 } 12239 12240 if (super.performLongClick()) { 12241 handled = true; 12242 performedHapticFeedback = true; 12243 } 12244 12245 if (mEditor != null) { 12246 handled |= mEditor.performLongClick(handled); 12247 mEditor.mIsBeingLongClicked = false; 12248 } 12249 12250 if (handled) { 12251 if (!performedHapticFeedback) { 12252 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12253 } 12254 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12255 } else { 12256 MetricsLogger.action( 12257 mContext, 12258 MetricsEvent.TEXT_LONGPRESS, 12259 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12260 } 12261 12262 return handled; 12263 } 12264 12265 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12266 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12267 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12268 if (mEditor != null) { 12269 mEditor.onScrollChanged(); 12270 } 12271 } 12272 12273 /** 12274 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12275 * by the IME or by the spell checker as the user types. This is done by adding 12276 * {@link SuggestionSpan}s to the text. 12277 * 12278 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12279 * user asks for them on these parts of the text. This value depends on the inputType of this 12280 * TextView. 12281 * 12282 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12283 * 12284 * In addition, the type variation must be one of 12285 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12286 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12287 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12288 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12289 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12290 * 12291 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12292 * 12293 * @return true if the suggestions popup window is enabled, based on the inputType. 12294 */ isSuggestionsEnabled()12295 public boolean isSuggestionsEnabled() { 12296 if (mEditor == null) return false; 12297 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12298 return false; 12299 } 12300 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12301 12302 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12303 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12304 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12305 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12306 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12307 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12308 } 12309 12310 /** 12311 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12312 * selection is initiated in this View. 12313 * 12314 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12315 * Paste, Replace and Share actions, depending on what this View supports. 12316 * 12317 * <p>A custom implementation can add new entries in the default menu in its 12318 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12319 * method. The default actions can also be removed from the menu using 12320 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12321 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12322 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12323 * 12324 * <p>Returning false from 12325 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12326 * will prevent the action mode from being started. 12327 * 12328 * <p>Action click events should be handled by the custom implementation of 12329 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12330 * android.view.MenuItem)}. 12331 * 12332 * <p>Note that text selection mode is not started when a TextView receives focus and the 12333 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12334 * that case, to allow for quick replacement. 12335 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12336 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12337 createEditorIfNeeded(); 12338 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12339 } 12340 12341 /** 12342 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12343 * 12344 * @return The current custom selection callback. 12345 */ getCustomSelectionActionModeCallback()12346 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12347 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12348 } 12349 12350 /** 12351 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12352 * insertion is initiated in this View. 12353 * The standard implementation populates the menu with a subset of Select All, 12354 * Paste and Replace actions, depending on what this View supports. 12355 * 12356 * <p>A custom implementation can add new entries in the default menu in its 12357 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12358 * android.view.Menu)} method. The default actions can also be removed from the menu using 12359 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12360 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12361 * 12362 * <p>Returning false from 12363 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12364 * android.view.Menu)} will prevent the action mode from being started.</p> 12365 * 12366 * <p>Action click events should be handled by the custom implementation of 12367 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12368 * android.view.MenuItem)}.</p> 12369 * 12370 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12371 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12372 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12373 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12374 createEditorIfNeeded(); 12375 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12376 } 12377 12378 /** 12379 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12380 * 12381 * @return The current custom insertion callback. 12382 */ getCustomInsertionActionModeCallback()12383 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12384 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12385 } 12386 12387 /** 12388 * Sets the {@link TextClassifier} for this TextView. 12389 */ setTextClassifier(@ullable TextClassifier textClassifier)12390 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12391 mTextClassifier = textClassifier; 12392 } 12393 12394 /** 12395 * Returns the {@link TextClassifier} used by this TextView. 12396 * If no TextClassifier has been set, this TextView uses the default set by the 12397 * {@link TextClassificationManager}. 12398 */ 12399 @NonNull getTextClassifier()12400 public TextClassifier getTextClassifier() { 12401 if (mTextClassifier == null) { 12402 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12403 if (tcm != null) { 12404 return tcm.getTextClassifier(); 12405 } 12406 return TextClassifier.NO_OP; 12407 } 12408 return mTextClassifier; 12409 } 12410 12411 /** 12412 * Returns a session-aware text classifier. 12413 * This method creates one if none already exists or the current one is destroyed. 12414 */ 12415 @NonNull getTextClassificationSession()12416 TextClassifier getTextClassificationSession() { 12417 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12418 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12419 if (tcm != null) { 12420 final String widgetType; 12421 if (isTextEditable()) { 12422 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12423 } else if (isTextSelectable()) { 12424 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12425 } else { 12426 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12427 } 12428 mTextClassificationContext = new TextClassificationContext.Builder( 12429 mContext.getPackageName(), widgetType) 12430 .build(); 12431 if (mTextClassifier != null) { 12432 mTextClassificationSession = tcm.createTextClassificationSession( 12433 mTextClassificationContext, mTextClassifier); 12434 } else { 12435 mTextClassificationSession = tcm.createTextClassificationSession( 12436 mTextClassificationContext); 12437 } 12438 } else { 12439 mTextClassificationSession = TextClassifier.NO_OP; 12440 } 12441 } 12442 return mTextClassificationSession; 12443 } 12444 12445 /** 12446 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12447 * @see #getTextClassificationSession() 12448 */ 12449 @Nullable getTextClassificationContext()12450 TextClassificationContext getTextClassificationContext() { 12451 return mTextClassificationContext; 12452 } 12453 12454 /** 12455 * Returns true if this TextView uses a no-op TextClassifier. 12456 */ usesNoOpTextClassifier()12457 boolean usesNoOpTextClassifier() { 12458 return getTextClassifier() == TextClassifier.NO_OP; 12459 } 12460 12461 12462 /** 12463 * Starts an ActionMode for the specified TextLinkSpan. 12464 * 12465 * @return Whether or not we're attempting to start the action mode. 12466 * @hide 12467 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12468 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12469 Preconditions.checkNotNull(clickedSpan); 12470 12471 if (!(mText instanceof Spanned)) { 12472 return false; 12473 } 12474 12475 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12476 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12477 12478 if (start < 0 || end > mText.length() || start >= end) { 12479 return false; 12480 } 12481 12482 createEditorIfNeeded(); 12483 mEditor.startLinkActionModeAsync(start, end); 12484 return true; 12485 } 12486 12487 /** 12488 * Handles a click on the specified TextLinkSpan. 12489 * 12490 * @return Whether or not the click is being handled. 12491 * @hide 12492 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12493 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12494 Preconditions.checkNotNull(clickedSpan); 12495 if (mText instanceof Spanned) { 12496 final Spanned spanned = (Spanned) mText; 12497 final int start = spanned.getSpanStart(clickedSpan); 12498 final int end = spanned.getSpanEnd(clickedSpan); 12499 if (start >= 0 && end <= mText.length() && start < end) { 12500 final TextClassification.Request request = new TextClassification.Request.Builder( 12501 mText, start, end) 12502 .setDefaultLocales(getTextLocales()) 12503 .build(); 12504 final Supplier<TextClassification> supplier = () -> 12505 getTextClassifier().classifyText(request); 12506 final Consumer<TextClassification> consumer = classification -> { 12507 if (classification != null) { 12508 if (!classification.getActions().isEmpty()) { 12509 try { 12510 classification.getActions().get(0).getActionIntent().send(); 12511 } catch (PendingIntent.CanceledException e) { 12512 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12513 } 12514 } else { 12515 Log.d(LOG_TAG, "No link action to perform"); 12516 } 12517 } else { 12518 // classification == null 12519 Log.d(LOG_TAG, "Timeout while classifying text"); 12520 } 12521 }; 12522 CompletableFuture.supplyAsync(supplier) 12523 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12524 .thenAccept(consumer); 12525 return true; 12526 } 12527 } 12528 return false; 12529 } 12530 12531 /** 12532 * @hide 12533 */ 12534 @UnsupportedAppUsage stopTextActionMode()12535 protected void stopTextActionMode() { 12536 if (mEditor != null) { 12537 mEditor.stopTextActionMode(); 12538 } 12539 } 12540 12541 /** @hide */ hideFloatingToolbar(int durationMs)12542 public void hideFloatingToolbar(int durationMs) { 12543 if (mEditor != null) { 12544 mEditor.hideFloatingToolbar(durationMs); 12545 } 12546 } 12547 canUndo()12548 boolean canUndo() { 12549 return mEditor != null && mEditor.canUndo(); 12550 } 12551 canRedo()12552 boolean canRedo() { 12553 return mEditor != null && mEditor.canRedo(); 12554 } 12555 canCut()12556 boolean canCut() { 12557 if (hasPasswordTransformationMethod()) { 12558 return false; 12559 } 12560 12561 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12562 && mEditor.mKeyListener != null) { 12563 return true; 12564 } 12565 12566 return false; 12567 } 12568 canCopy()12569 boolean canCopy() { 12570 if (hasPasswordTransformationMethod()) { 12571 return false; 12572 } 12573 12574 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12575 return true; 12576 } 12577 12578 return false; 12579 } 12580 canShare()12581 boolean canShare() { 12582 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12583 return false; 12584 } 12585 return canCopy(); 12586 } 12587 isDeviceProvisioned()12588 boolean isDeviceProvisioned() { 12589 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12590 mDeviceProvisionedState = Settings.Global.getInt( 12591 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12592 ? DEVICE_PROVISIONED_YES 12593 : DEVICE_PROVISIONED_NO; 12594 } 12595 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12596 } 12597 12598 @UnsupportedAppUsage canPaste()12599 boolean canPaste() { 12600 return (mText instanceof Editable 12601 && mEditor != null && mEditor.mKeyListener != null 12602 && getSelectionStart() >= 0 12603 && getSelectionEnd() >= 0 12604 && getClipboardManagerForUser().hasPrimaryClip()); 12605 } 12606 canPasteAsPlainText()12607 boolean canPasteAsPlainText() { 12608 if (!canPaste()) { 12609 return false; 12610 } 12611 12612 final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); 12613 final ClipDescription description = clipData.getDescription(); 12614 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 12615 final CharSequence text = clipData.getItemAt(0).getText(); 12616 if (isPlainType && (text instanceof Spanned)) { 12617 Spanned spanned = (Spanned) text; 12618 if (TextUtils.hasStyleSpan(spanned)) { 12619 return true; 12620 } 12621 } 12622 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 12623 } 12624 canProcessText()12625 boolean canProcessText() { 12626 if (getId() == View.NO_ID) { 12627 return false; 12628 } 12629 return canShare(); 12630 } 12631 canSelectAllText()12632 boolean canSelectAllText() { 12633 return canSelectText() && !hasPasswordTransformationMethod() 12634 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 12635 } 12636 selectAllText()12637 boolean selectAllText() { 12638 if (mEditor != null) { 12639 // Hide the toolbar before changing the selection to avoid flickering. 12640 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 12641 } 12642 final int length = mText.length(); 12643 Selection.setSelection(mSpannable, 0, length); 12644 return length > 0; 12645 } 12646 replaceSelectionWithText(CharSequence text)12647 void replaceSelectionWithText(CharSequence text) { 12648 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 12649 } 12650 12651 /** 12652 * Paste clipboard content between min and max positions. 12653 */ paste(int min, int max, boolean withFormatting)12654 private void paste(int min, int max, boolean withFormatting) { 12655 ClipboardManager clipboard = getClipboardManagerForUser(); 12656 ClipData clip = clipboard.getPrimaryClip(); 12657 if (clip != null) { 12658 boolean didFirst = false; 12659 for (int i = 0; i < clip.getItemCount(); i++) { 12660 final CharSequence paste; 12661 if (withFormatting) { 12662 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 12663 } else { 12664 // Get an item as text and remove all spans by toString(). 12665 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 12666 paste = (text instanceof Spanned) ? text.toString() : text; 12667 } 12668 if (paste != null) { 12669 if (!didFirst) { 12670 Selection.setSelection(mSpannable, max); 12671 ((Editable) mText).replace(min, max, paste); 12672 didFirst = true; 12673 } else { 12674 ((Editable) mText).insert(getSelectionEnd(), "\n"); 12675 ((Editable) mText).insert(getSelectionEnd(), paste); 12676 } 12677 } 12678 } 12679 sLastCutCopyOrTextChangedTime = 0; 12680 } 12681 } 12682 shareSelectedText()12683 private void shareSelectedText() { 12684 String selectedText = getSelectedText(); 12685 if (selectedText != null && !selectedText.isEmpty()) { 12686 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 12687 sharingIntent.setType("text/plain"); 12688 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 12689 selectedText = TextUtils.trimToParcelableSize(selectedText); 12690 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 12691 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 12692 Selection.setSelection(mSpannable, getSelectionEnd()); 12693 } 12694 } 12695 12696 @CheckResult setPrimaryClip(ClipData clip)12697 private boolean setPrimaryClip(ClipData clip) { 12698 ClipboardManager clipboard = getClipboardManagerForUser(); 12699 try { 12700 clipboard.setPrimaryClip(clip); 12701 } catch (Throwable t) { 12702 return false; 12703 } 12704 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 12705 return true; 12706 } 12707 12708 /** 12709 * Get the character offset closest to the specified absolute position. A typical use case is to 12710 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 12711 * 12712 * @param x The horizontal absolute position of a point on screen 12713 * @param y The vertical absolute position of a point on screen 12714 * @return the character offset for the character whose position is closest to the specified 12715 * position. Returns -1 if there is no layout. 12716 */ getOffsetForPosition(float x, float y)12717 public int getOffsetForPosition(float x, float y) { 12718 if (getLayout() == null) return -1; 12719 final int line = getLineAtCoordinate(y); 12720 final int offset = getOffsetAtCoordinate(line, x); 12721 return offset; 12722 } 12723 convertToLocalHorizontalCoordinate(float x)12724 float convertToLocalHorizontalCoordinate(float x) { 12725 x -= getTotalPaddingLeft(); 12726 // Clamp the position to inside of the view. 12727 x = Math.max(0.0f, x); 12728 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 12729 x += getScrollX(); 12730 return x; 12731 } 12732 12733 @UnsupportedAppUsage getLineAtCoordinate(float y)12734 int getLineAtCoordinate(float y) { 12735 y -= getTotalPaddingTop(); 12736 // Clamp the position to inside of the view. 12737 y = Math.max(0.0f, y); 12738 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 12739 y += getScrollY(); 12740 return getLayout().getLineForVertical((int) y); 12741 } 12742 getLineAtCoordinateUnclamped(float y)12743 int getLineAtCoordinateUnclamped(float y) { 12744 y -= getTotalPaddingTop(); 12745 y += getScrollY(); 12746 return getLayout().getLineForVertical((int) y); 12747 } 12748 getOffsetAtCoordinate(int line, float x)12749 int getOffsetAtCoordinate(int line, float x) { 12750 x = convertToLocalHorizontalCoordinate(x); 12751 return getLayout().getOffsetForHorizontal(line, x); 12752 } 12753 12754 @Override onDragEvent(DragEvent event)12755 public boolean onDragEvent(DragEvent event) { 12756 switch (event.getAction()) { 12757 case DragEvent.ACTION_DRAG_STARTED: 12758 return mEditor != null && mEditor.hasInsertionController(); 12759 12760 case DragEvent.ACTION_DRAG_ENTERED: 12761 TextView.this.requestFocus(); 12762 return true; 12763 12764 case DragEvent.ACTION_DRAG_LOCATION: 12765 if (mText instanceof Spannable) { 12766 final int offset = getOffsetForPosition(event.getX(), event.getY()); 12767 Selection.setSelection(mSpannable, offset); 12768 } 12769 return true; 12770 12771 case DragEvent.ACTION_DROP: 12772 if (mEditor != null) mEditor.onDrop(event); 12773 return true; 12774 12775 case DragEvent.ACTION_DRAG_ENDED: 12776 case DragEvent.ACTION_DRAG_EXITED: 12777 default: 12778 return true; 12779 } 12780 } 12781 isInBatchEditMode()12782 boolean isInBatchEditMode() { 12783 if (mEditor == null) return false; 12784 final Editor.InputMethodState ims = mEditor.mInputMethodState; 12785 if (ims != null) { 12786 return ims.mBatchEditNesting > 0; 12787 } 12788 return mEditor.mInBatchEditControllers; 12789 } 12790 12791 @Override onRtlPropertiesChanged(int layoutDirection)12792 public void onRtlPropertiesChanged(int layoutDirection) { 12793 super.onRtlPropertiesChanged(layoutDirection); 12794 12795 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 12796 if (mTextDir != newTextDir) { 12797 mTextDir = newTextDir; 12798 if (mLayout != null) { 12799 checkForRelayout(); 12800 } 12801 } 12802 } 12803 12804 /** 12805 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 12806 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 12807 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 12808 * return value may not be the same as the one TextView uses if the View's layout direction is 12809 * not resolved or detached from parent root view. 12810 */ getTextDirectionHeuristic()12811 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 12812 if (hasPasswordTransformationMethod()) { 12813 // passwords fields should be LTR 12814 return TextDirectionHeuristics.LTR; 12815 } 12816 12817 if (mEditor != null 12818 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12819 == EditorInfo.TYPE_CLASS_PHONE) { 12820 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 12821 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 12822 // RTL digits. 12823 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 12824 final String zero = symbols.getDigitStrings()[0]; 12825 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 12826 // direction. 12827 final int firstCodepoint = zero.codePointAt(0); 12828 final byte digitDirection = Character.getDirectionality(firstCodepoint); 12829 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 12830 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 12831 return TextDirectionHeuristics.RTL; 12832 } else { 12833 return TextDirectionHeuristics.LTR; 12834 } 12835 } 12836 12837 // Always need to resolve layout direction first 12838 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 12839 12840 // Now, we can select the heuristic 12841 switch (getTextDirection()) { 12842 default: 12843 case TEXT_DIRECTION_FIRST_STRONG: 12844 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 12845 TextDirectionHeuristics.FIRSTSTRONG_LTR); 12846 case TEXT_DIRECTION_ANY_RTL: 12847 return TextDirectionHeuristics.ANYRTL_LTR; 12848 case TEXT_DIRECTION_LTR: 12849 return TextDirectionHeuristics.LTR; 12850 case TEXT_DIRECTION_RTL: 12851 return TextDirectionHeuristics.RTL; 12852 case TEXT_DIRECTION_LOCALE: 12853 return TextDirectionHeuristics.LOCALE; 12854 case TEXT_DIRECTION_FIRST_STRONG_LTR: 12855 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 12856 case TEXT_DIRECTION_FIRST_STRONG_RTL: 12857 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 12858 } 12859 } 12860 12861 /** 12862 * @hide 12863 */ 12864 @Override onResolveDrawables(int layoutDirection)12865 public void onResolveDrawables(int layoutDirection) { 12866 // No need to resolve twice 12867 if (mLastLayoutDirection == layoutDirection) { 12868 return; 12869 } 12870 mLastLayoutDirection = layoutDirection; 12871 12872 // Resolve drawables 12873 if (mDrawables != null) { 12874 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 12875 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 12876 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 12877 applyCompoundDrawableTint(); 12878 } 12879 } 12880 } 12881 12882 /** 12883 * Prepares a drawable for display by propagating layout direction and 12884 * drawable state. 12885 * 12886 * @param dr the drawable to prepare 12887 */ prepareDrawableForDisplay(@ullable Drawable dr)12888 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 12889 if (dr == null) { 12890 return; 12891 } 12892 12893 dr.setLayoutDirection(getLayoutDirection()); 12894 12895 if (dr.isStateful()) { 12896 dr.setState(getDrawableState()); 12897 dr.jumpToCurrentState(); 12898 } 12899 } 12900 12901 /** 12902 * @hide 12903 */ resetResolvedDrawables()12904 protected void resetResolvedDrawables() { 12905 super.resetResolvedDrawables(); 12906 mLastLayoutDirection = -1; 12907 } 12908 12909 /** 12910 * @hide 12911 */ viewClicked(InputMethodManager imm)12912 protected void viewClicked(InputMethodManager imm) { 12913 if (imm != null) { 12914 imm.viewClicked(this); 12915 } 12916 } 12917 12918 /** 12919 * Deletes the range of text [start, end[. 12920 * @hide 12921 */ 12922 @UnsupportedAppUsage deleteText_internal(int start, int end)12923 protected void deleteText_internal(int start, int end) { 12924 ((Editable) mText).delete(start, end); 12925 } 12926 12927 /** 12928 * Replaces the range of text [start, end[ by replacement text 12929 * @hide 12930 */ replaceText_internal(int start, int end, CharSequence text)12931 protected void replaceText_internal(int start, int end, CharSequence text) { 12932 ((Editable) mText).replace(start, end, text); 12933 } 12934 12935 /** 12936 * Sets a span on the specified range of text 12937 * @hide 12938 */ setSpan_internal(Object span, int start, int end, int flags)12939 protected void setSpan_internal(Object span, int start, int end, int flags) { 12940 ((Editable) mText).setSpan(span, start, end, flags); 12941 } 12942 12943 /** 12944 * Moves the cursor to the specified offset position in text 12945 * @hide 12946 */ setCursorPosition_internal(int start, int end)12947 protected void setCursorPosition_internal(int start, int end) { 12948 Selection.setSelection(((Editable) mText), start, end); 12949 } 12950 12951 /** 12952 * An Editor should be created as soon as any of the editable-specific fields (grouped 12953 * inside the Editor object) is assigned to a non-default value. 12954 * This method will create the Editor if needed. 12955 * 12956 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 12957 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 12958 * Editor for backward compatibility, as soon as one of these fields is assigned. 12959 * 12960 * Also note that for performance reasons, the mEditor is created when needed, but not 12961 * reset when no more edit-specific fields are needed. 12962 */ 12963 @UnsupportedAppUsage createEditorIfNeeded()12964 private void createEditorIfNeeded() { 12965 if (mEditor == null) { 12966 mEditor = new Editor(this); 12967 } 12968 } 12969 12970 /** 12971 * @hide 12972 */ 12973 @Override 12974 @UnsupportedAppUsage getIterableTextForAccessibility()12975 public CharSequence getIterableTextForAccessibility() { 12976 return mText; 12977 } 12978 ensureIterableTextForAccessibilitySelectable()12979 private void ensureIterableTextForAccessibilitySelectable() { 12980 if (!(mText instanceof Spannable)) { 12981 setText(mText, BufferType.SPANNABLE); 12982 } 12983 } 12984 12985 /** 12986 * @hide 12987 */ 12988 @Override getIteratorForGranularity(int granularity)12989 public TextSegmentIterator getIteratorForGranularity(int granularity) { 12990 switch (granularity) { 12991 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 12992 Spannable text = (Spannable) getIterableTextForAccessibility(); 12993 if (!TextUtils.isEmpty(text) && getLayout() != null) { 12994 AccessibilityIterators.LineTextSegmentIterator iterator = 12995 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 12996 iterator.initialize(text, getLayout()); 12997 return iterator; 12998 } 12999 } break; 13000 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 13001 Spannable text = (Spannable) getIterableTextForAccessibility(); 13002 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13003 AccessibilityIterators.PageTextSegmentIterator iterator = 13004 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 13005 iterator.initialize(this); 13006 return iterator; 13007 } 13008 } break; 13009 } 13010 return super.getIteratorForGranularity(granularity); 13011 } 13012 13013 /** 13014 * @hide 13015 */ 13016 @Override getAccessibilitySelectionStart()13017 public int getAccessibilitySelectionStart() { 13018 return getSelectionStart(); 13019 } 13020 13021 /** 13022 * @hide 13023 */ isAccessibilitySelectionExtendable()13024 public boolean isAccessibilitySelectionExtendable() { 13025 return true; 13026 } 13027 13028 /** 13029 * @hide 13030 */ 13031 @Override getAccessibilitySelectionEnd()13032 public int getAccessibilitySelectionEnd() { 13033 return getSelectionEnd(); 13034 } 13035 13036 /** 13037 * @hide 13038 */ 13039 @Override setAccessibilitySelection(int start, int end)13040 public void setAccessibilitySelection(int start, int end) { 13041 if (getAccessibilitySelectionStart() == start 13042 && getAccessibilitySelectionEnd() == end) { 13043 return; 13044 } 13045 CharSequence text = getIterableTextForAccessibility(); 13046 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13047 Selection.setSelection((Spannable) text, start, end); 13048 } else { 13049 Selection.removeSelection((Spannable) text); 13050 } 13051 // Hide all selection controllers used for adjusting selection 13052 // since we are doing so explicitlty by other means and these 13053 // controllers interact with how selection behaves. 13054 if (mEditor != null) { 13055 mEditor.hideCursorAndSpanControllers(); 13056 mEditor.stopTextActionMode(); 13057 } 13058 } 13059 13060 /** @hide */ 13061 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13062 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13063 super.encodeProperties(stream); 13064 13065 TruncateAt ellipsize = getEllipsize(); 13066 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13067 stream.addProperty("text:textSize", getTextSize()); 13068 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13069 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13070 stream.addProperty("text:selectionStart", getSelectionStart()); 13071 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13072 stream.addProperty("text:curTextColor", mCurTextColor); 13073 stream.addProperty("text:text", mText == null ? null : mText.toString()); 13074 stream.addProperty("text:gravity", mGravity); 13075 } 13076 13077 /** 13078 * User interface state that is stored by TextView for implementing 13079 * {@link View#onSaveInstanceState}. 13080 */ 13081 public static class SavedState extends BaseSavedState { 13082 int selStart = -1; 13083 int selEnd = -1; 13084 @UnsupportedAppUsage 13085 CharSequence text; 13086 boolean frozenWithFocus; 13087 CharSequence error; 13088 ParcelableParcel editorState; // Optional state from Editor. 13089 SavedState(Parcelable superState)13090 SavedState(Parcelable superState) { 13091 super(superState); 13092 } 13093 13094 @Override writeToParcel(Parcel out, int flags)13095 public void writeToParcel(Parcel out, int flags) { 13096 super.writeToParcel(out, flags); 13097 out.writeInt(selStart); 13098 out.writeInt(selEnd); 13099 out.writeInt(frozenWithFocus ? 1 : 0); 13100 TextUtils.writeToParcel(text, out, flags); 13101 13102 if (error == null) { 13103 out.writeInt(0); 13104 } else { 13105 out.writeInt(1); 13106 TextUtils.writeToParcel(error, out, flags); 13107 } 13108 13109 if (editorState == null) { 13110 out.writeInt(0); 13111 } else { 13112 out.writeInt(1); 13113 editorState.writeToParcel(out, flags); 13114 } 13115 } 13116 13117 @Override toString()13118 public String toString() { 13119 String str = "TextView.SavedState{" 13120 + Integer.toHexString(System.identityHashCode(this)) 13121 + " start=" + selStart + " end=" + selEnd; 13122 if (text != null) { 13123 str += " text=" + text; 13124 } 13125 return str + "}"; 13126 } 13127 13128 @SuppressWarnings("hiding") 13129 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13130 new Parcelable.Creator<SavedState>() { 13131 public SavedState createFromParcel(Parcel in) { 13132 return new SavedState(in); 13133 } 13134 13135 public SavedState[] newArray(int size) { 13136 return new SavedState[size]; 13137 } 13138 }; 13139 SavedState(Parcel in)13140 private SavedState(Parcel in) { 13141 super(in); 13142 selStart = in.readInt(); 13143 selEnd = in.readInt(); 13144 frozenWithFocus = (in.readInt() != 0); 13145 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13146 13147 if (in.readInt() != 0) { 13148 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13149 } 13150 13151 if (in.readInt() != 0) { 13152 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13153 } 13154 } 13155 } 13156 13157 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13158 private char[] mChars; 13159 private int mStart, mLength; 13160 CharWrapper(char[] chars, int start, int len)13161 public CharWrapper(char[] chars, int start, int len) { 13162 mChars = chars; 13163 mStart = start; 13164 mLength = len; 13165 } 13166 set(char[] chars, int start, int len)13167 /* package */ void set(char[] chars, int start, int len) { 13168 mChars = chars; 13169 mStart = start; 13170 mLength = len; 13171 } 13172 length()13173 public int length() { 13174 return mLength; 13175 } 13176 charAt(int off)13177 public char charAt(int off) { 13178 return mChars[off + mStart]; 13179 } 13180 13181 @Override toString()13182 public String toString() { 13183 return new String(mChars, mStart, mLength); 13184 } 13185 subSequence(int start, int end)13186 public CharSequence subSequence(int start, int end) { 13187 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13188 throw new IndexOutOfBoundsException(start + ", " + end); 13189 } 13190 13191 return new String(mChars, start + mStart, end - start); 13192 } 13193 getChars(int start, int end, char[] buf, int off)13194 public void getChars(int start, int end, char[] buf, int off) { 13195 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13196 throw new IndexOutOfBoundsException(start + ", " + end); 13197 } 13198 13199 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13200 } 13201 13202 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13203 public void drawText(BaseCanvas c, int start, int end, 13204 float x, float y, Paint p) { 13205 c.drawText(mChars, start + mStart, end - start, x, y, p); 13206 } 13207 13208 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13209 public void drawTextRun(BaseCanvas c, int start, int end, 13210 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13211 int count = end - start; 13212 int contextCount = contextEnd - contextStart; 13213 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13214 contextCount, x, y, isRtl, p); 13215 } 13216 measureText(int start, int end, Paint p)13217 public float measureText(int start, int end, Paint p) { 13218 return p.measureText(mChars, start + mStart, end - start); 13219 } 13220 getTextWidths(int start, int end, float[] widths, Paint p)13221 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13222 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13223 } 13224 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13225 public float getTextRunAdvances(int start, int end, int contextStart, 13226 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13227 Paint p) { 13228 int count = end - start; 13229 int contextCount = contextEnd - contextStart; 13230 return p.getTextRunAdvances(mChars, start + mStart, count, 13231 contextStart + mStart, contextCount, isRtl, advances, 13232 advancesIndex); 13233 } 13234 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13235 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13236 int offset, int cursorOpt, Paint p) { 13237 int contextCount = contextEnd - contextStart; 13238 return p.getTextRunCursor(mChars, contextStart + mStart, 13239 contextCount, isRtl, offset + mStart, cursorOpt); 13240 } 13241 } 13242 13243 private static final class Marquee { 13244 // TODO: Add an option to configure this 13245 private static final float MARQUEE_DELTA_MAX = 0.07f; 13246 private static final int MARQUEE_DELAY = 1200; 13247 private static final int MARQUEE_DP_PER_SECOND = 30; 13248 13249 private static final byte MARQUEE_STOPPED = 0x0; 13250 private static final byte MARQUEE_STARTING = 0x1; 13251 private static final byte MARQUEE_RUNNING = 0x2; 13252 13253 private final WeakReference<TextView> mView; 13254 private final Choreographer mChoreographer; 13255 13256 private byte mStatus = MARQUEE_STOPPED; 13257 private final float mPixelsPerMs; 13258 private float mMaxScroll; 13259 private float mMaxFadeScroll; 13260 private float mGhostStart; 13261 private float mGhostOffset; 13262 private float mFadeStop; 13263 private int mRepeatLimit; 13264 13265 private float mScroll; 13266 private long mLastAnimationMs; 13267 Marquee(TextView v)13268 Marquee(TextView v) { 13269 final float density = v.getContext().getResources().getDisplayMetrics().density; 13270 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13271 mView = new WeakReference<TextView>(v); 13272 mChoreographer = Choreographer.getInstance(); 13273 } 13274 13275 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13276 @Override 13277 public void doFrame(long frameTimeNanos) { 13278 tick(); 13279 } 13280 }; 13281 13282 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13283 @Override 13284 public void doFrame(long frameTimeNanos) { 13285 mStatus = MARQUEE_RUNNING; 13286 mLastAnimationMs = mChoreographer.getFrameTime(); 13287 tick(); 13288 } 13289 }; 13290 13291 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13292 @Override 13293 public void doFrame(long frameTimeNanos) { 13294 if (mStatus == MARQUEE_RUNNING) { 13295 if (mRepeatLimit >= 0) { 13296 mRepeatLimit--; 13297 } 13298 start(mRepeatLimit); 13299 } 13300 } 13301 }; 13302 tick()13303 void tick() { 13304 if (mStatus != MARQUEE_RUNNING) { 13305 return; 13306 } 13307 13308 mChoreographer.removeFrameCallback(mTickCallback); 13309 13310 final TextView textView = mView.get(); 13311 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13312 long currentMs = mChoreographer.getFrameTime(); 13313 long deltaMs = currentMs - mLastAnimationMs; 13314 mLastAnimationMs = currentMs; 13315 float deltaPx = deltaMs * mPixelsPerMs; 13316 mScroll += deltaPx; 13317 if (mScroll > mMaxScroll) { 13318 mScroll = mMaxScroll; 13319 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13320 } else { 13321 mChoreographer.postFrameCallback(mTickCallback); 13322 } 13323 textView.invalidate(); 13324 } 13325 } 13326 stop()13327 void stop() { 13328 mStatus = MARQUEE_STOPPED; 13329 mChoreographer.removeFrameCallback(mStartCallback); 13330 mChoreographer.removeFrameCallback(mRestartCallback); 13331 mChoreographer.removeFrameCallback(mTickCallback); 13332 resetScroll(); 13333 } 13334 resetScroll()13335 private void resetScroll() { 13336 mScroll = 0.0f; 13337 final TextView textView = mView.get(); 13338 if (textView != null) textView.invalidate(); 13339 } 13340 start(int repeatLimit)13341 void start(int repeatLimit) { 13342 if (repeatLimit == 0) { 13343 stop(); 13344 return; 13345 } 13346 mRepeatLimit = repeatLimit; 13347 final TextView textView = mView.get(); 13348 if (textView != null && textView.mLayout != null) { 13349 mStatus = MARQUEE_STARTING; 13350 mScroll = 0.0f; 13351 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13352 - textView.getCompoundPaddingRight(); 13353 final float lineWidth = textView.mLayout.getLineWidth(0); 13354 final float gap = textWidth / 3.0f; 13355 mGhostStart = lineWidth - textWidth + gap; 13356 mMaxScroll = mGhostStart + textWidth; 13357 mGhostOffset = lineWidth + gap; 13358 mFadeStop = lineWidth + textWidth / 6.0f; 13359 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13360 13361 textView.invalidate(); 13362 mChoreographer.postFrameCallback(mStartCallback); 13363 } 13364 } 13365 getGhostOffset()13366 float getGhostOffset() { 13367 return mGhostOffset; 13368 } 13369 getScroll()13370 float getScroll() { 13371 return mScroll; 13372 } 13373 getMaxFadeScroll()13374 float getMaxFadeScroll() { 13375 return mMaxFadeScroll; 13376 } 13377 shouldDrawLeftFade()13378 boolean shouldDrawLeftFade() { 13379 return mScroll <= mFadeStop; 13380 } 13381 shouldDrawGhost()13382 boolean shouldDrawGhost() { 13383 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13384 } 13385 isRunning()13386 boolean isRunning() { 13387 return mStatus == MARQUEE_RUNNING; 13388 } 13389 isStopped()13390 boolean isStopped() { 13391 return mStatus == MARQUEE_STOPPED; 13392 } 13393 } 13394 13395 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13396 13397 private CharSequence mBeforeText; 13398 beforeTextChanged(CharSequence buffer, int start, int before, int after)13399 public void beforeTextChanged(CharSequence buffer, int start, 13400 int before, int after) { 13401 if (DEBUG_EXTRACT) { 13402 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13403 + " before=" + before + " after=" + after + ": " + buffer); 13404 } 13405 13406 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13407 mBeforeText = mTransformed.toString(); 13408 } 13409 13410 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13411 } 13412 onTextChanged(CharSequence buffer, int start, int before, int after)13413 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13414 if (DEBUG_EXTRACT) { 13415 Log.v(LOG_TAG, "onTextChanged start=" + start 13416 + " before=" + before + " after=" + after + ": " + buffer); 13417 } 13418 TextView.this.handleTextChanged(buffer, start, before, after); 13419 13420 if (AccessibilityManager.getInstance(mContext).isEnabled() 13421 && (isFocused() || isSelected() && isShown())) { 13422 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13423 mBeforeText = null; 13424 } 13425 } 13426 afterTextChanged(Editable buffer)13427 public void afterTextChanged(Editable buffer) { 13428 if (DEBUG_EXTRACT) { 13429 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13430 } 13431 TextView.this.sendAfterTextChanged(buffer); 13432 13433 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13434 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13435 } 13436 } 13437 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13438 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13439 if (DEBUG_EXTRACT) { 13440 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13441 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13442 } 13443 TextView.this.spanChange(buf, what, s, st, e, en); 13444 } 13445 onSpanAdded(Spannable buf, Object what, int s, int e)13446 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13447 if (DEBUG_EXTRACT) { 13448 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13449 } 13450 TextView.this.spanChange(buf, what, -1, s, -1, e); 13451 } 13452 onSpanRemoved(Spannable buf, Object what, int s, int e)13453 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13454 if (DEBUG_EXTRACT) { 13455 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13456 } 13457 TextView.this.spanChange(buf, what, s, -1, e, -1); 13458 } 13459 } 13460 } 13461