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.UnsupportedAppUsage; 40 import android.annotation.XmlRes; 41 import android.app.Activity; 42 import android.app.PendingIntent; 43 import android.app.assist.AssistStructure; 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.inputmethod.BaseInputConnection; 166 import android.view.inputmethod.CompletionInfo; 167 import android.view.inputmethod.CorrectionInfo; 168 import android.view.inputmethod.CursorAnchorInfo; 169 import android.view.inputmethod.EditorInfo; 170 import android.view.inputmethod.ExtractedText; 171 import android.view.inputmethod.ExtractedTextRequest; 172 import android.view.inputmethod.InputConnection; 173 import android.view.inputmethod.InputMethodManager; 174 import android.view.inspector.InspectableProperty; 175 import android.view.inspector.InspectableProperty.EnumEntry; 176 import android.view.inspector.InspectableProperty.FlagEntry; 177 import android.view.textclassifier.TextClassification; 178 import android.view.textclassifier.TextClassificationContext; 179 import android.view.textclassifier.TextClassificationManager; 180 import android.view.textclassifier.TextClassifier; 181 import android.view.textclassifier.TextLinks; 182 import android.view.textservice.SpellCheckerSubtype; 183 import android.view.textservice.TextServicesManager; 184 import android.widget.RemoteViews.RemoteView; 185 186 import com.android.internal.annotations.VisibleForTesting; 187 import com.android.internal.logging.MetricsLogger; 188 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 189 import com.android.internal.util.FastMath; 190 import com.android.internal.util.Preconditions; 191 import com.android.internal.widget.EditableInputConnection; 192 193 import libcore.util.EmptyArray; 194 195 import org.xmlpull.v1.XmlPullParserException; 196 197 import java.io.IOException; 198 import java.lang.annotation.Retention; 199 import java.lang.annotation.RetentionPolicy; 200 import java.lang.ref.WeakReference; 201 import java.util.ArrayList; 202 import java.util.Arrays; 203 import java.util.Locale; 204 import java.util.Objects; 205 import java.util.concurrent.CompletableFuture; 206 import java.util.concurrent.TimeUnit; 207 import java.util.function.Consumer; 208 import java.util.function.Supplier; 209 210 /** 211 * A user interface element that displays text to the user. 212 * To provide user-editable text, see {@link EditText}. 213 * <p> 214 * The following code sample shows a typical use, with an XML layout 215 * and code to modify the contents of the text view: 216 * </p> 217 218 * <pre> 219 * <LinearLayout 220 xmlns:android="http://schemas.android.com/apk/res/android" 221 android:layout_width="match_parent" 222 android:layout_height="match_parent"> 223 * <TextView 224 * android:id="@+id/text_view_id" 225 * android:layout_height="wrap_content" 226 * android:layout_width="wrap_content" 227 * android:text="@string/hello" /> 228 * </LinearLayout> 229 * </pre> 230 * <p> 231 * This code sample demonstrates how to modify the contents of the text view 232 * defined in the previous XML layout: 233 * </p> 234 * <pre> 235 * public class MainActivity extends Activity { 236 * 237 * protected void onCreate(Bundle savedInstanceState) { 238 * super.onCreate(savedInstanceState); 239 * setContentView(R.layout.activity_main); 240 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 241 * helloTextView.setText(R.string.user_greeting); 242 * } 243 * } 244 * </pre> 245 * <p> 246 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 247 * </p> 248 * <p> 249 * <b>XML attributes</b> 250 * <p> 251 * See {@link android.R.styleable#TextView TextView Attributes}, 252 * {@link android.R.styleable#View View Attributes} 253 * 254 * @attr ref android.R.styleable#TextView_text 255 * @attr ref android.R.styleable#TextView_bufferType 256 * @attr ref android.R.styleable#TextView_hint 257 * @attr ref android.R.styleable#TextView_textColor 258 * @attr ref android.R.styleable#TextView_textColorHighlight 259 * @attr ref android.R.styleable#TextView_textColorHint 260 * @attr ref android.R.styleable#TextView_textAppearance 261 * @attr ref android.R.styleable#TextView_textColorLink 262 * @attr ref android.R.styleable#TextView_textFontWeight 263 * @attr ref android.R.styleable#TextView_textSize 264 * @attr ref android.R.styleable#TextView_textScaleX 265 * @attr ref android.R.styleable#TextView_fontFamily 266 * @attr ref android.R.styleable#TextView_typeface 267 * @attr ref android.R.styleable#TextView_textStyle 268 * @attr ref android.R.styleable#TextView_cursorVisible 269 * @attr ref android.R.styleable#TextView_maxLines 270 * @attr ref android.R.styleable#TextView_maxHeight 271 * @attr ref android.R.styleable#TextView_lines 272 * @attr ref android.R.styleable#TextView_height 273 * @attr ref android.R.styleable#TextView_minLines 274 * @attr ref android.R.styleable#TextView_minHeight 275 * @attr ref android.R.styleable#TextView_maxEms 276 * @attr ref android.R.styleable#TextView_maxWidth 277 * @attr ref android.R.styleable#TextView_ems 278 * @attr ref android.R.styleable#TextView_width 279 * @attr ref android.R.styleable#TextView_minEms 280 * @attr ref android.R.styleable#TextView_minWidth 281 * @attr ref android.R.styleable#TextView_gravity 282 * @attr ref android.R.styleable#TextView_scrollHorizontally 283 * @attr ref android.R.styleable#TextView_password 284 * @attr ref android.R.styleable#TextView_singleLine 285 * @attr ref android.R.styleable#TextView_selectAllOnFocus 286 * @attr ref android.R.styleable#TextView_includeFontPadding 287 * @attr ref android.R.styleable#TextView_maxLength 288 * @attr ref android.R.styleable#TextView_shadowColor 289 * @attr ref android.R.styleable#TextView_shadowDx 290 * @attr ref android.R.styleable#TextView_shadowDy 291 * @attr ref android.R.styleable#TextView_shadowRadius 292 * @attr ref android.R.styleable#TextView_autoLink 293 * @attr ref android.R.styleable#TextView_linksClickable 294 * @attr ref android.R.styleable#TextView_numeric 295 * @attr ref android.R.styleable#TextView_digits 296 * @attr ref android.R.styleable#TextView_phoneNumber 297 * @attr ref android.R.styleable#TextView_inputMethod 298 * @attr ref android.R.styleable#TextView_capitalize 299 * @attr ref android.R.styleable#TextView_autoText 300 * @attr ref android.R.styleable#TextView_editable 301 * @attr ref android.R.styleable#TextView_freezesText 302 * @attr ref android.R.styleable#TextView_ellipsize 303 * @attr ref android.R.styleable#TextView_drawableTop 304 * @attr ref android.R.styleable#TextView_drawableBottom 305 * @attr ref android.R.styleable#TextView_drawableRight 306 * @attr ref android.R.styleable#TextView_drawableLeft 307 * @attr ref android.R.styleable#TextView_drawableStart 308 * @attr ref android.R.styleable#TextView_drawableEnd 309 * @attr ref android.R.styleable#TextView_drawablePadding 310 * @attr ref android.R.styleable#TextView_drawableTint 311 * @attr ref android.R.styleable#TextView_drawableTintMode 312 * @attr ref android.R.styleable#TextView_lineSpacingExtra 313 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 314 * @attr ref android.R.styleable#TextView_justificationMode 315 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 316 * @attr ref android.R.styleable#TextView_inputType 317 * @attr ref android.R.styleable#TextView_imeOptions 318 * @attr ref android.R.styleable#TextView_privateImeOptions 319 * @attr ref android.R.styleable#TextView_imeActionLabel 320 * @attr ref android.R.styleable#TextView_imeActionId 321 * @attr ref android.R.styleable#TextView_editorExtras 322 * @attr ref android.R.styleable#TextView_elegantTextHeight 323 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 324 * @attr ref android.R.styleable#TextView_letterSpacing 325 * @attr ref android.R.styleable#TextView_fontFeatureSettings 326 * @attr ref android.R.styleable#TextView_fontVariationSettings 327 * @attr ref android.R.styleable#TextView_breakStrategy 328 * @attr ref android.R.styleable#TextView_hyphenationFrequency 329 * @attr ref android.R.styleable#TextView_autoSizeTextType 330 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 331 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 332 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 333 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 334 * @attr ref android.R.styleable#TextView_textCursorDrawable 335 * @attr ref android.R.styleable#TextView_textSelectHandle 336 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 337 * @attr ref android.R.styleable#TextView_textSelectHandleRight 338 * @attr ref android.R.styleable#TextView_allowUndo 339 * @attr ref android.R.styleable#TextView_enabled 340 */ 341 @RemoteView 342 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 343 static final String LOG_TAG = "TextView"; 344 static final boolean DEBUG_EXTRACT = false; 345 private static final float[] TEMP_POSITION = new float[2]; 346 347 // Enum for the "typeface" XML parameter. 348 // TODO: How can we get this from the XML instead of hardcoding it here? 349 /** @hide */ 350 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 351 @Retention(RetentionPolicy.SOURCE) 352 public @interface XMLTypefaceAttr{} 353 private static final int DEFAULT_TYPEFACE = -1; 354 private static final int SANS = 1; 355 private static final int SERIF = 2; 356 private static final int MONOSPACE = 3; 357 358 // Enum for the "ellipsize" XML parameter. 359 private static final int ELLIPSIZE_NOT_SET = -1; 360 private static final int ELLIPSIZE_NONE = 0; 361 private static final int ELLIPSIZE_START = 1; 362 private static final int ELLIPSIZE_MIDDLE = 2; 363 private static final int ELLIPSIZE_END = 3; 364 private static final int ELLIPSIZE_MARQUEE = 4; 365 366 // Bitfield for the "numeric" XML parameter. 367 // TODO: How can we get this from the XML instead of hardcoding it here? 368 private static final int SIGNED = 2; 369 private static final int DECIMAL = 4; 370 371 /** 372 * Draw marquee text with fading edges as usual 373 */ 374 private static final int MARQUEE_FADE_NORMAL = 0; 375 376 /** 377 * Draw marquee text as ellipsize end while inactive instead of with the fade. 378 * (Useful for devices where the fade can be expensive if overdone) 379 */ 380 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 381 382 /** 383 * Draw marquee text with fading edges because it is currently active/animating. 384 */ 385 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 386 387 @UnsupportedAppUsage 388 private static final int LINES = 1; 389 private static final int EMS = LINES; 390 private static final int PIXELS = 2; 391 392 private static final RectF TEMP_RECTF = new RectF(); 393 394 /** @hide */ 395 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 396 private static final int ANIMATED_SCROLL_GAP = 250; 397 398 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 399 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 400 401 private static final int CHANGE_WATCHER_PRIORITY = 100; 402 403 // New state used to change background based on whether this TextView is multiline. 404 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 405 406 // Accessibility action to share selected text. 407 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 408 409 /** 410 * @hide 411 */ 412 // Accessibility action start id for "process text" actions. 413 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 414 415 /** 416 * @hide 417 */ 418 static final int PROCESS_TEXT_REQUEST_CODE = 100; 419 420 /** 421 * Return code of {@link #doKeyDown}. 422 */ 423 private static final int KEY_EVENT_NOT_HANDLED = 0; 424 private static final int KEY_EVENT_HANDLED = -1; 425 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 426 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 427 428 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 429 430 // System wide time for last cut, copy or text changed action. 431 static long sLastCutCopyOrTextChangedTime; 432 433 private ColorStateList mTextColor; 434 private ColorStateList mHintTextColor; 435 private ColorStateList mLinkTextColor; 436 @ViewDebug.ExportedProperty(category = "text") 437 438 /** 439 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 440 */ 441 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 442 private int mCurTextColor; 443 444 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 445 private int mCurHintTextColor; 446 private boolean mFreezesText; 447 448 @UnsupportedAppUsage 449 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 450 @UnsupportedAppUsage 451 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 452 453 @UnsupportedAppUsage 454 private float mShadowRadius; 455 @UnsupportedAppUsage 456 private float mShadowDx; 457 @UnsupportedAppUsage 458 private float mShadowDy; 459 private int mShadowColor; 460 461 private boolean mPreDrawRegistered; 462 private boolean mPreDrawListenerDetached; 463 464 private TextClassifier mTextClassifier; 465 private TextClassifier mTextClassificationSession; 466 private TextClassificationContext mTextClassificationContext; 467 468 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 469 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 470 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 471 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 472 // into by the user holding the movement key down) then we shouldn't prevent the focus from 473 // changing. 474 private boolean mPreventDefaultMovement; 475 476 private TextUtils.TruncateAt mEllipsize; 477 478 static class Drawables { 479 static final int LEFT = 0; 480 static final int TOP = 1; 481 static final int RIGHT = 2; 482 static final int BOTTOM = 3; 483 484 static final int DRAWABLE_NONE = -1; 485 static final int DRAWABLE_RIGHT = 0; 486 static final int DRAWABLE_LEFT = 1; 487 488 final Rect mCompoundRect = new Rect(); 489 490 final Drawable[] mShowing = new Drawable[4]; 491 492 ColorStateList mTintList; 493 BlendMode mBlendMode; 494 boolean mHasTint; 495 boolean mHasTintMode; 496 497 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 498 Drawable mDrawableLeftInitial, mDrawableRightInitial; 499 500 boolean mIsRtlCompatibilityMode; 501 boolean mOverride; 502 503 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 504 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 505 506 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 507 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 508 509 int mDrawablePadding; 510 511 int mDrawableSaved = DRAWABLE_NONE; 512 Drawables(Context context)513 public Drawables(Context context) { 514 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 515 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 516 || !context.getApplicationInfo().hasRtlSupport(); 517 mOverride = false; 518 } 519 520 /** 521 * @return {@code true} if this object contains metadata that needs to 522 * be retained, {@code false} otherwise 523 */ 524 public boolean hasMetadata() { 525 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 526 } 527 528 /** 529 * Updates the list of displayed drawables to account for the current 530 * layout direction. 531 * 532 * @param layoutDirection the current layout direction 533 * @return {@code true} if the displayed drawables changed 534 */ 535 public boolean resolveWithLayoutDirection(int layoutDirection) { 536 final Drawable previousLeft = mShowing[Drawables.LEFT]; 537 final Drawable previousRight = mShowing[Drawables.RIGHT]; 538 539 // First reset "left" and "right" drawables to their initial values 540 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 541 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 542 543 if (mIsRtlCompatibilityMode) { 544 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 545 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 546 mShowing[Drawables.LEFT] = mDrawableStart; 547 mDrawableSizeLeft = mDrawableSizeStart; 548 mDrawableHeightLeft = mDrawableHeightStart; 549 } 550 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 551 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 552 mShowing[Drawables.RIGHT] = mDrawableEnd; 553 mDrawableSizeRight = mDrawableSizeEnd; 554 mDrawableHeightRight = mDrawableHeightEnd; 555 } 556 } else { 557 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 558 // drawable if and only if they have been defined 559 switch(layoutDirection) { 560 case LAYOUT_DIRECTION_RTL: 561 if (mOverride) { 562 mShowing[Drawables.RIGHT] = mDrawableStart; 563 mDrawableSizeRight = mDrawableSizeStart; 564 mDrawableHeightRight = mDrawableHeightStart; 565 566 mShowing[Drawables.LEFT] = mDrawableEnd; 567 mDrawableSizeLeft = mDrawableSizeEnd; 568 mDrawableHeightLeft = mDrawableHeightEnd; 569 } 570 break; 571 572 case LAYOUT_DIRECTION_LTR: 573 default: 574 if (mOverride) { 575 mShowing[Drawables.LEFT] = mDrawableStart; 576 mDrawableSizeLeft = mDrawableSizeStart; 577 mDrawableHeightLeft = mDrawableHeightStart; 578 579 mShowing[Drawables.RIGHT] = mDrawableEnd; 580 mDrawableSizeRight = mDrawableSizeEnd; 581 mDrawableHeightRight = mDrawableHeightEnd; 582 } 583 break; 584 } 585 } 586 587 applyErrorDrawableIfNeeded(layoutDirection); 588 589 return mShowing[Drawables.LEFT] != previousLeft 590 || mShowing[Drawables.RIGHT] != previousRight; 591 } 592 593 public void setErrorDrawable(Drawable dr, TextView tv) { 594 if (mDrawableError != dr && mDrawableError != null) { 595 mDrawableError.setCallback(null); 596 } 597 mDrawableError = dr; 598 599 if (mDrawableError != null) { 600 final Rect compoundRect = mCompoundRect; 601 final int[] state = tv.getDrawableState(); 602 603 mDrawableError.setState(state); 604 mDrawableError.copyBounds(compoundRect); 605 mDrawableError.setCallback(tv); 606 mDrawableSizeError = compoundRect.width(); 607 mDrawableHeightError = compoundRect.height(); 608 } else { 609 mDrawableSizeError = mDrawableHeightError = 0; 610 } 611 } 612 613 private void applyErrorDrawableIfNeeded(int layoutDirection) { 614 // first restore the initial state if needed 615 switch (mDrawableSaved) { 616 case DRAWABLE_LEFT: 617 mShowing[Drawables.LEFT] = mDrawableTemp; 618 mDrawableSizeLeft = mDrawableSizeTemp; 619 mDrawableHeightLeft = mDrawableHeightTemp; 620 break; 621 case DRAWABLE_RIGHT: 622 mShowing[Drawables.RIGHT] = mDrawableTemp; 623 mDrawableSizeRight = mDrawableSizeTemp; 624 mDrawableHeightRight = mDrawableHeightTemp; 625 break; 626 case DRAWABLE_NONE: 627 default: 628 } 629 // then, if needed, assign the Error drawable to the correct location 630 if (mDrawableError != null) { 631 switch(layoutDirection) { 632 case LAYOUT_DIRECTION_RTL: 633 mDrawableSaved = DRAWABLE_LEFT; 634 635 mDrawableTemp = mShowing[Drawables.LEFT]; 636 mDrawableSizeTemp = mDrawableSizeLeft; 637 mDrawableHeightTemp = mDrawableHeightLeft; 638 639 mShowing[Drawables.LEFT] = mDrawableError; 640 mDrawableSizeLeft = mDrawableSizeError; 641 mDrawableHeightLeft = mDrawableHeightError; 642 break; 643 case LAYOUT_DIRECTION_LTR: 644 default: 645 mDrawableSaved = DRAWABLE_RIGHT; 646 647 mDrawableTemp = mShowing[Drawables.RIGHT]; 648 mDrawableSizeTemp = mDrawableSizeRight; 649 mDrawableHeightTemp = mDrawableHeightRight; 650 651 mShowing[Drawables.RIGHT] = mDrawableError; 652 mDrawableSizeRight = mDrawableSizeError; 653 mDrawableHeightRight = mDrawableHeightError; 654 break; 655 } 656 } 657 } 658 } 659 660 @UnsupportedAppUsage 661 Drawables mDrawables; 662 663 @UnsupportedAppUsage 664 private CharWrapper mCharWrapper; 665 666 @UnsupportedAppUsage(trackingBug = 124050217) 667 private Marquee mMarquee; 668 @UnsupportedAppUsage 669 private boolean mRestartMarquee; 670 671 private int mMarqueeRepeatLimit = 3; 672 673 private int mLastLayoutDirection = -1; 674 675 /** 676 * On some devices the fading edges add a performance penalty if used 677 * extensively in the same layout. This mode indicates how the marquee 678 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 679 */ 680 @UnsupportedAppUsage 681 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 682 683 /** 684 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 685 * the layout that should be used when the mode switches. 686 */ 687 @UnsupportedAppUsage 688 private Layout mSavedMarqueeModeLayout; 689 690 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 691 @ViewDebug.ExportedProperty(category = "text") 692 @UnsupportedAppUsage 693 private @Nullable CharSequence mText; 694 private @Nullable Spannable mSpannable; 695 private @Nullable PrecomputedText mPrecomputed; 696 697 @UnsupportedAppUsage 698 private CharSequence mTransformed; 699 @UnsupportedAppUsage 700 private BufferType mBufferType = BufferType.NORMAL; 701 702 private CharSequence mHint; 703 @UnsupportedAppUsage 704 private Layout mHintLayout; 705 706 private MovementMethod mMovement; 707 708 private TransformationMethod mTransformation; 709 @UnsupportedAppUsage 710 private boolean mAllowTransformationLengthChange; 711 @UnsupportedAppUsage 712 private ChangeWatcher mChangeWatcher; 713 714 @UnsupportedAppUsage(trackingBug = 123769451) 715 private ArrayList<TextWatcher> mListeners; 716 717 // display attributes 718 @UnsupportedAppUsage 719 private final TextPaint mTextPaint; 720 @UnsupportedAppUsage 721 private boolean mUserSetTextScaleX; 722 @UnsupportedAppUsage 723 private Layout mLayout; 724 private boolean mLocalesChanged = false; 725 726 // True if setKeyListener() has been explicitly called 727 private boolean mListenerChanged = false; 728 // True if internationalized input should be used for numbers and date and time. 729 private final boolean mUseInternationalizedInput; 730 // True if fallback fonts that end up getting used should be allowed to affect line spacing. 731 /* package */ boolean mUseFallbackLineSpacing; 732 733 @ViewDebug.ExportedProperty(category = "text") 734 @UnsupportedAppUsage 735 private int mGravity = Gravity.TOP | Gravity.START; 736 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 737 private boolean mHorizontallyScrolling; 738 739 private int mAutoLinkMask; 740 private boolean mLinksClickable = true; 741 742 @UnsupportedAppUsage 743 private float mSpacingMult = 1.0f; 744 @UnsupportedAppUsage 745 private float mSpacingAdd = 0.0f; 746 747 private int mBreakStrategy; 748 private int mHyphenationFrequency; 749 private int mJustificationMode; 750 751 @UnsupportedAppUsage 752 private int mMaximum = Integer.MAX_VALUE; 753 @UnsupportedAppUsage 754 private int mMaxMode = LINES; 755 @UnsupportedAppUsage 756 private int mMinimum = 0; 757 @UnsupportedAppUsage 758 private int mMinMode = LINES; 759 760 @UnsupportedAppUsage 761 private int mOldMaximum = mMaximum; 762 @UnsupportedAppUsage 763 private int mOldMaxMode = mMaxMode; 764 765 @UnsupportedAppUsage 766 private int mMaxWidth = Integer.MAX_VALUE; 767 @UnsupportedAppUsage 768 private int mMaxWidthMode = PIXELS; 769 @UnsupportedAppUsage 770 private int mMinWidth = 0; 771 @UnsupportedAppUsage 772 private int mMinWidthMode = PIXELS; 773 774 @UnsupportedAppUsage 775 private boolean mSingleLine; 776 @UnsupportedAppUsage 777 private int mDesiredHeightAtMeasure = -1; 778 @UnsupportedAppUsage 779 private boolean mIncludePad = true; 780 private int mDeferScroll = -1; 781 782 // tmp primitives, so we don't alloc them on each draw 783 private Rect mTempRect; 784 private long mLastScroll; 785 private Scroller mScroller; 786 private TextPaint mTempTextPaint; 787 788 @UnsupportedAppUsage 789 private BoringLayout.Metrics mBoring; 790 @UnsupportedAppUsage 791 private BoringLayout.Metrics mHintBoring; 792 @UnsupportedAppUsage 793 private BoringLayout mSavedLayout; 794 @UnsupportedAppUsage 795 private BoringLayout mSavedHintLayout; 796 797 @UnsupportedAppUsage 798 private TextDirectionHeuristic mTextDir; 799 800 private InputFilter[] mFilters = NO_FILTERS; 801 802 /** 803 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 804 * the same as {@link Process#myUserHandle()}. 805 * 806 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 807 * other apps may need to set this so that the system can use right user's resources and 808 * services such as input methods and spell checkers.</p> 809 * 810 * @see #setTextOperationUser(UserHandle) 811 */ 812 @Nullable 813 private UserHandle mTextOperationUser; 814 815 private volatile Locale mCurrentSpellCheckerLocaleCache; 816 817 // It is possible to have a selection even when mEditor is null (programmatically set, like when 818 // a link is pressed). These highlight-related fields do not go in mEditor. 819 @UnsupportedAppUsage 820 int mHighlightColor = 0x6633B5E5; 821 private Path mHighlightPath; 822 @UnsupportedAppUsage 823 private final Paint mHighlightPaint; 824 @UnsupportedAppUsage 825 private boolean mHighlightPathBogus = true; 826 827 // Although these fields are specific to editable text, they are not added to Editor because 828 // they are defined by the TextView's style and are theme-dependent. 829 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 830 int mCursorDrawableRes; 831 private Drawable mCursorDrawable; 832 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 833 // by removing it, but we would break apps targeting <= P that use it by reflection. 834 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 835 int mTextSelectHandleLeftRes; 836 private Drawable mTextSelectHandleLeft; 837 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 838 // by removing it, but we would break apps targeting <= P that use it by reflection. 839 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 840 int mTextSelectHandleRightRes; 841 private Drawable mTextSelectHandleRight; 842 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 843 // by removing it, but we would break apps targeting <= P that use it by reflection. 844 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 845 int mTextSelectHandleRes; 846 private Drawable mTextSelectHandle; 847 int mTextEditSuggestionItemLayout; 848 int mTextEditSuggestionContainerLayout; 849 int mTextEditSuggestionHighlightStyle; 850 851 /** 852 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 853 * See {@link #createEditorIfNeeded()}. 854 */ 855 @UnsupportedAppUsage 856 private Editor mEditor; 857 858 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 859 private static final int DEVICE_PROVISIONED_NO = 1; 860 private static final int DEVICE_PROVISIONED_YES = 2; 861 862 /** 863 * Some special options such as sharing selected text should only be shown if the device 864 * is provisioned. Only check the provisioned state once for a given view instance. 865 */ 866 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 867 868 /** 869 * The TextView does not auto-size text (default). 870 */ 871 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 872 873 /** 874 * The TextView scales text size both horizontally and vertically to fit within the 875 * container. 876 */ 877 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 878 879 /** @hide */ 880 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 881 AUTO_SIZE_TEXT_TYPE_NONE, 882 AUTO_SIZE_TEXT_TYPE_UNIFORM 883 }) 884 @Retention(RetentionPolicy.SOURCE) 885 public @interface AutoSizeTextType {} 886 // Default minimum size for auto-sizing text in scaled pixels. 887 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 888 // Default maximum size for auto-sizing text in scaled pixels. 889 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 890 // Default value for the step size in pixels. 891 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 892 // Use this to specify that any of the auto-size configuration int values have not been set. 893 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 894 // Auto-size text type. 895 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 896 // Specify if auto-size text is needed. 897 private boolean mNeedsAutoSizeText = false; 898 // Step size for auto-sizing in pixels. 899 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 900 // Minimum text size for auto-sizing in pixels. 901 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 902 // Maximum text size for auto-sizing in pixels. 903 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 904 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 905 // when auto-sizing text. 906 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 907 // Specifies whether auto-size should use the provided auto size steps set or if it should 908 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 909 // mAutoSizeStepGranularityInPx. 910 private boolean mHasPresetAutoSizeValues = false; 911 912 // Autofill-related attributes 913 // 914 // Indicates whether the text was set statically or dynamically, so it can be used to 915 // sanitize autofill requests. 916 private boolean mTextSetFromXmlOrResourceId = false; 917 // Resource id used to set the text. 918 private @StringRes int mTextId = Resources.ID_NULL; 919 // 920 // End of autofill-related attributes 921 922 /** 923 * Kick-start the font cache for the zygote process (to pay the cost of 924 * initializing freetype for our default font only once). 925 * @hide 926 */ 927 public static void preloadFontCache() { 928 Paint p = new Paint(); 929 p.setAntiAlias(true); 930 // Ensure that the Typeface is loaded here. 931 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 932 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 933 // since Paint.measureText can not be called without Typeface static initializer. 934 p.setTypeface(Typeface.DEFAULT); 935 // We don't care about the result, just the side-effect of measuring. 936 p.measureText("H"); 937 } 938 939 /** 940 * Interface definition for a callback to be invoked when an action is 941 * performed on the editor. 942 */ 943 public interface OnEditorActionListener { 944 /** 945 * Called when an action is being performed. 946 * 947 * @param v The view that was clicked. 948 * @param actionId Identifier of the action. This will be either the 949 * identifier you supplied, or {@link EditorInfo#IME_NULL 950 * EditorInfo.IME_NULL} if being called due to the enter key 951 * being pressed. 952 * @param event If triggered by an enter key, this is the event; 953 * otherwise, this is null. 954 * @return Return true if you have consumed the action, else false. 955 */ 956 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 957 } 958 959 public TextView(Context context) { 960 this(context, null); 961 } 962 963 public TextView(Context context, @Nullable AttributeSet attrs) { 964 this(context, attrs, com.android.internal.R.attr.textViewStyle); 965 } 966 967 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 968 this(context, attrs, defStyleAttr, 0); 969 } 970 971 @SuppressWarnings("deprecation") 972 public TextView( 973 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 974 super(context, attrs, defStyleAttr, defStyleRes); 975 976 // TextView is important by default, unless app developer overrode attribute. 977 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 978 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 979 } 980 981 setTextInternal(""); 982 983 final Resources res = getResources(); 984 final CompatibilityInfo compat = res.getCompatibilityInfo(); 985 986 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 987 mTextPaint.density = res.getDisplayMetrics().density; 988 mTextPaint.setCompatibilityScaling(compat.applicationScale); 989 990 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 991 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 992 993 mMovement = getDefaultMovementMethod(); 994 995 mTransformation = null; 996 997 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 998 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 999 attributes.mTextSize = 15; 1000 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1001 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1002 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1003 1004 final Resources.Theme theme = context.getTheme(); 1005 1006 /* 1007 * Look the appearance up without checking first if it exists because 1008 * almost every TextView has one and it greatly simplifies the logic 1009 * to be able to parse the appearance first and then let specific tags 1010 * for this View override it. 1011 */ 1012 TypedArray a = theme.obtainStyledAttributes(attrs, 1013 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1014 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1015 attrs, a, defStyleAttr, defStyleRes); 1016 TypedArray appearance = null; 1017 int ap = a.getResourceId( 1018 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1019 a.recycle(); 1020 if (ap != -1) { 1021 appearance = theme.obtainStyledAttributes( 1022 ap, com.android.internal.R.styleable.TextAppearance); 1023 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1024 null, appearance, 0, ap); 1025 } 1026 if (appearance != null) { 1027 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1028 attributes.mFontFamilyExplicit = false; 1029 appearance.recycle(); 1030 } 1031 1032 boolean editable = getDefaultEditable(); 1033 CharSequence inputMethod = null; 1034 int numeric = 0; 1035 CharSequence digits = null; 1036 boolean phone = false; 1037 boolean autotext = false; 1038 int autocap = -1; 1039 int buffertype = 0; 1040 boolean selectallonfocus = false; 1041 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1042 drawableBottom = null, drawableStart = null, drawableEnd = null; 1043 ColorStateList drawableTint = null; 1044 BlendMode drawableTintMode = null; 1045 int drawablePadding = 0; 1046 int ellipsize = ELLIPSIZE_NOT_SET; 1047 boolean singleLine = false; 1048 int maxlength = -1; 1049 CharSequence text = ""; 1050 CharSequence hint = null; 1051 boolean password = false; 1052 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1053 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1054 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1055 int inputType = EditorInfo.TYPE_NULL; 1056 a = theme.obtainStyledAttributes( 1057 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1058 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1059 defStyleAttr, defStyleRes); 1060 int firstBaselineToTopHeight = -1; 1061 int lastBaselineToBottomHeight = -1; 1062 int lineHeight = -1; 1063 1064 readTextAppearance(context, a, attributes, true /* styleArray */); 1065 1066 int n = a.getIndexCount(); 1067 1068 // Must set id in a temporary variable because it will be reset by setText() 1069 boolean textIsSetFromXml = false; 1070 for (int i = 0; i < n; i++) { 1071 int attr = a.getIndex(i); 1072 1073 switch (attr) { 1074 case com.android.internal.R.styleable.TextView_editable: 1075 editable = a.getBoolean(attr, editable); 1076 break; 1077 1078 case com.android.internal.R.styleable.TextView_inputMethod: 1079 inputMethod = a.getText(attr); 1080 break; 1081 1082 case com.android.internal.R.styleable.TextView_numeric: 1083 numeric = a.getInt(attr, numeric); 1084 break; 1085 1086 case com.android.internal.R.styleable.TextView_digits: 1087 digits = a.getText(attr); 1088 break; 1089 1090 case com.android.internal.R.styleable.TextView_phoneNumber: 1091 phone = a.getBoolean(attr, phone); 1092 break; 1093 1094 case com.android.internal.R.styleable.TextView_autoText: 1095 autotext = a.getBoolean(attr, autotext); 1096 break; 1097 1098 case com.android.internal.R.styleable.TextView_capitalize: 1099 autocap = a.getInt(attr, autocap); 1100 break; 1101 1102 case com.android.internal.R.styleable.TextView_bufferType: 1103 buffertype = a.getInt(attr, buffertype); 1104 break; 1105 1106 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1107 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1108 break; 1109 1110 case com.android.internal.R.styleable.TextView_autoLink: 1111 mAutoLinkMask = a.getInt(attr, 0); 1112 break; 1113 1114 case com.android.internal.R.styleable.TextView_linksClickable: 1115 mLinksClickable = a.getBoolean(attr, true); 1116 break; 1117 1118 case com.android.internal.R.styleable.TextView_drawableLeft: 1119 drawableLeft = a.getDrawable(attr); 1120 break; 1121 1122 case com.android.internal.R.styleable.TextView_drawableTop: 1123 drawableTop = a.getDrawable(attr); 1124 break; 1125 1126 case com.android.internal.R.styleable.TextView_drawableRight: 1127 drawableRight = a.getDrawable(attr); 1128 break; 1129 1130 case com.android.internal.R.styleable.TextView_drawableBottom: 1131 drawableBottom = a.getDrawable(attr); 1132 break; 1133 1134 case com.android.internal.R.styleable.TextView_drawableStart: 1135 drawableStart = a.getDrawable(attr); 1136 break; 1137 1138 case com.android.internal.R.styleable.TextView_drawableEnd: 1139 drawableEnd = a.getDrawable(attr); 1140 break; 1141 1142 case com.android.internal.R.styleable.TextView_drawableTint: 1143 drawableTint = a.getColorStateList(attr); 1144 break; 1145 1146 case com.android.internal.R.styleable.TextView_drawableTintMode: 1147 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1148 drawableTintMode); 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_drawablePadding: 1152 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1153 break; 1154 1155 case com.android.internal.R.styleable.TextView_maxLines: 1156 setMaxLines(a.getInt(attr, -1)); 1157 break; 1158 1159 case com.android.internal.R.styleable.TextView_maxHeight: 1160 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1161 break; 1162 1163 case com.android.internal.R.styleable.TextView_lines: 1164 setLines(a.getInt(attr, -1)); 1165 break; 1166 1167 case com.android.internal.R.styleable.TextView_height: 1168 setHeight(a.getDimensionPixelSize(attr, -1)); 1169 break; 1170 1171 case com.android.internal.R.styleable.TextView_minLines: 1172 setMinLines(a.getInt(attr, -1)); 1173 break; 1174 1175 case com.android.internal.R.styleable.TextView_minHeight: 1176 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1177 break; 1178 1179 case com.android.internal.R.styleable.TextView_maxEms: 1180 setMaxEms(a.getInt(attr, -1)); 1181 break; 1182 1183 case com.android.internal.R.styleable.TextView_maxWidth: 1184 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1185 break; 1186 1187 case com.android.internal.R.styleable.TextView_ems: 1188 setEms(a.getInt(attr, -1)); 1189 break; 1190 1191 case com.android.internal.R.styleable.TextView_width: 1192 setWidth(a.getDimensionPixelSize(attr, -1)); 1193 break; 1194 1195 case com.android.internal.R.styleable.TextView_minEms: 1196 setMinEms(a.getInt(attr, -1)); 1197 break; 1198 1199 case com.android.internal.R.styleable.TextView_minWidth: 1200 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1201 break; 1202 1203 case com.android.internal.R.styleable.TextView_gravity: 1204 setGravity(a.getInt(attr, -1)); 1205 break; 1206 1207 case com.android.internal.R.styleable.TextView_hint: 1208 hint = a.getText(attr); 1209 break; 1210 1211 case com.android.internal.R.styleable.TextView_text: 1212 textIsSetFromXml = true; 1213 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1214 text = a.getText(attr); 1215 break; 1216 1217 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1218 if (a.getBoolean(attr, false)) { 1219 setHorizontallyScrolling(true); 1220 } 1221 break; 1222 1223 case com.android.internal.R.styleable.TextView_singleLine: 1224 singleLine = a.getBoolean(attr, singleLine); 1225 break; 1226 1227 case com.android.internal.R.styleable.TextView_ellipsize: 1228 ellipsize = a.getInt(attr, ellipsize); 1229 break; 1230 1231 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1232 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1233 break; 1234 1235 case com.android.internal.R.styleable.TextView_includeFontPadding: 1236 if (!a.getBoolean(attr, true)) { 1237 setIncludeFontPadding(false); 1238 } 1239 break; 1240 1241 case com.android.internal.R.styleable.TextView_cursorVisible: 1242 if (!a.getBoolean(attr, true)) { 1243 setCursorVisible(false); 1244 } 1245 break; 1246 1247 case com.android.internal.R.styleable.TextView_maxLength: 1248 maxlength = a.getInt(attr, -1); 1249 break; 1250 1251 case com.android.internal.R.styleable.TextView_textScaleX: 1252 setTextScaleX(a.getFloat(attr, 1.0f)); 1253 break; 1254 1255 case com.android.internal.R.styleable.TextView_freezesText: 1256 mFreezesText = a.getBoolean(attr, false); 1257 break; 1258 1259 case com.android.internal.R.styleable.TextView_enabled: 1260 setEnabled(a.getBoolean(attr, isEnabled())); 1261 break; 1262 1263 case com.android.internal.R.styleable.TextView_password: 1264 password = a.getBoolean(attr, password); 1265 break; 1266 1267 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1268 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1269 break; 1270 1271 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1272 mSpacingMult = a.getFloat(attr, mSpacingMult); 1273 break; 1274 1275 case com.android.internal.R.styleable.TextView_inputType: 1276 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1277 break; 1278 1279 case com.android.internal.R.styleable.TextView_allowUndo: 1280 createEditorIfNeeded(); 1281 mEditor.mAllowUndo = a.getBoolean(attr, true); 1282 break; 1283 1284 case com.android.internal.R.styleable.TextView_imeOptions: 1285 createEditorIfNeeded(); 1286 mEditor.createInputContentTypeIfNeeded(); 1287 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1288 mEditor.mInputContentType.imeOptions); 1289 break; 1290 1291 case com.android.internal.R.styleable.TextView_imeActionLabel: 1292 createEditorIfNeeded(); 1293 mEditor.createInputContentTypeIfNeeded(); 1294 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1295 break; 1296 1297 case com.android.internal.R.styleable.TextView_imeActionId: 1298 createEditorIfNeeded(); 1299 mEditor.createInputContentTypeIfNeeded(); 1300 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1301 mEditor.mInputContentType.imeActionId); 1302 break; 1303 1304 case com.android.internal.R.styleable.TextView_privateImeOptions: 1305 setPrivateImeOptions(a.getString(attr)); 1306 break; 1307 1308 case com.android.internal.R.styleable.TextView_editorExtras: 1309 try { 1310 setInputExtras(a.getResourceId(attr, 0)); 1311 } catch (XmlPullParserException e) { 1312 Log.w(LOG_TAG, "Failure reading input extras", e); 1313 } catch (IOException e) { 1314 Log.w(LOG_TAG, "Failure reading input extras", e); 1315 } 1316 break; 1317 1318 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1319 mCursorDrawableRes = a.getResourceId(attr, 0); 1320 break; 1321 1322 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1323 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1324 break; 1325 1326 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1327 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1328 break; 1329 1330 case com.android.internal.R.styleable.TextView_textSelectHandle: 1331 mTextSelectHandleRes = a.getResourceId(attr, 0); 1332 break; 1333 1334 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1335 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1336 break; 1337 1338 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1339 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1340 break; 1341 1342 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1343 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1344 break; 1345 1346 case com.android.internal.R.styleable.TextView_textIsSelectable: 1347 setTextIsSelectable(a.getBoolean(attr, false)); 1348 break; 1349 1350 case com.android.internal.R.styleable.TextView_breakStrategy: 1351 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1352 break; 1353 1354 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1355 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1356 break; 1357 1358 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1359 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1360 break; 1361 1362 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1363 autoSizeStepGranularityInPx = a.getDimension(attr, 1364 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1365 break; 1366 1367 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1368 autoSizeMinTextSizeInPx = a.getDimension(attr, 1369 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1370 break; 1371 1372 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1373 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1374 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1375 break; 1376 1377 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1378 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1379 if (autoSizeStepSizeArrayResId > 0) { 1380 final TypedArray autoSizePresetTextSizes = a.getResources() 1381 .obtainTypedArray(autoSizeStepSizeArrayResId); 1382 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1383 autoSizePresetTextSizes.recycle(); 1384 } 1385 break; 1386 case com.android.internal.R.styleable.TextView_justificationMode: 1387 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1388 break; 1389 1390 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1391 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1392 break; 1393 1394 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1395 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1396 break; 1397 1398 case com.android.internal.R.styleable.TextView_lineHeight: 1399 lineHeight = a.getDimensionPixelSize(attr, -1); 1400 break; 1401 } 1402 } 1403 1404 a.recycle(); 1405 1406 BufferType bufferType = BufferType.EDITABLE; 1407 1408 final int variation = 1409 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1410 final boolean passwordInputType = variation 1411 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1412 final boolean webPasswordInputType = variation 1413 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1414 final boolean numberPasswordInputType = variation 1415 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1416 1417 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1418 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1419 mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; 1420 1421 if (inputMethod != null) { 1422 Class<?> c; 1423 1424 try { 1425 c = Class.forName(inputMethod.toString()); 1426 } catch (ClassNotFoundException ex) { 1427 throw new RuntimeException(ex); 1428 } 1429 1430 try { 1431 createEditorIfNeeded(); 1432 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1433 } catch (InstantiationException ex) { 1434 throw new RuntimeException(ex); 1435 } catch (IllegalAccessException ex) { 1436 throw new RuntimeException(ex); 1437 } 1438 try { 1439 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1440 ? inputType 1441 : mEditor.mKeyListener.getInputType(); 1442 } catch (IncompatibleClassChangeError e) { 1443 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1444 } 1445 } else if (digits != null) { 1446 createEditorIfNeeded(); 1447 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1448 // If no input type was specified, we will default to generic 1449 // text, since we can't tell the IME about the set of digits 1450 // that was selected. 1451 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1452 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1453 } else if (inputType != EditorInfo.TYPE_NULL) { 1454 setInputType(inputType, true); 1455 // If set, the input type overrides what was set using the deprecated singleLine flag. 1456 singleLine = !isMultilineInputType(inputType); 1457 } else if (phone) { 1458 createEditorIfNeeded(); 1459 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1460 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1461 } else if (numeric != 0) { 1462 createEditorIfNeeded(); 1463 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1464 null, // locale 1465 (numeric & SIGNED) != 0, 1466 (numeric & DECIMAL) != 0); 1467 inputType = mEditor.mKeyListener.getInputType(); 1468 mEditor.mInputType = inputType; 1469 } else if (autotext || autocap != -1) { 1470 TextKeyListener.Capitalize cap; 1471 1472 inputType = EditorInfo.TYPE_CLASS_TEXT; 1473 1474 switch (autocap) { 1475 case 1: 1476 cap = TextKeyListener.Capitalize.SENTENCES; 1477 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1478 break; 1479 1480 case 2: 1481 cap = TextKeyListener.Capitalize.WORDS; 1482 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1483 break; 1484 1485 case 3: 1486 cap = TextKeyListener.Capitalize.CHARACTERS; 1487 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1488 break; 1489 1490 default: 1491 cap = TextKeyListener.Capitalize.NONE; 1492 break; 1493 } 1494 1495 createEditorIfNeeded(); 1496 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1497 mEditor.mInputType = inputType; 1498 } else if (editable) { 1499 createEditorIfNeeded(); 1500 mEditor.mKeyListener = TextKeyListener.getInstance(); 1501 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1502 } else if (isTextSelectable()) { 1503 // Prevent text changes from keyboard. 1504 if (mEditor != null) { 1505 mEditor.mKeyListener = null; 1506 mEditor.mInputType = EditorInfo.TYPE_NULL; 1507 } 1508 bufferType = BufferType.SPANNABLE; 1509 // So that selection can be changed using arrow keys and touch is handled. 1510 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1511 } else { 1512 if (mEditor != null) mEditor.mKeyListener = null; 1513 1514 switch (buffertype) { 1515 case 0: 1516 bufferType = BufferType.NORMAL; 1517 break; 1518 case 1: 1519 bufferType = BufferType.SPANNABLE; 1520 break; 1521 case 2: 1522 bufferType = BufferType.EDITABLE; 1523 break; 1524 } 1525 } 1526 1527 if (mEditor != null) { 1528 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1529 numberPasswordInputType); 1530 } 1531 1532 if (selectallonfocus) { 1533 createEditorIfNeeded(); 1534 mEditor.mSelectAllOnFocus = true; 1535 1536 if (bufferType == BufferType.NORMAL) { 1537 bufferType = BufferType.SPANNABLE; 1538 } 1539 } 1540 1541 // Set up the tint (if needed) before setting the drawables so that it 1542 // gets applied correctly. 1543 if (drawableTint != null || drawableTintMode != null) { 1544 if (mDrawables == null) { 1545 mDrawables = new Drawables(context); 1546 } 1547 if (drawableTint != null) { 1548 mDrawables.mTintList = drawableTint; 1549 mDrawables.mHasTint = true; 1550 } 1551 if (drawableTintMode != null) { 1552 mDrawables.mBlendMode = drawableTintMode; 1553 mDrawables.mHasTintMode = true; 1554 } 1555 } 1556 1557 // This call will save the initial left/right drawables 1558 setCompoundDrawablesWithIntrinsicBounds( 1559 drawableLeft, drawableTop, drawableRight, drawableBottom); 1560 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1561 setCompoundDrawablePadding(drawablePadding); 1562 1563 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1564 // of lines of height are unchanged for multi-line TextViews. 1565 setInputTypeSingleLine(singleLine); 1566 applySingleLine(singleLine, singleLine, singleLine); 1567 1568 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1569 ellipsize = ELLIPSIZE_END; 1570 } 1571 1572 switch (ellipsize) { 1573 case ELLIPSIZE_START: 1574 setEllipsize(TextUtils.TruncateAt.START); 1575 break; 1576 case ELLIPSIZE_MIDDLE: 1577 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1578 break; 1579 case ELLIPSIZE_END: 1580 setEllipsize(TextUtils.TruncateAt.END); 1581 break; 1582 case ELLIPSIZE_MARQUEE: 1583 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1584 setHorizontalFadingEdgeEnabled(true); 1585 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1586 } else { 1587 setHorizontalFadingEdgeEnabled(false); 1588 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1589 } 1590 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1591 break; 1592 } 1593 1594 final boolean isPassword = password || passwordInputType || webPasswordInputType 1595 || numberPasswordInputType; 1596 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1597 && (mEditor.mInputType 1598 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1599 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1600 if (isMonospaceEnforced) { 1601 attributes.mTypefaceIndex = MONOSPACE; 1602 } 1603 1604 applyTextAppearance(attributes); 1605 1606 if (isPassword) { 1607 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1608 } 1609 1610 if (maxlength >= 0) { 1611 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1612 } else { 1613 setFilters(NO_FILTERS); 1614 } 1615 1616 setText(text, bufferType); 1617 if (mText == null) { 1618 mText = ""; 1619 } 1620 if (mTransformed == null) { 1621 mTransformed = ""; 1622 } 1623 1624 if (textIsSetFromXml) { 1625 mTextSetFromXmlOrResourceId = true; 1626 } 1627 1628 if (hint != null) setHint(hint); 1629 1630 /* 1631 * Views are not normally clickable unless specified to be. 1632 * However, TextViews that have input or movement methods *are* 1633 * clickable by default. By setting clickable here, we implicitly set focusable as well 1634 * if not overridden by the developer. 1635 */ 1636 a = context.obtainStyledAttributes( 1637 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1638 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1639 boolean clickable = canInputOrMove || isClickable(); 1640 boolean longClickable = canInputOrMove || isLongClickable(); 1641 int focusable = getFocusable(); 1642 1643 n = a.getIndexCount(); 1644 for (int i = 0; i < n; i++) { 1645 int attr = a.getIndex(i); 1646 1647 switch (attr) { 1648 case com.android.internal.R.styleable.View_focusable: 1649 TypedValue val = new TypedValue(); 1650 if (a.getValue(attr, val)) { 1651 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1652 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1653 : val.data; 1654 } 1655 break; 1656 1657 case com.android.internal.R.styleable.View_clickable: 1658 clickable = a.getBoolean(attr, clickable); 1659 break; 1660 1661 case com.android.internal.R.styleable.View_longClickable: 1662 longClickable = a.getBoolean(attr, longClickable); 1663 break; 1664 } 1665 } 1666 a.recycle(); 1667 1668 // Some apps were relying on the undefined behavior of focusable winning over 1669 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1670 // when starting with EditText and setting only focusable=false). To keep those apps from 1671 // breaking, re-apply the focusable attribute here. 1672 if (focusable != getFocusable()) { 1673 setFocusable(focusable); 1674 } 1675 setClickable(clickable); 1676 setLongClickable(longClickable); 1677 1678 if (mEditor != null) mEditor.prepareCursorControllers(); 1679 1680 // If not explicitly specified this view is important for accessibility. 1681 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1682 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1683 } 1684 1685 if (supportsAutoSizeText()) { 1686 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1687 // If uniform auto-size has been specified but preset values have not been set then 1688 // replace the auto-size configuration values that have not been specified with the 1689 // defaults. 1690 if (!mHasPresetAutoSizeValues) { 1691 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1692 1693 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1694 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1695 TypedValue.COMPLEX_UNIT_SP, 1696 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1697 displayMetrics); 1698 } 1699 1700 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1701 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1702 TypedValue.COMPLEX_UNIT_SP, 1703 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1704 displayMetrics); 1705 } 1706 1707 if (autoSizeStepGranularityInPx 1708 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1709 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1710 } 1711 1712 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1713 autoSizeMaxTextSizeInPx, 1714 autoSizeStepGranularityInPx); 1715 } 1716 1717 setupAutoSizeText(); 1718 } 1719 } else { 1720 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1721 } 1722 1723 if (firstBaselineToTopHeight >= 0) { 1724 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1725 } 1726 if (lastBaselineToBottomHeight >= 0) { 1727 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1728 } 1729 if (lineHeight >= 0) { 1730 setLineHeight(lineHeight); 1731 } 1732 } 1733 1734 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1735 private void setTextInternal(@Nullable CharSequence text) { 1736 mText = text; 1737 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1738 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1739 } 1740 1741 /** 1742 * Specify whether this widget should automatically scale the text to try to perfectly fit 1743 * within the layout bounds by using the default auto-size configuration. 1744 * 1745 * @param autoSizeTextType the type of auto-size. Must be one of 1746 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1747 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1748 * 1749 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1750 * 1751 * @attr ref android.R.styleable#TextView_autoSizeTextType 1752 * 1753 * @see #getAutoSizeTextType() 1754 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1755 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1756 if (supportsAutoSizeText()) { 1757 switch (autoSizeTextType) { 1758 case AUTO_SIZE_TEXT_TYPE_NONE: 1759 clearAutoSizeConfiguration(); 1760 break; 1761 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1762 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1763 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1764 TypedValue.COMPLEX_UNIT_SP, 1765 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1766 displayMetrics); 1767 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1768 TypedValue.COMPLEX_UNIT_SP, 1769 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1770 displayMetrics); 1771 1772 validateAndSetAutoSizeTextTypeUniformConfiguration( 1773 autoSizeMinTextSizeInPx, 1774 autoSizeMaxTextSizeInPx, 1775 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1776 if (setupAutoSizeText()) { 1777 autoSizeText(); 1778 invalidate(); 1779 } 1780 break; 1781 default: 1782 throw new IllegalArgumentException( 1783 "Unknown auto-size text type: " + autoSizeTextType); 1784 } 1785 } 1786 } 1787 1788 /** 1789 * Specify whether this widget should automatically scale the text to try to perfectly fit 1790 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1791 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1792 * 1793 * @param autoSizeMinTextSize the minimum text size available for auto-size 1794 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1795 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1796 * the minimum and maximum text size in order to build the set of 1797 * text sizes the system uses to choose from when auto-sizing 1798 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1799 * possible dimension units 1800 * 1801 * @throws IllegalArgumentException if any of the configuration params are invalid. 1802 * 1803 * @attr ref android.R.styleable#TextView_autoSizeTextType 1804 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1805 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1806 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1807 * 1808 * @see #setAutoSizeTextTypeWithDefaults(int) 1809 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1810 * @see #getAutoSizeMinTextSize() 1811 * @see #getAutoSizeMaxTextSize() 1812 * @see #getAutoSizeStepGranularity() 1813 * @see #getAutoSizeTextAvailableSizes() 1814 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1815 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1816 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1817 if (supportsAutoSizeText()) { 1818 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1819 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1820 unit, autoSizeMinTextSize, displayMetrics); 1821 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1822 unit, autoSizeMaxTextSize, displayMetrics); 1823 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1824 unit, autoSizeStepGranularity, displayMetrics); 1825 1826 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1827 autoSizeMaxTextSizeInPx, 1828 autoSizeStepGranularityInPx); 1829 1830 if (setupAutoSizeText()) { 1831 autoSizeText(); 1832 invalidate(); 1833 } 1834 } 1835 } 1836 1837 /** 1838 * Specify whether this widget should automatically scale the text to try to perfectly fit 1839 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1840 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1841 * 1842 * @param presetSizes an {@code int} array of sizes in pixels 1843 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1844 * the possible dimension units 1845 * 1846 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1847 * 1848 * @attr ref android.R.styleable#TextView_autoSizeTextType 1849 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1850 * 1851 * @see #setAutoSizeTextTypeWithDefaults(int) 1852 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1853 * @see #getAutoSizeMinTextSize() 1854 * @see #getAutoSizeMaxTextSize() 1855 * @see #getAutoSizeTextAvailableSizes() 1856 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1857 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1858 if (supportsAutoSizeText()) { 1859 final int presetSizesLength = presetSizes.length; 1860 if (presetSizesLength > 0) { 1861 int[] presetSizesInPx = new int[presetSizesLength]; 1862 1863 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1864 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1865 } else { 1866 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1867 // Convert all to sizes to pixels. 1868 for (int i = 0; i < presetSizesLength; i++) { 1869 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 1870 presetSizes[i], displayMetrics)); 1871 } 1872 } 1873 1874 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1875 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1876 throw new IllegalArgumentException("None of the preset sizes is valid: " 1877 + Arrays.toString(presetSizes)); 1878 } 1879 } else { 1880 mHasPresetAutoSizeValues = false; 1881 } 1882 1883 if (setupAutoSizeText()) { 1884 autoSizeText(); 1885 invalidate(); 1886 } 1887 } 1888 } 1889 1890 /** 1891 * Returns the type of auto-size set for this widget. 1892 * 1893 * @return an {@code int} corresponding to one of the auto-size types: 1894 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1895 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1896 * 1897 * @attr ref android.R.styleable#TextView_autoSizeTextType 1898 * 1899 * @see #setAutoSizeTextTypeWithDefaults(int) 1900 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1901 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1902 */ 1903 @InspectableProperty(enumMapping = { 1904 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 1905 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 1906 }) 1907 @AutoSizeTextType getAutoSizeTextType()1908 public int getAutoSizeTextType() { 1909 return mAutoSizeTextType; 1910 } 1911 1912 /** 1913 * @return the current auto-size step granularity in pixels. 1914 * 1915 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1916 * 1917 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1918 */ 1919 @InspectableProperty getAutoSizeStepGranularity()1920 public int getAutoSizeStepGranularity() { 1921 return Math.round(mAutoSizeStepGranularityInPx); 1922 } 1923 1924 /** 1925 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 1926 * if auto-size has not been configured this function returns {@code -1}. 1927 * 1928 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1929 * 1930 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1931 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1932 */ 1933 @InspectableProperty getAutoSizeMinTextSize()1934 public int getAutoSizeMinTextSize() { 1935 return Math.round(mAutoSizeMinTextSizeInPx); 1936 } 1937 1938 /** 1939 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 1940 * if auto-size has not been configured this function returns {@code -1}. 1941 * 1942 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1943 * 1944 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1945 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1946 */ 1947 @InspectableProperty getAutoSizeMaxTextSize()1948 public int getAutoSizeMaxTextSize() { 1949 return Math.round(mAutoSizeMaxTextSizeInPx); 1950 } 1951 1952 /** 1953 * @return the current auto-size {@code int} sizes array (in pixels). 1954 * 1955 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1956 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1957 */ getAutoSizeTextAvailableSizes()1958 public int[] getAutoSizeTextAvailableSizes() { 1959 return mAutoSizeTextSizesInPx; 1960 } 1961 setupAutoSizeUniformPresetSizes(TypedArray textSizes)1962 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 1963 final int textSizesLength = textSizes.length(); 1964 final int[] parsedSizes = new int[textSizesLength]; 1965 1966 if (textSizesLength > 0) { 1967 for (int i = 0; i < textSizesLength; i++) { 1968 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 1969 } 1970 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 1971 setupAutoSizeUniformPresetSizesConfiguration(); 1972 } 1973 } 1974 setupAutoSizeUniformPresetSizesConfiguration()1975 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 1976 final int sizesLength = mAutoSizeTextSizesInPx.length; 1977 mHasPresetAutoSizeValues = sizesLength > 0; 1978 if (mHasPresetAutoSizeValues) { 1979 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 1980 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 1981 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 1982 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1983 } 1984 return mHasPresetAutoSizeValues; 1985 } 1986 1987 /** 1988 * If all params are valid then save the auto-size configuration. 1989 * 1990 * @throws IllegalArgumentException if any of the params are invalid 1991 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)1992 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 1993 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 1994 // First validate. 1995 if (autoSizeMinTextSizeInPx <= 0) { 1996 throw new IllegalArgumentException("Minimum auto-size text size (" 1997 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 1998 } 1999 2000 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2001 throw new IllegalArgumentException("Maximum auto-size text size (" 2002 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2003 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2004 } 2005 2006 if (autoSizeStepGranularityInPx <= 0) { 2007 throw new IllegalArgumentException("The auto-size step granularity (" 2008 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2009 } 2010 2011 // All good, persist the configuration. 2012 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2013 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2014 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2015 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2016 mHasPresetAutoSizeValues = false; 2017 } 2018 clearAutoSizeConfiguration()2019 private void clearAutoSizeConfiguration() { 2020 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2021 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2022 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2023 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2024 mAutoSizeTextSizesInPx = EmptyArray.INT; 2025 mNeedsAutoSizeText = false; 2026 } 2027 2028 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2029 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2030 final int presetValuesLength = presetValues.length; 2031 if (presetValuesLength == 0) { 2032 return presetValues; 2033 } 2034 Arrays.sort(presetValues); 2035 2036 final IntArray uniqueValidSizes = new IntArray(); 2037 for (int i = 0; i < presetValuesLength; i++) { 2038 final int currentPresetValue = presetValues[i]; 2039 2040 if (currentPresetValue > 0 2041 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2042 uniqueValidSizes.add(currentPresetValue); 2043 } 2044 } 2045 2046 return presetValuesLength == uniqueValidSizes.size() 2047 ? presetValues 2048 : uniqueValidSizes.toArray(); 2049 } 2050 setupAutoSizeText()2051 private boolean setupAutoSizeText() { 2052 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2053 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2054 // not have a predefined set of sizes or if the current sizes array is empty. 2055 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2056 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2057 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2058 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2059 for (int i = 0; i < autoSizeValuesLength; i++) { 2060 autoSizeTextSizesInPx[i] = Math.round( 2061 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2062 } 2063 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2064 } 2065 2066 mNeedsAutoSizeText = true; 2067 } else { 2068 mNeedsAutoSizeText = false; 2069 } 2070 2071 return mNeedsAutoSizeText; 2072 } 2073 parseDimensionArray(TypedArray dimens)2074 private int[] parseDimensionArray(TypedArray dimens) { 2075 if (dimens == null) { 2076 return null; 2077 } 2078 int[] result = new int[dimens.length()]; 2079 for (int i = 0; i < result.length; i++) { 2080 result[i] = dimens.getDimensionPixelSize(i, 0); 2081 } 2082 return result; 2083 } 2084 2085 /** 2086 * @hide 2087 */ 2088 @Override onActivityResult(int requestCode, int resultCode, Intent data)2089 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2090 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2091 if (resultCode == Activity.RESULT_OK && data != null) { 2092 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2093 if (result != null) { 2094 if (isTextEditable()) { 2095 replaceSelectionWithText(result); 2096 if (mEditor != null) { 2097 mEditor.refreshTextActionMode(); 2098 } 2099 } else { 2100 if (result.length() > 0) { 2101 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2102 .show(); 2103 } 2104 } 2105 } 2106 } else if (mSpannable != null) { 2107 // Reset the selection. 2108 Selection.setSelection(mSpannable, getSelectionEnd()); 2109 } 2110 } 2111 } 2112 2113 /** 2114 * Sets the Typeface taking into account the given attributes. 2115 * 2116 * @param typeface a typeface 2117 * @param familyName family name string, e.g. "serif" 2118 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2119 * @param style a typeface style 2120 * @param weight a weight value for the Typeface or -1 if not specified. 2121 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2122 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2123 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2124 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2125 if (typeface == null && familyName != null) { 2126 // Lookup normal Typeface from system font map. 2127 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2128 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2129 } else if (typeface != null) { 2130 resolveStyleAndSetTypeface(typeface, style, weight); 2131 } else { // both typeface and familyName is null. 2132 switch (typefaceIndex) { 2133 case SANS: 2134 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2135 break; 2136 case SERIF: 2137 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2138 break; 2139 case MONOSPACE: 2140 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2141 break; 2142 case DEFAULT_TYPEFACE: 2143 default: 2144 resolveStyleAndSetTypeface(null, style, weight); 2145 break; 2146 } 2147 } 2148 } 2149 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2150 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2151 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2152 if (weight >= 0) { 2153 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2154 final boolean italic = (style & Typeface.ITALIC) != 0; 2155 setTypeface(Typeface.create(typeface, weight, italic)); 2156 } else { 2157 setTypeface(typeface, style); 2158 } 2159 } 2160 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2161 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2162 boolean hasRelativeDrawables = (start != null) || (end != null); 2163 if (hasRelativeDrawables) { 2164 Drawables dr = mDrawables; 2165 if (dr == null) { 2166 mDrawables = dr = new Drawables(getContext()); 2167 } 2168 mDrawables.mOverride = true; 2169 final Rect compoundRect = dr.mCompoundRect; 2170 int[] state = getDrawableState(); 2171 if (start != null) { 2172 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2173 start.setState(state); 2174 start.copyBounds(compoundRect); 2175 start.setCallback(this); 2176 2177 dr.mDrawableStart = start; 2178 dr.mDrawableSizeStart = compoundRect.width(); 2179 dr.mDrawableHeightStart = compoundRect.height(); 2180 } else { 2181 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2182 } 2183 if (end != null) { 2184 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2185 end.setState(state); 2186 end.copyBounds(compoundRect); 2187 end.setCallback(this); 2188 2189 dr.mDrawableEnd = end; 2190 dr.mDrawableSizeEnd = compoundRect.width(); 2191 dr.mDrawableHeightEnd = compoundRect.height(); 2192 } else { 2193 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2194 } 2195 resetResolvedDrawables(); 2196 resolveDrawables(); 2197 applyCompoundDrawableTint(); 2198 } 2199 } 2200 2201 @android.view.RemotableViewMethod 2202 @Override setEnabled(boolean enabled)2203 public void setEnabled(boolean enabled) { 2204 if (enabled == isEnabled()) { 2205 return; 2206 } 2207 2208 if (!enabled) { 2209 // Hide the soft input if the currently active TextView is disabled 2210 InputMethodManager imm = getInputMethodManager(); 2211 if (imm != null && imm.isActive(this)) { 2212 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2213 } 2214 } 2215 2216 super.setEnabled(enabled); 2217 2218 if (enabled) { 2219 // Make sure IME is updated with current editor info. 2220 InputMethodManager imm = getInputMethodManager(); 2221 if (imm != null) imm.restartInput(this); 2222 } 2223 2224 // Will change text color 2225 if (mEditor != null) { 2226 mEditor.invalidateTextDisplayList(); 2227 mEditor.prepareCursorControllers(); 2228 2229 // start or stop the cursor blinking as appropriate 2230 mEditor.makeBlink(); 2231 } 2232 } 2233 2234 /** 2235 * Sets the typeface and style in which the text should be displayed, 2236 * and turns on the fake bold and italic bits in the Paint if the 2237 * Typeface that you provided does not have all the bits in the 2238 * style that you specified. 2239 * 2240 * @attr ref android.R.styleable#TextView_typeface 2241 * @attr ref android.R.styleable#TextView_textStyle 2242 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2243 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2244 if (style > 0) { 2245 if (tf == null) { 2246 tf = Typeface.defaultFromStyle(style); 2247 } else { 2248 tf = Typeface.create(tf, style); 2249 } 2250 2251 setTypeface(tf); 2252 // now compute what (if any) algorithmic styling is needed 2253 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2254 int need = style & ~typefaceStyle; 2255 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2256 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2257 } else { 2258 mTextPaint.setFakeBoldText(false); 2259 mTextPaint.setTextSkewX(0); 2260 setTypeface(tf); 2261 } 2262 } 2263 2264 /** 2265 * Subclasses override this to specify that they have a KeyListener 2266 * by default even if not specifically called for in the XML options. 2267 */ getDefaultEditable()2268 protected boolean getDefaultEditable() { 2269 return false; 2270 } 2271 2272 /** 2273 * Subclasses override this to specify a default movement method. 2274 */ getDefaultMovementMethod()2275 protected MovementMethod getDefaultMovementMethod() { 2276 return null; 2277 } 2278 2279 /** 2280 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2281 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2282 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2283 * the return value from this method to Spannable or Editable, respectively. 2284 * 2285 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2286 * should make your own copy first.</p> 2287 * 2288 * @return The text displayed by the text view. 2289 * @attr ref android.R.styleable#TextView_text 2290 */ 2291 @ViewDebug.CapturedViewProperty 2292 @InspectableProperty getText()2293 public CharSequence getText() { 2294 return mText; 2295 } 2296 2297 /** 2298 * Returns the length, in characters, of the text managed by this TextView 2299 * @return The length of the text managed by the TextView in characters. 2300 */ length()2301 public int length() { 2302 return mText.length(); 2303 } 2304 2305 /** 2306 * Return the text that TextView is displaying as an Editable object. If the text is not 2307 * editable, null is returned. 2308 * 2309 * @see #getText 2310 */ getEditableText()2311 public Editable getEditableText() { 2312 return (mText instanceof Editable) ? (Editable) mText : null; 2313 } 2314 2315 /** 2316 * @hide 2317 */ 2318 @VisibleForTesting getTransformed()2319 public CharSequence getTransformed() { 2320 return mTransformed; 2321 } 2322 2323 /** 2324 * Gets the vertical distance between lines of text, in pixels. 2325 * Note that markup within the text can cause individual lines 2326 * to be taller or shorter than this height, and the layout may 2327 * contain additional first-or last-line padding. 2328 * @return The height of one standard line in pixels. 2329 */ 2330 @InspectableProperty getLineHeight()2331 public int getLineHeight() { 2332 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2333 } 2334 2335 /** 2336 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2337 * This value can be null if the text or width has recently changed. 2338 * @return The Layout that is currently being used to display the text. 2339 */ getLayout()2340 public final Layout getLayout() { 2341 return mLayout; 2342 } 2343 2344 /** 2345 * @return the {@link android.text.Layout} that is currently being used to 2346 * display the hint text. This can be null. 2347 */ 2348 @UnsupportedAppUsage getHintLayout()2349 final Layout getHintLayout() { 2350 return mHintLayout; 2351 } 2352 2353 /** 2354 * Retrieve the {@link android.content.UndoManager} that is currently associated 2355 * with this TextView. By default there is no associated UndoManager, so null 2356 * is returned. One can be associated with the TextView through 2357 * {@link #setUndoManager(android.content.UndoManager, String)} 2358 * 2359 * @hide 2360 */ getUndoManager()2361 public final UndoManager getUndoManager() { 2362 // TODO: Consider supporting a global undo manager. 2363 throw new UnsupportedOperationException("not implemented"); 2364 } 2365 2366 2367 /** 2368 * @hide 2369 */ 2370 @VisibleForTesting getEditorForTesting()2371 public final Editor getEditorForTesting() { 2372 return mEditor; 2373 } 2374 2375 /** 2376 * Associate an {@link android.content.UndoManager} with this TextView. Once 2377 * done, all edit operations on the TextView will result in appropriate 2378 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2379 * stack. 2380 * 2381 * @param undoManager The {@link android.content.UndoManager} to associate with 2382 * this TextView, or null to clear any existing association. 2383 * @param tag String tag identifying this particular TextView owner in the 2384 * UndoManager. This is used to keep the correct association with the 2385 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2386 * 2387 * @hide 2388 */ setUndoManager(UndoManager undoManager, String tag)2389 public final void setUndoManager(UndoManager undoManager, String tag) { 2390 // TODO: Consider supporting a global undo manager. An implementation will need to: 2391 // * createEditorIfNeeded() 2392 // * Promote to BufferType.EDITABLE if needed. 2393 // * Update the UndoManager and UndoOwner. 2394 // Likewise it will need to be able to restore the default UndoManager. 2395 throw new UnsupportedOperationException("not implemented"); 2396 } 2397 2398 /** 2399 * Gets the current {@link KeyListener} for the TextView. 2400 * This will frequently be null for non-EditText TextViews. 2401 * @return the current key listener for this TextView. 2402 * 2403 * @attr ref android.R.styleable#TextView_numeric 2404 * @attr ref android.R.styleable#TextView_digits 2405 * @attr ref android.R.styleable#TextView_phoneNumber 2406 * @attr ref android.R.styleable#TextView_inputMethod 2407 * @attr ref android.R.styleable#TextView_capitalize 2408 * @attr ref android.R.styleable#TextView_autoText 2409 */ getKeyListener()2410 public final KeyListener getKeyListener() { 2411 return mEditor == null ? null : mEditor.mKeyListener; 2412 } 2413 2414 /** 2415 * Sets the key listener to be used with this TextView. This can be null 2416 * to disallow user input. Note that this method has significant and 2417 * subtle interactions with soft keyboards and other input method: 2418 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2419 * for important details. Calling this method will replace the current 2420 * content type of the text view with the content type returned by the 2421 * key listener. 2422 * <p> 2423 * Be warned that if you want a TextView with a key listener or movement 2424 * method not to be focusable, or if you want a TextView without a 2425 * key listener or movement method to be focusable, you must call 2426 * {@link #setFocusable} again after calling this to get the focusability 2427 * back the way you want it. 2428 * 2429 * @attr ref android.R.styleable#TextView_numeric 2430 * @attr ref android.R.styleable#TextView_digits 2431 * @attr ref android.R.styleable#TextView_phoneNumber 2432 * @attr ref android.R.styleable#TextView_inputMethod 2433 * @attr ref android.R.styleable#TextView_capitalize 2434 * @attr ref android.R.styleable#TextView_autoText 2435 */ setKeyListener(KeyListener input)2436 public void setKeyListener(KeyListener input) { 2437 mListenerChanged = true; 2438 setKeyListenerOnly(input); 2439 fixFocusableAndClickableSettings(); 2440 2441 if (input != null) { 2442 createEditorIfNeeded(); 2443 setInputTypeFromEditor(); 2444 } else { 2445 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2446 } 2447 2448 InputMethodManager imm = getInputMethodManager(); 2449 if (imm != null) imm.restartInput(this); 2450 } 2451 setInputTypeFromEditor()2452 private void setInputTypeFromEditor() { 2453 try { 2454 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2455 } catch (IncompatibleClassChangeError e) { 2456 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2457 } 2458 // Change inputType, without affecting transformation. 2459 // No need to applySingleLine since mSingleLine is unchanged. 2460 setInputTypeSingleLine(mSingleLine); 2461 } 2462 setKeyListenerOnly(KeyListener input)2463 private void setKeyListenerOnly(KeyListener input) { 2464 if (mEditor == null && input == null) return; // null is the default value 2465 2466 createEditorIfNeeded(); 2467 if (mEditor.mKeyListener != input) { 2468 mEditor.mKeyListener = input; 2469 if (input != null && !(mText instanceof Editable)) { 2470 setText(mText); 2471 } 2472 2473 setFilters((Editable) mText, mFilters); 2474 } 2475 } 2476 2477 /** 2478 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2479 * which provides positioning, scrolling, and text selection functionality. 2480 * This will frequently be null for non-EditText TextViews. 2481 * @return the movement method being used for this TextView. 2482 * @see android.text.method.MovementMethod 2483 */ getMovementMethod()2484 public final MovementMethod getMovementMethod() { 2485 return mMovement; 2486 } 2487 2488 /** 2489 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2490 * for this TextView. This can be null to disallow using the arrow keys to move the 2491 * cursor or scroll the view. 2492 * <p> 2493 * Be warned that if you want a TextView with a key listener or movement 2494 * method not to be focusable, or if you want a TextView without a 2495 * key listener or movement method to be focusable, you must call 2496 * {@link #setFocusable} again after calling this to get the focusability 2497 * back the way you want it. 2498 */ setMovementMethod(MovementMethod movement)2499 public final void setMovementMethod(MovementMethod movement) { 2500 if (mMovement != movement) { 2501 mMovement = movement; 2502 2503 if (movement != null && mSpannable == null) { 2504 setText(mText); 2505 } 2506 2507 fixFocusableAndClickableSettings(); 2508 2509 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2510 // mMovement 2511 if (mEditor != null) mEditor.prepareCursorControllers(); 2512 } 2513 } 2514 fixFocusableAndClickableSettings()2515 private void fixFocusableAndClickableSettings() { 2516 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2517 setFocusable(FOCUSABLE); 2518 setClickable(true); 2519 setLongClickable(true); 2520 } else { 2521 setFocusable(FOCUSABLE_AUTO); 2522 setClickable(false); 2523 setLongClickable(false); 2524 } 2525 } 2526 2527 /** 2528 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2529 * This is frequently null, except for single-line and password fields. 2530 * @return the current transformation method for this TextView. 2531 * 2532 * @attr ref android.R.styleable#TextView_password 2533 * @attr ref android.R.styleable#TextView_singleLine 2534 */ getTransformationMethod()2535 public final TransformationMethod getTransformationMethod() { 2536 return mTransformation; 2537 } 2538 2539 /** 2540 * Sets the transformation that is applied to the text that this 2541 * TextView is displaying. 2542 * 2543 * @attr ref android.R.styleable#TextView_password 2544 * @attr ref android.R.styleable#TextView_singleLine 2545 */ setTransformationMethod(TransformationMethod method)2546 public final void setTransformationMethod(TransformationMethod method) { 2547 if (method == mTransformation) { 2548 // Avoid the setText() below if the transformation is 2549 // the same. 2550 return; 2551 } 2552 if (mTransformation != null) { 2553 if (mSpannable != null) { 2554 mSpannable.removeSpan(mTransformation); 2555 } 2556 } 2557 2558 mTransformation = method; 2559 2560 if (method instanceof TransformationMethod2) { 2561 TransformationMethod2 method2 = (TransformationMethod2) method; 2562 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2563 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2564 } else { 2565 mAllowTransformationLengthChange = false; 2566 } 2567 2568 setText(mText); 2569 2570 if (hasPasswordTransformationMethod()) { 2571 notifyViewAccessibilityStateChangedIfNeeded( 2572 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2573 } 2574 2575 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2576 // getTextDirectionHeuristic, needs reset 2577 mTextDir = getTextDirectionHeuristic(); 2578 } 2579 2580 /** 2581 * Returns the top padding of the view, plus space for the top 2582 * Drawable if any. 2583 */ getCompoundPaddingTop()2584 public int getCompoundPaddingTop() { 2585 final Drawables dr = mDrawables; 2586 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2587 return mPaddingTop; 2588 } else { 2589 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2590 } 2591 } 2592 2593 /** 2594 * Returns the bottom padding of the view, plus space for the bottom 2595 * Drawable if any. 2596 */ getCompoundPaddingBottom()2597 public int getCompoundPaddingBottom() { 2598 final Drawables dr = mDrawables; 2599 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2600 return mPaddingBottom; 2601 } else { 2602 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2603 } 2604 } 2605 2606 /** 2607 * Returns the left padding of the view, plus space for the left 2608 * Drawable if any. 2609 */ getCompoundPaddingLeft()2610 public int getCompoundPaddingLeft() { 2611 final Drawables dr = mDrawables; 2612 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2613 return mPaddingLeft; 2614 } else { 2615 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2616 } 2617 } 2618 2619 /** 2620 * Returns the right padding of the view, plus space for the right 2621 * Drawable if any. 2622 */ getCompoundPaddingRight()2623 public int getCompoundPaddingRight() { 2624 final Drawables dr = mDrawables; 2625 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2626 return mPaddingRight; 2627 } else { 2628 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2629 } 2630 } 2631 2632 /** 2633 * Returns the start padding of the view, plus space for the start 2634 * Drawable if any. 2635 */ getCompoundPaddingStart()2636 public int getCompoundPaddingStart() { 2637 resolveDrawables(); 2638 switch(getLayoutDirection()) { 2639 default: 2640 case LAYOUT_DIRECTION_LTR: 2641 return getCompoundPaddingLeft(); 2642 case LAYOUT_DIRECTION_RTL: 2643 return getCompoundPaddingRight(); 2644 } 2645 } 2646 2647 /** 2648 * Returns the end padding of the view, plus space for the end 2649 * Drawable if any. 2650 */ getCompoundPaddingEnd()2651 public int getCompoundPaddingEnd() { 2652 resolveDrawables(); 2653 switch(getLayoutDirection()) { 2654 default: 2655 case LAYOUT_DIRECTION_LTR: 2656 return getCompoundPaddingRight(); 2657 case LAYOUT_DIRECTION_RTL: 2658 return getCompoundPaddingLeft(); 2659 } 2660 } 2661 2662 /** 2663 * Returns the extended top padding of the view, including both the 2664 * top Drawable if any and any extra space to keep more than maxLines 2665 * of text from showing. It is only valid to call this after measuring. 2666 */ getExtendedPaddingTop()2667 public int getExtendedPaddingTop() { 2668 if (mMaxMode != LINES) { 2669 return getCompoundPaddingTop(); 2670 } 2671 2672 if (mLayout == null) { 2673 assumeLayout(); 2674 } 2675 2676 if (mLayout.getLineCount() <= mMaximum) { 2677 return getCompoundPaddingTop(); 2678 } 2679 2680 int top = getCompoundPaddingTop(); 2681 int bottom = getCompoundPaddingBottom(); 2682 int viewht = getHeight() - top - bottom; 2683 int layoutht = mLayout.getLineTop(mMaximum); 2684 2685 if (layoutht >= viewht) { 2686 return top; 2687 } 2688 2689 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2690 if (gravity == Gravity.TOP) { 2691 return top; 2692 } else if (gravity == Gravity.BOTTOM) { 2693 return top + viewht - layoutht; 2694 } else { // (gravity == Gravity.CENTER_VERTICAL) 2695 return top + (viewht - layoutht) / 2; 2696 } 2697 } 2698 2699 /** 2700 * Returns the extended bottom padding of the view, including both the 2701 * bottom Drawable if any and any extra space to keep more than maxLines 2702 * of text from showing. It is only valid to call this after measuring. 2703 */ getExtendedPaddingBottom()2704 public int getExtendedPaddingBottom() { 2705 if (mMaxMode != LINES) { 2706 return getCompoundPaddingBottom(); 2707 } 2708 2709 if (mLayout == null) { 2710 assumeLayout(); 2711 } 2712 2713 if (mLayout.getLineCount() <= mMaximum) { 2714 return getCompoundPaddingBottom(); 2715 } 2716 2717 int top = getCompoundPaddingTop(); 2718 int bottom = getCompoundPaddingBottom(); 2719 int viewht = getHeight() - top - bottom; 2720 int layoutht = mLayout.getLineTop(mMaximum); 2721 2722 if (layoutht >= viewht) { 2723 return bottom; 2724 } 2725 2726 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2727 if (gravity == Gravity.TOP) { 2728 return bottom + viewht - layoutht; 2729 } else if (gravity == Gravity.BOTTOM) { 2730 return bottom; 2731 } else { // (gravity == Gravity.CENTER_VERTICAL) 2732 return bottom + (viewht - layoutht) / 2; 2733 } 2734 } 2735 2736 /** 2737 * Returns the total left padding of the view, including the left 2738 * Drawable if any. 2739 */ getTotalPaddingLeft()2740 public int getTotalPaddingLeft() { 2741 return getCompoundPaddingLeft(); 2742 } 2743 2744 /** 2745 * Returns the total right padding of the view, including the right 2746 * Drawable if any. 2747 */ getTotalPaddingRight()2748 public int getTotalPaddingRight() { 2749 return getCompoundPaddingRight(); 2750 } 2751 2752 /** 2753 * Returns the total start padding of the view, including the start 2754 * Drawable if any. 2755 */ getTotalPaddingStart()2756 public int getTotalPaddingStart() { 2757 return getCompoundPaddingStart(); 2758 } 2759 2760 /** 2761 * Returns the total end padding of the view, including the end 2762 * Drawable if any. 2763 */ getTotalPaddingEnd()2764 public int getTotalPaddingEnd() { 2765 return getCompoundPaddingEnd(); 2766 } 2767 2768 /** 2769 * Returns the total top padding of the view, including the top 2770 * Drawable if any, the extra space to keep more than maxLines 2771 * from showing, and the vertical offset for gravity, if any. 2772 */ getTotalPaddingTop()2773 public int getTotalPaddingTop() { 2774 return getExtendedPaddingTop() + getVerticalOffset(true); 2775 } 2776 2777 /** 2778 * Returns the total bottom padding of the view, including the bottom 2779 * Drawable if any, the extra space to keep more than maxLines 2780 * from showing, and the vertical offset for gravity, if any. 2781 */ getTotalPaddingBottom()2782 public int getTotalPaddingBottom() { 2783 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2784 } 2785 2786 /** 2787 * Sets the Drawables (if any) to appear to the left of, above, to the 2788 * right of, and below the text. Use {@code null} if you do not want a 2789 * Drawable there. The Drawables must already have had 2790 * {@link Drawable#setBounds} called. 2791 * <p> 2792 * Calling this method will overwrite any Drawables previously set using 2793 * {@link #setCompoundDrawablesRelative} or related methods. 2794 * 2795 * @attr ref android.R.styleable#TextView_drawableLeft 2796 * @attr ref android.R.styleable#TextView_drawableTop 2797 * @attr ref android.R.styleable#TextView_drawableRight 2798 * @attr ref android.R.styleable#TextView_drawableBottom 2799 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2800 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2801 @Nullable Drawable right, @Nullable Drawable bottom) { 2802 Drawables dr = mDrawables; 2803 2804 // We're switching to absolute, discard relative. 2805 if (dr != null) { 2806 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2807 dr.mDrawableStart = null; 2808 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2809 dr.mDrawableEnd = null; 2810 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2811 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2812 } 2813 2814 final boolean drawables = left != null || top != null || right != null || bottom != null; 2815 if (!drawables) { 2816 // Clearing drawables... can we free the data structure? 2817 if (dr != null) { 2818 if (!dr.hasMetadata()) { 2819 mDrawables = null; 2820 } else { 2821 // We need to retain the last set padding, so just clear 2822 // out all of the fields in the existing structure. 2823 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2824 if (dr.mShowing[i] != null) { 2825 dr.mShowing[i].setCallback(null); 2826 } 2827 dr.mShowing[i] = null; 2828 } 2829 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2830 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2831 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2832 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2833 } 2834 } 2835 } else { 2836 if (dr == null) { 2837 mDrawables = dr = new Drawables(getContext()); 2838 } 2839 2840 mDrawables.mOverride = false; 2841 2842 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2843 dr.mShowing[Drawables.LEFT].setCallback(null); 2844 } 2845 dr.mShowing[Drawables.LEFT] = left; 2846 2847 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2848 dr.mShowing[Drawables.TOP].setCallback(null); 2849 } 2850 dr.mShowing[Drawables.TOP] = top; 2851 2852 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2853 dr.mShowing[Drawables.RIGHT].setCallback(null); 2854 } 2855 dr.mShowing[Drawables.RIGHT] = right; 2856 2857 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2858 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2859 } 2860 dr.mShowing[Drawables.BOTTOM] = bottom; 2861 2862 final Rect compoundRect = dr.mCompoundRect; 2863 int[] state; 2864 2865 state = getDrawableState(); 2866 2867 if (left != null) { 2868 left.setState(state); 2869 left.copyBounds(compoundRect); 2870 left.setCallback(this); 2871 dr.mDrawableSizeLeft = compoundRect.width(); 2872 dr.mDrawableHeightLeft = compoundRect.height(); 2873 } else { 2874 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2875 } 2876 2877 if (right != null) { 2878 right.setState(state); 2879 right.copyBounds(compoundRect); 2880 right.setCallback(this); 2881 dr.mDrawableSizeRight = compoundRect.width(); 2882 dr.mDrawableHeightRight = compoundRect.height(); 2883 } else { 2884 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2885 } 2886 2887 if (top != null) { 2888 top.setState(state); 2889 top.copyBounds(compoundRect); 2890 top.setCallback(this); 2891 dr.mDrawableSizeTop = compoundRect.height(); 2892 dr.mDrawableWidthTop = compoundRect.width(); 2893 } else { 2894 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2895 } 2896 2897 if (bottom != null) { 2898 bottom.setState(state); 2899 bottom.copyBounds(compoundRect); 2900 bottom.setCallback(this); 2901 dr.mDrawableSizeBottom = compoundRect.height(); 2902 dr.mDrawableWidthBottom = compoundRect.width(); 2903 } else { 2904 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2905 } 2906 } 2907 2908 // Save initial left/right drawables 2909 if (dr != null) { 2910 dr.mDrawableLeftInitial = left; 2911 dr.mDrawableRightInitial = right; 2912 } 2913 2914 resetResolvedDrawables(); 2915 resolveDrawables(); 2916 applyCompoundDrawableTint(); 2917 invalidate(); 2918 requestLayout(); 2919 } 2920 2921 /** 2922 * Sets the Drawables (if any) to appear to the left of, above, to the 2923 * right of, and below the text. Use 0 if you do not want a Drawable there. 2924 * The Drawables' bounds will be set to their intrinsic bounds. 2925 * <p> 2926 * Calling this method will overwrite any Drawables previously set using 2927 * {@link #setCompoundDrawablesRelative} or related methods. 2928 * 2929 * @param left Resource identifier of the left Drawable. 2930 * @param top Resource identifier of the top Drawable. 2931 * @param right Resource identifier of the right Drawable. 2932 * @param bottom Resource identifier of the bottom Drawable. 2933 * 2934 * @attr ref android.R.styleable#TextView_drawableLeft 2935 * @attr ref android.R.styleable#TextView_drawableTop 2936 * @attr ref android.R.styleable#TextView_drawableRight 2937 * @attr ref android.R.styleable#TextView_drawableBottom 2938 */ 2939 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2940 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 2941 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 2942 final Context context = getContext(); 2943 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2944 top != 0 ? context.getDrawable(top) : null, 2945 right != 0 ? context.getDrawable(right) : null, 2946 bottom != 0 ? context.getDrawable(bottom) : null); 2947 } 2948 2949 /** 2950 * Sets the Drawables (if any) to appear to the left of, above, to the 2951 * right of, and below the text. Use {@code null} if you do not want a 2952 * Drawable there. The Drawables' bounds will be set to their intrinsic 2953 * bounds. 2954 * <p> 2955 * Calling this method will overwrite any Drawables previously set using 2956 * {@link #setCompoundDrawablesRelative} or related methods. 2957 * 2958 * @attr ref android.R.styleable#TextView_drawableLeft 2959 * @attr ref android.R.styleable#TextView_drawableTop 2960 * @attr ref android.R.styleable#TextView_drawableRight 2961 * @attr ref android.R.styleable#TextView_drawableBottom 2962 */ 2963 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2964 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 2965 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 2966 2967 if (left != null) { 2968 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 2969 } 2970 if (right != null) { 2971 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 2972 } 2973 if (top != null) { 2974 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2975 } 2976 if (bottom != null) { 2977 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2978 } 2979 setCompoundDrawables(left, top, right, bottom); 2980 } 2981 2982 /** 2983 * Sets the Drawables (if any) to appear to the start of, above, to the end 2984 * of, and below the text. Use {@code null} if you do not want a Drawable 2985 * there. The Drawables must already have had {@link Drawable#setBounds} 2986 * called. 2987 * <p> 2988 * Calling this method will overwrite any Drawables previously set using 2989 * {@link #setCompoundDrawables} or related methods. 2990 * 2991 * @attr ref android.R.styleable#TextView_drawableStart 2992 * @attr ref android.R.styleable#TextView_drawableTop 2993 * @attr ref android.R.styleable#TextView_drawableEnd 2994 * @attr ref android.R.styleable#TextView_drawableBottom 2995 */ 2996 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)2997 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 2998 @Nullable Drawable end, @Nullable Drawable bottom) { 2999 Drawables dr = mDrawables; 3000 3001 // We're switching to relative, discard absolute. 3002 if (dr != null) { 3003 if (dr.mShowing[Drawables.LEFT] != null) { 3004 dr.mShowing[Drawables.LEFT].setCallback(null); 3005 } 3006 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3007 if (dr.mShowing[Drawables.RIGHT] != null) { 3008 dr.mShowing[Drawables.RIGHT].setCallback(null); 3009 } 3010 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3011 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3012 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3013 } 3014 3015 final boolean drawables = start != null || top != null 3016 || end != null || bottom != null; 3017 3018 if (!drawables) { 3019 // Clearing drawables... can we free the data structure? 3020 if (dr != null) { 3021 if (!dr.hasMetadata()) { 3022 mDrawables = null; 3023 } else { 3024 // We need to retain the last set padding, so just clear 3025 // out all of the fields in the existing structure. 3026 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3027 dr.mDrawableStart = null; 3028 if (dr.mShowing[Drawables.TOP] != null) { 3029 dr.mShowing[Drawables.TOP].setCallback(null); 3030 } 3031 dr.mShowing[Drawables.TOP] = null; 3032 if (dr.mDrawableEnd != null) { 3033 dr.mDrawableEnd.setCallback(null); 3034 } 3035 dr.mDrawableEnd = null; 3036 if (dr.mShowing[Drawables.BOTTOM] != null) { 3037 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3038 } 3039 dr.mShowing[Drawables.BOTTOM] = null; 3040 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3041 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3042 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3043 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3044 } 3045 } 3046 } else { 3047 if (dr == null) { 3048 mDrawables = dr = new Drawables(getContext()); 3049 } 3050 3051 mDrawables.mOverride = true; 3052 3053 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3054 dr.mDrawableStart.setCallback(null); 3055 } 3056 dr.mDrawableStart = start; 3057 3058 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3059 dr.mShowing[Drawables.TOP].setCallback(null); 3060 } 3061 dr.mShowing[Drawables.TOP] = top; 3062 3063 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3064 dr.mDrawableEnd.setCallback(null); 3065 } 3066 dr.mDrawableEnd = end; 3067 3068 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3069 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3070 } 3071 dr.mShowing[Drawables.BOTTOM] = bottom; 3072 3073 final Rect compoundRect = dr.mCompoundRect; 3074 int[] state; 3075 3076 state = getDrawableState(); 3077 3078 if (start != null) { 3079 start.setState(state); 3080 start.copyBounds(compoundRect); 3081 start.setCallback(this); 3082 dr.mDrawableSizeStart = compoundRect.width(); 3083 dr.mDrawableHeightStart = compoundRect.height(); 3084 } else { 3085 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3086 } 3087 3088 if (end != null) { 3089 end.setState(state); 3090 end.copyBounds(compoundRect); 3091 end.setCallback(this); 3092 dr.mDrawableSizeEnd = compoundRect.width(); 3093 dr.mDrawableHeightEnd = compoundRect.height(); 3094 } else { 3095 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3096 } 3097 3098 if (top != null) { 3099 top.setState(state); 3100 top.copyBounds(compoundRect); 3101 top.setCallback(this); 3102 dr.mDrawableSizeTop = compoundRect.height(); 3103 dr.mDrawableWidthTop = compoundRect.width(); 3104 } else { 3105 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3106 } 3107 3108 if (bottom != null) { 3109 bottom.setState(state); 3110 bottom.copyBounds(compoundRect); 3111 bottom.setCallback(this); 3112 dr.mDrawableSizeBottom = compoundRect.height(); 3113 dr.mDrawableWidthBottom = compoundRect.width(); 3114 } else { 3115 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3116 } 3117 } 3118 3119 resetResolvedDrawables(); 3120 resolveDrawables(); 3121 invalidate(); 3122 requestLayout(); 3123 } 3124 3125 /** 3126 * Sets the Drawables (if any) to appear to the start of, above, to the end 3127 * of, and below the text. Use 0 if you do not want a Drawable there. The 3128 * Drawables' bounds will be set to their intrinsic bounds. 3129 * <p> 3130 * Calling this method will overwrite any Drawables previously set using 3131 * {@link #setCompoundDrawables} or related methods. 3132 * 3133 * @param start Resource identifier of the start Drawable. 3134 * @param top Resource identifier of the top Drawable. 3135 * @param end Resource identifier of the end Drawable. 3136 * @param bottom Resource identifier of the bottom Drawable. 3137 * 3138 * @attr ref android.R.styleable#TextView_drawableStart 3139 * @attr ref android.R.styleable#TextView_drawableTop 3140 * @attr ref android.R.styleable#TextView_drawableEnd 3141 * @attr ref android.R.styleable#TextView_drawableBottom 3142 */ 3143 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3144 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3145 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3146 final Context context = getContext(); 3147 setCompoundDrawablesRelativeWithIntrinsicBounds( 3148 start != 0 ? context.getDrawable(start) : null, 3149 top != 0 ? context.getDrawable(top) : null, 3150 end != 0 ? context.getDrawable(end) : null, 3151 bottom != 0 ? context.getDrawable(bottom) : null); 3152 } 3153 3154 /** 3155 * Sets the Drawables (if any) to appear to the start of, above, to the end 3156 * of, and below the text. Use {@code null} if you do not want a Drawable 3157 * there. The Drawables' bounds will be set to their intrinsic bounds. 3158 * <p> 3159 * Calling this method will overwrite any Drawables previously set using 3160 * {@link #setCompoundDrawables} or related methods. 3161 * 3162 * @attr ref android.R.styleable#TextView_drawableStart 3163 * @attr ref android.R.styleable#TextView_drawableTop 3164 * @attr ref android.R.styleable#TextView_drawableEnd 3165 * @attr ref android.R.styleable#TextView_drawableBottom 3166 */ 3167 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3168 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3169 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3170 3171 if (start != null) { 3172 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3173 } 3174 if (end != null) { 3175 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3176 } 3177 if (top != null) { 3178 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3179 } 3180 if (bottom != null) { 3181 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3182 } 3183 setCompoundDrawablesRelative(start, top, end, bottom); 3184 } 3185 3186 /** 3187 * Returns drawables for the left, top, right, and bottom borders. 3188 * 3189 * @attr ref android.R.styleable#TextView_drawableLeft 3190 * @attr ref android.R.styleable#TextView_drawableTop 3191 * @attr ref android.R.styleable#TextView_drawableRight 3192 * @attr ref android.R.styleable#TextView_drawableBottom 3193 */ 3194 @NonNull getCompoundDrawables()3195 public Drawable[] getCompoundDrawables() { 3196 final Drawables dr = mDrawables; 3197 if (dr != null) { 3198 return dr.mShowing.clone(); 3199 } else { 3200 return new Drawable[] { null, null, null, null }; 3201 } 3202 } 3203 3204 /** 3205 * Returns drawables for the start, top, end, and bottom borders. 3206 * 3207 * @attr ref android.R.styleable#TextView_drawableStart 3208 * @attr ref android.R.styleable#TextView_drawableTop 3209 * @attr ref android.R.styleable#TextView_drawableEnd 3210 * @attr ref android.R.styleable#TextView_drawableBottom 3211 */ 3212 @NonNull getCompoundDrawablesRelative()3213 public Drawable[] getCompoundDrawablesRelative() { 3214 final Drawables dr = mDrawables; 3215 if (dr != null) { 3216 return new Drawable[] { 3217 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3218 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3219 }; 3220 } else { 3221 return new Drawable[] { null, null, null, null }; 3222 } 3223 } 3224 3225 /** 3226 * Sets the size of the padding between the compound drawables and 3227 * the text. 3228 * 3229 * @attr ref android.R.styleable#TextView_drawablePadding 3230 */ 3231 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3232 public void setCompoundDrawablePadding(int pad) { 3233 Drawables dr = mDrawables; 3234 if (pad == 0) { 3235 if (dr != null) { 3236 dr.mDrawablePadding = pad; 3237 } 3238 } else { 3239 if (dr == null) { 3240 mDrawables = dr = new Drawables(getContext()); 3241 } 3242 dr.mDrawablePadding = pad; 3243 } 3244 3245 invalidate(); 3246 requestLayout(); 3247 } 3248 3249 /** 3250 * Returns the padding between the compound drawables and the text. 3251 * 3252 * @attr ref android.R.styleable#TextView_drawablePadding 3253 */ 3254 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3255 public int getCompoundDrawablePadding() { 3256 final Drawables dr = mDrawables; 3257 return dr != null ? dr.mDrawablePadding : 0; 3258 } 3259 3260 /** 3261 * Applies a tint to the compound drawables. Does not modify the 3262 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 3263 * <p> 3264 * Subsequent calls to 3265 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3266 * and related methods will automatically mutate the drawables and apply 3267 * the specified tint and tint mode using 3268 * {@link Drawable#setTintList(ColorStateList)}. 3269 * 3270 * @param tint the tint to apply, may be {@code null} to clear tint 3271 * 3272 * @attr ref android.R.styleable#TextView_drawableTint 3273 * @see #getCompoundDrawableTintList() 3274 * @see Drawable#setTintList(ColorStateList) 3275 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3276 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3277 if (mDrawables == null) { 3278 mDrawables = new Drawables(getContext()); 3279 } 3280 mDrawables.mTintList = tint; 3281 mDrawables.mHasTint = true; 3282 3283 applyCompoundDrawableTint(); 3284 } 3285 3286 /** 3287 * @return the tint applied to the compound drawables 3288 * @attr ref android.R.styleable#TextView_drawableTint 3289 * @see #setCompoundDrawableTintList(ColorStateList) 3290 */ 3291 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3292 public ColorStateList getCompoundDrawableTintList() { 3293 return mDrawables != null ? mDrawables.mTintList : null; 3294 } 3295 3296 /** 3297 * Specifies the blending mode used to apply the tint specified by 3298 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3299 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3300 * 3301 * @param tintMode the blending mode used to apply the tint, may be 3302 * {@code null} to clear tint 3303 * @attr ref android.R.styleable#TextView_drawableTintMode 3304 * @see #setCompoundDrawableTintList(ColorStateList) 3305 * @see Drawable#setTintMode(PorterDuff.Mode) 3306 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3307 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3308 setCompoundDrawableTintBlendMode(tintMode != null 3309 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3310 } 3311 3312 /** 3313 * Specifies the blending mode used to apply the tint specified by 3314 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3315 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3316 * 3317 * @param blendMode the blending mode used to apply the tint, may be 3318 * {@code null} to clear tint 3319 * @attr ref android.R.styleable#TextView_drawableTintMode 3320 * @see #setCompoundDrawableTintList(ColorStateList) 3321 * @see Drawable#setTintBlendMode(BlendMode) 3322 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3323 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3324 if (mDrawables == null) { 3325 mDrawables = new Drawables(getContext()); 3326 } 3327 mDrawables.mBlendMode = blendMode; 3328 mDrawables.mHasTintMode = true; 3329 3330 applyCompoundDrawableTint(); 3331 } 3332 3333 /** 3334 * Returns the blending mode used to apply the tint to the compound 3335 * drawables, if specified. 3336 * 3337 * @return the blending mode used to apply the tint to the compound 3338 * drawables 3339 * @attr ref android.R.styleable#TextView_drawableTintMode 3340 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3341 * 3342 */ 3343 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3344 public PorterDuff.Mode getCompoundDrawableTintMode() { 3345 BlendMode mode = getCompoundDrawableTintBlendMode(); 3346 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3347 } 3348 3349 /** 3350 * Returns the blending mode used to apply the tint to the compound 3351 * drawables, if specified. 3352 * 3353 * @return the blending mode used to apply the tint to the compound 3354 * drawables 3355 * @attr ref android.R.styleable#TextView_drawableTintMode 3356 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3357 */ 3358 @InspectableProperty(name = "drawableBlendMode", 3359 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3360 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3361 return mDrawables != null ? mDrawables.mBlendMode : null; 3362 } 3363 applyCompoundDrawableTint()3364 private void applyCompoundDrawableTint() { 3365 if (mDrawables == null) { 3366 return; 3367 } 3368 3369 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3370 final ColorStateList tintList = mDrawables.mTintList; 3371 final BlendMode blendMode = mDrawables.mBlendMode; 3372 final boolean hasTint = mDrawables.mHasTint; 3373 final boolean hasTintMode = mDrawables.mHasTintMode; 3374 final int[] state = getDrawableState(); 3375 3376 for (Drawable dr : mDrawables.mShowing) { 3377 if (dr == null) { 3378 continue; 3379 } 3380 3381 if (dr == mDrawables.mDrawableError) { 3382 // From a developer's perspective, the error drawable isn't 3383 // a compound drawable. Don't apply the generic compound 3384 // drawable tint to it. 3385 continue; 3386 } 3387 3388 dr.mutate(); 3389 3390 if (hasTint) { 3391 dr.setTintList(tintList); 3392 } 3393 3394 if (hasTintMode) { 3395 dr.setTintBlendMode(blendMode); 3396 } 3397 3398 // The drawable (or one of its children) may not have been 3399 // stateful before applying the tint, so let's try again. 3400 if (dr.isStateful()) { 3401 dr.setState(state); 3402 } 3403 } 3404 } 3405 } 3406 3407 /** 3408 * @inheritDoc 3409 * 3410 * @see #setFirstBaselineToTopHeight(int) 3411 * @see #setLastBaselineToBottomHeight(int) 3412 */ 3413 @Override setPadding(int left, int top, int right, int bottom)3414 public void setPadding(int left, int top, int right, int bottom) { 3415 if (left != mPaddingLeft 3416 || right != mPaddingRight 3417 || top != mPaddingTop 3418 || bottom != mPaddingBottom) { 3419 nullLayouts(); 3420 } 3421 3422 // the super call will requestLayout() 3423 super.setPadding(left, top, right, bottom); 3424 invalidate(); 3425 } 3426 3427 /** 3428 * @inheritDoc 3429 * 3430 * @see #setFirstBaselineToTopHeight(int) 3431 * @see #setLastBaselineToBottomHeight(int) 3432 */ 3433 @Override setPaddingRelative(int start, int top, int end, int bottom)3434 public void setPaddingRelative(int start, int top, int end, int bottom) { 3435 if (start != getPaddingStart() 3436 || end != getPaddingEnd() 3437 || top != mPaddingTop 3438 || bottom != mPaddingBottom) { 3439 nullLayouts(); 3440 } 3441 3442 // the super call will requestLayout() 3443 super.setPaddingRelative(start, top, end, bottom); 3444 invalidate(); 3445 } 3446 3447 /** 3448 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3449 * the distance between the top of the TextView and first line's baseline. 3450 * <p> 3451 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3452 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3453 * 3454 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3455 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3456 * Moreover since this function sets the top padding, if the height of the TextView is less than 3457 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3458 * down and bottom will be clipped. 3459 * 3460 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3461 * in pixels 3462 * 3463 * @see #getFirstBaselineToTopHeight() 3464 * @see #setLastBaselineToBottomHeight(int) 3465 * @see #setPadding(int, int, int, int) 3466 * @see #setPaddingRelative(int, int, int, int) 3467 * 3468 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3469 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3470 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3471 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3472 3473 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3474 final int fontMetricsTop; 3475 if (getIncludeFontPadding()) { 3476 fontMetricsTop = fontMetrics.top; 3477 } else { 3478 fontMetricsTop = fontMetrics.ascent; 3479 } 3480 3481 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3482 // in settings). At the moment, we don't. 3483 3484 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3485 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3486 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3487 } 3488 } 3489 3490 /** 3491 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3492 * the distance between the bottom of the TextView and the last line's baseline. 3493 * <p> 3494 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3495 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3496 * 3497 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3498 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3499 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3500 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3501 * clipped. 3502 * 3503 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3504 * in pixels 3505 * 3506 * @see #getLastBaselineToBottomHeight() 3507 * @see #setFirstBaselineToTopHeight(int) 3508 * @see #setPadding(int, int, int, int) 3509 * @see #setPaddingRelative(int, int, int, int) 3510 * 3511 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3512 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3513 public void setLastBaselineToBottomHeight( 3514 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3515 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3516 3517 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3518 final int fontMetricsBottom; 3519 if (getIncludeFontPadding()) { 3520 fontMetricsBottom = fontMetrics.bottom; 3521 } else { 3522 fontMetricsBottom = fontMetrics.descent; 3523 } 3524 3525 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3526 // in settings). At the moment, we don't. 3527 3528 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3529 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3530 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3531 } 3532 } 3533 3534 /** 3535 * Returns the distance between the first text baseline and the top of this TextView. 3536 * 3537 * @see #setFirstBaselineToTopHeight(int) 3538 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3539 */ 3540 @InspectableProperty getFirstBaselineToTopHeight()3541 public int getFirstBaselineToTopHeight() { 3542 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3543 } 3544 3545 /** 3546 * Returns the distance between the last text baseline and the bottom of this TextView. 3547 * 3548 * @see #setLastBaselineToBottomHeight(int) 3549 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3550 */ 3551 @InspectableProperty getLastBaselineToBottomHeight()3552 public int getLastBaselineToBottomHeight() { 3553 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3554 } 3555 3556 /** 3557 * Gets the autolink mask of the text. 3558 * 3559 * See {@link Linkify#ALL} and peers for possible values. 3560 * 3561 * @attr ref android.R.styleable#TextView_autoLink 3562 */ 3563 @InspectableProperty(name = "autoLink", flagMapping = { 3564 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3565 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3566 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3567 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3568 }) getAutoLinkMask()3569 public final int getAutoLinkMask() { 3570 return mAutoLinkMask; 3571 } 3572 3573 /** 3574 * Sets the Drawable corresponding to the selection handle used for 3575 * positioning the cursor within text. The Drawable defaults to the value 3576 * of the textSelectHandle attribute. 3577 * Note that any change applied to the handle Drawable will not be visible 3578 * until the handle is hidden and then drawn again. 3579 * 3580 * @see #setTextSelectHandle(int) 3581 * @attr ref android.R.styleable#TextView_textSelectHandle 3582 */ 3583 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3584 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3585 Preconditions.checkNotNull(textSelectHandle, 3586 "The text select handle should not be null."); 3587 mTextSelectHandle = textSelectHandle; 3588 mTextSelectHandleRes = 0; 3589 if (mEditor != null) { 3590 mEditor.loadHandleDrawables(true /* overwrite */); 3591 } 3592 } 3593 3594 /** 3595 * Sets the Drawable corresponding to the selection handle used for 3596 * positioning the cursor within text. The Drawable defaults to the value 3597 * of the textSelectHandle attribute. 3598 * Note that any change applied to the handle Drawable will not be visible 3599 * until the handle is hidden and then drawn again. 3600 * 3601 * @see #setTextSelectHandle(Drawable) 3602 * @attr ref android.R.styleable#TextView_textSelectHandle 3603 */ 3604 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3605 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3606 Preconditions.checkArgument(textSelectHandle != 0, 3607 "The text select handle should be a valid drawable resource id."); 3608 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3609 } 3610 3611 /** 3612 * Returns the Drawable corresponding to the selection handle used 3613 * for positioning the cursor within text. 3614 * Note that any change applied to the handle Drawable will not be visible 3615 * until the handle is hidden and then drawn again. 3616 * 3617 * @return the text select handle drawable 3618 * 3619 * @see #setTextSelectHandle(Drawable) 3620 * @see #setTextSelectHandle(int) 3621 * @attr ref android.R.styleable#TextView_textSelectHandle 3622 */ getTextSelectHandle()3623 @Nullable public Drawable getTextSelectHandle() { 3624 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3625 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3626 } 3627 return mTextSelectHandle; 3628 } 3629 3630 /** 3631 * Sets the Drawable corresponding to the left handle used 3632 * for selecting text. The Drawable defaults to the value of the 3633 * textSelectHandleLeft attribute. 3634 * Note that any change applied to the handle Drawable will not be visible 3635 * until the handle is hidden and then drawn again. 3636 * 3637 * @see #setTextSelectHandleLeft(int) 3638 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3639 */ 3640 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3641 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3642 Preconditions.checkNotNull(textSelectHandleLeft, 3643 "The left text select handle should not be null."); 3644 mTextSelectHandleLeft = textSelectHandleLeft; 3645 mTextSelectHandleLeftRes = 0; 3646 if (mEditor != null) { 3647 mEditor.loadHandleDrawables(true /* overwrite */); 3648 } 3649 } 3650 3651 /** 3652 * Sets the Drawable corresponding to the left handle used 3653 * for selecting text. The Drawable defaults to the value of the 3654 * textSelectHandleLeft attribute. 3655 * Note that any change applied to the handle Drawable will not be visible 3656 * until the handle is hidden and then drawn again. 3657 * 3658 * @see #setTextSelectHandleLeft(Drawable) 3659 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3660 */ 3661 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3662 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3663 Preconditions.checkArgument(textSelectHandleLeft != 0, 3664 "The text select left handle should be a valid drawable resource id."); 3665 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3666 } 3667 3668 /** 3669 * Returns the Drawable corresponding to the left handle used 3670 * for selecting text. 3671 * Note that any change applied to the handle Drawable will not be visible 3672 * until the handle is hidden and then drawn again. 3673 * 3674 * @return the left text selection handle drawable 3675 * 3676 * @see #setTextSelectHandleLeft(Drawable) 3677 * @see #setTextSelectHandleLeft(int) 3678 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3679 */ getTextSelectHandleLeft()3680 @Nullable public Drawable getTextSelectHandleLeft() { 3681 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3682 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3683 } 3684 return mTextSelectHandleLeft; 3685 } 3686 3687 /** 3688 * Sets the Drawable corresponding to the right handle used 3689 * for selecting text. The Drawable defaults to the value of the 3690 * textSelectHandleRight attribute. 3691 * Note that any change applied to the handle Drawable will not be visible 3692 * until the handle is hidden and then drawn again. 3693 * 3694 * @see #setTextSelectHandleRight(int) 3695 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3696 */ 3697 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3698 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3699 Preconditions.checkNotNull(textSelectHandleRight, 3700 "The right text select handle should not be null."); 3701 mTextSelectHandleRight = textSelectHandleRight; 3702 mTextSelectHandleRightRes = 0; 3703 if (mEditor != null) { 3704 mEditor.loadHandleDrawables(true /* overwrite */); 3705 } 3706 } 3707 3708 /** 3709 * Sets the Drawable corresponding to the right handle used 3710 * for selecting text. The Drawable defaults to the value of the 3711 * textSelectHandleRight attribute. 3712 * Note that any change applied to the handle Drawable will not be visible 3713 * until the handle is hidden and then drawn again. 3714 * 3715 * @see #setTextSelectHandleRight(Drawable) 3716 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3717 */ 3718 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3719 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3720 Preconditions.checkArgument(textSelectHandleRight != 0, 3721 "The text select right handle should be a valid drawable resource id."); 3722 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3723 } 3724 3725 /** 3726 * Returns the Drawable corresponding to the right handle used 3727 * for selecting text. 3728 * Note that any change applied to the handle Drawable will not be visible 3729 * until the handle is hidden and then drawn again. 3730 * 3731 * @return the right text selection handle drawable 3732 * 3733 * @see #setTextSelectHandleRight(Drawable) 3734 * @see #setTextSelectHandleRight(int) 3735 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3736 */ getTextSelectHandleRight()3737 @Nullable public Drawable getTextSelectHandleRight() { 3738 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3739 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3740 } 3741 return mTextSelectHandleRight; 3742 } 3743 3744 /** 3745 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3746 * value of the textCursorDrawable attribute. 3747 * Note that any change applied to the cursor Drawable will not be visible 3748 * until the cursor is hidden and then drawn again. 3749 * 3750 * @see #setTextCursorDrawable(int) 3751 * @attr ref android.R.styleable#TextView_textCursorDrawable 3752 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3753 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3754 mCursorDrawable = textCursorDrawable; 3755 mCursorDrawableRes = 0; 3756 if (mEditor != null) { 3757 mEditor.loadCursorDrawable(); 3758 } 3759 } 3760 3761 /** 3762 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3763 * value of the textCursorDrawable attribute. 3764 * Note that any change applied to the cursor Drawable will not be visible 3765 * until the cursor is hidden and then drawn again. 3766 * 3767 * @see #setTextCursorDrawable(Drawable) 3768 * @attr ref android.R.styleable#TextView_textCursorDrawable 3769 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3770 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3771 setTextCursorDrawable( 3772 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3773 } 3774 3775 /** 3776 * Returns the Drawable corresponding to the text cursor. 3777 * Note that any change applied to the cursor Drawable will not be visible 3778 * until the cursor is hidden and then drawn again. 3779 * 3780 * @return the text cursor drawable 3781 * 3782 * @see #setTextCursorDrawable(Drawable) 3783 * @see #setTextCursorDrawable(int) 3784 * @attr ref android.R.styleable#TextView_textCursorDrawable 3785 */ getTextCursorDrawable()3786 @Nullable public Drawable getTextCursorDrawable() { 3787 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3788 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3789 } 3790 return mCursorDrawable; 3791 } 3792 3793 /** 3794 * Sets the text appearance from the specified style resource. 3795 * <p> 3796 * Use a framework-defined {@code TextAppearance} style like 3797 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3798 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3799 * set of attributes that can be used in a custom style. 3800 * 3801 * @param resId the resource identifier of the style to apply 3802 * @attr ref android.R.styleable#TextView_textAppearance 3803 */ 3804 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3805 public void setTextAppearance(@StyleRes int resId) { 3806 setTextAppearance(mContext, resId); 3807 } 3808 3809 /** 3810 * Sets the text color, size, style, hint color, and highlight color 3811 * from the specified TextAppearance resource. 3812 * 3813 * @deprecated Use {@link #setTextAppearance(int)} instead. 3814 */ 3815 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3816 public void setTextAppearance(Context context, @StyleRes int resId) { 3817 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3818 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3819 readTextAppearance(context, ta, attributes, false /* styleArray */); 3820 ta.recycle(); 3821 applyTextAppearance(attributes); 3822 } 3823 3824 /** 3825 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3826 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3827 */ 3828 private static class TextAppearanceAttributes { 3829 int mTextColorHighlight = 0; 3830 ColorStateList mTextColor = null; 3831 ColorStateList mTextColorHint = null; 3832 ColorStateList mTextColorLink = null; 3833 int mTextSize = -1; 3834 LocaleList mTextLocales = null; 3835 String mFontFamily = null; 3836 Typeface mFontTypeface = null; 3837 boolean mFontFamilyExplicit = false; 3838 int mTypefaceIndex = -1; 3839 int mTextStyle = 0; 3840 int mFontWeight = -1; 3841 boolean mAllCaps = false; 3842 int mShadowColor = 0; 3843 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3844 boolean mHasElegant = false; 3845 boolean mElegant = false; 3846 boolean mHasFallbackLineSpacing = false; 3847 boolean mFallbackLineSpacing = false; 3848 boolean mHasLetterSpacing = false; 3849 float mLetterSpacing = 0; 3850 String mFontFeatureSettings = null; 3851 String mFontVariationSettings = null; 3852 3853 @Override toString()3854 public String toString() { 3855 return "TextAppearanceAttributes {\n" 3856 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 3857 + " mTextColor:" + mTextColor + "\n" 3858 + " mTextColorHint:" + mTextColorHint + "\n" 3859 + " mTextColorLink:" + mTextColorLink + "\n" 3860 + " mTextSize:" + mTextSize + "\n" 3861 + " mTextLocales:" + mTextLocales + "\n" 3862 + " mFontFamily:" + mFontFamily + "\n" 3863 + " mFontTypeface:" + mFontTypeface + "\n" 3864 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 3865 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 3866 + " mTextStyle:" + mTextStyle + "\n" 3867 + " mFontWeight:" + mFontWeight + "\n" 3868 + " mAllCaps:" + mAllCaps + "\n" 3869 + " mShadowColor:" + mShadowColor + "\n" 3870 + " mShadowDx:" + mShadowDx + "\n" 3871 + " mShadowDy:" + mShadowDy + "\n" 3872 + " mShadowRadius:" + mShadowRadius + "\n" 3873 + " mHasElegant:" + mHasElegant + "\n" 3874 + " mElegant:" + mElegant + "\n" 3875 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 3876 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 3877 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 3878 + " mLetterSpacing:" + mLetterSpacing + "\n" 3879 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 3880 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 3881 + "}"; 3882 } 3883 } 3884 3885 // Maps styleable attributes that exist both in TextView style and TextAppearance. 3886 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 3887 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3888 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 3889 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3890 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 3891 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3892 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 3893 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3894 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 3895 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3896 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 3897 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3898 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 3899 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3900 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 3901 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3902 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 3903 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3904 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 3905 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3906 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 3907 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3908 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 3909 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3910 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 3911 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3912 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 3913 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3914 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 3915 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3916 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 3917 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3918 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 3919 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3920 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 3921 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3922 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 3923 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3924 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 3925 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3926 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 3927 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 3928 } 3929 3930 /** 3931 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 3932 * set. If the TypedArray contains a value that was already set in the given attributes, that 3933 * will be overridden. 3934 * 3935 * @param context The Context to be used 3936 * @param appearance The TypedArray to read properties from 3937 * @param attributes the TextAppearanceAttributes to fill in 3938 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 3939 * what attribute indexes will be used to read the properties. 3940 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3941 private void readTextAppearance(Context context, TypedArray appearance, 3942 TextAppearanceAttributes attributes, boolean styleArray) { 3943 final int n = appearance.getIndexCount(); 3944 for (int i = 0; i < n; i++) { 3945 final int attr = appearance.getIndex(i); 3946 int index = attr; 3947 // Translate style array index ids to TextAppearance ids. 3948 if (styleArray) { 3949 index = sAppearanceValues.get(attr, -1); 3950 if (index == -1) { 3951 // This value is not part of a Text Appearance and should be ignored. 3952 continue; 3953 } 3954 } 3955 switch (index) { 3956 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 3957 attributes.mTextColorHighlight = 3958 appearance.getColor(attr, attributes.mTextColorHighlight); 3959 break; 3960 case com.android.internal.R.styleable.TextAppearance_textColor: 3961 attributes.mTextColor = appearance.getColorStateList(attr); 3962 break; 3963 case com.android.internal.R.styleable.TextAppearance_textColorHint: 3964 attributes.mTextColorHint = appearance.getColorStateList(attr); 3965 break; 3966 case com.android.internal.R.styleable.TextAppearance_textColorLink: 3967 attributes.mTextColorLink = appearance.getColorStateList(attr); 3968 break; 3969 case com.android.internal.R.styleable.TextAppearance_textSize: 3970 attributes.mTextSize = 3971 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 3972 break; 3973 case com.android.internal.R.styleable.TextAppearance_textLocale: 3974 final String localeString = appearance.getString(attr); 3975 if (localeString != null) { 3976 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 3977 if (!localeList.isEmpty()) { 3978 attributes.mTextLocales = localeList; 3979 } 3980 } 3981 break; 3982 case com.android.internal.R.styleable.TextAppearance_typeface: 3983 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 3984 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 3985 attributes.mFontFamily = null; 3986 } 3987 break; 3988 case com.android.internal.R.styleable.TextAppearance_fontFamily: 3989 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 3990 try { 3991 attributes.mFontTypeface = appearance.getFont(attr); 3992 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 3993 // Expected if it is not a font resource. 3994 } 3995 } 3996 if (attributes.mFontTypeface == null) { 3997 attributes.mFontFamily = appearance.getString(attr); 3998 } 3999 attributes.mFontFamilyExplicit = true; 4000 break; 4001 case com.android.internal.R.styleable.TextAppearance_textStyle: 4002 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4003 break; 4004 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4005 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4006 break; 4007 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4008 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4009 break; 4010 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4011 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4012 break; 4013 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4014 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4015 break; 4016 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4017 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4018 break; 4019 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4020 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4021 break; 4022 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4023 attributes.mHasElegant = true; 4024 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4025 break; 4026 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4027 attributes.mHasFallbackLineSpacing = true; 4028 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4029 attributes.mFallbackLineSpacing); 4030 break; 4031 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4032 attributes.mHasLetterSpacing = true; 4033 attributes.mLetterSpacing = 4034 appearance.getFloat(attr, attributes.mLetterSpacing); 4035 break; 4036 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4037 attributes.mFontFeatureSettings = appearance.getString(attr); 4038 break; 4039 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4040 attributes.mFontVariationSettings = appearance.getString(attr); 4041 break; 4042 default: 4043 } 4044 } 4045 } 4046 applyTextAppearance(TextAppearanceAttributes attributes)4047 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4048 if (attributes.mTextColor != null) { 4049 setTextColor(attributes.mTextColor); 4050 } 4051 4052 if (attributes.mTextColorHint != null) { 4053 setHintTextColor(attributes.mTextColorHint); 4054 } 4055 4056 if (attributes.mTextColorLink != null) { 4057 setLinkTextColor(attributes.mTextColorLink); 4058 } 4059 4060 if (attributes.mTextColorHighlight != 0) { 4061 setHighlightColor(attributes.mTextColorHighlight); 4062 } 4063 4064 if (attributes.mTextSize != -1) { 4065 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4066 } 4067 4068 if (attributes.mTextLocales != null) { 4069 setTextLocales(attributes.mTextLocales); 4070 } 4071 4072 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4073 attributes.mFontFamily = null; 4074 } 4075 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4076 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4077 4078 if (attributes.mShadowColor != 0) { 4079 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4080 attributes.mShadowColor); 4081 } 4082 4083 if (attributes.mAllCaps) { 4084 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4085 } 4086 4087 if (attributes.mHasElegant) { 4088 setElegantTextHeight(attributes.mElegant); 4089 } 4090 4091 if (attributes.mHasFallbackLineSpacing) { 4092 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4093 } 4094 4095 if (attributes.mHasLetterSpacing) { 4096 setLetterSpacing(attributes.mLetterSpacing); 4097 } 4098 4099 if (attributes.mFontFeatureSettings != null) { 4100 setFontFeatureSettings(attributes.mFontFeatureSettings); 4101 } 4102 4103 if (attributes.mFontVariationSettings != null) { 4104 setFontVariationSettings(attributes.mFontVariationSettings); 4105 } 4106 } 4107 4108 /** 4109 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4110 * the first member of {@link #getTextLocales()}. 4111 * @return the default primary {@link Locale} of the text in this TextView. 4112 */ 4113 @NonNull getTextLocale()4114 public Locale getTextLocale() { 4115 return mTextPaint.getTextLocale(); 4116 } 4117 4118 /** 4119 * Get the default {@link LocaleList} of the text in this TextView. 4120 * @return the default {@link LocaleList} of the text in this TextView. 4121 */ 4122 @NonNull @Size(min = 1) getTextLocales()4123 public LocaleList getTextLocales() { 4124 return mTextPaint.getTextLocales(); 4125 } 4126 changeListenerLocaleTo(@ullable Locale locale)4127 private void changeListenerLocaleTo(@Nullable Locale locale) { 4128 if (mListenerChanged) { 4129 // If a listener has been explicitly set, don't change it. We may break something. 4130 return; 4131 } 4132 // The following null check is not absolutely necessary since all calling points of 4133 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4134 // here in case others would want to call this method in the future. 4135 if (mEditor != null) { 4136 KeyListener listener = mEditor.mKeyListener; 4137 if (listener instanceof DigitsKeyListener) { 4138 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4139 } else if (listener instanceof DateKeyListener) { 4140 listener = DateKeyListener.getInstance(locale); 4141 } else if (listener instanceof TimeKeyListener) { 4142 listener = TimeKeyListener.getInstance(locale); 4143 } else if (listener instanceof DateTimeKeyListener) { 4144 listener = DateTimeKeyListener.getInstance(locale); 4145 } else { 4146 return; 4147 } 4148 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4149 setKeyListenerOnly(listener); 4150 setInputTypeFromEditor(); 4151 if (wasPasswordType) { 4152 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4153 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4154 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4155 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4156 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4157 } 4158 } 4159 } 4160 } 4161 4162 /** 4163 * Set the default {@link Locale} of the text in this TextView to a one-member 4164 * {@link LocaleList} containing just the given Locale. 4165 * 4166 * @param locale the {@link Locale} for drawing text, must not be null. 4167 * 4168 * @see #setTextLocales 4169 */ setTextLocale(@onNull Locale locale)4170 public void setTextLocale(@NonNull Locale locale) { 4171 mLocalesChanged = true; 4172 mTextPaint.setTextLocale(locale); 4173 if (mLayout != null) { 4174 nullLayouts(); 4175 requestLayout(); 4176 invalidate(); 4177 } 4178 } 4179 4180 /** 4181 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4182 * 4183 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4184 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4185 * other aspects of text display, including line breaking. 4186 * 4187 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4188 * 4189 * @see Paint#setTextLocales 4190 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4191 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4192 mLocalesChanged = true; 4193 mTextPaint.setTextLocales(locales); 4194 if (mLayout != null) { 4195 nullLayouts(); 4196 requestLayout(); 4197 invalidate(); 4198 } 4199 } 4200 4201 @Override onConfigurationChanged(Configuration newConfig)4202 protected void onConfigurationChanged(Configuration newConfig) { 4203 super.onConfigurationChanged(newConfig); 4204 if (!mLocalesChanged) { 4205 mTextPaint.setTextLocales(LocaleList.getDefault()); 4206 if (mLayout != null) { 4207 nullLayouts(); 4208 requestLayout(); 4209 invalidate(); 4210 } 4211 } 4212 } 4213 4214 /** 4215 * @return the size (in pixels) of the default text size in this TextView. 4216 */ 4217 @InspectableProperty 4218 @ViewDebug.ExportedProperty(category = "text") getTextSize()4219 public float getTextSize() { 4220 return mTextPaint.getTextSize(); 4221 } 4222 4223 /** 4224 * @return the size (in scaled pixels) of the default text size in this TextView. 4225 * @hide 4226 */ 4227 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4228 public float getScaledTextSize() { 4229 return mTextPaint.getTextSize() / mTextPaint.density; 4230 } 4231 4232 /** @hide */ 4233 @ViewDebug.ExportedProperty(category = "text", mapping = { 4234 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4235 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4236 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4237 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4238 }) getTypefaceStyle()4239 public int getTypefaceStyle() { 4240 Typeface typeface = mTextPaint.getTypeface(); 4241 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4242 } 4243 4244 /** 4245 * Set the default text size to the given value, interpreted as "scaled 4246 * pixel" units. This size is adjusted based on the current density and 4247 * user font size preference. 4248 * 4249 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4250 * 4251 * @param size The scaled pixel size. 4252 * 4253 * @attr ref android.R.styleable#TextView_textSize 4254 */ 4255 @android.view.RemotableViewMethod setTextSize(float size)4256 public void setTextSize(float size) { 4257 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4258 } 4259 4260 /** 4261 * Set the default text size to a given unit and value. See {@link 4262 * TypedValue} for the possible dimension units. 4263 * 4264 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4265 * 4266 * @param unit The desired dimension unit. 4267 * @param size The desired size in the given units. 4268 * 4269 * @attr ref android.R.styleable#TextView_textSize 4270 */ setTextSize(int unit, float size)4271 public void setTextSize(int unit, float size) { 4272 if (!isAutoSizeEnabled()) { 4273 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4274 } 4275 } 4276 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4277 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4278 Context c = getContext(); 4279 Resources r; 4280 4281 if (c == null) { 4282 r = Resources.getSystem(); 4283 } else { 4284 r = c.getResources(); 4285 } 4286 4287 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4288 shouldRequestLayout); 4289 } 4290 4291 @UnsupportedAppUsage setRawTextSize(float size, boolean shouldRequestLayout)4292 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4293 if (size != mTextPaint.getTextSize()) { 4294 mTextPaint.setTextSize(size); 4295 4296 if (shouldRequestLayout && mLayout != null) { 4297 // Do not auto-size right after setting the text size. 4298 mNeedsAutoSizeText = false; 4299 nullLayouts(); 4300 requestLayout(); 4301 invalidate(); 4302 } 4303 } 4304 } 4305 4306 /** 4307 * Gets the extent by which text should be stretched horizontally. 4308 * This will usually be 1.0. 4309 * @return The horizontal scale factor. 4310 */ 4311 @InspectableProperty getTextScaleX()4312 public float getTextScaleX() { 4313 return mTextPaint.getTextScaleX(); 4314 } 4315 4316 /** 4317 * Sets the horizontal scale factor for text. The default value 4318 * is 1.0. Values greater than 1.0 stretch the text wider. 4319 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4320 * @param size The horizontal scale factor. 4321 * @attr ref android.R.styleable#TextView_textScaleX 4322 */ 4323 @android.view.RemotableViewMethod setTextScaleX(float size)4324 public void setTextScaleX(float size) { 4325 if (size != mTextPaint.getTextScaleX()) { 4326 mUserSetTextScaleX = true; 4327 mTextPaint.setTextScaleX(size); 4328 4329 if (mLayout != null) { 4330 nullLayouts(); 4331 requestLayout(); 4332 invalidate(); 4333 } 4334 } 4335 } 4336 4337 /** 4338 * Sets the typeface and style in which the text should be displayed. 4339 * Note that not all Typeface families actually have bold and italic 4340 * variants, so you may need to use 4341 * {@link #setTypeface(Typeface, int)} to get the appearance 4342 * that you actually want. 4343 * 4344 * @see #getTypeface() 4345 * 4346 * @attr ref android.R.styleable#TextView_fontFamily 4347 * @attr ref android.R.styleable#TextView_typeface 4348 * @attr ref android.R.styleable#TextView_textStyle 4349 */ setTypeface(@ullable Typeface tf)4350 public void setTypeface(@Nullable Typeface tf) { 4351 if (mTextPaint.getTypeface() != tf) { 4352 mTextPaint.setTypeface(tf); 4353 4354 if (mLayout != null) { 4355 nullLayouts(); 4356 requestLayout(); 4357 invalidate(); 4358 } 4359 } 4360 } 4361 4362 /** 4363 * Gets the current {@link Typeface} that is used to style the text. 4364 * @return The current Typeface. 4365 * 4366 * @see #setTypeface(Typeface) 4367 * 4368 * @attr ref android.R.styleable#TextView_fontFamily 4369 * @attr ref android.R.styleable#TextView_typeface 4370 * @attr ref android.R.styleable#TextView_textStyle 4371 */ 4372 @InspectableProperty getTypeface()4373 public Typeface getTypeface() { 4374 return mTextPaint.getTypeface(); 4375 } 4376 4377 /** 4378 * Set the TextView's elegant height metrics flag. This setting selects font 4379 * variants that have not been compacted to fit Latin-based vertical 4380 * metrics, and also increases top and bottom bounds to provide more space. 4381 * 4382 * @param elegant set the paint's elegant metrics flag. 4383 * 4384 * @see #isElegantTextHeight() 4385 * @see Paint#isElegantTextHeight() 4386 * 4387 * @attr ref android.R.styleable#TextView_elegantTextHeight 4388 */ setElegantTextHeight(boolean elegant)4389 public void setElegantTextHeight(boolean elegant) { 4390 if (elegant != mTextPaint.isElegantTextHeight()) { 4391 mTextPaint.setElegantTextHeight(elegant); 4392 if (mLayout != null) { 4393 nullLayouts(); 4394 requestLayout(); 4395 invalidate(); 4396 } 4397 } 4398 } 4399 4400 /** 4401 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4402 * displaying the text (which is needed to avoid text from consecutive lines running into 4403 * each other). If set, fallback fonts that end up getting used can increase the ascent 4404 * and descent of the lines that they are used on. 4405 * <p/> 4406 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4407 * is typically much taller or deeper than Latin text. 4408 * 4409 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4410 * 4411 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4412 * 4413 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4414 */ setFallbackLineSpacing(boolean enabled)4415 public void setFallbackLineSpacing(boolean enabled) { 4416 if (mUseFallbackLineSpacing != enabled) { 4417 mUseFallbackLineSpacing = enabled; 4418 if (mLayout != null) { 4419 nullLayouts(); 4420 requestLayout(); 4421 invalidate(); 4422 } 4423 } 4424 } 4425 4426 /** 4427 * @return whether fallback line spacing is enabled, {@code true} by default 4428 * 4429 * @see #setFallbackLineSpacing(boolean) 4430 * 4431 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4432 */ 4433 @InspectableProperty isFallbackLineSpacing()4434 public boolean isFallbackLineSpacing() { 4435 return mUseFallbackLineSpacing; 4436 } 4437 4438 /** 4439 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4440 * variants that have not been compacted to fit Latin-based vertical 4441 * metrics, and also increases top and bottom bounds to provide more space. 4442 * @return {@code true} if the elegant height metrics flag is set. 4443 * 4444 * @see #setElegantTextHeight(boolean) 4445 * @see Paint#setElegantTextHeight(boolean) 4446 */ 4447 @InspectableProperty isElegantTextHeight()4448 public boolean isElegantTextHeight() { 4449 return mTextPaint.isElegantTextHeight(); 4450 } 4451 4452 /** 4453 * Gets the text letter-space value, which determines the spacing between characters. 4454 * The value returned is in ems. Normally, this value is 0.0. 4455 * @return The text letter-space value in ems. 4456 * 4457 * @see #setLetterSpacing(float) 4458 * @see Paint#setLetterSpacing 4459 */ 4460 @InspectableProperty getLetterSpacing()4461 public float getLetterSpacing() { 4462 return mTextPaint.getLetterSpacing(); 4463 } 4464 4465 /** 4466 * Sets text letter-spacing in em units. Typical values 4467 * for slight expansion will be around 0.05. Negative values tighten text. 4468 * 4469 * @see #getLetterSpacing() 4470 * @see Paint#getLetterSpacing 4471 * 4472 * @param letterSpacing A text letter-space value in ems. 4473 * @attr ref android.R.styleable#TextView_letterSpacing 4474 */ 4475 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4476 public void setLetterSpacing(float letterSpacing) { 4477 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4478 mTextPaint.setLetterSpacing(letterSpacing); 4479 4480 if (mLayout != null) { 4481 nullLayouts(); 4482 requestLayout(); 4483 invalidate(); 4484 } 4485 } 4486 } 4487 4488 /** 4489 * Returns the font feature settings. The format is the same as the CSS 4490 * font-feature-settings attribute: 4491 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4492 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4493 * 4494 * @return the currently set font feature settings. Default is null. 4495 * 4496 * @see #setFontFeatureSettings(String) 4497 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4498 */ 4499 @InspectableProperty 4500 @Nullable getFontFeatureSettings()4501 public String getFontFeatureSettings() { 4502 return mTextPaint.getFontFeatureSettings(); 4503 } 4504 4505 /** 4506 * Returns the font variation settings. 4507 * 4508 * @return the currently set font variation settings. Returns null if no variation is 4509 * specified. 4510 * 4511 * @see #setFontVariationSettings(String) 4512 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4513 */ 4514 @Nullable getFontVariationSettings()4515 public String getFontVariationSettings() { 4516 return mTextPaint.getFontVariationSettings(); 4517 } 4518 4519 /** 4520 * Sets the break strategy for breaking paragraphs into lines. The default value for 4521 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4522 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4523 * text "dancing" when being edited. 4524 * <p/> 4525 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4526 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4527 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4528 * improves the structure of text layout however has performance impact and requires more time 4529 * to do the text layout. 4530 * 4531 * @attr ref android.R.styleable#TextView_breakStrategy 4532 * @see #getBreakStrategy() 4533 * @see #setHyphenationFrequency(int) 4534 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4535 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4536 mBreakStrategy = breakStrategy; 4537 if (mLayout != null) { 4538 nullLayouts(); 4539 requestLayout(); 4540 invalidate(); 4541 } 4542 } 4543 4544 /** 4545 * Gets the current strategy for breaking paragraphs into lines. 4546 * @return the current strategy for breaking paragraphs into lines. 4547 * 4548 * @attr ref android.R.styleable#TextView_breakStrategy 4549 * @see #setBreakStrategy(int) 4550 */ 4551 @InspectableProperty(enumMapping = { 4552 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4553 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4554 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4555 }) 4556 @Layout.BreakStrategy getBreakStrategy()4557 public int getBreakStrategy() { 4558 return mBreakStrategy; 4559 } 4560 4561 /** 4562 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4563 * The default value for both TextView and {@link EditText} is 4564 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4565 * is set from the theme. 4566 * <p/> 4567 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4568 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4569 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4570 * improves the structure of text layout however has performance impact and requires more time 4571 * to do the text layout. 4572 * <p/> 4573 * Note: Before Android Q, in the theme hyphenation frequency is set to 4574 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4575 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4576 * 4577 * @param hyphenationFrequency the hyphenation frequency to use, one of 4578 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4579 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4580 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4581 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4582 * @see #getHyphenationFrequency() 4583 * @see #getBreakStrategy() 4584 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4585 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4586 mHyphenationFrequency = hyphenationFrequency; 4587 if (mLayout != null) { 4588 nullLayouts(); 4589 requestLayout(); 4590 invalidate(); 4591 } 4592 } 4593 4594 /** 4595 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4596 * @return the current frequency of automatic hyphenation to be used when determining word 4597 * breaks. 4598 * 4599 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4600 * @see #setHyphenationFrequency(int) 4601 */ 4602 @InspectableProperty(enumMapping = { 4603 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4604 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4605 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4606 }) 4607 @Layout.HyphenationFrequency getHyphenationFrequency()4608 public int getHyphenationFrequency() { 4609 return mHyphenationFrequency; 4610 } 4611 4612 /** 4613 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4614 * 4615 * @return a current {@link PrecomputedText.Params} 4616 * @see PrecomputedText 4617 */ getTextMetricsParams()4618 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4619 return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), 4620 mBreakStrategy, mHyphenationFrequency); 4621 } 4622 4623 /** 4624 * Apply the text layout parameter. 4625 * 4626 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4627 * @see PrecomputedText 4628 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4629 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4630 mTextPaint.set(params.getTextPaint()); 4631 mUserSetTextScaleX = true; 4632 mTextDir = params.getTextDirection(); 4633 mBreakStrategy = params.getBreakStrategy(); 4634 mHyphenationFrequency = params.getHyphenationFrequency(); 4635 if (mLayout != null) { 4636 nullLayouts(); 4637 requestLayout(); 4638 invalidate(); 4639 } 4640 } 4641 4642 /** 4643 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4644 * last line is too short for justification, the last line will be displayed with the 4645 * alignment set by {@link android.view.View#setTextAlignment}. 4646 * 4647 * @see #getJustificationMode() 4648 */ 4649 @Layout.JustificationMode setJustificationMode(@ayout.JustificationMode int justificationMode)4650 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 4651 mJustificationMode = justificationMode; 4652 if (mLayout != null) { 4653 nullLayouts(); 4654 requestLayout(); 4655 invalidate(); 4656 } 4657 } 4658 4659 /** 4660 * @return true if currently paragraph justification mode. 4661 * 4662 * @see #setJustificationMode(int) 4663 */ 4664 @InspectableProperty(enumMapping = { 4665 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 4666 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 4667 }) getJustificationMode()4668 public @Layout.JustificationMode int getJustificationMode() { 4669 return mJustificationMode; 4670 } 4671 4672 /** 4673 * Sets font feature settings. The format is the same as the CSS 4674 * font-feature-settings attribute: 4675 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4676 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4677 * 4678 * @param fontFeatureSettings font feature settings represented as CSS compatible string 4679 * 4680 * @see #getFontFeatureSettings() 4681 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 4682 * 4683 * @attr ref android.R.styleable#TextView_fontFeatureSettings 4684 */ 4685 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)4686 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 4687 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 4688 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 4689 4690 if (mLayout != null) { 4691 nullLayouts(); 4692 requestLayout(); 4693 invalidate(); 4694 } 4695 } 4696 } 4697 4698 4699 /** 4700 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 4701 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 4702 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 4703 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 4704 * are invalid. If a specified axis name is not defined in the font, the settings will be 4705 * ignored. 4706 * 4707 * <p> 4708 * Examples, 4709 * <ul> 4710 * <li>Set font width to 150. 4711 * <pre> 4712 * <code> 4713 * TextView textView = (TextView) findViewById(R.id.textView); 4714 * textView.setFontVariationSettings("'wdth' 150"); 4715 * </code> 4716 * </pre> 4717 * </li> 4718 * 4719 * <li>Set the font slant to 20 degrees and ask for italic style. 4720 * <pre> 4721 * <code> 4722 * TextView textView = (TextView) findViewById(R.id.textView); 4723 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 4724 * </code> 4725 * </pre> 4726 * </p> 4727 * </li> 4728 * </ul> 4729 * 4730 * @param fontVariationSettings font variation settings. You can pass null or empty string as 4731 * no variation settings. 4732 * @return true if the given settings is effective to at least one font file underlying this 4733 * TextView. This function also returns true for empty settings string. Otherwise 4734 * returns false. 4735 * 4736 * @throws IllegalArgumentException If given string is not a valid font variation settings 4737 * format. 4738 * 4739 * @see #getFontVariationSettings() 4740 * @see FontVariationAxis 4741 * 4742 * @attr ref android.R.styleable#TextView_fontVariationSettings 4743 */ setFontVariationSettings(@ullable String fontVariationSettings)4744 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 4745 final String existingSettings = mTextPaint.getFontVariationSettings(); 4746 if (fontVariationSettings == existingSettings 4747 || (fontVariationSettings != null 4748 && fontVariationSettings.equals(existingSettings))) { 4749 return true; 4750 } 4751 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 4752 4753 if (effective && mLayout != null) { 4754 nullLayouts(); 4755 requestLayout(); 4756 invalidate(); 4757 } 4758 return effective; 4759 } 4760 4761 /** 4762 * Sets the text color for all the states (normal, selected, 4763 * focused) to be this color. 4764 * 4765 * @param color A color value in the form 0xAARRGGBB. 4766 * Do not pass a resource ID. To get a color value from a resource ID, call 4767 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 4768 * 4769 * @see #setTextColor(ColorStateList) 4770 * @see #getTextColors() 4771 * 4772 * @attr ref android.R.styleable#TextView_textColor 4773 */ 4774 @android.view.RemotableViewMethod setTextColor(@olorInt int color)4775 public void setTextColor(@ColorInt int color) { 4776 mTextColor = ColorStateList.valueOf(color); 4777 updateTextColors(); 4778 } 4779 4780 /** 4781 * Sets the text color. 4782 * 4783 * @see #setTextColor(int) 4784 * @see #getTextColors() 4785 * @see #setHintTextColor(ColorStateList) 4786 * @see #setLinkTextColor(ColorStateList) 4787 * 4788 * @attr ref android.R.styleable#TextView_textColor 4789 */ 4790 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)4791 public void setTextColor(ColorStateList colors) { 4792 if (colors == null) { 4793 throw new NullPointerException(); 4794 } 4795 4796 mTextColor = colors; 4797 updateTextColors(); 4798 } 4799 4800 /** 4801 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4802 * 4803 * @see #setTextColor(ColorStateList) 4804 * @see #setTextColor(int) 4805 * 4806 * @attr ref android.R.styleable#TextView_textColor 4807 */ 4808 @InspectableProperty(name = "textColor") getTextColors()4809 public final ColorStateList getTextColors() { 4810 return mTextColor; 4811 } 4812 4813 /** 4814 * Return the current color selected for normal text. 4815 * 4816 * @return Returns the current text color. 4817 */ 4818 @ColorInt getCurrentTextColor()4819 public final int getCurrentTextColor() { 4820 return mCurTextColor; 4821 } 4822 4823 /** 4824 * Sets the color used to display the selection highlight. 4825 * 4826 * @attr ref android.R.styleable#TextView_textColorHighlight 4827 */ 4828 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)4829 public void setHighlightColor(@ColorInt int color) { 4830 if (mHighlightColor != color) { 4831 mHighlightColor = color; 4832 invalidate(); 4833 } 4834 } 4835 4836 /** 4837 * @return the color used to display the selection highlight 4838 * 4839 * @see #setHighlightColor(int) 4840 * 4841 * @attr ref android.R.styleable#TextView_textColorHighlight 4842 */ 4843 @InspectableProperty(name = "textColorHighlight") 4844 @ColorInt getHighlightColor()4845 public int getHighlightColor() { 4846 return mHighlightColor; 4847 } 4848 4849 /** 4850 * Sets whether the soft input method will be made visible when this 4851 * TextView gets focused. The default is true. 4852 */ 4853 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)4854 public final void setShowSoftInputOnFocus(boolean show) { 4855 createEditorIfNeeded(); 4856 mEditor.mShowSoftInputOnFocus = show; 4857 } 4858 4859 /** 4860 * Returns whether the soft input method will be made visible when this 4861 * TextView gets focused. The default is true. 4862 */ getShowSoftInputOnFocus()4863 public final boolean getShowSoftInputOnFocus() { 4864 // When there is no Editor, return default true value 4865 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4866 } 4867 4868 /** 4869 * Gives the text a shadow of the specified blur radius and color, the specified 4870 * distance from its drawn position. 4871 * <p> 4872 * The text shadow produced does not interact with the properties on view 4873 * that are responsible for real time shadows, 4874 * {@link View#getElevation() elevation} and 4875 * {@link View#getTranslationZ() translationZ}. 4876 * 4877 * @see Paint#setShadowLayer(float, float, float, int) 4878 * 4879 * @attr ref android.R.styleable#TextView_shadowColor 4880 * @attr ref android.R.styleable#TextView_shadowDx 4881 * @attr ref android.R.styleable#TextView_shadowDy 4882 * @attr ref android.R.styleable#TextView_shadowRadius 4883 */ setShadowLayer(float radius, float dx, float dy, int color)4884 public void setShadowLayer(float radius, float dx, float dy, int color) { 4885 mTextPaint.setShadowLayer(radius, dx, dy, color); 4886 4887 mShadowRadius = radius; 4888 mShadowDx = dx; 4889 mShadowDy = dy; 4890 mShadowColor = color; 4891 4892 // Will change text clip region 4893 if (mEditor != null) { 4894 mEditor.invalidateTextDisplayList(); 4895 mEditor.invalidateHandlesAndActionMode(); 4896 } 4897 invalidate(); 4898 } 4899 4900 /** 4901 * Gets the radius of the shadow layer. 4902 * 4903 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 4904 * 4905 * @see #setShadowLayer(float, float, float, int) 4906 * 4907 * @attr ref android.R.styleable#TextView_shadowRadius 4908 */ 4909 @InspectableProperty getShadowRadius()4910 public float getShadowRadius() { 4911 return mShadowRadius; 4912 } 4913 4914 /** 4915 * @return the horizontal offset of the shadow layer 4916 * 4917 * @see #setShadowLayer(float, float, float, int) 4918 * 4919 * @attr ref android.R.styleable#TextView_shadowDx 4920 */ 4921 @InspectableProperty getShadowDx()4922 public float getShadowDx() { 4923 return mShadowDx; 4924 } 4925 4926 /** 4927 * Gets the vertical offset of the shadow layer. 4928 * @return The vertical offset of the shadow layer. 4929 * 4930 * @see #setShadowLayer(float, float, float, int) 4931 * 4932 * @attr ref android.R.styleable#TextView_shadowDy 4933 */ 4934 @InspectableProperty getShadowDy()4935 public float getShadowDy() { 4936 return mShadowDy; 4937 } 4938 4939 /** 4940 * Gets the color of the shadow layer. 4941 * @return the color of the shadow layer 4942 * 4943 * @see #setShadowLayer(float, float, float, int) 4944 * 4945 * @attr ref android.R.styleable#TextView_shadowColor 4946 */ 4947 @InspectableProperty 4948 @ColorInt getShadowColor()4949 public int getShadowColor() { 4950 return mShadowColor; 4951 } 4952 4953 /** 4954 * Gets the {@link TextPaint} used for the text. 4955 * Use this only to consult the Paint's properties and not to change them. 4956 * @return The base paint used for the text. 4957 */ getPaint()4958 public TextPaint getPaint() { 4959 return mTextPaint; 4960 } 4961 4962 /** 4963 * Sets the autolink mask of the text. See {@link 4964 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 4965 * possible values. 4966 * 4967 * <p class="note"><b>Note:</b> 4968 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 4969 * is deprecated and should be avoided; see its documentation. 4970 * 4971 * @attr ref android.R.styleable#TextView_autoLink 4972 */ 4973 @android.view.RemotableViewMethod setAutoLinkMask(int mask)4974 public final void setAutoLinkMask(int mask) { 4975 mAutoLinkMask = mask; 4976 } 4977 4978 /** 4979 * Sets whether the movement method will automatically be set to 4980 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4981 * set to nonzero and links are detected in {@link #setText}. 4982 * The default is true. 4983 * 4984 * @attr ref android.R.styleable#TextView_linksClickable 4985 */ 4986 @android.view.RemotableViewMethod setLinksClickable(boolean whether)4987 public final void setLinksClickable(boolean whether) { 4988 mLinksClickable = whether; 4989 } 4990 4991 /** 4992 * Returns whether the movement method will automatically be set to 4993 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4994 * set to nonzero and links are detected in {@link #setText}. 4995 * The default is true. 4996 * 4997 * @attr ref android.R.styleable#TextView_linksClickable 4998 */ 4999 @InspectableProperty getLinksClickable()5000 public final boolean getLinksClickable() { 5001 return mLinksClickable; 5002 } 5003 5004 /** 5005 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5006 * (by {@link Linkify} or otherwise) if any. You can call 5007 * {@link URLSpan#getURL} on them to find where they link to 5008 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5009 * to find the region of the text they are attached to. 5010 */ getUrls()5011 public URLSpan[] getUrls() { 5012 if (mText instanceof Spanned) { 5013 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5014 } else { 5015 return new URLSpan[0]; 5016 } 5017 } 5018 5019 /** 5020 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5021 * TextView. 5022 * 5023 * @see #setHintTextColor(ColorStateList) 5024 * @see #getHintTextColors() 5025 * @see #setTextColor(int) 5026 * 5027 * @attr ref android.R.styleable#TextView_textColorHint 5028 */ 5029 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5030 public final void setHintTextColor(@ColorInt int color) { 5031 mHintTextColor = ColorStateList.valueOf(color); 5032 updateTextColors(); 5033 } 5034 5035 /** 5036 * Sets the color of the hint text. 5037 * 5038 * @see #getHintTextColors() 5039 * @see #setHintTextColor(int) 5040 * @see #setTextColor(ColorStateList) 5041 * @see #setLinkTextColor(ColorStateList) 5042 * 5043 * @attr ref android.R.styleable#TextView_textColorHint 5044 */ setHintTextColor(ColorStateList colors)5045 public final void setHintTextColor(ColorStateList colors) { 5046 mHintTextColor = colors; 5047 updateTextColors(); 5048 } 5049 5050 /** 5051 * @return the color of the hint text, for the different states of this TextView. 5052 * 5053 * @see #setHintTextColor(ColorStateList) 5054 * @see #setHintTextColor(int) 5055 * @see #setTextColor(ColorStateList) 5056 * @see #setLinkTextColor(ColorStateList) 5057 * 5058 * @attr ref android.R.styleable#TextView_textColorHint 5059 */ 5060 @InspectableProperty(name = "textColorHint") getHintTextColors()5061 public final ColorStateList getHintTextColors() { 5062 return mHintTextColor; 5063 } 5064 5065 /** 5066 * <p>Return the current color selected to paint the hint text.</p> 5067 * 5068 * @return Returns the current hint text color. 5069 */ 5070 @ColorInt getCurrentHintTextColor()5071 public final int getCurrentHintTextColor() { 5072 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5073 } 5074 5075 /** 5076 * Sets the color of links in the text. 5077 * 5078 * @see #setLinkTextColor(ColorStateList) 5079 * @see #getLinkTextColors() 5080 * 5081 * @attr ref android.R.styleable#TextView_textColorLink 5082 */ 5083 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5084 public final void setLinkTextColor(@ColorInt int color) { 5085 mLinkTextColor = ColorStateList.valueOf(color); 5086 updateTextColors(); 5087 } 5088 5089 /** 5090 * Sets the color of links in the text. 5091 * 5092 * @see #setLinkTextColor(int) 5093 * @see #getLinkTextColors() 5094 * @see #setTextColor(ColorStateList) 5095 * @see #setHintTextColor(ColorStateList) 5096 * 5097 * @attr ref android.R.styleable#TextView_textColorLink 5098 */ setLinkTextColor(ColorStateList colors)5099 public final void setLinkTextColor(ColorStateList colors) { 5100 mLinkTextColor = colors; 5101 updateTextColors(); 5102 } 5103 5104 /** 5105 * @return the list of colors used to paint the links in the text, for the different states of 5106 * this TextView 5107 * 5108 * @see #setLinkTextColor(ColorStateList) 5109 * @see #setLinkTextColor(int) 5110 * 5111 * @attr ref android.R.styleable#TextView_textColorLink 5112 */ 5113 @InspectableProperty(name = "textColorLink") getLinkTextColors()5114 public final ColorStateList getLinkTextColors() { 5115 return mLinkTextColor; 5116 } 5117 5118 /** 5119 * Sets the horizontal alignment of the text and the 5120 * vertical gravity that will be used when there is extra space 5121 * in the TextView beyond what is required for the text itself. 5122 * 5123 * @see android.view.Gravity 5124 * @attr ref android.R.styleable#TextView_gravity 5125 */ setGravity(int gravity)5126 public void setGravity(int gravity) { 5127 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5128 gravity |= Gravity.START; 5129 } 5130 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5131 gravity |= Gravity.TOP; 5132 } 5133 5134 boolean newLayout = false; 5135 5136 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5137 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5138 newLayout = true; 5139 } 5140 5141 if (gravity != mGravity) { 5142 invalidate(); 5143 } 5144 5145 mGravity = gravity; 5146 5147 if (mLayout != null && newLayout) { 5148 // XXX this is heavy-handed because no actual content changes. 5149 int want = mLayout.getWidth(); 5150 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5151 5152 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5153 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5154 } 5155 } 5156 5157 /** 5158 * Returns the horizontal and vertical alignment of this TextView. 5159 * 5160 * @see android.view.Gravity 5161 * @attr ref android.R.styleable#TextView_gravity 5162 */ 5163 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5164 public int getGravity() { 5165 return mGravity; 5166 } 5167 5168 /** 5169 * Gets the flags on the Paint being used to display the text. 5170 * @return The flags on the Paint being used to display the text. 5171 * @see Paint#getFlags 5172 */ getPaintFlags()5173 public int getPaintFlags() { 5174 return mTextPaint.getFlags(); 5175 } 5176 5177 /** 5178 * Sets flags on the Paint being used to display the text and 5179 * reflows the text if they are different from the old flags. 5180 * @see Paint#setFlags 5181 */ 5182 @android.view.RemotableViewMethod setPaintFlags(int flags)5183 public void setPaintFlags(int flags) { 5184 if (mTextPaint.getFlags() != flags) { 5185 mTextPaint.setFlags(flags); 5186 5187 if (mLayout != null) { 5188 nullLayouts(); 5189 requestLayout(); 5190 invalidate(); 5191 } 5192 } 5193 } 5194 5195 /** 5196 * Sets whether the text should be allowed to be wider than the 5197 * View is. If false, it will be wrapped to the width of the View. 5198 * 5199 * @attr ref android.R.styleable#TextView_scrollHorizontally 5200 */ setHorizontallyScrolling(boolean whether)5201 public void setHorizontallyScrolling(boolean whether) { 5202 if (mHorizontallyScrolling != whether) { 5203 mHorizontallyScrolling = whether; 5204 5205 if (mLayout != null) { 5206 nullLayouts(); 5207 requestLayout(); 5208 invalidate(); 5209 } 5210 } 5211 } 5212 5213 /** 5214 * Returns whether the text is allowed to be wider than the View. 5215 * If false, the text will be wrapped to the width of the View. 5216 * 5217 * @attr ref android.R.styleable#TextView_scrollHorizontally 5218 * @see #setHorizontallyScrolling(boolean) 5219 */ 5220 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5221 public final boolean isHorizontallyScrollable() { 5222 return mHorizontallyScrolling; 5223 } 5224 5225 /** 5226 * Returns whether the text is allowed to be wider than the View. 5227 * If false, the text will be wrapped to the width of the View. 5228 * 5229 * @attr ref android.R.styleable#TextView_scrollHorizontally 5230 * @hide 5231 */ 5232 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5233 public boolean getHorizontallyScrolling() { 5234 return mHorizontallyScrolling; 5235 } 5236 5237 /** 5238 * Sets the height of the TextView to be at least {@code minLines} tall. 5239 * <p> 5240 * This value is used for height calculation if LayoutParams does not force TextView to have an 5241 * exact height. Setting this value overrides other previous minimum height configurations such 5242 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5243 * this value to 1. 5244 * 5245 * @param minLines the minimum height of TextView in terms of number of lines 5246 * 5247 * @see #getMinLines() 5248 * @see #setLines(int) 5249 * 5250 * @attr ref android.R.styleable#TextView_minLines 5251 */ 5252 @android.view.RemotableViewMethod setMinLines(int minLines)5253 public void setMinLines(int minLines) { 5254 mMinimum = minLines; 5255 mMinMode = LINES; 5256 5257 requestLayout(); 5258 invalidate(); 5259 } 5260 5261 /** 5262 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5263 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5264 * 5265 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5266 * height is not defined in lines 5267 * 5268 * @see #setMinLines(int) 5269 * @see #setLines(int) 5270 * 5271 * @attr ref android.R.styleable#TextView_minLines 5272 */ 5273 @InspectableProperty getMinLines()5274 public int getMinLines() { 5275 return mMinMode == LINES ? mMinimum : -1; 5276 } 5277 5278 /** 5279 * Sets the height of the TextView to be at least {@code minPixels} tall. 5280 * <p> 5281 * This value is used for height calculation if LayoutParams does not force TextView to have an 5282 * exact height. Setting this value overrides previous minimum height configurations such as 5283 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5284 * <p> 5285 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5286 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5287 * used to decide the final height. 5288 * 5289 * @param minPixels the minimum height of TextView in terms of pixels 5290 * 5291 * @see #getMinHeight() 5292 * @see #setHeight(int) 5293 * 5294 * @attr ref android.R.styleable#TextView_minHeight 5295 */ 5296 @android.view.RemotableViewMethod setMinHeight(int minPixels)5297 public void setMinHeight(int minPixels) { 5298 mMinimum = minPixels; 5299 mMinMode = PIXELS; 5300 5301 requestLayout(); 5302 invalidate(); 5303 } 5304 5305 /** 5306 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5307 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5308 * 5309 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5310 * defined in pixels 5311 * 5312 * @see #setMinHeight(int) 5313 * @see #setHeight(int) 5314 * 5315 * @attr ref android.R.styleable#TextView_minHeight 5316 */ getMinHeight()5317 public int getMinHeight() { 5318 return mMinMode == PIXELS ? mMinimum : -1; 5319 } 5320 5321 /** 5322 * Sets the height of the TextView to be at most {@code maxLines} tall. 5323 * <p> 5324 * This value is used for height calculation if LayoutParams does not force TextView to have an 5325 * exact height. Setting this value overrides previous maximum height configurations such as 5326 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5327 * 5328 * @param maxLines the maximum height of TextView in terms of number of lines 5329 * 5330 * @see #getMaxLines() 5331 * @see #setLines(int) 5332 * 5333 * @attr ref android.R.styleable#TextView_maxLines 5334 */ 5335 @android.view.RemotableViewMethod setMaxLines(int maxLines)5336 public void setMaxLines(int maxLines) { 5337 mMaximum = maxLines; 5338 mMaxMode = LINES; 5339 5340 requestLayout(); 5341 invalidate(); 5342 } 5343 5344 /** 5345 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5346 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5347 * 5348 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5349 * is not defined in lines. 5350 * 5351 * @see #setMaxLines(int) 5352 * @see #setLines(int) 5353 * 5354 * @attr ref android.R.styleable#TextView_maxLines 5355 */ 5356 @InspectableProperty getMaxLines()5357 public int getMaxLines() { 5358 return mMaxMode == LINES ? mMaximum : -1; 5359 } 5360 5361 /** 5362 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5363 * <p> 5364 * This value is used for height calculation if LayoutParams does not force TextView to have an 5365 * exact height. Setting this value overrides previous maximum height configurations such as 5366 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5367 * 5368 * @param maxPixels the maximum height of TextView in terms of pixels 5369 * 5370 * @see #getMaxHeight() 5371 * @see #setHeight(int) 5372 * 5373 * @attr ref android.R.styleable#TextView_maxHeight 5374 */ 5375 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5376 public void setMaxHeight(int maxPixels) { 5377 mMaximum = maxPixels; 5378 mMaxMode = PIXELS; 5379 5380 requestLayout(); 5381 invalidate(); 5382 } 5383 5384 /** 5385 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5386 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5387 * 5388 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5389 * is not defined in pixels 5390 * 5391 * @see #setMaxHeight(int) 5392 * @see #setHeight(int) 5393 * 5394 * @attr ref android.R.styleable#TextView_maxHeight 5395 */ 5396 @InspectableProperty getMaxHeight()5397 public int getMaxHeight() { 5398 return mMaxMode == PIXELS ? mMaximum : -1; 5399 } 5400 5401 /** 5402 * Sets the height of the TextView to be exactly {@code lines} tall. 5403 * <p> 5404 * This value is used for height calculation if LayoutParams does not force TextView to have an 5405 * exact height. Setting this value overrides previous minimum/maximum height configurations 5406 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5407 * set this value to 1. 5408 * 5409 * @param lines the exact height of the TextView in terms of lines 5410 * 5411 * @see #setHeight(int) 5412 * 5413 * @attr ref android.R.styleable#TextView_lines 5414 */ 5415 @android.view.RemotableViewMethod setLines(int lines)5416 public void setLines(int lines) { 5417 mMaximum = mMinimum = lines; 5418 mMaxMode = mMinMode = LINES; 5419 5420 requestLayout(); 5421 invalidate(); 5422 } 5423 5424 /** 5425 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5426 * <p> 5427 * This value is used for height calculation if LayoutParams does not force TextView to have an 5428 * exact height. Setting this value overrides previous minimum/maximum height configurations 5429 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5430 * 5431 * @param pixels the exact height of the TextView in terms of pixels 5432 * 5433 * @see #setLines(int) 5434 * 5435 * @attr ref android.R.styleable#TextView_height 5436 */ 5437 @android.view.RemotableViewMethod setHeight(int pixels)5438 public void setHeight(int pixels) { 5439 mMaximum = mMinimum = pixels; 5440 mMaxMode = mMinMode = PIXELS; 5441 5442 requestLayout(); 5443 invalidate(); 5444 } 5445 5446 /** 5447 * Sets the width of the TextView to be at least {@code minEms} wide. 5448 * <p> 5449 * This value is used for width calculation if LayoutParams does not force TextView to have an 5450 * exact width. Setting this value overrides previous minimum width configurations such as 5451 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5452 * 5453 * @param minEms the minimum width of TextView in terms of ems 5454 * 5455 * @see #getMinEms() 5456 * @see #setEms(int) 5457 * 5458 * @attr ref android.R.styleable#TextView_minEms 5459 */ 5460 @android.view.RemotableViewMethod setMinEms(int minEms)5461 public void setMinEms(int minEms) { 5462 mMinWidth = minEms; 5463 mMinWidthMode = EMS; 5464 5465 requestLayout(); 5466 invalidate(); 5467 } 5468 5469 /** 5470 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5471 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5472 * 5473 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5474 * defined in ems 5475 * 5476 * @see #setMinEms(int) 5477 * @see #setEms(int) 5478 * 5479 * @attr ref android.R.styleable#TextView_minEms 5480 */ 5481 @InspectableProperty getMinEms()5482 public int getMinEms() { 5483 return mMinWidthMode == EMS ? mMinWidth : -1; 5484 } 5485 5486 /** 5487 * Sets the width of the TextView to be at least {@code minPixels} wide. 5488 * <p> 5489 * This value is used for width calculation if LayoutParams does not force TextView to have an 5490 * exact width. Setting this value overrides previous minimum width configurations such as 5491 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5492 * <p> 5493 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5494 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5495 * to decide the final width. 5496 * 5497 * @param minPixels the minimum width of TextView in terms of pixels 5498 * 5499 * @see #getMinWidth() 5500 * @see #setWidth(int) 5501 * 5502 * @attr ref android.R.styleable#TextView_minWidth 5503 */ 5504 @android.view.RemotableViewMethod setMinWidth(int minPixels)5505 public void setMinWidth(int minPixels) { 5506 mMinWidth = minPixels; 5507 mMinWidthMode = PIXELS; 5508 5509 requestLayout(); 5510 invalidate(); 5511 } 5512 5513 /** 5514 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5515 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5516 * 5517 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5518 * defined in pixels 5519 * 5520 * @see #setMinWidth(int) 5521 * @see #setWidth(int) 5522 * 5523 * @attr ref android.R.styleable#TextView_minWidth 5524 */ 5525 @InspectableProperty getMinWidth()5526 public int getMinWidth() { 5527 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5528 } 5529 5530 /** 5531 * Sets the width of the TextView to be at most {@code maxEms} wide. 5532 * <p> 5533 * This value is used for width calculation if LayoutParams does not force TextView to have an 5534 * exact width. Setting this value overrides previous maximum width configurations such as 5535 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5536 * 5537 * @param maxEms the maximum width of TextView in terms of ems 5538 * 5539 * @see #getMaxEms() 5540 * @see #setEms(int) 5541 * 5542 * @attr ref android.R.styleable#TextView_maxEms 5543 */ 5544 @android.view.RemotableViewMethod setMaxEms(int maxEms)5545 public void setMaxEms(int maxEms) { 5546 mMaxWidth = maxEms; 5547 mMaxWidthMode = EMS; 5548 5549 requestLayout(); 5550 invalidate(); 5551 } 5552 5553 /** 5554 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5555 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5556 * 5557 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5558 * defined in ems 5559 * 5560 * @see #setMaxEms(int) 5561 * @see #setEms(int) 5562 * 5563 * @attr ref android.R.styleable#TextView_maxEms 5564 */ 5565 @InspectableProperty getMaxEms()5566 public int getMaxEms() { 5567 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5568 } 5569 5570 /** 5571 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5572 * <p> 5573 * This value is used for width calculation if LayoutParams does not force TextView to have an 5574 * exact width. Setting this value overrides previous maximum width configurations such as 5575 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5576 * 5577 * @param maxPixels the maximum width of TextView in terms of pixels 5578 * 5579 * @see #getMaxWidth() 5580 * @see #setWidth(int) 5581 * 5582 * @attr ref android.R.styleable#TextView_maxWidth 5583 */ 5584 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5585 public void setMaxWidth(int maxPixels) { 5586 mMaxWidth = maxPixels; 5587 mMaxWidthMode = PIXELS; 5588 5589 requestLayout(); 5590 invalidate(); 5591 } 5592 5593 /** 5594 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5595 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5596 * 5597 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5598 * defined in pixels 5599 * 5600 * @see #setMaxWidth(int) 5601 * @see #setWidth(int) 5602 * 5603 * @attr ref android.R.styleable#TextView_maxWidth 5604 */ 5605 @InspectableProperty getMaxWidth()5606 public int getMaxWidth() { 5607 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5608 } 5609 5610 /** 5611 * Sets the width of the TextView to be exactly {@code ems} wide. 5612 * 5613 * This value is used for width calculation if LayoutParams does not force TextView to have an 5614 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5615 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5616 * 5617 * @param ems the exact width of the TextView in terms of ems 5618 * 5619 * @see #setWidth(int) 5620 * 5621 * @attr ref android.R.styleable#TextView_ems 5622 */ 5623 @android.view.RemotableViewMethod setEms(int ems)5624 public void setEms(int ems) { 5625 mMaxWidth = mMinWidth = ems; 5626 mMaxWidthMode = mMinWidthMode = EMS; 5627 5628 requestLayout(); 5629 invalidate(); 5630 } 5631 5632 /** 5633 * Sets the width of the TextView to be exactly {@code pixels} wide. 5634 * <p> 5635 * This value is used for width calculation if LayoutParams does not force TextView to have an 5636 * exact width. Setting this value overrides previous minimum/maximum width configurations 5637 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5638 * 5639 * @param pixels the exact width of the TextView in terms of pixels 5640 * 5641 * @see #setEms(int) 5642 * 5643 * @attr ref android.R.styleable#TextView_width 5644 */ 5645 @android.view.RemotableViewMethod setWidth(int pixels)5646 public void setWidth(int pixels) { 5647 mMaxWidth = mMinWidth = pixels; 5648 mMaxWidthMode = mMinWidthMode = PIXELS; 5649 5650 requestLayout(); 5651 invalidate(); 5652 } 5653 5654 /** 5655 * Sets line spacing for this TextView. Each line other than the last line will have its height 5656 * multiplied by {@code mult} and have {@code add} added to it. 5657 * 5658 * @param add The value in pixels that should be added to each line other than the last line. 5659 * This will be applied after the multiplier 5660 * @param mult The value by which each line height other than the last line will be multiplied 5661 * by 5662 * 5663 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5664 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5665 */ setLineSpacing(float add, float mult)5666 public void setLineSpacing(float add, float mult) { 5667 if (mSpacingAdd != add || mSpacingMult != mult) { 5668 mSpacingAdd = add; 5669 mSpacingMult = mult; 5670 5671 if (mLayout != null) { 5672 nullLayouts(); 5673 requestLayout(); 5674 invalidate(); 5675 } 5676 } 5677 } 5678 5679 /** 5680 * Gets the line spacing multiplier 5681 * 5682 * @return the value by which each line's height is multiplied to get its actual height. 5683 * 5684 * @see #setLineSpacing(float, float) 5685 * @see #getLineSpacingExtra() 5686 * 5687 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5688 */ 5689 @InspectableProperty getLineSpacingMultiplier()5690 public float getLineSpacingMultiplier() { 5691 return mSpacingMult; 5692 } 5693 5694 /** 5695 * Gets the line spacing extra space 5696 * 5697 * @return the extra space that is added to the height of each lines of this TextView. 5698 * 5699 * @see #setLineSpacing(float, float) 5700 * @see #getLineSpacingMultiplier() 5701 * 5702 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5703 */ 5704 @InspectableProperty getLineSpacingExtra()5705 public float getLineSpacingExtra() { 5706 return mSpacingAdd; 5707 } 5708 5709 /** 5710 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 5711 * between subsequent baselines in the TextView. 5712 * 5713 * @param lineHeight the line height in pixels 5714 * 5715 * @see #setLineSpacing(float, float) 5716 * @see #getLineSpacingExtra() 5717 * 5718 * @attr ref android.R.styleable#TextView_lineHeight 5719 */ setLineHeight(@x @ntRangefrom = 0) int lineHeight)5720 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 5721 Preconditions.checkArgumentNonnegative(lineHeight); 5722 5723 final int fontHeight = getPaint().getFontMetricsInt(null); 5724 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 5725 if (lineHeight != fontHeight) { 5726 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 5727 setLineSpacing(lineHeight - fontHeight, 1f); 5728 } 5729 } 5730 5731 /** 5732 * Convenience method to append the specified text to the TextView's 5733 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5734 * if it was not already editable. 5735 * 5736 * @param text text to be appended to the already displayed text 5737 */ append(CharSequence text)5738 public final void append(CharSequence text) { 5739 append(text, 0, text.length()); 5740 } 5741 5742 /** 5743 * Convenience method to append the specified text slice to the TextView's 5744 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5745 * if it was not already editable. 5746 * 5747 * @param text text to be appended to the already displayed text 5748 * @param start the index of the first character in the {@code text} 5749 * @param end the index of the character following the last character in the {@code text} 5750 * 5751 * @see Appendable#append(CharSequence, int, int) 5752 */ append(CharSequence text, int start, int end)5753 public void append(CharSequence text, int start, int end) { 5754 if (!(mText instanceof Editable)) { 5755 setText(mText, BufferType.EDITABLE); 5756 } 5757 5758 ((Editable) mText).append(text, start, end); 5759 5760 if (mAutoLinkMask != 0) { 5761 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 5762 // Do not change the movement method for text that support text selection as it 5763 // would prevent an arbitrary cursor displacement. 5764 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 5765 setMovementMethod(LinkMovementMethod.getInstance()); 5766 } 5767 } 5768 } 5769 updateTextColors()5770 private void updateTextColors() { 5771 boolean inval = false; 5772 final int[] drawableState = getDrawableState(); 5773 int color = mTextColor.getColorForState(drawableState, 0); 5774 if (color != mCurTextColor) { 5775 mCurTextColor = color; 5776 inval = true; 5777 } 5778 if (mLinkTextColor != null) { 5779 color = mLinkTextColor.getColorForState(drawableState, 0); 5780 if (color != mTextPaint.linkColor) { 5781 mTextPaint.linkColor = color; 5782 inval = true; 5783 } 5784 } 5785 if (mHintTextColor != null) { 5786 color = mHintTextColor.getColorForState(drawableState, 0); 5787 if (color != mCurHintTextColor) { 5788 mCurHintTextColor = color; 5789 if (mText.length() == 0) { 5790 inval = true; 5791 } 5792 } 5793 } 5794 if (inval) { 5795 // Text needs to be redrawn with the new color 5796 if (mEditor != null) mEditor.invalidateTextDisplayList(); 5797 invalidate(); 5798 } 5799 } 5800 5801 @Override drawableStateChanged()5802 protected void drawableStateChanged() { 5803 super.drawableStateChanged(); 5804 5805 if (mTextColor != null && mTextColor.isStateful() 5806 || (mHintTextColor != null && mHintTextColor.isStateful()) 5807 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 5808 updateTextColors(); 5809 } 5810 5811 if (mDrawables != null) { 5812 final int[] state = getDrawableState(); 5813 for (Drawable dr : mDrawables.mShowing) { 5814 if (dr != null && dr.isStateful() && dr.setState(state)) { 5815 invalidateDrawable(dr); 5816 } 5817 } 5818 } 5819 } 5820 5821 @Override drawableHotspotChanged(float x, float y)5822 public void drawableHotspotChanged(float x, float y) { 5823 super.drawableHotspotChanged(x, y); 5824 5825 if (mDrawables != null) { 5826 for (Drawable dr : mDrawables.mShowing) { 5827 if (dr != null) { 5828 dr.setHotspot(x, y); 5829 } 5830 } 5831 } 5832 } 5833 5834 @Override onSaveInstanceState()5835 public Parcelable onSaveInstanceState() { 5836 Parcelable superState = super.onSaveInstanceState(); 5837 5838 // Save state if we are forced to 5839 final boolean freezesText = getFreezesText(); 5840 boolean hasSelection = false; 5841 int start = -1; 5842 int end = -1; 5843 5844 if (mText != null) { 5845 start = getSelectionStart(); 5846 end = getSelectionEnd(); 5847 if (start >= 0 || end >= 0) { 5848 // Or save state if there is a selection 5849 hasSelection = true; 5850 } 5851 } 5852 5853 if (freezesText || hasSelection) { 5854 SavedState ss = new SavedState(superState); 5855 5856 if (freezesText) { 5857 if (mText instanceof Spanned) { 5858 final Spannable sp = new SpannableStringBuilder(mText); 5859 5860 if (mEditor != null) { 5861 removeMisspelledSpans(sp); 5862 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5863 } 5864 5865 ss.text = sp; 5866 } else { 5867 ss.text = mText.toString(); 5868 } 5869 } 5870 5871 if (hasSelection) { 5872 // XXX Should also save the current scroll position! 5873 ss.selStart = start; 5874 ss.selEnd = end; 5875 } 5876 5877 if (isFocused() && start >= 0 && end >= 0) { 5878 ss.frozenWithFocus = true; 5879 } 5880 5881 ss.error = getError(); 5882 5883 if (mEditor != null) { 5884 ss.editorState = mEditor.saveInstanceState(); 5885 } 5886 return ss; 5887 } 5888 5889 return superState; 5890 } 5891 removeMisspelledSpans(Spannable spannable)5892 void removeMisspelledSpans(Spannable spannable) { 5893 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 5894 SuggestionSpan.class); 5895 for (int i = 0; i < suggestionSpans.length; i++) { 5896 int flags = suggestionSpans[i].getFlags(); 5897 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 5898 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 5899 spannable.removeSpan(suggestionSpans[i]); 5900 } 5901 } 5902 } 5903 5904 @Override onRestoreInstanceState(Parcelable state)5905 public void onRestoreInstanceState(Parcelable state) { 5906 if (!(state instanceof SavedState)) { 5907 super.onRestoreInstanceState(state); 5908 return; 5909 } 5910 5911 SavedState ss = (SavedState) state; 5912 super.onRestoreInstanceState(ss.getSuperState()); 5913 5914 // XXX restore buffer type too, as well as lots of other stuff 5915 if (ss.text != null) { 5916 setText(ss.text); 5917 } 5918 5919 if (ss.selStart >= 0 && ss.selEnd >= 0) { 5920 if (mSpannable != null) { 5921 int len = mText.length(); 5922 5923 if (ss.selStart > len || ss.selEnd > len) { 5924 String restored = ""; 5925 5926 if (ss.text != null) { 5927 restored = "(restored) "; 5928 } 5929 5930 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 5931 + " out of range for " + restored + "text " + mText); 5932 } else { 5933 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 5934 5935 if (ss.frozenWithFocus) { 5936 createEditorIfNeeded(); 5937 mEditor.mFrozenWithFocus = true; 5938 } 5939 } 5940 } 5941 } 5942 5943 if (ss.error != null) { 5944 final CharSequence error = ss.error; 5945 // Display the error later, after the first layout pass 5946 post(new Runnable() { 5947 public void run() { 5948 if (mEditor == null || !mEditor.mErrorWasChanged) { 5949 setError(error); 5950 } 5951 } 5952 }); 5953 } 5954 5955 if (ss.editorState != null) { 5956 createEditorIfNeeded(); 5957 mEditor.restoreInstanceState(ss.editorState); 5958 } 5959 } 5960 5961 /** 5962 * Control whether this text view saves its entire text contents when 5963 * freezing to an icicle, in addition to dynamic state such as cursor 5964 * position. By default this is false, not saving the text. Set to true 5965 * if the text in the text view is not being saved somewhere else in 5966 * persistent storage (such as in a content provider) so that if the 5967 * view is later thawed the user will not lose their data. For 5968 * {@link android.widget.EditText} it is always enabled, regardless of 5969 * the value of the attribute. 5970 * 5971 * @param freezesText Controls whether a frozen icicle should include the 5972 * entire text data: true to include it, false to not. 5973 * 5974 * @attr ref android.R.styleable#TextView_freezesText 5975 */ 5976 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)5977 public void setFreezesText(boolean freezesText) { 5978 mFreezesText = freezesText; 5979 } 5980 5981 /** 5982 * Return whether this text view is including its entire text contents 5983 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 5984 * 5985 * @return Returns true if text is included, false if it isn't. 5986 * 5987 * @see #setFreezesText 5988 */ 5989 @InspectableProperty getFreezesText()5990 public boolean getFreezesText() { 5991 return mFreezesText; 5992 } 5993 5994 /////////////////////////////////////////////////////////////////////////// 5995 5996 /** 5997 * Sets the Factory used to create new {@link Editable Editables}. 5998 * 5999 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6000 * 6001 * @see android.text.Editable.Factory 6002 * @see android.widget.TextView.BufferType#EDITABLE 6003 */ setEditableFactory(Editable.Factory factory)6004 public final void setEditableFactory(Editable.Factory factory) { 6005 mEditableFactory = factory; 6006 setText(mText); 6007 } 6008 6009 /** 6010 * Sets the Factory used to create new {@link Spannable Spannables}. 6011 * 6012 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6013 * 6014 * @see android.text.Spannable.Factory 6015 * @see android.widget.TextView.BufferType#SPANNABLE 6016 */ setSpannableFactory(Spannable.Factory factory)6017 public final void setSpannableFactory(Spannable.Factory factory) { 6018 mSpannableFactory = factory; 6019 setText(mText); 6020 } 6021 6022 /** 6023 * Sets the text to be displayed. TextView <em>does not</em> accept 6024 * HTML-like formatting, which you can do with text strings in XML resource files. 6025 * To style your strings, attach android.text.style.* objects to a 6026 * {@link android.text.SpannableString}, or see the 6027 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6028 * Available Resource Types</a> documentation for an example of setting 6029 * formatted text in the XML resource file. 6030 * <p/> 6031 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6032 * intermediate {@link Spannable Spannables}. Likewise it will use 6033 * {@link android.text.Editable.Factory} to create final or intermediate 6034 * {@link Editable Editables}. 6035 * 6036 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6037 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6038 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6039 * 6040 * @param text text to be displayed 6041 * 6042 * @attr ref android.R.styleable#TextView_text 6043 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6044 * parameters used to create the PrecomputedText mismatches 6045 * with this TextView. 6046 */ 6047 @android.view.RemotableViewMethod setText(CharSequence text)6048 public final void setText(CharSequence text) { 6049 setText(text, mBufferType); 6050 } 6051 6052 /** 6053 * Sets the text to be displayed but retains the cursor position. Same as 6054 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6055 * new text. 6056 * <p/> 6057 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6058 * intermediate {@link Spannable Spannables}. Likewise it will use 6059 * {@link android.text.Editable.Factory} to create final or intermediate 6060 * {@link Editable Editables}. 6061 * 6062 * @param text text to be displayed 6063 * 6064 * @see #setText(CharSequence) 6065 */ 6066 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6067 public final void setTextKeepState(CharSequence text) { 6068 setTextKeepState(text, mBufferType); 6069 } 6070 6071 /** 6072 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6073 * <p/> 6074 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6075 * intermediate {@link Spannable Spannables}. Likewise it will use 6076 * {@link android.text.Editable.Factory} to create final or intermediate 6077 * {@link Editable Editables}. 6078 * 6079 * Subclasses overriding this method should ensure that the following post condition holds, 6080 * in order to guarantee the safety of the view's measurement and layout operations: 6081 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6082 * will be different from {@code null}. 6083 * 6084 * @param text text to be displayed 6085 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6086 * stored as a static text, styleable/spannable text, or editable text 6087 * 6088 * @see #setText(CharSequence) 6089 * @see android.widget.TextView.BufferType 6090 * @see #setSpannableFactory(Spannable.Factory) 6091 * @see #setEditableFactory(Editable.Factory) 6092 * 6093 * @attr ref android.R.styleable#TextView_text 6094 * @attr ref android.R.styleable#TextView_bufferType 6095 */ setText(CharSequence text, BufferType type)6096 public void setText(CharSequence text, BufferType type) { 6097 setText(text, type, true, 0); 6098 6099 if (mCharWrapper != null) { 6100 mCharWrapper.mChars = null; 6101 } 6102 } 6103 6104 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6105 private void setText(CharSequence text, BufferType type, 6106 boolean notifyBefore, int oldlen) { 6107 mTextSetFromXmlOrResourceId = false; 6108 if (text == null) { 6109 text = ""; 6110 } 6111 6112 // If suggestions are not enabled, remove the suggestion spans from the text 6113 if (!isSuggestionsEnabled()) { 6114 text = removeSuggestionSpans(text); 6115 } 6116 6117 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6118 6119 if (text instanceof Spanned 6120 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6121 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6122 setHorizontalFadingEdgeEnabled(true); 6123 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6124 } else { 6125 setHorizontalFadingEdgeEnabled(false); 6126 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6127 } 6128 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6129 } 6130 6131 int n = mFilters.length; 6132 for (int i = 0; i < n; i++) { 6133 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6134 if (out != null) { 6135 text = out; 6136 } 6137 } 6138 6139 if (notifyBefore) { 6140 if (mText != null) { 6141 oldlen = mText.length(); 6142 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6143 } else { 6144 sendBeforeTextChanged("", 0, 0, text.length()); 6145 } 6146 } 6147 6148 boolean needEditableForNotification = false; 6149 6150 if (mListeners != null && mListeners.size() != 0) { 6151 needEditableForNotification = true; 6152 } 6153 6154 PrecomputedText precomputed = 6155 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6156 if (type == BufferType.EDITABLE || getKeyListener() != null 6157 || needEditableForNotification) { 6158 createEditorIfNeeded(); 6159 mEditor.forgetUndoRedo(); 6160 Editable t = mEditableFactory.newEditable(text); 6161 text = t; 6162 setFilters(t, mFilters); 6163 InputMethodManager imm = getInputMethodManager(); 6164 if (imm != null) imm.restartInput(this); 6165 } else if (precomputed != null) { 6166 if (mTextDir == null) { 6167 mTextDir = getTextDirectionHeuristic(); 6168 } 6169 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6170 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6171 mHyphenationFrequency); 6172 switch (checkResult) { 6173 case PrecomputedText.Params.UNUSABLE: 6174 throw new IllegalArgumentException( 6175 "PrecomputedText's Parameters don't match the parameters of this TextView." 6176 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6177 + "to override the settings of this TextView: " 6178 + "PrecomputedText: " + precomputed.getParams() 6179 + "TextView: " + getTextMetricsParams()); 6180 case PrecomputedText.Params.NEED_RECOMPUTE: 6181 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6182 break; 6183 case PrecomputedText.Params.USABLE: 6184 // pass through 6185 } 6186 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6187 text = mSpannableFactory.newSpannable(text); 6188 } else if (!(text instanceof CharWrapper)) { 6189 text = TextUtils.stringOrSpannedString(text); 6190 } 6191 6192 if (mAutoLinkMask != 0) { 6193 Spannable s2; 6194 6195 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6196 s2 = (Spannable) text; 6197 } else { 6198 s2 = mSpannableFactory.newSpannable(text); 6199 } 6200 6201 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6202 text = s2; 6203 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6204 6205 /* 6206 * We must go ahead and set the text before changing the 6207 * movement method, because setMovementMethod() may call 6208 * setText() again to try to upgrade the buffer type. 6209 */ 6210 setTextInternal(text); 6211 6212 // Do not change the movement method for text that support text selection as it 6213 // would prevent an arbitrary cursor displacement. 6214 if (mLinksClickable && !textCanBeSelected()) { 6215 setMovementMethod(LinkMovementMethod.getInstance()); 6216 } 6217 } 6218 } 6219 6220 mBufferType = type; 6221 setTextInternal(text); 6222 6223 if (mTransformation == null) { 6224 mTransformed = text; 6225 } else { 6226 mTransformed = mTransformation.getTransformation(text, this); 6227 } 6228 if (mTransformed == null) { 6229 // Should not happen if the transformation method follows the non-null postcondition. 6230 mTransformed = ""; 6231 } 6232 6233 final int textLength = text.length(); 6234 6235 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6236 Spannable sp = (Spannable) text; 6237 6238 // Remove any ChangeWatchers that might have come from other TextViews. 6239 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6240 final int count = watchers.length; 6241 for (int i = 0; i < count; i++) { 6242 sp.removeSpan(watchers[i]); 6243 } 6244 6245 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6246 6247 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6248 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6249 6250 if (mEditor != null) mEditor.addSpanWatchers(sp); 6251 6252 if (mTransformation != null) { 6253 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6254 } 6255 6256 if (mMovement != null) { 6257 mMovement.initialize(this, (Spannable) text); 6258 6259 /* 6260 * Initializing the movement method will have set the 6261 * selection, so reset mSelectionMoved to keep that from 6262 * interfering with the normal on-focus selection-setting. 6263 */ 6264 if (mEditor != null) mEditor.mSelectionMoved = false; 6265 } 6266 } 6267 6268 if (mLayout != null) { 6269 checkForRelayout(); 6270 } 6271 6272 sendOnTextChanged(text, 0, oldlen, textLength); 6273 onTextChanged(text, 0, oldlen, textLength); 6274 6275 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6276 6277 if (needEditableForNotification) { 6278 sendAfterTextChanged((Editable) text); 6279 } else { 6280 notifyListeningManagersAfterTextChanged(); 6281 } 6282 6283 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6284 if (mEditor != null) mEditor.prepareCursorControllers(); 6285 } 6286 6287 /** 6288 * Sets the TextView to display the specified slice of the specified 6289 * char array. You must promise that you will not change the contents 6290 * of the array except for right before another call to setText(), 6291 * since the TextView has no way to know that the text 6292 * has changed and that it needs to invalidate and re-layout. 6293 * 6294 * @param text char array to be displayed 6295 * @param start start index in the char array 6296 * @param len length of char count after {@code start} 6297 */ setText(char[] text, int start, int len)6298 public final void setText(char[] text, int start, int len) { 6299 int oldlen = 0; 6300 6301 if (start < 0 || len < 0 || start + len > text.length) { 6302 throw new IndexOutOfBoundsException(start + ", " + len); 6303 } 6304 6305 /* 6306 * We must do the before-notification here ourselves because if 6307 * the old text is a CharWrapper we destroy it before calling 6308 * into the normal path. 6309 */ 6310 if (mText != null) { 6311 oldlen = mText.length(); 6312 sendBeforeTextChanged(mText, 0, oldlen, len); 6313 } else { 6314 sendBeforeTextChanged("", 0, 0, len); 6315 } 6316 6317 if (mCharWrapper == null) { 6318 mCharWrapper = new CharWrapper(text, start, len); 6319 } else { 6320 mCharWrapper.set(text, start, len); 6321 } 6322 6323 setText(mCharWrapper, mBufferType, false, oldlen); 6324 } 6325 6326 /** 6327 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6328 * the cursor position. Same as 6329 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6330 * position (if any) is retained in the new text. 6331 * <p/> 6332 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6333 * intermediate {@link Spannable Spannables}. Likewise it will use 6334 * {@link android.text.Editable.Factory} to create final or intermediate 6335 * {@link Editable Editables}. 6336 * 6337 * @param text text to be displayed 6338 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6339 * stored as a static text, styleable/spannable text, or editable text 6340 * 6341 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6342 */ setTextKeepState(CharSequence text, BufferType type)6343 public final void setTextKeepState(CharSequence text, BufferType type) { 6344 int start = getSelectionStart(); 6345 int end = getSelectionEnd(); 6346 int len = text.length(); 6347 6348 setText(text, type); 6349 6350 if (start >= 0 || end >= 0) { 6351 if (mSpannable != null) { 6352 Selection.setSelection(mSpannable, 6353 Math.max(0, Math.min(start, len)), 6354 Math.max(0, Math.min(end, len))); 6355 } 6356 } 6357 } 6358 6359 /** 6360 * Sets the text to be displayed using a string resource identifier. 6361 * 6362 * @param resid the resource identifier of the string resource to be displayed 6363 * 6364 * @see #setText(CharSequence) 6365 * 6366 * @attr ref android.R.styleable#TextView_text 6367 */ 6368 @android.view.RemotableViewMethod setText(@tringRes int resid)6369 public final void setText(@StringRes int resid) { 6370 setText(getContext().getResources().getText(resid)); 6371 mTextSetFromXmlOrResourceId = true; 6372 mTextId = resid; 6373 } 6374 6375 /** 6376 * Sets the text to be displayed using a string resource identifier and the 6377 * {@link android.widget.TextView.BufferType}. 6378 * <p/> 6379 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6380 * intermediate {@link Spannable Spannables}. Likewise it will use 6381 * {@link android.text.Editable.Factory} to create final or intermediate 6382 * {@link Editable Editables}. 6383 * 6384 * @param resid the resource identifier of the string resource to be displayed 6385 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6386 * stored as a static text, styleable/spannable text, or editable text 6387 * 6388 * @see #setText(int) 6389 * @see #setText(CharSequence) 6390 * @see android.widget.TextView.BufferType 6391 * @see #setSpannableFactory(Spannable.Factory) 6392 * @see #setEditableFactory(Editable.Factory) 6393 * 6394 * @attr ref android.R.styleable#TextView_text 6395 * @attr ref android.R.styleable#TextView_bufferType 6396 */ setText(@tringRes int resid, BufferType type)6397 public final void setText(@StringRes int resid, BufferType type) { 6398 setText(getContext().getResources().getText(resid), type); 6399 mTextSetFromXmlOrResourceId = true; 6400 mTextId = resid; 6401 } 6402 6403 /** 6404 * Sets the text to be displayed when the text of the TextView is empty. 6405 * Null means to use the normal empty text. The hint does not currently 6406 * participate in determining the size of the view. 6407 * 6408 * @attr ref android.R.styleable#TextView_hint 6409 */ 6410 @android.view.RemotableViewMethod setHint(CharSequence hint)6411 public final void setHint(CharSequence hint) { 6412 setHintInternal(hint); 6413 6414 if (mEditor != null && isInputMethodTarget()) { 6415 mEditor.reportExtractedText(); 6416 } 6417 } 6418 setHintInternal(CharSequence hint)6419 private void setHintInternal(CharSequence hint) { 6420 mHint = TextUtils.stringOrSpannedString(hint); 6421 6422 if (mLayout != null) { 6423 checkForRelayout(); 6424 } 6425 6426 if (mText.length() == 0) { 6427 invalidate(); 6428 } 6429 6430 // Invalidate display list if hint is currently used 6431 if (mEditor != null && mText.length() == 0 && mHint != null) { 6432 mEditor.invalidateTextDisplayList(); 6433 } 6434 } 6435 6436 /** 6437 * Sets the text to be displayed when the text of the TextView is empty, 6438 * from a resource. 6439 * 6440 * @attr ref android.R.styleable#TextView_hint 6441 */ 6442 @android.view.RemotableViewMethod setHint(@tringRes int resid)6443 public final void setHint(@StringRes int resid) { 6444 setHint(getContext().getResources().getText(resid)); 6445 } 6446 6447 /** 6448 * Returns the hint that is displayed when the text of the TextView 6449 * is empty. 6450 * 6451 * @attr ref android.R.styleable#TextView_hint 6452 */ 6453 @InspectableProperty 6454 @ViewDebug.CapturedViewProperty getHint()6455 public CharSequence getHint() { 6456 return mHint; 6457 } 6458 6459 /** 6460 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6461 * line characters instead of letting it wrap onto multiple lines. 6462 * 6463 * @attr ref android.R.styleable#TextView_singleLine 6464 */ 6465 @InspectableProperty isSingleLine()6466 public boolean isSingleLine() { 6467 return mSingleLine; 6468 } 6469 isMultilineInputType(int type)6470 private static boolean isMultilineInputType(int type) { 6471 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6472 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6473 } 6474 6475 /** 6476 * Removes the suggestion spans. 6477 */ removeSuggestionSpans(CharSequence text)6478 CharSequence removeSuggestionSpans(CharSequence text) { 6479 if (text instanceof Spanned) { 6480 Spannable spannable; 6481 if (text instanceof Spannable) { 6482 spannable = (Spannable) text; 6483 } else { 6484 spannable = mSpannableFactory.newSpannable(text); 6485 } 6486 6487 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6488 if (spans.length == 0) { 6489 return text; 6490 } else { 6491 text = spannable; 6492 } 6493 6494 for (int i = 0; i < spans.length; i++) { 6495 spannable.removeSpan(spans[i]); 6496 } 6497 } 6498 return text; 6499 } 6500 6501 /** 6502 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6503 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6504 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6505 * then a soft keyboard will not be displayed for this text view. 6506 * 6507 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6508 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6509 * type. 6510 * 6511 * @see #getInputType() 6512 * @see #setRawInputType(int) 6513 * @see android.text.InputType 6514 * @attr ref android.R.styleable#TextView_inputType 6515 */ setInputType(int type)6516 public void setInputType(int type) { 6517 final boolean wasPassword = isPasswordInputType(getInputType()); 6518 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6519 setInputType(type, false); 6520 final boolean isPassword = isPasswordInputType(type); 6521 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6522 boolean forceUpdate = false; 6523 if (isPassword) { 6524 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6525 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6526 Typeface.NORMAL, -1 /* weight, not specifeid */); 6527 } else if (isVisiblePassword) { 6528 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6529 forceUpdate = true; 6530 } 6531 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6532 Typeface.NORMAL, -1 /* weight, not specified */); 6533 } else if (wasPassword || wasVisiblePassword) { 6534 // not in password mode, clean up typeface and transformation 6535 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6536 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6537 -1 /* weight, not specified */); 6538 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6539 forceUpdate = true; 6540 } 6541 } 6542 6543 boolean singleLine = !isMultilineInputType(type); 6544 6545 // We need to update the single line mode if it has changed or we 6546 // were previously in password mode. 6547 if (mSingleLine != singleLine || forceUpdate) { 6548 // Change single line mode, but only change the transformation if 6549 // we are not in password mode. 6550 applySingleLine(singleLine, !isPassword, true); 6551 } 6552 6553 if (!isSuggestionsEnabled()) { 6554 setTextInternal(removeSuggestionSpans(mText)); 6555 } 6556 6557 InputMethodManager imm = getInputMethodManager(); 6558 if (imm != null) imm.restartInput(this); 6559 } 6560 6561 /** 6562 * It would be better to rely on the input type for everything. A password inputType should have 6563 * a password transformation. We should hence use isPasswordInputType instead of this method. 6564 * 6565 * We should: 6566 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6567 * would install the correct transformation). 6568 * - Refuse the installation of a non-password transformation in setTransformation if the input 6569 * type is password. 6570 * 6571 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6572 * is useful since it matches what the user can see (obfuscated text or not). 6573 * 6574 * @return true if the current transformation method is of the password type. 6575 */ hasPasswordTransformationMethod()6576 boolean hasPasswordTransformationMethod() { 6577 return mTransformation instanceof PasswordTransformationMethod; 6578 } 6579 6580 /** 6581 * Returns true if the current inputType is any type of password. 6582 * 6583 * @hide 6584 */ isAnyPasswordInputType()6585 public boolean isAnyPasswordInputType() { 6586 final int inputType = getInputType(); 6587 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6588 } 6589 isPasswordInputType(int inputType)6590 static boolean isPasswordInputType(int inputType) { 6591 final int variation = 6592 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6593 return variation 6594 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6595 || variation 6596 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6597 || variation 6598 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6599 } 6600 isVisiblePasswordInputType(int inputType)6601 private static boolean isVisiblePasswordInputType(int inputType) { 6602 final int variation = 6603 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6604 return variation 6605 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6606 } 6607 6608 /** 6609 * Directly change the content type integer of the text view, without 6610 * modifying any other state. 6611 * @see #setInputType(int) 6612 * @see android.text.InputType 6613 * @attr ref android.R.styleable#TextView_inputType 6614 */ setRawInputType(int type)6615 public void setRawInputType(int type) { 6616 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6617 createEditorIfNeeded(); 6618 mEditor.mInputType = type; 6619 } 6620 6621 /** 6622 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6623 * a {@code Locale} object that can be used to customize key various listeners. 6624 * @see DateKeyListener#getInstance(Locale) 6625 * @see DateTimeKeyListener#getInstance(Locale) 6626 * @see DigitsKeyListener#getInstance(Locale) 6627 * @see TimeKeyListener#getInstance(Locale) 6628 */ 6629 @Nullable getCustomLocaleForKeyListenerOrNull()6630 private Locale getCustomLocaleForKeyListenerOrNull() { 6631 if (!mUseInternationalizedInput) { 6632 // If the application does not target O, stick to the previous behavior. 6633 return null; 6634 } 6635 final LocaleList locales = getImeHintLocales(); 6636 if (locales == null) { 6637 // If the application does not explicitly specify IME hint locale, also stick to the 6638 // previous behavior. 6639 return null; 6640 } 6641 return locales.get(0); 6642 } 6643 6644 @UnsupportedAppUsage setInputType(int type, boolean direct)6645 private void setInputType(int type, boolean direct) { 6646 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6647 KeyListener input; 6648 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6649 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6650 TextKeyListener.Capitalize cap; 6651 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6652 cap = TextKeyListener.Capitalize.CHARACTERS; 6653 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6654 cap = TextKeyListener.Capitalize.WORDS; 6655 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6656 cap = TextKeyListener.Capitalize.SENTENCES; 6657 } else { 6658 cap = TextKeyListener.Capitalize.NONE; 6659 } 6660 input = TextKeyListener.getInstance(autotext, cap); 6661 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6662 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6663 input = DigitsKeyListener.getInstance( 6664 locale, 6665 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6666 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6667 if (locale != null) { 6668 // Override type, if necessary for i18n. 6669 int newType = input.getInputType(); 6670 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6671 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6672 // The class is different from the original class. So we need to override 6673 // 'type'. But we want to keep the password flag if it's there. 6674 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6675 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6676 } 6677 type = newType; 6678 } 6679 } 6680 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6681 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6682 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6683 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6684 input = DateKeyListener.getInstance(locale); 6685 break; 6686 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6687 input = TimeKeyListener.getInstance(locale); 6688 break; 6689 default: 6690 input = DateTimeKeyListener.getInstance(locale); 6691 break; 6692 } 6693 if (mUseInternationalizedInput) { 6694 type = input.getInputType(); // Override type, if necessary for i18n. 6695 } 6696 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6697 input = DialerKeyListener.getInstance(); 6698 } else { 6699 input = TextKeyListener.getInstance(); 6700 } 6701 setRawInputType(type); 6702 mListenerChanged = false; 6703 if (direct) { 6704 createEditorIfNeeded(); 6705 mEditor.mKeyListener = input; 6706 } else { 6707 setKeyListenerOnly(input); 6708 } 6709 } 6710 6711 /** 6712 * Get the type of the editable content. 6713 * 6714 * @see #setInputType(int) 6715 * @see android.text.InputType 6716 */ 6717 @InspectableProperty(flagMapping = { 6718 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6719 @FlagEntry( 6720 name = "text", 6721 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6722 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6723 @FlagEntry( 6724 name = "textUri", 6725 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6726 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6727 @FlagEntry( 6728 name = "textEmailAddress", 6729 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6730 target = InputType.TYPE_CLASS_TEXT 6731 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6732 @FlagEntry( 6733 name = "textEmailSubject", 6734 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6735 target = InputType.TYPE_CLASS_TEXT 6736 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6737 @FlagEntry( 6738 name = "textShortMessage", 6739 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6740 target = InputType.TYPE_CLASS_TEXT 6741 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6742 @FlagEntry( 6743 name = "textLongMessage", 6744 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6745 target = InputType.TYPE_CLASS_TEXT 6746 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6747 @FlagEntry( 6748 name = "textPersonName", 6749 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6750 target = InputType.TYPE_CLASS_TEXT 6751 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6752 @FlagEntry( 6753 name = "textPostalAddress", 6754 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6755 target = InputType.TYPE_CLASS_TEXT 6756 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6757 @FlagEntry( 6758 name = "textPassword", 6759 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6760 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6761 @FlagEntry( 6762 name = "textVisiblePassword", 6763 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6764 target = InputType.TYPE_CLASS_TEXT 6765 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6766 @FlagEntry( 6767 name = "textWebEditText", 6768 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6769 target = InputType.TYPE_CLASS_TEXT 6770 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6771 @FlagEntry( 6772 name = "textFilter", 6773 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6774 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6775 @FlagEntry( 6776 name = "textPhonetic", 6777 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6778 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6779 @FlagEntry( 6780 name = "textWebEmailAddress", 6781 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6782 target = InputType.TYPE_CLASS_TEXT 6783 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6784 @FlagEntry( 6785 name = "textWebPassword", 6786 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6787 target = InputType.TYPE_CLASS_TEXT 6788 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6789 @FlagEntry( 6790 name = "number", 6791 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6792 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6793 @FlagEntry( 6794 name = "numberPassword", 6795 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6796 target = InputType.TYPE_CLASS_NUMBER 6797 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6798 @FlagEntry( 6799 name = "phone", 6800 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6801 target = InputType.TYPE_CLASS_PHONE), 6802 @FlagEntry( 6803 name = "datetime", 6804 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6805 target = InputType.TYPE_CLASS_DATETIME 6806 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6807 @FlagEntry( 6808 name = "date", 6809 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6810 target = InputType.TYPE_CLASS_DATETIME 6811 | InputType.TYPE_DATETIME_VARIATION_DATE), 6812 @FlagEntry( 6813 name = "time", 6814 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6815 target = InputType.TYPE_CLASS_DATETIME 6816 | InputType.TYPE_DATETIME_VARIATION_TIME), 6817 @FlagEntry( 6818 name = "textCapCharacters", 6819 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6820 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6821 @FlagEntry( 6822 name = "textCapWords", 6823 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6824 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6825 @FlagEntry( 6826 name = "textCapSentences", 6827 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6828 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6829 @FlagEntry( 6830 name = "textAutoCorrect", 6831 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6832 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6833 @FlagEntry( 6834 name = "textAutoComplete", 6835 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6836 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6837 @FlagEntry( 6838 name = "textMultiLine", 6839 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6840 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6841 @FlagEntry( 6842 name = "textImeMultiLine", 6843 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6844 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6845 @FlagEntry( 6846 name = "textNoSuggestions", 6847 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6848 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6849 @FlagEntry( 6850 name = "numberSigned", 6851 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6852 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6853 @FlagEntry( 6854 name = "numberDecimal", 6855 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6856 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6857 }) getInputType()6858 public int getInputType() { 6859 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6860 } 6861 6862 /** 6863 * Change the editor type integer associated with the text view, which 6864 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6865 * when it has focus. 6866 * @see #getImeOptions 6867 * @see android.view.inputmethod.EditorInfo 6868 * @attr ref android.R.styleable#TextView_imeOptions 6869 */ setImeOptions(int imeOptions)6870 public void setImeOptions(int imeOptions) { 6871 createEditorIfNeeded(); 6872 mEditor.createInputContentTypeIfNeeded(); 6873 mEditor.mInputContentType.imeOptions = imeOptions; 6874 } 6875 6876 /** 6877 * Get the type of the Input Method Editor (IME). 6878 * @return the type of the IME 6879 * @see #setImeOptions(int) 6880 * @see EditorInfo 6881 */ 6882 @InspectableProperty(flagMapping = { 6883 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 6884 @FlagEntry( 6885 name = "actionUnspecified", 6886 mask = EditorInfo.IME_MASK_ACTION, 6887 target = EditorInfo.IME_ACTION_UNSPECIFIED), 6888 @FlagEntry( 6889 name = "actionNone", 6890 mask = EditorInfo.IME_MASK_ACTION, 6891 target = EditorInfo.IME_ACTION_NONE), 6892 @FlagEntry( 6893 name = "actionGo", 6894 mask = EditorInfo.IME_MASK_ACTION, 6895 target = EditorInfo.IME_ACTION_GO), 6896 @FlagEntry( 6897 name = "actionSearch", 6898 mask = EditorInfo.IME_MASK_ACTION, 6899 target = EditorInfo.IME_ACTION_SEARCH), 6900 @FlagEntry( 6901 name = "actionSend", 6902 mask = EditorInfo.IME_MASK_ACTION, 6903 target = EditorInfo.IME_ACTION_SEND), 6904 @FlagEntry( 6905 name = "actionNext", 6906 mask = EditorInfo.IME_MASK_ACTION, 6907 target = EditorInfo.IME_ACTION_NEXT), 6908 @FlagEntry( 6909 name = "actionDone", 6910 mask = EditorInfo.IME_MASK_ACTION, 6911 target = EditorInfo.IME_ACTION_DONE), 6912 @FlagEntry( 6913 name = "actionPrevious", 6914 mask = EditorInfo.IME_MASK_ACTION, 6915 target = EditorInfo.IME_ACTION_PREVIOUS), 6916 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 6917 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 6918 @FlagEntry( 6919 name = "flagNavigatePrevious", 6920 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 6921 @FlagEntry( 6922 name = "flagNoAccessoryAction", 6923 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 6924 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 6925 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 6926 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 6927 @FlagEntry( 6928 name = "flagNoPersonalizedLearning", 6929 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 6930 }) getImeOptions()6931 public int getImeOptions() { 6932 return mEditor != null && mEditor.mInputContentType != null 6933 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 6934 } 6935 6936 /** 6937 * Change the custom IME action associated with the text view, which 6938 * will be reported to an IME with {@link EditorInfo#actionLabel} 6939 * and {@link EditorInfo#actionId} when it has focus. 6940 * @see #getImeActionLabel 6941 * @see #getImeActionId 6942 * @see android.view.inputmethod.EditorInfo 6943 * @attr ref android.R.styleable#TextView_imeActionLabel 6944 * @attr ref android.R.styleable#TextView_imeActionId 6945 */ setImeActionLabel(CharSequence label, int actionId)6946 public void setImeActionLabel(CharSequence label, int actionId) { 6947 createEditorIfNeeded(); 6948 mEditor.createInputContentTypeIfNeeded(); 6949 mEditor.mInputContentType.imeActionLabel = label; 6950 mEditor.mInputContentType.imeActionId = actionId; 6951 } 6952 6953 /** 6954 * Get the IME action label previous set with {@link #setImeActionLabel}. 6955 * 6956 * @see #setImeActionLabel 6957 * @see android.view.inputmethod.EditorInfo 6958 */ 6959 @InspectableProperty getImeActionLabel()6960 public CharSequence getImeActionLabel() { 6961 return mEditor != null && mEditor.mInputContentType != null 6962 ? mEditor.mInputContentType.imeActionLabel : null; 6963 } 6964 6965 /** 6966 * Get the IME action ID previous set with {@link #setImeActionLabel}. 6967 * 6968 * @see #setImeActionLabel 6969 * @see android.view.inputmethod.EditorInfo 6970 */ 6971 @InspectableProperty getImeActionId()6972 public int getImeActionId() { 6973 return mEditor != null && mEditor.mInputContentType != null 6974 ? mEditor.mInputContentType.imeActionId : 0; 6975 } 6976 6977 /** 6978 * Set a special listener to be called when an action is performed 6979 * on the text view. This will be called when the enter key is pressed, 6980 * or when an action supplied to the IME is selected by the user. Setting 6981 * this means that the normal hard key event will not insert a newline 6982 * into the text view, even if it is multi-line; holding down the ALT 6983 * modifier will, however, allow the user to insert a newline character. 6984 */ setOnEditorActionListener(OnEditorActionListener l)6985 public void setOnEditorActionListener(OnEditorActionListener l) { 6986 createEditorIfNeeded(); 6987 mEditor.createInputContentTypeIfNeeded(); 6988 mEditor.mInputContentType.onEditorActionListener = l; 6989 } 6990 6991 /** 6992 * Called when an attached input method calls 6993 * {@link InputConnection#performEditorAction(int) 6994 * InputConnection.performEditorAction()} 6995 * for this text view. The default implementation will call your action 6996 * listener supplied to {@link #setOnEditorActionListener}, or perform 6997 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 6998 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 6999 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7000 * EditorInfo.IME_ACTION_DONE}. 7001 * 7002 * <p>For backwards compatibility, if no IME options have been set and the 7003 * text view would not normally advance focus on enter, then 7004 * the NEXT and DONE actions received here will be turned into an enter 7005 * key down/up pair to go through the normal key handling. 7006 * 7007 * @param actionCode The code of the action being performed. 7008 * 7009 * @see #setOnEditorActionListener 7010 */ onEditorAction(int actionCode)7011 public void onEditorAction(int actionCode) { 7012 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7013 if (ict != null) { 7014 if (ict.onEditorActionListener != null) { 7015 if (ict.onEditorActionListener.onEditorAction(this, 7016 actionCode, null)) { 7017 return; 7018 } 7019 } 7020 7021 // This is the handling for some default action. 7022 // Note that for backwards compatibility we don't do this 7023 // default handling if explicit ime options have not been given, 7024 // instead turning this into the normal enter key codes that an 7025 // app may be expecting. 7026 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7027 View v = focusSearch(FOCUS_FORWARD); 7028 if (v != null) { 7029 if (!v.requestFocus(FOCUS_FORWARD)) { 7030 throw new IllegalStateException("focus search returned a view " 7031 + "that wasn't able to take focus!"); 7032 } 7033 } 7034 return; 7035 7036 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7037 View v = focusSearch(FOCUS_BACKWARD); 7038 if (v != null) { 7039 if (!v.requestFocus(FOCUS_BACKWARD)) { 7040 throw new IllegalStateException("focus search returned a view " 7041 + "that wasn't able to take focus!"); 7042 } 7043 } 7044 return; 7045 7046 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7047 InputMethodManager imm = getInputMethodManager(); 7048 if (imm != null && imm.isActive(this)) { 7049 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7050 } 7051 return; 7052 } 7053 } 7054 7055 ViewRootImpl viewRootImpl = getViewRootImpl(); 7056 if (viewRootImpl != null) { 7057 long eventTime = SystemClock.uptimeMillis(); 7058 viewRootImpl.dispatchKeyFromIme( 7059 new KeyEvent(eventTime, eventTime, 7060 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7061 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7062 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7063 | KeyEvent.FLAG_EDITOR_ACTION)); 7064 viewRootImpl.dispatchKeyFromIme( 7065 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7066 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7067 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7068 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7069 | KeyEvent.FLAG_EDITOR_ACTION)); 7070 } 7071 } 7072 7073 /** 7074 * Set the private content type of the text, which is the 7075 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7076 * field that will be filled in when creating an input connection. 7077 * 7078 * @see #getPrivateImeOptions() 7079 * @see EditorInfo#privateImeOptions 7080 * @attr ref android.R.styleable#TextView_privateImeOptions 7081 */ setPrivateImeOptions(String type)7082 public void setPrivateImeOptions(String type) { 7083 createEditorIfNeeded(); 7084 mEditor.createInputContentTypeIfNeeded(); 7085 mEditor.mInputContentType.privateImeOptions = type; 7086 } 7087 7088 /** 7089 * Get the private type of the content. 7090 * 7091 * @see #setPrivateImeOptions(String) 7092 * @see EditorInfo#privateImeOptions 7093 */ 7094 @InspectableProperty getPrivateImeOptions()7095 public String getPrivateImeOptions() { 7096 return mEditor != null && mEditor.mInputContentType != null 7097 ? mEditor.mInputContentType.privateImeOptions : null; 7098 } 7099 7100 /** 7101 * Set the extra input data of the text, which is the 7102 * {@link EditorInfo#extras TextBoxAttribute.extras} 7103 * Bundle that will be filled in when creating an input connection. The 7104 * given integer is the resource identifier of an XML resource holding an 7105 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7106 * 7107 * @see #getInputExtras(boolean) 7108 * @see EditorInfo#extras 7109 * @attr ref android.R.styleable#TextView_editorExtras 7110 */ setInputExtras(@mlRes int xmlResId)7111 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7112 createEditorIfNeeded(); 7113 XmlResourceParser parser = getResources().getXml(xmlResId); 7114 mEditor.createInputContentTypeIfNeeded(); 7115 mEditor.mInputContentType.extras = new Bundle(); 7116 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7117 } 7118 7119 /** 7120 * Retrieve the input extras currently associated with the text view, which 7121 * can be viewed as well as modified. 7122 * 7123 * @param create If true, the extras will be created if they don't already 7124 * exist. Otherwise, null will be returned if none have been created. 7125 * @see #setInputExtras(int) 7126 * @see EditorInfo#extras 7127 * @attr ref android.R.styleable#TextView_editorExtras 7128 */ getInputExtras(boolean create)7129 public Bundle getInputExtras(boolean create) { 7130 if (mEditor == null && !create) return null; 7131 createEditorIfNeeded(); 7132 if (mEditor.mInputContentType == null) { 7133 if (!create) return null; 7134 mEditor.createInputContentTypeIfNeeded(); 7135 } 7136 if (mEditor.mInputContentType.extras == null) { 7137 if (!create) return null; 7138 mEditor.mInputContentType.extras = new Bundle(); 7139 } 7140 return mEditor.mInputContentType.extras; 7141 } 7142 7143 /** 7144 * Change "hint" locales associated with the text view, which will be reported to an IME with 7145 * {@link EditorInfo#hintLocales} when it has focus. 7146 * 7147 * Starting with Android O, this also causes internationalized listeners to be created (or 7148 * change locale) based on the first locale in the input locale list. 7149 * 7150 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7151 * call {@link InputMethodManager#restartInput(View)}.</p> 7152 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7153 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7154 * @see #getImeHintLocales() 7155 * @see android.view.inputmethod.EditorInfo#hintLocales 7156 */ setImeHintLocales(@ullable LocaleList hintLocales)7157 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7158 createEditorIfNeeded(); 7159 mEditor.createInputContentTypeIfNeeded(); 7160 mEditor.mInputContentType.imeHintLocales = hintLocales; 7161 if (mUseInternationalizedInput) { 7162 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7163 } 7164 } 7165 7166 /** 7167 * @return The current languages list "hint". {@code null} when no "hint" is available. 7168 * @see #setImeHintLocales(LocaleList) 7169 * @see android.view.inputmethod.EditorInfo#hintLocales 7170 */ 7171 @Nullable getImeHintLocales()7172 public LocaleList getImeHintLocales() { 7173 if (mEditor == null) { 7174 return null; 7175 } 7176 if (mEditor.mInputContentType == null) { 7177 return null; 7178 } 7179 return mEditor.mInputContentType.imeHintLocales; 7180 } 7181 7182 /** 7183 * Returns the error message that was set to be displayed with 7184 * {@link #setError}, or <code>null</code> if no error was set 7185 * or if it the error was cleared by the widget after user input. 7186 */ getError()7187 public CharSequence getError() { 7188 return mEditor == null ? null : mEditor.mError; 7189 } 7190 7191 /** 7192 * Sets the right-hand compound drawable of the TextView to the "error" 7193 * icon and sets an error message that will be displayed in a popup when 7194 * the TextView has focus. The icon and error message will be reset to 7195 * null when any key events cause changes to the TextView's text. If the 7196 * <code>error</code> is <code>null</code>, the error message and icon 7197 * will be cleared. 7198 */ 7199 @android.view.RemotableViewMethod setError(CharSequence error)7200 public void setError(CharSequence error) { 7201 if (error == null) { 7202 setError(null, null); 7203 } else { 7204 Drawable dr = getContext().getDrawable( 7205 com.android.internal.R.drawable.indicator_input_error); 7206 7207 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7208 setError(error, dr); 7209 } 7210 } 7211 7212 /** 7213 * Sets the right-hand compound drawable of the TextView to the specified 7214 * icon and sets an error message that will be displayed in a popup when 7215 * the TextView has focus. The icon and error message will be reset to 7216 * null when any key events cause changes to the TextView's text. The 7217 * drawable must already have had {@link Drawable#setBounds} set on it. 7218 * If the <code>error</code> is <code>null</code>, the error message will 7219 * be cleared (and you should provide a <code>null</code> icon as well). 7220 */ setError(CharSequence error, Drawable icon)7221 public void setError(CharSequence error, Drawable icon) { 7222 createEditorIfNeeded(); 7223 mEditor.setError(error, icon); 7224 notifyViewAccessibilityStateChangedIfNeeded( 7225 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7226 } 7227 7228 @Override setFrame(int l, int t, int r, int b)7229 protected boolean setFrame(int l, int t, int r, int b) { 7230 boolean result = super.setFrame(l, t, r, b); 7231 7232 if (mEditor != null) mEditor.setFrame(); 7233 7234 restartMarqueeIfNeeded(); 7235 7236 return result; 7237 } 7238 restartMarqueeIfNeeded()7239 private void restartMarqueeIfNeeded() { 7240 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7241 mRestartMarquee = false; 7242 startMarquee(); 7243 } 7244 } 7245 7246 /** 7247 * Sets the list of input filters that will be used if the buffer is 7248 * Editable. Has no effect otherwise. 7249 * 7250 * @attr ref android.R.styleable#TextView_maxLength 7251 */ setFilters(InputFilter[] filters)7252 public void setFilters(InputFilter[] filters) { 7253 if (filters == null) { 7254 throw new IllegalArgumentException(); 7255 } 7256 7257 mFilters = filters; 7258 7259 if (mText instanceof Editable) { 7260 setFilters((Editable) mText, filters); 7261 } 7262 } 7263 7264 /** 7265 * Sets the list of input filters on the specified Editable, 7266 * and includes mInput in the list if it is an InputFilter. 7267 */ setFilters(Editable e, InputFilter[] filters)7268 private void setFilters(Editable e, InputFilter[] filters) { 7269 if (mEditor != null) { 7270 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7271 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7272 int num = 0; 7273 if (undoFilter) num++; 7274 if (keyFilter) num++; 7275 if (num > 0) { 7276 InputFilter[] nf = new InputFilter[filters.length + num]; 7277 7278 System.arraycopy(filters, 0, nf, 0, filters.length); 7279 num = 0; 7280 if (undoFilter) { 7281 nf[filters.length] = mEditor.mUndoInputFilter; 7282 num++; 7283 } 7284 if (keyFilter) { 7285 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7286 } 7287 7288 e.setFilters(nf); 7289 return; 7290 } 7291 } 7292 e.setFilters(filters); 7293 } 7294 7295 /** 7296 * Returns the current list of input filters. 7297 * 7298 * @attr ref android.R.styleable#TextView_maxLength 7299 */ getFilters()7300 public InputFilter[] getFilters() { 7301 return mFilters; 7302 } 7303 7304 ///////////////////////////////////////////////////////////////////////// 7305 getBoxHeight(Layout l)7306 private int getBoxHeight(Layout l) { 7307 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7308 int padding = (l == mHintLayout) 7309 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7310 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7311 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7312 } 7313 7314 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7315 int getVerticalOffset(boolean forceNormal) { 7316 int voffset = 0; 7317 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7318 7319 Layout l = mLayout; 7320 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7321 l = mHintLayout; 7322 } 7323 7324 if (gravity != Gravity.TOP) { 7325 int boxht = getBoxHeight(l); 7326 int textht = l.getHeight(); 7327 7328 if (textht < boxht) { 7329 if (gravity == Gravity.BOTTOM) { 7330 voffset = boxht - textht; 7331 } else { // (gravity == Gravity.CENTER_VERTICAL) 7332 voffset = (boxht - textht) >> 1; 7333 } 7334 } 7335 } 7336 return voffset; 7337 } 7338 getBottomVerticalOffset(boolean forceNormal)7339 private int getBottomVerticalOffset(boolean forceNormal) { 7340 int voffset = 0; 7341 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7342 7343 Layout l = mLayout; 7344 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7345 l = mHintLayout; 7346 } 7347 7348 if (gravity != Gravity.BOTTOM) { 7349 int boxht = getBoxHeight(l); 7350 int textht = l.getHeight(); 7351 7352 if (textht < boxht) { 7353 if (gravity == Gravity.TOP) { 7354 voffset = boxht - textht; 7355 } else { // (gravity == Gravity.CENTER_VERTICAL) 7356 voffset = (boxht - textht) >> 1; 7357 } 7358 } 7359 } 7360 return voffset; 7361 } 7362 invalidateCursorPath()7363 void invalidateCursorPath() { 7364 if (mHighlightPathBogus) { 7365 invalidateCursor(); 7366 } else { 7367 final int horizontalPadding = getCompoundPaddingLeft(); 7368 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7369 7370 if (mEditor.mDrawableForCursor == null) { 7371 synchronized (TEMP_RECTF) { 7372 /* 7373 * The reason for this concern about the thickness of the 7374 * cursor and doing the floor/ceil on the coordinates is that 7375 * some EditTexts (notably textfields in the Browser) have 7376 * anti-aliased text where not all the characters are 7377 * necessarily at integer-multiple locations. This should 7378 * make sure the entire cursor gets invalidated instead of 7379 * sometimes missing half a pixel. 7380 */ 7381 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7382 if (thick < 1.0f) { 7383 thick = 1.0f; 7384 } 7385 7386 thick /= 2.0f; 7387 7388 // mHighlightPath is guaranteed to be non null at that point. 7389 mHighlightPath.computeBounds(TEMP_RECTF, false); 7390 7391 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7392 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7393 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7394 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7395 } 7396 } else { 7397 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7398 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7399 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7400 } 7401 } 7402 } 7403 invalidateCursor()7404 void invalidateCursor() { 7405 int where = getSelectionEnd(); 7406 7407 invalidateCursor(where, where, where); 7408 } 7409 invalidateCursor(int a, int b, int c)7410 private void invalidateCursor(int a, int b, int c) { 7411 if (a >= 0 || b >= 0 || c >= 0) { 7412 int start = Math.min(Math.min(a, b), c); 7413 int end = Math.max(Math.max(a, b), c); 7414 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7415 } 7416 } 7417 7418 /** 7419 * Invalidates the region of text enclosed between the start and end text offsets. 7420 */ invalidateRegion(int start, int end, boolean invalidateCursor)7421 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7422 if (mLayout == null) { 7423 invalidate(); 7424 } else { 7425 int lineStart = mLayout.getLineForOffset(start); 7426 int top = mLayout.getLineTop(lineStart); 7427 7428 // This is ridiculous, but the descent from the line above 7429 // can hang down into the line we really want to redraw, 7430 // so we have to invalidate part of the line above to make 7431 // sure everything that needs to be redrawn really is. 7432 // (But not the whole line above, because that would cause 7433 // the same problem with the descenders on the line above it!) 7434 if (lineStart > 0) { 7435 top -= mLayout.getLineDescent(lineStart - 1); 7436 } 7437 7438 int lineEnd; 7439 7440 if (start == end) { 7441 lineEnd = lineStart; 7442 } else { 7443 lineEnd = mLayout.getLineForOffset(end); 7444 } 7445 7446 int bottom = mLayout.getLineBottom(lineEnd); 7447 7448 // mEditor can be null in case selection is set programmatically. 7449 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7450 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7451 top = Math.min(top, bounds.top); 7452 bottom = Math.max(bottom, bounds.bottom); 7453 } 7454 7455 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7456 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7457 7458 int left, right; 7459 if (lineStart == lineEnd && !invalidateCursor) { 7460 left = (int) mLayout.getPrimaryHorizontal(start); 7461 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7462 left += compoundPaddingLeft; 7463 right += compoundPaddingLeft; 7464 } else { 7465 // Rectangle bounding box when the region spans several lines 7466 left = compoundPaddingLeft; 7467 right = getWidth() - getCompoundPaddingRight(); 7468 } 7469 7470 invalidate(mScrollX + left, verticalPadding + top, 7471 mScrollX + right, verticalPadding + bottom); 7472 } 7473 } 7474 registerForPreDraw()7475 private void registerForPreDraw() { 7476 if (!mPreDrawRegistered) { 7477 getViewTreeObserver().addOnPreDrawListener(this); 7478 mPreDrawRegistered = true; 7479 } 7480 } 7481 unregisterForPreDraw()7482 private void unregisterForPreDraw() { 7483 getViewTreeObserver().removeOnPreDrawListener(this); 7484 mPreDrawRegistered = false; 7485 mPreDrawListenerDetached = false; 7486 } 7487 7488 /** 7489 * {@inheritDoc} 7490 */ 7491 @Override onPreDraw()7492 public boolean onPreDraw() { 7493 if (mLayout == null) { 7494 assumeLayout(); 7495 } 7496 7497 if (mMovement != null) { 7498 /* This code also provides auto-scrolling when a cursor is moved using a 7499 * CursorController (insertion point or selection limits). 7500 * For selection, ensure start or end is visible depending on controller's state. 7501 */ 7502 int curs = getSelectionEnd(); 7503 // Do not create the controller if it is not already created. 7504 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7505 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7506 curs = getSelectionStart(); 7507 } 7508 7509 /* 7510 * TODO: This should really only keep the end in view if 7511 * it already was before the text changed. I'm not sure 7512 * of a good way to tell from here if it was. 7513 */ 7514 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7515 curs = mText.length(); 7516 } 7517 7518 if (curs >= 0) { 7519 bringPointIntoView(curs); 7520 } 7521 } else { 7522 bringTextIntoView(); 7523 } 7524 7525 // This has to be checked here since: 7526 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7527 // a screen rotation) since layout is not yet initialized at that point. 7528 if (mEditor != null && mEditor.mCreatedWithASelection) { 7529 mEditor.refreshTextActionMode(); 7530 mEditor.mCreatedWithASelection = false; 7531 } 7532 7533 unregisterForPreDraw(); 7534 7535 return true; 7536 } 7537 7538 @Override onAttachedToWindow()7539 protected void onAttachedToWindow() { 7540 super.onAttachedToWindow(); 7541 7542 if (mEditor != null) mEditor.onAttachedToWindow(); 7543 7544 if (mPreDrawListenerDetached) { 7545 getViewTreeObserver().addOnPreDrawListener(this); 7546 mPreDrawListenerDetached = false; 7547 } 7548 } 7549 7550 /** @hide */ 7551 @Override onDetachedFromWindowInternal()7552 protected void onDetachedFromWindowInternal() { 7553 if (mPreDrawRegistered) { 7554 getViewTreeObserver().removeOnPreDrawListener(this); 7555 mPreDrawListenerDetached = true; 7556 } 7557 7558 resetResolvedDrawables(); 7559 7560 if (mEditor != null) mEditor.onDetachedFromWindow(); 7561 7562 super.onDetachedFromWindowInternal(); 7563 } 7564 7565 @Override onScreenStateChanged(int screenState)7566 public void onScreenStateChanged(int screenState) { 7567 super.onScreenStateChanged(screenState); 7568 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7569 } 7570 7571 @Override isPaddingOffsetRequired()7572 protected boolean isPaddingOffsetRequired() { 7573 return mShadowRadius != 0 || mDrawables != null; 7574 } 7575 7576 @Override getLeftPaddingOffset()7577 protected int getLeftPaddingOffset() { 7578 return getCompoundPaddingLeft() - mPaddingLeft 7579 + (int) Math.min(0, mShadowDx - mShadowRadius); 7580 } 7581 7582 @Override getTopPaddingOffset()7583 protected int getTopPaddingOffset() { 7584 return (int) Math.min(0, mShadowDy - mShadowRadius); 7585 } 7586 7587 @Override getBottomPaddingOffset()7588 protected int getBottomPaddingOffset() { 7589 return (int) Math.max(0, mShadowDy + mShadowRadius); 7590 } 7591 7592 @Override getRightPaddingOffset()7593 protected int getRightPaddingOffset() { 7594 return -(getCompoundPaddingRight() - mPaddingRight) 7595 + (int) Math.max(0, mShadowDx + mShadowRadius); 7596 } 7597 7598 @Override verifyDrawable(@onNull Drawable who)7599 protected boolean verifyDrawable(@NonNull Drawable who) { 7600 final boolean verified = super.verifyDrawable(who); 7601 if (!verified && mDrawables != null) { 7602 for (Drawable dr : mDrawables.mShowing) { 7603 if (who == dr) { 7604 return true; 7605 } 7606 } 7607 } 7608 return verified; 7609 } 7610 7611 @Override jumpDrawablesToCurrentState()7612 public void jumpDrawablesToCurrentState() { 7613 super.jumpDrawablesToCurrentState(); 7614 if (mDrawables != null) { 7615 for (Drawable dr : mDrawables.mShowing) { 7616 if (dr != null) { 7617 dr.jumpToCurrentState(); 7618 } 7619 } 7620 } 7621 } 7622 7623 @Override invalidateDrawable(@onNull Drawable drawable)7624 public void invalidateDrawable(@NonNull Drawable drawable) { 7625 boolean handled = false; 7626 7627 if (verifyDrawable(drawable)) { 7628 final Rect dirty = drawable.getBounds(); 7629 int scrollX = mScrollX; 7630 int scrollY = mScrollY; 7631 7632 // IMPORTANT: The coordinates below are based on the coordinates computed 7633 // for each compound drawable in onDraw(). Make sure to update each section 7634 // accordingly. 7635 final TextView.Drawables drawables = mDrawables; 7636 if (drawables != null) { 7637 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7638 final int compoundPaddingTop = getCompoundPaddingTop(); 7639 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7640 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7641 7642 scrollX += mPaddingLeft; 7643 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7644 handled = true; 7645 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7646 final int compoundPaddingTop = getCompoundPaddingTop(); 7647 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7648 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7649 7650 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7651 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7652 handled = true; 7653 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7654 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7655 final int compoundPaddingRight = getCompoundPaddingRight(); 7656 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7657 7658 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7659 scrollY += mPaddingTop; 7660 handled = true; 7661 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7662 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7663 final int compoundPaddingRight = getCompoundPaddingRight(); 7664 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7665 7666 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7667 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7668 handled = true; 7669 } 7670 } 7671 7672 if (handled) { 7673 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7674 dirty.right + scrollX, dirty.bottom + scrollY); 7675 } 7676 } 7677 7678 if (!handled) { 7679 super.invalidateDrawable(drawable); 7680 } 7681 } 7682 7683 @Override hasOverlappingRendering()7684 public boolean hasOverlappingRendering() { 7685 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7686 return ((getBackground() != null && getBackground().getCurrent() != null) 7687 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7688 || mShadowColor != 0); 7689 } 7690 7691 /** 7692 * 7693 * Returns the state of the {@code textIsSelectable} flag (See 7694 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7695 * to allow users to select and copy text in a non-editable TextView, the content of an 7696 * {@link EditText} can always be selected, independently of the value of this flag. 7697 * <p> 7698 * 7699 * @return True if the text displayed in this TextView can be selected by the user. 7700 * 7701 * @attr ref android.R.styleable#TextView_textIsSelectable 7702 */ 7703 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7704 public boolean isTextSelectable() { 7705 return mEditor == null ? false : mEditor.mTextIsSelectable; 7706 } 7707 7708 /** 7709 * Sets whether the content of this view is selectable by the user. The default is 7710 * {@code false}, meaning that the content is not selectable. 7711 * <p> 7712 * When you use a TextView to display a useful piece of information to the user (such as a 7713 * contact's address), make it selectable, so that the user can select and copy its 7714 * content. You can also use set the XML attribute 7715 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7716 * <p> 7717 * When you call this method to set the value of {@code textIsSelectable}, it sets 7718 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7719 * and {@code longClickable} to the same value. These flags correspond to the attributes 7720 * {@link android.R.styleable#View_focusable android:focusable}, 7721 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7722 * {@link android.R.styleable#View_clickable android:clickable}, and 7723 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7724 * flags to a state you had set previously, call one or more of the following methods: 7725 * {@link #setFocusable(boolean) setFocusable()}, 7726 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7727 * {@link #setClickable(boolean) setClickable()} or 7728 * {@link #setLongClickable(boolean) setLongClickable()}. 7729 * 7730 * @param selectable Whether the content of this TextView should be selectable. 7731 */ setTextIsSelectable(boolean selectable)7732 public void setTextIsSelectable(boolean selectable) { 7733 if (!selectable && mEditor == null) return; // false is default value with no edit data 7734 7735 createEditorIfNeeded(); 7736 if (mEditor.mTextIsSelectable == selectable) return; 7737 7738 mEditor.mTextIsSelectable = selectable; 7739 setFocusableInTouchMode(selectable); 7740 setFocusable(FOCUSABLE_AUTO); 7741 setClickable(selectable); 7742 setLongClickable(selectable); 7743 7744 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7745 7746 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7747 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7748 7749 // Called by setText above, but safer in case of future code changes 7750 mEditor.prepareCursorControllers(); 7751 } 7752 7753 @Override onCreateDrawableState(int extraSpace)7754 protected int[] onCreateDrawableState(int extraSpace) { 7755 final int[] drawableState; 7756 7757 if (mSingleLine) { 7758 drawableState = super.onCreateDrawableState(extraSpace); 7759 } else { 7760 drawableState = super.onCreateDrawableState(extraSpace + 1); 7761 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7762 } 7763 7764 if (isTextSelectable()) { 7765 // Disable pressed state, which was introduced when TextView was made clickable. 7766 // Prevents text color change. 7767 // setClickable(false) would have a similar effect, but it also disables focus changes 7768 // and long press actions, which are both needed by text selection. 7769 final int length = drawableState.length; 7770 for (int i = 0; i < length; i++) { 7771 if (drawableState[i] == R.attr.state_pressed) { 7772 final int[] nonPressedState = new int[length - 1]; 7773 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7774 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7775 return nonPressedState; 7776 } 7777 } 7778 } 7779 7780 return drawableState; 7781 } 7782 7783 @UnsupportedAppUsage getUpdatedHighlightPath()7784 private Path getUpdatedHighlightPath() { 7785 Path highlight = null; 7786 Paint highlightPaint = mHighlightPaint; 7787 7788 final int selStart = getSelectionStart(); 7789 final int selEnd = getSelectionEnd(); 7790 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7791 if (selStart == selEnd) { 7792 if (mEditor != null && mEditor.shouldRenderCursor()) { 7793 if (mHighlightPathBogus) { 7794 if (mHighlightPath == null) mHighlightPath = new Path(); 7795 mHighlightPath.reset(); 7796 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7797 mEditor.updateCursorPosition(); 7798 mHighlightPathBogus = false; 7799 } 7800 7801 // XXX should pass to skin instead of drawing directly 7802 highlightPaint.setColor(mCurTextColor); 7803 highlightPaint.setStyle(Paint.Style.STROKE); 7804 highlight = mHighlightPath; 7805 } 7806 } else { 7807 if (mHighlightPathBogus) { 7808 if (mHighlightPath == null) mHighlightPath = new Path(); 7809 mHighlightPath.reset(); 7810 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7811 mHighlightPathBogus = false; 7812 } 7813 7814 // XXX should pass to skin instead of drawing directly 7815 highlightPaint.setColor(mHighlightColor); 7816 highlightPaint.setStyle(Paint.Style.FILL); 7817 7818 highlight = mHighlightPath; 7819 } 7820 } 7821 return highlight; 7822 } 7823 7824 /** 7825 * @hide 7826 */ getHorizontalOffsetForDrawables()7827 public int getHorizontalOffsetForDrawables() { 7828 return 0; 7829 } 7830 7831 @Override onDraw(Canvas canvas)7832 protected void onDraw(Canvas canvas) { 7833 restartMarqueeIfNeeded(); 7834 7835 // Draw the background for this view 7836 super.onDraw(canvas); 7837 7838 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7839 final int compoundPaddingTop = getCompoundPaddingTop(); 7840 final int compoundPaddingRight = getCompoundPaddingRight(); 7841 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7842 final int scrollX = mScrollX; 7843 final int scrollY = mScrollY; 7844 final int right = mRight; 7845 final int left = mLeft; 7846 final int bottom = mBottom; 7847 final int top = mTop; 7848 final boolean isLayoutRtl = isLayoutRtl(); 7849 final int offset = getHorizontalOffsetForDrawables(); 7850 final int leftOffset = isLayoutRtl ? 0 : offset; 7851 final int rightOffset = isLayoutRtl ? offset : 0; 7852 7853 final Drawables dr = mDrawables; 7854 if (dr != null) { 7855 /* 7856 * Compound, not extended, because the icon is not clipped 7857 * if the text height is smaller. 7858 */ 7859 7860 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7861 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7862 7863 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7864 // Make sure to update invalidateDrawable() when changing this code. 7865 if (dr.mShowing[Drawables.LEFT] != null) { 7866 canvas.save(); 7867 canvas.translate(scrollX + mPaddingLeft + leftOffset, 7868 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 7869 dr.mShowing[Drawables.LEFT].draw(canvas); 7870 canvas.restore(); 7871 } 7872 7873 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7874 // Make sure to update invalidateDrawable() when changing this code. 7875 if (dr.mShowing[Drawables.RIGHT] != null) { 7876 canvas.save(); 7877 canvas.translate(scrollX + right - left - mPaddingRight 7878 - dr.mDrawableSizeRight - rightOffset, 7879 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 7880 dr.mShowing[Drawables.RIGHT].draw(canvas); 7881 canvas.restore(); 7882 } 7883 7884 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7885 // Make sure to update invalidateDrawable() when changing this code. 7886 if (dr.mShowing[Drawables.TOP] != null) { 7887 canvas.save(); 7888 canvas.translate(scrollX + compoundPaddingLeft 7889 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 7890 dr.mShowing[Drawables.TOP].draw(canvas); 7891 canvas.restore(); 7892 } 7893 7894 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7895 // Make sure to update invalidateDrawable() when changing this code. 7896 if (dr.mShowing[Drawables.BOTTOM] != null) { 7897 canvas.save(); 7898 canvas.translate(scrollX + compoundPaddingLeft 7899 + (hspace - dr.mDrawableWidthBottom) / 2, 7900 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 7901 dr.mShowing[Drawables.BOTTOM].draw(canvas); 7902 canvas.restore(); 7903 } 7904 } 7905 7906 int color = mCurTextColor; 7907 7908 if (mLayout == null) { 7909 assumeLayout(); 7910 } 7911 7912 Layout layout = mLayout; 7913 7914 if (mHint != null && mText.length() == 0) { 7915 if (mHintTextColor != null) { 7916 color = mCurHintTextColor; 7917 } 7918 7919 layout = mHintLayout; 7920 } 7921 7922 mTextPaint.setColor(color); 7923 mTextPaint.drawableState = getDrawableState(); 7924 7925 canvas.save(); 7926 /* Would be faster if we didn't have to do this. Can we chop the 7927 (displayable) text so that we don't need to do this ever? 7928 */ 7929 7930 int extendedPaddingTop = getExtendedPaddingTop(); 7931 int extendedPaddingBottom = getExtendedPaddingBottom(); 7932 7933 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7934 final int maxScrollY = mLayout.getHeight() - vspace; 7935 7936 float clipLeft = compoundPaddingLeft + scrollX; 7937 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 7938 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 7939 float clipBottom = bottom - top + scrollY 7940 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 7941 7942 if (mShadowRadius != 0) { 7943 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 7944 clipRight += Math.max(0, mShadowDx + mShadowRadius); 7945 7946 clipTop += Math.min(0, mShadowDy - mShadowRadius); 7947 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 7948 } 7949 7950 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 7951 7952 int voffsetText = 0; 7953 int voffsetCursor = 0; 7954 7955 // translate in by our padding 7956 /* shortcircuit calling getVerticaOffset() */ 7957 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7958 voffsetText = getVerticalOffset(false); 7959 voffsetCursor = getVerticalOffset(true); 7960 } 7961 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 7962 7963 final int layoutDirection = getLayoutDirection(); 7964 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 7965 if (isMarqueeFadeEnabled()) { 7966 if (!mSingleLine && getLineCount() == 1 && canMarquee() 7967 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 7968 final int width = mRight - mLeft; 7969 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 7970 final float dx = mLayout.getLineRight(0) - (width - padding); 7971 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7972 } 7973 7974 if (mMarquee != null && mMarquee.isRunning()) { 7975 final float dx = -mMarquee.getScroll(); 7976 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7977 } 7978 } 7979 7980 final int cursorOffsetVertical = voffsetCursor - voffsetText; 7981 7982 Path highlight = getUpdatedHighlightPath(); 7983 if (mEditor != null) { 7984 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 7985 } else { 7986 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7987 } 7988 7989 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 7990 final float dx = mMarquee.getGhostOffset(); 7991 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7992 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7993 } 7994 7995 canvas.restore(); 7996 } 7997 7998 @Override getFocusedRect(Rect r)7999 public void getFocusedRect(Rect r) { 8000 if (mLayout == null) { 8001 super.getFocusedRect(r); 8002 return; 8003 } 8004 8005 int selEnd = getSelectionEnd(); 8006 if (selEnd < 0) { 8007 super.getFocusedRect(r); 8008 return; 8009 } 8010 8011 int selStart = getSelectionStart(); 8012 if (selStart < 0 || selStart >= selEnd) { 8013 int line = mLayout.getLineForOffset(selEnd); 8014 r.top = mLayout.getLineTop(line); 8015 r.bottom = mLayout.getLineBottom(line); 8016 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8017 r.right = r.left + 4; 8018 } else { 8019 int lineStart = mLayout.getLineForOffset(selStart); 8020 int lineEnd = mLayout.getLineForOffset(selEnd); 8021 r.top = mLayout.getLineTop(lineStart); 8022 r.bottom = mLayout.getLineBottom(lineEnd); 8023 if (lineStart == lineEnd) { 8024 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8025 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8026 } else { 8027 // Selection extends across multiple lines -- make the focused 8028 // rect cover the entire width. 8029 if (mHighlightPathBogus) { 8030 if (mHighlightPath == null) mHighlightPath = new Path(); 8031 mHighlightPath.reset(); 8032 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8033 mHighlightPathBogus = false; 8034 } 8035 synchronized (TEMP_RECTF) { 8036 mHighlightPath.computeBounds(TEMP_RECTF, true); 8037 r.left = (int) TEMP_RECTF.left - 1; 8038 r.right = (int) TEMP_RECTF.right + 1; 8039 } 8040 } 8041 } 8042 8043 // Adjust for padding and gravity. 8044 int paddingLeft = getCompoundPaddingLeft(); 8045 int paddingTop = getExtendedPaddingTop(); 8046 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8047 paddingTop += getVerticalOffset(false); 8048 } 8049 r.offset(paddingLeft, paddingTop); 8050 int paddingBottom = getExtendedPaddingBottom(); 8051 r.bottom += paddingBottom; 8052 } 8053 8054 /** 8055 * Return the number of lines of text, or 0 if the internal Layout has not 8056 * been built. 8057 */ getLineCount()8058 public int getLineCount() { 8059 return mLayout != null ? mLayout.getLineCount() : 0; 8060 } 8061 8062 /** 8063 * Return the baseline for the specified line (0...getLineCount() - 1) 8064 * If bounds is not null, return the top, left, right, bottom extents 8065 * of the specified line in it. If the internal Layout has not been built, 8066 * return 0 and set bounds to (0, 0, 0, 0) 8067 * @param line which line to examine (0..getLineCount() - 1) 8068 * @param bounds Optional. If not null, it returns the extent of the line 8069 * @return the Y-coordinate of the baseline 8070 */ getLineBounds(int line, Rect bounds)8071 public int getLineBounds(int line, Rect bounds) { 8072 if (mLayout == null) { 8073 if (bounds != null) { 8074 bounds.set(0, 0, 0, 0); 8075 } 8076 return 0; 8077 } else { 8078 int baseline = mLayout.getLineBounds(line, bounds); 8079 8080 int voffset = getExtendedPaddingTop(); 8081 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8082 voffset += getVerticalOffset(true); 8083 } 8084 if (bounds != null) { 8085 bounds.offset(getCompoundPaddingLeft(), voffset); 8086 } 8087 return baseline + voffset; 8088 } 8089 } 8090 8091 @Override getBaseline()8092 public int getBaseline() { 8093 if (mLayout == null) { 8094 return super.getBaseline(); 8095 } 8096 8097 return getBaselineOffset() + mLayout.getLineBaseline(0); 8098 } 8099 getBaselineOffset()8100 int getBaselineOffset() { 8101 int voffset = 0; 8102 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8103 voffset = getVerticalOffset(true); 8104 } 8105 8106 if (isLayoutModeOptical(mParent)) { 8107 voffset -= getOpticalInsets().top; 8108 } 8109 8110 return getExtendedPaddingTop() + voffset; 8111 } 8112 8113 /** 8114 * @hide 8115 */ 8116 @Override getFadeTop(boolean offsetRequired)8117 protected int getFadeTop(boolean offsetRequired) { 8118 if (mLayout == null) return 0; 8119 8120 int voffset = 0; 8121 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8122 voffset = getVerticalOffset(true); 8123 } 8124 8125 if (offsetRequired) voffset += getTopPaddingOffset(); 8126 8127 return getExtendedPaddingTop() + voffset; 8128 } 8129 8130 /** 8131 * @hide 8132 */ 8133 @Override getFadeHeight(boolean offsetRequired)8134 protected int getFadeHeight(boolean offsetRequired) { 8135 return mLayout != null ? mLayout.getHeight() : 0; 8136 } 8137 8138 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8139 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8140 if (mSpannable != null && mLinksClickable) { 8141 final float x = event.getX(pointerIndex); 8142 final float y = event.getY(pointerIndex); 8143 final int offset = getOffsetForPosition(x, y); 8144 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8145 ClickableSpan.class); 8146 if (clickables.length > 0) { 8147 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8148 } 8149 } 8150 if (isTextSelectable() || isTextEditable()) { 8151 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8152 } 8153 return super.onResolvePointerIcon(event, pointerIndex); 8154 } 8155 8156 @Override onKeyPreIme(int keyCode, KeyEvent event)8157 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8158 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8159 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8160 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8161 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8162 return true; 8163 } 8164 return super.onKeyPreIme(keyCode, event); 8165 } 8166 8167 /** 8168 * @hide 8169 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8170 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8171 // Do nothing unless mEditor is in text action mode. 8172 if (mEditor == null || mEditor.getTextActionMode() == null) { 8173 return false; 8174 } 8175 8176 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8177 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8178 if (state != null) { 8179 state.startTracking(event, this); 8180 } 8181 return true; 8182 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8183 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8184 if (state != null) { 8185 state.handleUpEvent(event); 8186 } 8187 if (event.isTracking() && !event.isCanceled()) { 8188 stopTextActionMode(); 8189 return true; 8190 } 8191 } 8192 return false; 8193 } 8194 8195 @Override onKeyDown(int keyCode, KeyEvent event)8196 public boolean onKeyDown(int keyCode, KeyEvent event) { 8197 final int which = doKeyDown(keyCode, event, null); 8198 if (which == KEY_EVENT_NOT_HANDLED) { 8199 return super.onKeyDown(keyCode, event); 8200 } 8201 8202 return true; 8203 } 8204 8205 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8206 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8207 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8208 final int which = doKeyDown(keyCode, down, event); 8209 if (which == KEY_EVENT_NOT_HANDLED) { 8210 // Go through default dispatching. 8211 return super.onKeyMultiple(keyCode, repeatCount, event); 8212 } 8213 if (which == KEY_EVENT_HANDLED) { 8214 // Consumed the whole thing. 8215 return true; 8216 } 8217 8218 repeatCount--; 8219 8220 // We are going to dispatch the remaining events to either the input 8221 // or movement method. To do this, we will just send a repeated stream 8222 // of down and up events until we have done the complete repeatCount. 8223 // It would be nice if those interfaces had an onKeyMultiple() method, 8224 // but adding that is a more complicated change. 8225 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8226 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8227 // mEditor and mEditor.mInput are not null from doKeyDown 8228 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8229 while (--repeatCount > 0) { 8230 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8231 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8232 } 8233 hideErrorIfUnchanged(); 8234 8235 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8236 // mMovement is not null from doKeyDown 8237 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8238 while (--repeatCount > 0) { 8239 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8240 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8241 } 8242 } 8243 8244 return true; 8245 } 8246 8247 /** 8248 * Returns true if pressing ENTER in this field advances focus instead 8249 * of inserting the character. This is true mostly in single-line fields, 8250 * but also in mail addresses and subjects which will display on multiple 8251 * lines but where it doesn't make sense to insert newlines. 8252 */ shouldAdvanceFocusOnEnter()8253 private boolean shouldAdvanceFocusOnEnter() { 8254 if (getKeyListener() == null) { 8255 return false; 8256 } 8257 8258 if (mSingleLine) { 8259 return true; 8260 } 8261 8262 if (mEditor != null 8263 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8264 == EditorInfo.TYPE_CLASS_TEXT) { 8265 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8266 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8267 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8268 return true; 8269 } 8270 } 8271 8272 return false; 8273 } 8274 8275 /** 8276 * Returns true if pressing TAB in this field advances focus instead 8277 * of inserting the character. Insert tabs only in multi-line editors. 8278 */ shouldAdvanceFocusOnTab()8279 private boolean shouldAdvanceFocusOnTab() { 8280 if (getKeyListener() != null && !mSingleLine && mEditor != null 8281 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8282 == EditorInfo.TYPE_CLASS_TEXT) { 8283 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8284 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 8285 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 8286 return false; 8287 } 8288 } 8289 return true; 8290 } 8291 isDirectionalNavigationKey(int keyCode)8292 private boolean isDirectionalNavigationKey(int keyCode) { 8293 switch(keyCode) { 8294 case KeyEvent.KEYCODE_DPAD_UP: 8295 case KeyEvent.KEYCODE_DPAD_DOWN: 8296 case KeyEvent.KEYCODE_DPAD_LEFT: 8297 case KeyEvent.KEYCODE_DPAD_RIGHT: 8298 return true; 8299 } 8300 return false; 8301 } 8302 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8303 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8304 if (!isEnabled()) { 8305 return KEY_EVENT_NOT_HANDLED; 8306 } 8307 8308 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8309 // While this shouldn't be necessary because any time we're preventing default movement we 8310 // should be restricting the focus to remain within this view, thus we'll also receive 8311 // the key up event, occasionally key up events will get dropped and we don't want to 8312 // prevent the user from traversing out of this on the next key down. 8313 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8314 mPreventDefaultMovement = false; 8315 } 8316 8317 switch (keyCode) { 8318 case KeyEvent.KEYCODE_ENTER: 8319 if (event.hasNoModifiers()) { 8320 // When mInputContentType is set, we know that we are 8321 // running in a "modern" cupcake environment, so don't need 8322 // to worry about the application trying to capture 8323 // enter key events. 8324 if (mEditor != null && mEditor.mInputContentType != null) { 8325 // If there is an action listener, given them a 8326 // chance to consume the event. 8327 if (mEditor.mInputContentType.onEditorActionListener != null 8328 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8329 this, EditorInfo.IME_NULL, event)) { 8330 mEditor.mInputContentType.enterDown = true; 8331 // We are consuming the enter key for them. 8332 return KEY_EVENT_HANDLED; 8333 } 8334 } 8335 8336 // If our editor should move focus when enter is pressed, or 8337 // this is a generated event from an IME action button, then 8338 // don't let it be inserted into the text. 8339 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8340 || shouldAdvanceFocusOnEnter()) { 8341 if (hasOnClickListeners()) { 8342 return KEY_EVENT_NOT_HANDLED; 8343 } 8344 return KEY_EVENT_HANDLED; 8345 } 8346 } 8347 break; 8348 8349 case KeyEvent.KEYCODE_DPAD_CENTER: 8350 if (event.hasNoModifiers()) { 8351 if (shouldAdvanceFocusOnEnter()) { 8352 return KEY_EVENT_NOT_HANDLED; 8353 } 8354 } 8355 break; 8356 8357 case KeyEvent.KEYCODE_TAB: 8358 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8359 if (shouldAdvanceFocusOnTab()) { 8360 return KEY_EVENT_NOT_HANDLED; 8361 } 8362 } 8363 break; 8364 8365 // Has to be done on key down (and not on key up) to correctly be intercepted. 8366 case KeyEvent.KEYCODE_BACK: 8367 if (mEditor != null && mEditor.getTextActionMode() != null) { 8368 stopTextActionMode(); 8369 return KEY_EVENT_HANDLED; 8370 } 8371 break; 8372 8373 case KeyEvent.KEYCODE_CUT: 8374 if (event.hasNoModifiers() && canCut()) { 8375 if (onTextContextMenuItem(ID_CUT)) { 8376 return KEY_EVENT_HANDLED; 8377 } 8378 } 8379 break; 8380 8381 case KeyEvent.KEYCODE_COPY: 8382 if (event.hasNoModifiers() && canCopy()) { 8383 if (onTextContextMenuItem(ID_COPY)) { 8384 return KEY_EVENT_HANDLED; 8385 } 8386 } 8387 break; 8388 8389 case KeyEvent.KEYCODE_PASTE: 8390 if (event.hasNoModifiers() && canPaste()) { 8391 if (onTextContextMenuItem(ID_PASTE)) { 8392 return KEY_EVENT_HANDLED; 8393 } 8394 } 8395 break; 8396 8397 case KeyEvent.KEYCODE_FORWARD_DEL: 8398 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8399 if (onTextContextMenuItem(ID_CUT)) { 8400 return KEY_EVENT_HANDLED; 8401 } 8402 } 8403 break; 8404 8405 case KeyEvent.KEYCODE_INSERT: 8406 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8407 if (onTextContextMenuItem(ID_COPY)) { 8408 return KEY_EVENT_HANDLED; 8409 } 8410 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8411 if (onTextContextMenuItem(ID_PASTE)) { 8412 return KEY_EVENT_HANDLED; 8413 } 8414 } 8415 break; 8416 } 8417 8418 if (mEditor != null && mEditor.mKeyListener != null) { 8419 boolean doDown = true; 8420 if (otherEvent != null) { 8421 try { 8422 beginBatchEdit(); 8423 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8424 otherEvent); 8425 hideErrorIfUnchanged(); 8426 doDown = false; 8427 if (handled) { 8428 return KEY_EVENT_HANDLED; 8429 } 8430 } catch (AbstractMethodError e) { 8431 // onKeyOther was added after 1.0, so if it isn't 8432 // implemented we need to try to dispatch as a regular down. 8433 } finally { 8434 endBatchEdit(); 8435 } 8436 } 8437 8438 if (doDown) { 8439 beginBatchEdit(); 8440 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8441 keyCode, event); 8442 endBatchEdit(); 8443 hideErrorIfUnchanged(); 8444 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8445 } 8446 } 8447 8448 // bug 650865: sometimes we get a key event before a layout. 8449 // don't try to move around if we don't know the layout. 8450 8451 if (mMovement != null && mLayout != null) { 8452 boolean doDown = true; 8453 if (otherEvent != null) { 8454 try { 8455 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8456 doDown = false; 8457 if (handled) { 8458 return KEY_EVENT_HANDLED; 8459 } 8460 } catch (AbstractMethodError e) { 8461 // onKeyOther was added after 1.0, so if it isn't 8462 // implemented we need to try to dispatch as a regular down. 8463 } 8464 } 8465 if (doDown) { 8466 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8467 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8468 mPreventDefaultMovement = true; 8469 } 8470 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8471 } 8472 } 8473 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8474 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8475 // to move focus with arrows. 8476 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8477 && isDirectionalNavigationKey(keyCode)) { 8478 return KEY_EVENT_HANDLED; 8479 } 8480 } 8481 8482 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8483 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8484 } 8485 8486 /** 8487 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8488 * can be recorded. 8489 * @hide 8490 */ resetErrorChangedFlag()8491 public void resetErrorChangedFlag() { 8492 /* 8493 * Keep track of what the error was before doing the input 8494 * so that if an input filter changed the error, we leave 8495 * that error showing. Otherwise, we take down whatever 8496 * error was showing when the user types something. 8497 */ 8498 if (mEditor != null) mEditor.mErrorWasChanged = false; 8499 } 8500 8501 /** 8502 * @hide 8503 */ hideErrorIfUnchanged()8504 public void hideErrorIfUnchanged() { 8505 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8506 setError(null, null); 8507 } 8508 } 8509 8510 @Override onKeyUp(int keyCode, KeyEvent event)8511 public boolean onKeyUp(int keyCode, KeyEvent event) { 8512 if (!isEnabled()) { 8513 return super.onKeyUp(keyCode, event); 8514 } 8515 8516 if (!KeyEvent.isModifierKey(keyCode)) { 8517 mPreventDefaultMovement = false; 8518 } 8519 8520 switch (keyCode) { 8521 case KeyEvent.KEYCODE_DPAD_CENTER: 8522 if (event.hasNoModifiers()) { 8523 /* 8524 * If there is a click listener, just call through to 8525 * super, which will invoke it. 8526 * 8527 * If there isn't a click listener, try to show the soft 8528 * input method. (It will also 8529 * call performClick(), but that won't do anything in 8530 * this case.) 8531 */ 8532 if (!hasOnClickListeners()) { 8533 if (mMovement != null && mText instanceof Editable 8534 && mLayout != null && onCheckIsTextEditor()) { 8535 InputMethodManager imm = getInputMethodManager(); 8536 viewClicked(imm); 8537 if (imm != null && getShowSoftInputOnFocus()) { 8538 imm.showSoftInput(this, 0); 8539 } 8540 } 8541 } 8542 } 8543 return super.onKeyUp(keyCode, event); 8544 8545 case KeyEvent.KEYCODE_ENTER: 8546 if (event.hasNoModifiers()) { 8547 if (mEditor != null && mEditor.mInputContentType != null 8548 && mEditor.mInputContentType.onEditorActionListener != null 8549 && mEditor.mInputContentType.enterDown) { 8550 mEditor.mInputContentType.enterDown = false; 8551 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8552 this, EditorInfo.IME_NULL, event)) { 8553 return true; 8554 } 8555 } 8556 8557 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8558 || shouldAdvanceFocusOnEnter()) { 8559 /* 8560 * If there is a click listener, just call through to 8561 * super, which will invoke it. 8562 * 8563 * If there isn't a click listener, try to advance focus, 8564 * but still call through to super, which will reset the 8565 * pressed state and longpress state. (It will also 8566 * call performClick(), but that won't do anything in 8567 * this case.) 8568 */ 8569 if (!hasOnClickListeners()) { 8570 View v = focusSearch(FOCUS_DOWN); 8571 8572 if (v != null) { 8573 if (!v.requestFocus(FOCUS_DOWN)) { 8574 throw new IllegalStateException("focus search returned a view " 8575 + "that wasn't able to take focus!"); 8576 } 8577 8578 /* 8579 * Return true because we handled the key; super 8580 * will return false because there was no click 8581 * listener. 8582 */ 8583 super.onKeyUp(keyCode, event); 8584 return true; 8585 } else if ((event.getFlags() 8586 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8587 // No target for next focus, but make sure the IME 8588 // if this came from it. 8589 InputMethodManager imm = getInputMethodManager(); 8590 if (imm != null && imm.isActive(this)) { 8591 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8592 } 8593 } 8594 } 8595 } 8596 return super.onKeyUp(keyCode, event); 8597 } 8598 break; 8599 } 8600 8601 if (mEditor != null && mEditor.mKeyListener != null) { 8602 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8603 return true; 8604 } 8605 } 8606 8607 if (mMovement != null && mLayout != null) { 8608 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8609 return true; 8610 } 8611 } 8612 8613 return super.onKeyUp(keyCode, event); 8614 } 8615 8616 @Override onCheckIsTextEditor()8617 public boolean onCheckIsTextEditor() { 8618 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8619 } 8620 8621 @Override onCreateInputConnection(EditorInfo outAttrs)8622 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8623 if (onCheckIsTextEditor() && isEnabled()) { 8624 mEditor.createInputMethodStateIfNeeded(); 8625 outAttrs.inputType = getInputType(); 8626 if (mEditor.mInputContentType != null) { 8627 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8628 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8629 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8630 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8631 outAttrs.extras = mEditor.mInputContentType.extras; 8632 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8633 } else { 8634 outAttrs.imeOptions = EditorInfo.IME_NULL; 8635 outAttrs.hintLocales = null; 8636 } 8637 if (focusSearch(FOCUS_DOWN) != null) { 8638 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8639 } 8640 if (focusSearch(FOCUS_UP) != null) { 8641 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8642 } 8643 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8644 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8645 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8646 // An action has not been set, but the enter key will move to 8647 // the next focus, so set the action to that. 8648 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8649 } else { 8650 // An action has not been set, and there is no focus to move 8651 // to, so let's just supply a "done" action. 8652 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8653 } 8654 if (!shouldAdvanceFocusOnEnter()) { 8655 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8656 } 8657 } 8658 if (isMultilineInputType(outAttrs.inputType)) { 8659 // Multi-line text editors should always show an enter key. 8660 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8661 } 8662 outAttrs.hintText = mHint; 8663 outAttrs.targetInputMethodUser = mTextOperationUser; 8664 if (mText instanceof Editable) { 8665 InputConnection ic = new EditableInputConnection(this); 8666 outAttrs.initialSelStart = getSelectionStart(); 8667 outAttrs.initialSelEnd = getSelectionEnd(); 8668 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8669 return ic; 8670 } 8671 } 8672 return null; 8673 } 8674 8675 /** 8676 * If this TextView contains editable content, extract a portion of it 8677 * based on the information in <var>request</var> in to <var>outText</var>. 8678 * @return Returns true if the text was successfully extracted, else false. 8679 */ extractText(ExtractedTextRequest request, ExtractedText outText)8680 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8681 createEditorIfNeeded(); 8682 return mEditor.extractText(request, outText); 8683 } 8684 8685 /** 8686 * This is used to remove all style-impacting spans from text before new 8687 * extracted text is being replaced into it, so that we don't have any 8688 * lingering spans applied during the replace. 8689 */ removeParcelableSpans(Spannable spannable, int start, int end)8690 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8691 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8692 int i = spans.length; 8693 while (i > 0) { 8694 i--; 8695 spannable.removeSpan(spans[i]); 8696 } 8697 } 8698 8699 /** 8700 * Apply to this text view the given extracted text, as previously 8701 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8702 */ setExtractedText(ExtractedText text)8703 public void setExtractedText(ExtractedText text) { 8704 Editable content = getEditableText(); 8705 if (text.text != null) { 8706 if (content == null) { 8707 setText(text.text, TextView.BufferType.EDITABLE); 8708 } else { 8709 int start = 0; 8710 int end = content.length(); 8711 8712 if (text.partialStartOffset >= 0) { 8713 final int N = content.length(); 8714 start = text.partialStartOffset; 8715 if (start > N) start = N; 8716 end = text.partialEndOffset; 8717 if (end > N) end = N; 8718 } 8719 8720 removeParcelableSpans(content, start, end); 8721 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8722 if (text.text instanceof Spanned) { 8723 // OK to copy spans only. 8724 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8725 Object.class, content, start); 8726 } 8727 } else { 8728 content.replace(start, end, text.text); 8729 } 8730 } 8731 } 8732 8733 // Now set the selection position... make sure it is in range, to 8734 // avoid crashes. If this is a partial update, it is possible that 8735 // the underlying text may have changed, causing us problems here. 8736 // Also we just don't want to trust clients to do the right thing. 8737 Spannable sp = (Spannable) getText(); 8738 final int N = sp.length(); 8739 int start = text.selectionStart; 8740 if (start < 0) { 8741 start = 0; 8742 } else if (start > N) { 8743 start = N; 8744 } 8745 int end = text.selectionEnd; 8746 if (end < 0) { 8747 end = 0; 8748 } else if (end > N) { 8749 end = N; 8750 } 8751 Selection.setSelection(sp, start, end); 8752 8753 // Finally, update the selection mode. 8754 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8755 MetaKeyKeyListener.startSelecting(this, sp); 8756 } else { 8757 MetaKeyKeyListener.stopSelecting(this, sp); 8758 } 8759 8760 setHintInternal(text.hint); 8761 } 8762 8763 /** 8764 * @hide 8765 */ setExtracting(ExtractedTextRequest req)8766 public void setExtracting(ExtractedTextRequest req) { 8767 if (mEditor.mInputMethodState != null) { 8768 mEditor.mInputMethodState.mExtractedTextRequest = req; 8769 } 8770 // This would stop a possible selection mode, but no such mode is started in case 8771 // extracted mode will start. Some text is selected though, and will trigger an action mode 8772 // in the extracted view. 8773 mEditor.hideCursorAndSpanControllers(); 8774 stopTextActionMode(); 8775 if (mEditor.mSelectionModifierCursorController != null) { 8776 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8777 } 8778 } 8779 8780 /** 8781 * Called by the framework in response to a text completion from 8782 * the current input method, provided by it calling 8783 * {@link InputConnection#commitCompletion 8784 * InputConnection.commitCompletion()}. The default implementation does 8785 * nothing; text views that are supporting auto-completion should override 8786 * this to do their desired behavior. 8787 * 8788 * @param text The auto complete text the user has selected. 8789 */ onCommitCompletion(CompletionInfo text)8790 public void onCommitCompletion(CompletionInfo text) { 8791 // intentionally empty 8792 } 8793 8794 /** 8795 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8796 * dictionary) from the current input method, provided by it calling 8797 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8798 * The default implementation flashes the background of the corrected word to provide 8799 * feedback to the user. 8800 * 8801 * @param info The auto correct info about the text that was corrected. 8802 */ onCommitCorrection(CorrectionInfo info)8803 public void onCommitCorrection(CorrectionInfo info) { 8804 if (mEditor != null) mEditor.onCommitCorrection(info); 8805 } 8806 beginBatchEdit()8807 public void beginBatchEdit() { 8808 if (mEditor != null) mEditor.beginBatchEdit(); 8809 } 8810 endBatchEdit()8811 public void endBatchEdit() { 8812 if (mEditor != null) mEditor.endBatchEdit(); 8813 } 8814 8815 /** 8816 * Called by the framework in response to a request to begin a batch 8817 * of edit operations through a call to link {@link #beginBatchEdit()}. 8818 */ onBeginBatchEdit()8819 public void onBeginBatchEdit() { 8820 // intentionally empty 8821 } 8822 8823 /** 8824 * Called by the framework in response to a request to end a batch 8825 * of edit operations through a call to link {@link #endBatchEdit}. 8826 */ onEndBatchEdit()8827 public void onEndBatchEdit() { 8828 // intentionally empty 8829 } 8830 8831 /** 8832 * Called by the framework in response to a private command from the 8833 * current method, provided by it calling 8834 * {@link InputConnection#performPrivateCommand 8835 * InputConnection.performPrivateCommand()}. 8836 * 8837 * @param action The action name of the command. 8838 * @param data Any additional data for the command. This may be null. 8839 * @return Return true if you handled the command, else false. 8840 */ onPrivateIMECommand(String action, Bundle data)8841 public boolean onPrivateIMECommand(String action, Bundle data) { 8842 return false; 8843 } 8844 8845 /** @hide */ 8846 @VisibleForTesting 8847 @UnsupportedAppUsage nullLayouts()8848 public void nullLayouts() { 8849 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8850 mSavedLayout = (BoringLayout) mLayout; 8851 } 8852 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8853 mSavedHintLayout = (BoringLayout) mHintLayout; 8854 } 8855 8856 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8857 8858 mBoring = mHintBoring = null; 8859 8860 // Since it depends on the value of mLayout 8861 if (mEditor != null) mEditor.prepareCursorControllers(); 8862 } 8863 8864 /** 8865 * Make a new Layout based on the already-measured size of the view, 8866 * on the assumption that it was measured correctly at some point. 8867 */ 8868 @UnsupportedAppUsage assumeLayout()8869 private void assumeLayout() { 8870 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8871 8872 if (width < 1) { 8873 width = 0; 8874 } 8875 8876 int physicalWidth = width; 8877 8878 if (mHorizontallyScrolling) { 8879 width = VERY_WIDE; 8880 } 8881 8882 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 8883 physicalWidth, false); 8884 } 8885 8886 @UnsupportedAppUsage getLayoutAlignment()8887 private Layout.Alignment getLayoutAlignment() { 8888 Layout.Alignment alignment; 8889 switch (getTextAlignment()) { 8890 case TEXT_ALIGNMENT_GRAVITY: 8891 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 8892 case Gravity.START: 8893 alignment = Layout.Alignment.ALIGN_NORMAL; 8894 break; 8895 case Gravity.END: 8896 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8897 break; 8898 case Gravity.LEFT: 8899 alignment = Layout.Alignment.ALIGN_LEFT; 8900 break; 8901 case Gravity.RIGHT: 8902 alignment = Layout.Alignment.ALIGN_RIGHT; 8903 break; 8904 case Gravity.CENTER_HORIZONTAL: 8905 alignment = Layout.Alignment.ALIGN_CENTER; 8906 break; 8907 default: 8908 alignment = Layout.Alignment.ALIGN_NORMAL; 8909 break; 8910 } 8911 break; 8912 case TEXT_ALIGNMENT_TEXT_START: 8913 alignment = Layout.Alignment.ALIGN_NORMAL; 8914 break; 8915 case TEXT_ALIGNMENT_TEXT_END: 8916 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8917 break; 8918 case TEXT_ALIGNMENT_CENTER: 8919 alignment = Layout.Alignment.ALIGN_CENTER; 8920 break; 8921 case TEXT_ALIGNMENT_VIEW_START: 8922 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8923 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8924 break; 8925 case TEXT_ALIGNMENT_VIEW_END: 8926 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8927 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8928 break; 8929 case TEXT_ALIGNMENT_INHERIT: 8930 // This should never happen as we have already resolved the text alignment 8931 // but better safe than sorry so we just fall through 8932 default: 8933 alignment = Layout.Alignment.ALIGN_NORMAL; 8934 break; 8935 } 8936 return alignment; 8937 } 8938 8939 /** 8940 * The width passed in is now the desired layout width, 8941 * not the full view width with padding. 8942 * {@hide} 8943 */ 8944 @VisibleForTesting 8945 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8946 public void makeNewLayout(int wantWidth, int hintWidth, 8947 BoringLayout.Metrics boring, 8948 BoringLayout.Metrics hintBoring, 8949 int ellipsisWidth, boolean bringIntoView) { 8950 stopMarquee(); 8951 8952 // Update "old" cached values 8953 mOldMaximum = mMaximum; 8954 mOldMaxMode = mMaxMode; 8955 8956 mHighlightPathBogus = true; 8957 8958 if (wantWidth < 0) { 8959 wantWidth = 0; 8960 } 8961 if (hintWidth < 0) { 8962 hintWidth = 0; 8963 } 8964 8965 Layout.Alignment alignment = getLayoutAlignment(); 8966 final boolean testDirChange = mSingleLine && mLayout != null 8967 && (alignment == Layout.Alignment.ALIGN_NORMAL 8968 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 8969 int oldDir = 0; 8970 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 8971 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 8972 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 8973 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 8974 TruncateAt effectiveEllipsize = mEllipsize; 8975 if (mEllipsize == TruncateAt.MARQUEE 8976 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8977 effectiveEllipsize = TruncateAt.END_SMALL; 8978 } 8979 8980 if (mTextDir == null) { 8981 mTextDir = getTextDirectionHeuristic(); 8982 } 8983 8984 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 8985 effectiveEllipsize, effectiveEllipsize == mEllipsize); 8986 if (switchEllipsize) { 8987 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 8988 ? TruncateAt.END : TruncateAt.MARQUEE; 8989 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 8990 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 8991 } 8992 8993 shouldEllipsize = mEllipsize != null; 8994 mHintLayout = null; 8995 8996 if (mHint != null) { 8997 if (shouldEllipsize) hintWidth = wantWidth; 8998 8999 if (hintBoring == UNKNOWN_BORING) { 9000 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9001 mHintBoring); 9002 if (hintBoring != null) { 9003 mHintBoring = hintBoring; 9004 } 9005 } 9006 9007 if (hintBoring != null) { 9008 if (hintBoring.width <= hintWidth 9009 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9010 if (mSavedHintLayout != null) { 9011 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9012 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9013 hintBoring, mIncludePad); 9014 } else { 9015 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9016 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9017 hintBoring, mIncludePad); 9018 } 9019 9020 mSavedHintLayout = (BoringLayout) mHintLayout; 9021 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9022 if (mSavedHintLayout != null) { 9023 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9024 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9025 hintBoring, mIncludePad, mEllipsize, 9026 ellipsisWidth); 9027 } else { 9028 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9029 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9030 hintBoring, mIncludePad, mEllipsize, 9031 ellipsisWidth); 9032 } 9033 } 9034 } 9035 // TODO: code duplication with makeSingleLayout() 9036 if (mHintLayout == null) { 9037 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9038 mHint.length(), mTextPaint, hintWidth) 9039 .setAlignment(alignment) 9040 .setTextDirection(mTextDir) 9041 .setLineSpacing(mSpacingAdd, mSpacingMult) 9042 .setIncludePad(mIncludePad) 9043 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9044 .setBreakStrategy(mBreakStrategy) 9045 .setHyphenationFrequency(mHyphenationFrequency) 9046 .setJustificationMode(mJustificationMode) 9047 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9048 if (shouldEllipsize) { 9049 builder.setEllipsize(mEllipsize) 9050 .setEllipsizedWidth(ellipsisWidth); 9051 } 9052 mHintLayout = builder.build(); 9053 } 9054 } 9055 9056 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9057 registerForPreDraw(); 9058 } 9059 9060 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9061 if (!compressText(ellipsisWidth)) { 9062 final int height = mLayoutParams.height; 9063 // If the size of the view does not depend on the size of the text, try to 9064 // start the marquee immediately 9065 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9066 startMarquee(); 9067 } else { 9068 // Defer the start of the marquee until we know our width (see setFrame()) 9069 mRestartMarquee = true; 9070 } 9071 } 9072 } 9073 9074 // CursorControllers need a non-null mLayout 9075 if (mEditor != null) mEditor.prepareCursorControllers(); 9076 } 9077 9078 /** 9079 * Returns true if DynamicLayout is required 9080 * 9081 * @hide 9082 */ 9083 @VisibleForTesting useDynamicLayout()9084 public boolean useDynamicLayout() { 9085 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9086 } 9087 9088 /** 9089 * @hide 9090 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9091 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9092 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9093 boolean useSaved) { 9094 Layout result = null; 9095 if (useDynamicLayout()) { 9096 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9097 wantWidth) 9098 .setDisplayText(mTransformed) 9099 .setAlignment(alignment) 9100 .setTextDirection(mTextDir) 9101 .setLineSpacing(mSpacingAdd, mSpacingMult) 9102 .setIncludePad(mIncludePad) 9103 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9104 .setBreakStrategy(mBreakStrategy) 9105 .setHyphenationFrequency(mHyphenationFrequency) 9106 .setJustificationMode(mJustificationMode) 9107 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9108 .setEllipsizedWidth(ellipsisWidth); 9109 result = builder.build(); 9110 } else { 9111 if (boring == UNKNOWN_BORING) { 9112 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9113 if (boring != null) { 9114 mBoring = boring; 9115 } 9116 } 9117 9118 if (boring != null) { 9119 if (boring.width <= wantWidth 9120 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9121 if (useSaved && mSavedLayout != null) { 9122 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9123 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9124 boring, mIncludePad); 9125 } else { 9126 result = BoringLayout.make(mTransformed, mTextPaint, 9127 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9128 boring, mIncludePad); 9129 } 9130 9131 if (useSaved) { 9132 mSavedLayout = (BoringLayout) result; 9133 } 9134 } else if (shouldEllipsize && boring.width <= wantWidth) { 9135 if (useSaved && mSavedLayout != null) { 9136 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9137 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9138 boring, mIncludePad, effectiveEllipsize, 9139 ellipsisWidth); 9140 } else { 9141 result = BoringLayout.make(mTransformed, mTextPaint, 9142 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9143 boring, mIncludePad, effectiveEllipsize, 9144 ellipsisWidth); 9145 } 9146 } 9147 } 9148 } 9149 if (result == null) { 9150 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9151 0, mTransformed.length(), mTextPaint, wantWidth) 9152 .setAlignment(alignment) 9153 .setTextDirection(mTextDir) 9154 .setLineSpacing(mSpacingAdd, mSpacingMult) 9155 .setIncludePad(mIncludePad) 9156 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9157 .setBreakStrategy(mBreakStrategy) 9158 .setHyphenationFrequency(mHyphenationFrequency) 9159 .setJustificationMode(mJustificationMode) 9160 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9161 if (shouldEllipsize) { 9162 builder.setEllipsize(effectiveEllipsize) 9163 .setEllipsizedWidth(ellipsisWidth); 9164 } 9165 result = builder.build(); 9166 } 9167 return result; 9168 } 9169 9170 @UnsupportedAppUsage compressText(float width)9171 private boolean compressText(float width) { 9172 if (isHardwareAccelerated()) return false; 9173 9174 // Only compress the text if it hasn't been compressed by the previous pass 9175 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9176 && mTextPaint.getTextScaleX() == 1.0f) { 9177 final float textWidth = mLayout.getLineWidth(0); 9178 final float overflow = (textWidth + 1.0f - width) / width; 9179 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9180 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9181 post(new Runnable() { 9182 public void run() { 9183 requestLayout(); 9184 } 9185 }); 9186 return true; 9187 } 9188 } 9189 9190 return false; 9191 } 9192 desired(Layout layout)9193 private static int desired(Layout layout) { 9194 int n = layout.getLineCount(); 9195 CharSequence text = layout.getText(); 9196 float max = 0; 9197 9198 // if any line was wrapped, we can't use it. 9199 // but it's ok for the last line not to have a newline 9200 9201 for (int i = 0; i < n - 1; i++) { 9202 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9203 return -1; 9204 } 9205 } 9206 9207 for (int i = 0; i < n; i++) { 9208 max = Math.max(max, layout.getLineWidth(i)); 9209 } 9210 9211 return (int) Math.ceil(max); 9212 } 9213 9214 /** 9215 * Set whether the TextView includes extra top and bottom padding to make 9216 * room for accents that go above the normal ascent and descent. 9217 * The default is true. 9218 * 9219 * @see #getIncludeFontPadding() 9220 * 9221 * @attr ref android.R.styleable#TextView_includeFontPadding 9222 */ setIncludeFontPadding(boolean includepad)9223 public void setIncludeFontPadding(boolean includepad) { 9224 if (mIncludePad != includepad) { 9225 mIncludePad = includepad; 9226 9227 if (mLayout != null) { 9228 nullLayouts(); 9229 requestLayout(); 9230 invalidate(); 9231 } 9232 } 9233 } 9234 9235 /** 9236 * Gets whether the TextView includes extra top and bottom padding to make 9237 * room for accents that go above the normal ascent and descent. 9238 * 9239 * @see #setIncludeFontPadding(boolean) 9240 * 9241 * @attr ref android.R.styleable#TextView_includeFontPadding 9242 */ 9243 @InspectableProperty getIncludeFontPadding()9244 public boolean getIncludeFontPadding() { 9245 return mIncludePad; 9246 } 9247 9248 /** @hide */ 9249 @VisibleForTesting 9250 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9251 9252 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9253 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9254 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9255 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9256 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9257 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9258 9259 int width; 9260 int height; 9261 9262 BoringLayout.Metrics boring = UNKNOWN_BORING; 9263 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9264 9265 if (mTextDir == null) { 9266 mTextDir = getTextDirectionHeuristic(); 9267 } 9268 9269 int des = -1; 9270 boolean fromexisting = false; 9271 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9272 ? (float) widthSize : Float.MAX_VALUE; 9273 9274 if (widthMode == MeasureSpec.EXACTLY) { 9275 // Parent has told us how big to be. So be it. 9276 width = widthSize; 9277 } else { 9278 if (mLayout != null && mEllipsize == null) { 9279 des = desired(mLayout); 9280 } 9281 9282 if (des < 0) { 9283 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9284 if (boring != null) { 9285 mBoring = boring; 9286 } 9287 } else { 9288 fromexisting = true; 9289 } 9290 9291 if (boring == null || boring == UNKNOWN_BORING) { 9292 if (des < 0) { 9293 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9294 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9295 } 9296 width = des; 9297 } else { 9298 width = boring.width; 9299 } 9300 9301 final Drawables dr = mDrawables; 9302 if (dr != null) { 9303 width = Math.max(width, dr.mDrawableWidthTop); 9304 width = Math.max(width, dr.mDrawableWidthBottom); 9305 } 9306 9307 if (mHint != null) { 9308 int hintDes = -1; 9309 int hintWidth; 9310 9311 if (mHintLayout != null && mEllipsize == null) { 9312 hintDes = desired(mHintLayout); 9313 } 9314 9315 if (hintDes < 0) { 9316 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9317 if (hintBoring != null) { 9318 mHintBoring = hintBoring; 9319 } 9320 } 9321 9322 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9323 if (hintDes < 0) { 9324 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9325 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9326 } 9327 hintWidth = hintDes; 9328 } else { 9329 hintWidth = hintBoring.width; 9330 } 9331 9332 if (hintWidth > width) { 9333 width = hintWidth; 9334 } 9335 } 9336 9337 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9338 9339 if (mMaxWidthMode == EMS) { 9340 width = Math.min(width, mMaxWidth * getLineHeight()); 9341 } else { 9342 width = Math.min(width, mMaxWidth); 9343 } 9344 9345 if (mMinWidthMode == EMS) { 9346 width = Math.max(width, mMinWidth * getLineHeight()); 9347 } else { 9348 width = Math.max(width, mMinWidth); 9349 } 9350 9351 // Check against our minimum width 9352 width = Math.max(width, getSuggestedMinimumWidth()); 9353 9354 if (widthMode == MeasureSpec.AT_MOST) { 9355 width = Math.min(widthSize, width); 9356 } 9357 } 9358 9359 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9360 int unpaddedWidth = want; 9361 9362 if (mHorizontallyScrolling) want = VERY_WIDE; 9363 9364 int hintWant = want; 9365 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9366 9367 if (mLayout == null) { 9368 makeNewLayout(want, hintWant, boring, hintBoring, 9369 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9370 } else { 9371 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9372 || (mLayout.getEllipsizedWidth() 9373 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9374 9375 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9376 && (want > mLayout.getWidth()) 9377 && (mLayout instanceof BoringLayout 9378 || (fromexisting && des >= 0 && des <= want)); 9379 9380 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9381 9382 if (layoutChanged || maximumChanged) { 9383 if (!maximumChanged && widthChanged) { 9384 mLayout.increaseWidthTo(want); 9385 } else { 9386 makeNewLayout(want, hintWant, boring, hintBoring, 9387 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9388 } 9389 } else { 9390 // Nothing has changed 9391 } 9392 } 9393 9394 if (heightMode == MeasureSpec.EXACTLY) { 9395 // Parent has told us how big to be. So be it. 9396 height = heightSize; 9397 mDesiredHeightAtMeasure = -1; 9398 } else { 9399 int desired = getDesiredHeight(); 9400 9401 height = desired; 9402 mDesiredHeightAtMeasure = desired; 9403 9404 if (heightMode == MeasureSpec.AT_MOST) { 9405 height = Math.min(desired, heightSize); 9406 } 9407 } 9408 9409 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9410 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9411 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9412 } 9413 9414 /* 9415 * We didn't let makeNewLayout() register to bring the cursor into view, 9416 * so do it here if there is any possibility that it is needed. 9417 */ 9418 if (mMovement != null 9419 || mLayout.getWidth() > unpaddedWidth 9420 || mLayout.getHeight() > unpaddedHeight) { 9421 registerForPreDraw(); 9422 } else { 9423 scrollTo(0, 0); 9424 } 9425 9426 setMeasuredDimension(width, height); 9427 } 9428 9429 /** 9430 * Automatically computes and sets the text size. 9431 */ autoSizeText()9432 private void autoSizeText() { 9433 if (!isAutoSizeEnabled()) { 9434 return; 9435 } 9436 9437 if (mNeedsAutoSizeText) { 9438 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9439 return; 9440 } 9441 9442 final int availableWidth = mHorizontallyScrolling 9443 ? VERY_WIDE 9444 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9445 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9446 - getExtendedPaddingTop(); 9447 9448 if (availableWidth <= 0 || availableHeight <= 0) { 9449 return; 9450 } 9451 9452 synchronized (TEMP_RECTF) { 9453 TEMP_RECTF.setEmpty(); 9454 TEMP_RECTF.right = availableWidth; 9455 TEMP_RECTF.bottom = availableHeight; 9456 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9457 9458 if (optimalTextSize != getTextSize()) { 9459 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9460 false /* shouldRequestLayout */); 9461 9462 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9463 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9464 false /* bringIntoView */); 9465 } 9466 } 9467 } 9468 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9469 // after the next layout pass should set this to false. 9470 mNeedsAutoSizeText = true; 9471 } 9472 9473 /** 9474 * Performs a binary search to find the largest text size that will still fit within the size 9475 * available to this view. 9476 */ findLargestTextSizeWhichFits(RectF availableSpace)9477 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9478 final int sizesCount = mAutoSizeTextSizesInPx.length; 9479 if (sizesCount == 0) { 9480 throw new IllegalStateException("No available text sizes to choose from."); 9481 } 9482 9483 int bestSizeIndex = 0; 9484 int lowIndex = bestSizeIndex + 1; 9485 int highIndex = sizesCount - 1; 9486 int sizeToTryIndex; 9487 while (lowIndex <= highIndex) { 9488 sizeToTryIndex = (lowIndex + highIndex) / 2; 9489 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9490 bestSizeIndex = lowIndex; 9491 lowIndex = sizeToTryIndex + 1; 9492 } else { 9493 highIndex = sizeToTryIndex - 1; 9494 bestSizeIndex = highIndex; 9495 } 9496 } 9497 9498 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9499 } 9500 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9501 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9502 final CharSequence text = mTransformed != null 9503 ? mTransformed 9504 : getText(); 9505 final int maxLines = getMaxLines(); 9506 if (mTempTextPaint == null) { 9507 mTempTextPaint = new TextPaint(); 9508 } else { 9509 mTempTextPaint.reset(); 9510 } 9511 mTempTextPaint.set(getPaint()); 9512 mTempTextPaint.setTextSize(suggestedSizeInPx); 9513 9514 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9515 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9516 9517 layoutBuilder.setAlignment(getLayoutAlignment()) 9518 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9519 .setIncludePad(getIncludeFontPadding()) 9520 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9521 .setBreakStrategy(getBreakStrategy()) 9522 .setHyphenationFrequency(getHyphenationFrequency()) 9523 .setJustificationMode(getJustificationMode()) 9524 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9525 .setTextDirection(getTextDirectionHeuristic()); 9526 9527 final StaticLayout layout = layoutBuilder.build(); 9528 9529 // Lines overflow. 9530 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9531 return false; 9532 } 9533 9534 // Height overflow. 9535 if (layout.getHeight() > availableSpace.bottom) { 9536 return false; 9537 } 9538 9539 return true; 9540 } 9541 getDesiredHeight()9542 private int getDesiredHeight() { 9543 return Math.max( 9544 getDesiredHeight(mLayout, true), 9545 getDesiredHeight(mHintLayout, mEllipsize != null)); 9546 } 9547 getDesiredHeight(Layout layout, boolean cap)9548 private int getDesiredHeight(Layout layout, boolean cap) { 9549 if (layout == null) { 9550 return 0; 9551 } 9552 9553 /* 9554 * Don't cap the hint to a certain number of lines. 9555 * (Do cap it, though, if we have a maximum pixel height.) 9556 */ 9557 int desired = layout.getHeight(cap); 9558 9559 final Drawables dr = mDrawables; 9560 if (dr != null) { 9561 desired = Math.max(desired, dr.mDrawableHeightLeft); 9562 desired = Math.max(desired, dr.mDrawableHeightRight); 9563 } 9564 9565 int linecount = layout.getLineCount(); 9566 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9567 desired += padding; 9568 9569 if (mMaxMode != LINES) { 9570 desired = Math.min(desired, mMaximum); 9571 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9572 || layout instanceof BoringLayout)) { 9573 desired = layout.getLineTop(mMaximum); 9574 9575 if (dr != null) { 9576 desired = Math.max(desired, dr.mDrawableHeightLeft); 9577 desired = Math.max(desired, dr.mDrawableHeightRight); 9578 } 9579 9580 desired += padding; 9581 linecount = mMaximum; 9582 } 9583 9584 if (mMinMode == LINES) { 9585 if (linecount < mMinimum) { 9586 desired += getLineHeight() * (mMinimum - linecount); 9587 } 9588 } else { 9589 desired = Math.max(desired, mMinimum); 9590 } 9591 9592 // Check against our minimum height 9593 desired = Math.max(desired, getSuggestedMinimumHeight()); 9594 9595 return desired; 9596 } 9597 9598 /** 9599 * Check whether a change to the existing text layout requires a 9600 * new view layout. 9601 */ checkForResize()9602 private void checkForResize() { 9603 boolean sizeChanged = false; 9604 9605 if (mLayout != null) { 9606 // Check if our width changed 9607 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9608 sizeChanged = true; 9609 invalidate(); 9610 } 9611 9612 // Check if our height changed 9613 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9614 int desiredHeight = getDesiredHeight(); 9615 9616 if (desiredHeight != this.getHeight()) { 9617 sizeChanged = true; 9618 } 9619 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9620 if (mDesiredHeightAtMeasure >= 0) { 9621 int desiredHeight = getDesiredHeight(); 9622 9623 if (desiredHeight != mDesiredHeightAtMeasure) { 9624 sizeChanged = true; 9625 } 9626 } 9627 } 9628 } 9629 9630 if (sizeChanged) { 9631 requestLayout(); 9632 // caller will have already invalidated 9633 } 9634 } 9635 9636 /** 9637 * Check whether entirely new text requires a new view layout 9638 * or merely a new text layout. 9639 */ 9640 @UnsupportedAppUsage checkForRelayout()9641 private void checkForRelayout() { 9642 // If we have a fixed width, we can just swap in a new text layout 9643 // if the text height stays the same or if the view height is fixed. 9644 9645 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9646 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9647 && (mHint == null || mHintLayout != null) 9648 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9649 // Static width, so try making a new text layout. 9650 9651 int oldht = mLayout.getHeight(); 9652 int want = mLayout.getWidth(); 9653 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9654 9655 /* 9656 * No need to bring the text into view, since the size is not 9657 * changing (unless we do the requestLayout(), in which case it 9658 * will happen at measure). 9659 */ 9660 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9661 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9662 false); 9663 9664 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9665 // In a fixed-height view, so use our new text layout. 9666 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9667 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9668 autoSizeText(); 9669 invalidate(); 9670 return; 9671 } 9672 9673 // Dynamic height, but height has stayed the same, 9674 // so use our new text layout. 9675 if (mLayout.getHeight() == oldht 9676 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9677 autoSizeText(); 9678 invalidate(); 9679 return; 9680 } 9681 } 9682 9683 // We lose: the height has changed and we have a dynamic height. 9684 // Request a new view layout using our new text layout. 9685 requestLayout(); 9686 invalidate(); 9687 } else { 9688 // Dynamic width, so we have no choice but to request a new 9689 // view layout with a new text layout. 9690 nullLayouts(); 9691 requestLayout(); 9692 invalidate(); 9693 } 9694 } 9695 9696 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9697 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9698 super.onLayout(changed, left, top, right, bottom); 9699 if (mDeferScroll >= 0) { 9700 int curs = mDeferScroll; 9701 mDeferScroll = -1; 9702 bringPointIntoView(Math.min(curs, mText.length())); 9703 } 9704 // Call auto-size after the width and height have been calculated. 9705 autoSizeText(); 9706 } 9707 isShowingHint()9708 private boolean isShowingHint() { 9709 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9710 } 9711 9712 /** 9713 * Returns true if anything changed. 9714 */ 9715 @UnsupportedAppUsage bringTextIntoView()9716 private boolean bringTextIntoView() { 9717 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9718 int line = 0; 9719 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9720 line = layout.getLineCount() - 1; 9721 } 9722 9723 Layout.Alignment a = layout.getParagraphAlignment(line); 9724 int dir = layout.getParagraphDirection(line); 9725 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9726 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9727 int ht = layout.getHeight(); 9728 9729 int scrollx, scrolly; 9730 9731 // Convert to left, center, or right alignment. 9732 if (a == Layout.Alignment.ALIGN_NORMAL) { 9733 a = dir == Layout.DIR_LEFT_TO_RIGHT 9734 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9735 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9736 a = dir == Layout.DIR_LEFT_TO_RIGHT 9737 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9738 } 9739 9740 if (a == Layout.Alignment.ALIGN_CENTER) { 9741 /* 9742 * Keep centered if possible, or, if it is too wide to fit, 9743 * keep leading edge in view. 9744 */ 9745 9746 int left = (int) Math.floor(layout.getLineLeft(line)); 9747 int right = (int) Math.ceil(layout.getLineRight(line)); 9748 9749 if (right - left < hspace) { 9750 scrollx = (right + left) / 2 - hspace / 2; 9751 } else { 9752 if (dir < 0) { 9753 scrollx = right - hspace; 9754 } else { 9755 scrollx = left; 9756 } 9757 } 9758 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9759 int right = (int) Math.ceil(layout.getLineRight(line)); 9760 scrollx = right - hspace; 9761 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9762 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9763 } 9764 9765 if (ht < vspace) { 9766 scrolly = 0; 9767 } else { 9768 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9769 scrolly = ht - vspace; 9770 } else { 9771 scrolly = 0; 9772 } 9773 } 9774 9775 if (scrollx != mScrollX || scrolly != mScrollY) { 9776 scrollTo(scrollx, scrolly); 9777 return true; 9778 } else { 9779 return false; 9780 } 9781 } 9782 9783 /** 9784 * Move the point, specified by the offset, into the view if it is needed. 9785 * This has to be called after layout. Returns true if anything changed. 9786 */ bringPointIntoView(int offset)9787 public boolean bringPointIntoView(int offset) { 9788 if (isLayoutRequested()) { 9789 mDeferScroll = offset; 9790 return false; 9791 } 9792 boolean changed = false; 9793 9794 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9795 9796 if (layout == null) return changed; 9797 9798 int line = layout.getLineForOffset(offset); 9799 9800 int grav; 9801 9802 switch (layout.getParagraphAlignment(line)) { 9803 case ALIGN_LEFT: 9804 grav = 1; 9805 break; 9806 case ALIGN_RIGHT: 9807 grav = -1; 9808 break; 9809 case ALIGN_NORMAL: 9810 grav = layout.getParagraphDirection(line); 9811 break; 9812 case ALIGN_OPPOSITE: 9813 grav = -layout.getParagraphDirection(line); 9814 break; 9815 case ALIGN_CENTER: 9816 default: 9817 grav = 0; 9818 break; 9819 } 9820 9821 // We only want to clamp the cursor to fit within the layout width 9822 // in left-to-right modes, because in a right to left alignment, 9823 // we want to scroll to keep the line-right on the screen, as other 9824 // lines are likely to have text flush with the right margin, which 9825 // we want to keep visible. 9826 // A better long-term solution would probably be to measure both 9827 // the full line and a blank-trimmed version, and, for example, use 9828 // the latter measurement for centering and right alignment, but for 9829 // the time being we only implement the cursor clamping in left to 9830 // right where it is most likely to be annoying. 9831 final boolean clamped = grav > 0; 9832 // FIXME: Is it okay to truncate this, or should we round? 9833 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9834 final int top = layout.getLineTop(line); 9835 final int bottom = layout.getLineTop(line + 1); 9836 9837 int left = (int) Math.floor(layout.getLineLeft(line)); 9838 int right = (int) Math.ceil(layout.getLineRight(line)); 9839 int ht = layout.getHeight(); 9840 9841 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9842 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9843 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9844 // If cursor has been clamped, make sure we don't scroll. 9845 right = Math.max(x, left + hspace); 9846 } 9847 9848 int hslack = (bottom - top) / 2; 9849 int vslack = hslack; 9850 9851 if (vslack > vspace / 4) { 9852 vslack = vspace / 4; 9853 } 9854 if (hslack > hspace / 4) { 9855 hslack = hspace / 4; 9856 } 9857 9858 int hs = mScrollX; 9859 int vs = mScrollY; 9860 9861 if (top - vs < vslack) { 9862 vs = top - vslack; 9863 } 9864 if (bottom - vs > vspace - vslack) { 9865 vs = bottom - (vspace - vslack); 9866 } 9867 if (ht - vs < vspace) { 9868 vs = ht - vspace; 9869 } 9870 if (0 - vs > 0) { 9871 vs = 0; 9872 } 9873 9874 if (grav != 0) { 9875 if (x - hs < hslack) { 9876 hs = x - hslack; 9877 } 9878 if (x - hs > hspace - hslack) { 9879 hs = x - (hspace - hslack); 9880 } 9881 } 9882 9883 if (grav < 0) { 9884 if (left - hs > 0) { 9885 hs = left; 9886 } 9887 if (right - hs < hspace) { 9888 hs = right - hspace; 9889 } 9890 } else if (grav > 0) { 9891 if (right - hs < hspace) { 9892 hs = right - hspace; 9893 } 9894 if (left - hs > 0) { 9895 hs = left; 9896 } 9897 } else /* grav == 0 */ { 9898 if (right - left <= hspace) { 9899 /* 9900 * If the entire text fits, center it exactly. 9901 */ 9902 hs = left - (hspace - (right - left)) / 2; 9903 } else if (x > right - hslack) { 9904 /* 9905 * If we are near the right edge, keep the right edge 9906 * at the edge of the view. 9907 */ 9908 hs = right - hspace; 9909 } else if (x < left + hslack) { 9910 /* 9911 * If we are near the left edge, keep the left edge 9912 * at the edge of the view. 9913 */ 9914 hs = left; 9915 } else if (left > hs) { 9916 /* 9917 * Is there whitespace visible at the left? Fix it if so. 9918 */ 9919 hs = left; 9920 } else if (right < hs + hspace) { 9921 /* 9922 * Is there whitespace visible at the right? Fix it if so. 9923 */ 9924 hs = right - hspace; 9925 } else { 9926 /* 9927 * Otherwise, float as needed. 9928 */ 9929 if (x - hs < hslack) { 9930 hs = x - hslack; 9931 } 9932 if (x - hs > hspace - hslack) { 9933 hs = x - (hspace - hslack); 9934 } 9935 } 9936 } 9937 9938 if (hs != mScrollX || vs != mScrollY) { 9939 if (mScroller == null) { 9940 scrollTo(hs, vs); 9941 } else { 9942 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 9943 int dx = hs - mScrollX; 9944 int dy = vs - mScrollY; 9945 9946 if (duration > ANIMATED_SCROLL_GAP) { 9947 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 9948 awakenScrollBars(mScroller.getDuration()); 9949 invalidate(); 9950 } else { 9951 if (!mScroller.isFinished()) { 9952 mScroller.abortAnimation(); 9953 } 9954 9955 scrollBy(dx, dy); 9956 } 9957 9958 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 9959 } 9960 9961 changed = true; 9962 } 9963 9964 if (isFocused()) { 9965 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 9966 // requestRectangleOnScreen() is in terms of content coordinates. 9967 9968 // The offsets here are to ensure the rectangle we are using is 9969 // within our view bounds, in case the cursor is on the far left 9970 // or right. If it isn't withing the bounds, then this request 9971 // will be ignored. 9972 if (mTempRect == null) mTempRect = new Rect(); 9973 mTempRect.set(x - 2, top, x + 2, bottom); 9974 getInterestingRect(mTempRect, line); 9975 mTempRect.offset(mScrollX, mScrollY); 9976 9977 if (requestRectangleOnScreen(mTempRect)) { 9978 changed = true; 9979 } 9980 } 9981 9982 return changed; 9983 } 9984 9985 /** 9986 * Move the cursor, if needed, so that it is at an offset that is visible 9987 * to the user. This will not move the cursor if it represents more than 9988 * one character (a selection range). This will only work if the 9989 * TextView contains spannable text; otherwise it will do nothing. 9990 * 9991 * @return True if the cursor was actually moved, false otherwise. 9992 */ moveCursorToVisibleOffset()9993 public boolean moveCursorToVisibleOffset() { 9994 if (!(mText instanceof Spannable)) { 9995 return false; 9996 } 9997 int start = getSelectionStart(); 9998 int end = getSelectionEnd(); 9999 if (start != end) { 10000 return false; 10001 } 10002 10003 // First: make sure the line is visible on screen: 10004 10005 int line = mLayout.getLineForOffset(start); 10006 10007 final int top = mLayout.getLineTop(line); 10008 final int bottom = mLayout.getLineTop(line + 1); 10009 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10010 int vslack = (bottom - top) / 2; 10011 if (vslack > vspace / 4) { 10012 vslack = vspace / 4; 10013 } 10014 final int vs = mScrollY; 10015 10016 if (top < (vs + vslack)) { 10017 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10018 } else if (bottom > (vspace + vs - vslack)) { 10019 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10020 } 10021 10022 // Next: make sure the character is visible on screen: 10023 10024 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10025 final int hs = mScrollX; 10026 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10027 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10028 10029 // line might contain bidirectional text 10030 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10031 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10032 10033 int newStart = start; 10034 if (newStart < lowChar) { 10035 newStart = lowChar; 10036 } else if (newStart > highChar) { 10037 newStart = highChar; 10038 } 10039 10040 if (newStart != start) { 10041 Selection.setSelection(mSpannable, newStart); 10042 return true; 10043 } 10044 10045 return false; 10046 } 10047 10048 @Override computeScroll()10049 public void computeScroll() { 10050 if (mScroller != null) { 10051 if (mScroller.computeScrollOffset()) { 10052 mScrollX = mScroller.getCurrX(); 10053 mScrollY = mScroller.getCurrY(); 10054 invalidateParentCaches(); 10055 postInvalidate(); // So we draw again 10056 } 10057 } 10058 } 10059 getInterestingRect(Rect r, int line)10060 private void getInterestingRect(Rect r, int line) { 10061 convertFromViewportToContentCoordinates(r); 10062 10063 // Rectangle can can be expanded on first and last line to take 10064 // padding into account. 10065 // TODO Take left/right padding into account too? 10066 if (line == 0) r.top -= getExtendedPaddingTop(); 10067 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10068 } 10069 convertFromViewportToContentCoordinates(Rect r)10070 private void convertFromViewportToContentCoordinates(Rect r) { 10071 final int horizontalOffset = viewportToContentHorizontalOffset(); 10072 r.left += horizontalOffset; 10073 r.right += horizontalOffset; 10074 10075 final int verticalOffset = viewportToContentVerticalOffset(); 10076 r.top += verticalOffset; 10077 r.bottom += verticalOffset; 10078 } 10079 viewportToContentHorizontalOffset()10080 int viewportToContentHorizontalOffset() { 10081 return getCompoundPaddingLeft() - mScrollX; 10082 } 10083 10084 @UnsupportedAppUsage viewportToContentVerticalOffset()10085 int viewportToContentVerticalOffset() { 10086 int offset = getExtendedPaddingTop() - mScrollY; 10087 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10088 offset += getVerticalOffset(false); 10089 } 10090 return offset; 10091 } 10092 10093 @Override debug(int depth)10094 public void debug(int depth) { 10095 super.debug(depth); 10096 10097 String output = debugIndent(depth); 10098 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10099 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10100 + "} "; 10101 10102 if (mText != null) { 10103 10104 output += "mText=\"" + mText + "\" "; 10105 if (mLayout != null) { 10106 output += "mLayout width=" + mLayout.getWidth() 10107 + " height=" + mLayout.getHeight(); 10108 } 10109 } else { 10110 output += "mText=NULL"; 10111 } 10112 Log.d(VIEW_LOG_TAG, output); 10113 } 10114 10115 /** 10116 * Convenience for {@link Selection#getSelectionStart}. 10117 */ 10118 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10119 public int getSelectionStart() { 10120 return Selection.getSelectionStart(getText()); 10121 } 10122 10123 /** 10124 * Convenience for {@link Selection#getSelectionEnd}. 10125 */ 10126 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10127 public int getSelectionEnd() { 10128 return Selection.getSelectionEnd(getText()); 10129 } 10130 10131 /** 10132 * Return true iff there is a selection of nonzero length inside this text view. 10133 */ hasSelection()10134 public boolean hasSelection() { 10135 final int selectionStart = getSelectionStart(); 10136 final int selectionEnd = getSelectionEnd(); 10137 10138 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10139 } 10140 getSelectedText()10141 String getSelectedText() { 10142 if (!hasSelection()) { 10143 return null; 10144 } 10145 10146 final int start = getSelectionStart(); 10147 final int end = getSelectionEnd(); 10148 return String.valueOf( 10149 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10150 } 10151 10152 /** 10153 * Sets the properties of this field (lines, horizontally scrolling, 10154 * transformation method) to be for a single-line input. 10155 * 10156 * @attr ref android.R.styleable#TextView_singleLine 10157 */ setSingleLine()10158 public void setSingleLine() { 10159 setSingleLine(true); 10160 } 10161 10162 /** 10163 * Sets the properties of this field to transform input to ALL CAPS 10164 * display. This may use a "small caps" formatting if available. 10165 * This setting will be ignored if this field is editable or selectable. 10166 * 10167 * This call replaces the current transformation method. Disabling this 10168 * will not necessarily restore the previous behavior from before this 10169 * was enabled. 10170 * 10171 * @see #setTransformationMethod(TransformationMethod) 10172 * @attr ref android.R.styleable#TextView_textAllCaps 10173 */ setAllCaps(boolean allCaps)10174 public void setAllCaps(boolean allCaps) { 10175 if (allCaps) { 10176 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10177 } else { 10178 setTransformationMethod(null); 10179 } 10180 } 10181 10182 /** 10183 * 10184 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10185 * @return Whether the current transformation method is for ALL CAPS. 10186 * 10187 * @see #setAllCaps(boolean) 10188 * @see #setTransformationMethod(TransformationMethod) 10189 */ 10190 @InspectableProperty(name = "textAllCaps") isAllCaps()10191 public boolean isAllCaps() { 10192 final TransformationMethod method = getTransformationMethod(); 10193 return method != null && method instanceof AllCapsTransformationMethod; 10194 } 10195 10196 /** 10197 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10198 * transformation method) to be for a single-line input; if false, restores these to the default 10199 * conditions. 10200 * 10201 * Note that the default conditions are not necessarily those that were in effect prior this 10202 * method, and you may want to reset these properties to your custom values. 10203 * 10204 * @attr ref android.R.styleable#TextView_singleLine 10205 */ 10206 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10207 public void setSingleLine(boolean singleLine) { 10208 // Could be used, but may break backward compatibility. 10209 // if (mSingleLine == singleLine) return; 10210 setInputTypeSingleLine(singleLine); 10211 applySingleLine(singleLine, true, true); 10212 } 10213 10214 /** 10215 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10216 * @param singleLine 10217 */ setInputTypeSingleLine(boolean singleLine)10218 private void setInputTypeSingleLine(boolean singleLine) { 10219 if (mEditor != null 10220 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10221 == EditorInfo.TYPE_CLASS_TEXT) { 10222 if (singleLine) { 10223 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10224 } else { 10225 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10226 } 10227 } 10228 } 10229 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10230 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10231 boolean changeMaxLines) { 10232 mSingleLine = singleLine; 10233 if (singleLine) { 10234 setLines(1); 10235 setHorizontallyScrolling(true); 10236 if (applyTransformation) { 10237 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10238 } 10239 } else { 10240 if (changeMaxLines) { 10241 setMaxLines(Integer.MAX_VALUE); 10242 } 10243 setHorizontallyScrolling(false); 10244 if (applyTransformation) { 10245 setTransformationMethod(null); 10246 } 10247 } 10248 } 10249 10250 /** 10251 * Causes words in the text that are longer than the view's width 10252 * to be ellipsized instead of broken in the middle. You may also 10253 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10254 * to constrain the text to a single line. Use <code>null</code> 10255 * to turn off ellipsizing. 10256 * 10257 * If {@link #setMaxLines} has been used to set two or more lines, 10258 * only {@link android.text.TextUtils.TruncateAt#END} and 10259 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10260 * (other ellipsizing types will not do anything). 10261 * 10262 * @attr ref android.R.styleable#TextView_ellipsize 10263 */ setEllipsize(TextUtils.TruncateAt where)10264 public void setEllipsize(TextUtils.TruncateAt where) { 10265 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10266 if (mEllipsize != where) { 10267 mEllipsize = where; 10268 10269 if (mLayout != null) { 10270 nullLayouts(); 10271 requestLayout(); 10272 invalidate(); 10273 } 10274 } 10275 } 10276 10277 /** 10278 * Sets how many times to repeat the marquee animation. Only applied if the 10279 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10280 * 10281 * @see #getMarqueeRepeatLimit() 10282 * 10283 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10284 */ setMarqueeRepeatLimit(int marqueeLimit)10285 public void setMarqueeRepeatLimit(int marqueeLimit) { 10286 mMarqueeRepeatLimit = marqueeLimit; 10287 } 10288 10289 /** 10290 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10291 * TextView has marquee enabled. 10292 * 10293 * @return the number of times the marquee animation is repeated. -1 if the animation 10294 * repeats indefinitely 10295 * 10296 * @see #setMarqueeRepeatLimit(int) 10297 * 10298 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10299 */ 10300 @InspectableProperty getMarqueeRepeatLimit()10301 public int getMarqueeRepeatLimit() { 10302 return mMarqueeRepeatLimit; 10303 } 10304 10305 /** 10306 * Returns where, if anywhere, words that are longer than the view 10307 * is wide should be ellipsized. 10308 */ 10309 @InspectableProperty 10310 @ViewDebug.ExportedProperty getEllipsize()10311 public TextUtils.TruncateAt getEllipsize() { 10312 return mEllipsize; 10313 } 10314 10315 /** 10316 * Set the TextView so that when it takes focus, all the text is 10317 * selected. 10318 * 10319 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10320 */ 10321 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10322 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10323 createEditorIfNeeded(); 10324 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10325 10326 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10327 setText(mText, BufferType.SPANNABLE); 10328 } 10329 } 10330 10331 /** 10332 * Set whether the cursor is visible. The default is true. Note that this property only 10333 * makes sense for editable TextView. 10334 * 10335 * @see #isCursorVisible() 10336 * 10337 * @attr ref android.R.styleable#TextView_cursorVisible 10338 */ 10339 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10340 public void setCursorVisible(boolean visible) { 10341 if (visible && mEditor == null) return; // visible is the default value with no edit data 10342 createEditorIfNeeded(); 10343 if (mEditor.mCursorVisible != visible) { 10344 mEditor.mCursorVisible = visible; 10345 invalidate(); 10346 10347 mEditor.makeBlink(); 10348 10349 // InsertionPointCursorController depends on mCursorVisible 10350 mEditor.prepareCursorControllers(); 10351 } 10352 } 10353 10354 /** 10355 * @return whether or not the cursor is visible (assuming this TextView is editable) 10356 * 10357 * @see #setCursorVisible(boolean) 10358 * 10359 * @attr ref android.R.styleable#TextView_cursorVisible 10360 */ 10361 @InspectableProperty isCursorVisible()10362 public boolean isCursorVisible() { 10363 // true is the default value 10364 return mEditor == null ? true : mEditor.mCursorVisible; 10365 } 10366 canMarquee()10367 private boolean canMarquee() { 10368 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10369 return width > 0 && (mLayout.getLineWidth(0) > width 10370 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10371 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10372 } 10373 10374 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10375 private void startMarquee() { 10376 // Do not ellipsize EditText 10377 if (getKeyListener() != null) return; 10378 10379 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10380 return; 10381 } 10382 10383 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10384 && getLineCount() == 1 && canMarquee()) { 10385 10386 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10387 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10388 final Layout tmp = mLayout; 10389 mLayout = mSavedMarqueeModeLayout; 10390 mSavedMarqueeModeLayout = tmp; 10391 setHorizontalFadingEdgeEnabled(true); 10392 requestLayout(); 10393 invalidate(); 10394 } 10395 10396 if (mMarquee == null) mMarquee = new Marquee(this); 10397 mMarquee.start(mMarqueeRepeatLimit); 10398 } 10399 } 10400 stopMarquee()10401 private void stopMarquee() { 10402 if (mMarquee != null && !mMarquee.isStopped()) { 10403 mMarquee.stop(); 10404 } 10405 10406 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10407 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10408 final Layout tmp = mSavedMarqueeModeLayout; 10409 mSavedMarqueeModeLayout = mLayout; 10410 mLayout = tmp; 10411 setHorizontalFadingEdgeEnabled(false); 10412 requestLayout(); 10413 invalidate(); 10414 } 10415 } 10416 10417 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10418 private void startStopMarquee(boolean start) { 10419 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10420 if (start) { 10421 startMarquee(); 10422 } else { 10423 stopMarquee(); 10424 } 10425 } 10426 } 10427 10428 /** 10429 * This method is called when the text is changed, in case any subclasses 10430 * would like to know. 10431 * 10432 * Within <code>text</code>, the <code>lengthAfter</code> characters 10433 * beginning at <code>start</code> have just replaced old text that had 10434 * length <code>lengthBefore</code>. It is an error to attempt to make 10435 * changes to <code>text</code> from this callback. 10436 * 10437 * @param text The text the TextView is displaying 10438 * @param start The offset of the start of the range of the text that was 10439 * modified 10440 * @param lengthBefore The length of the former text that has been replaced 10441 * @param lengthAfter The length of the replacement modified text 10442 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10443 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10444 // intentionally empty, template pattern method can be overridden by subclasses 10445 } 10446 10447 /** 10448 * This method is called when the selection has changed, in case any 10449 * subclasses would like to know. 10450 * 10451 * @param selStart The new selection start location. 10452 * @param selEnd The new selection end location. 10453 */ onSelectionChanged(int selStart, int selEnd)10454 protected void onSelectionChanged(int selStart, int selEnd) { 10455 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10456 } 10457 10458 /** 10459 * Adds a TextWatcher to the list of those whose methods are called 10460 * whenever this TextView's text changes. 10461 * <p> 10462 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10463 * not called after {@link #setText} calls. Now, doing {@link #setText} 10464 * if there are any text changed listeners forces the buffer type to 10465 * Editable if it would not otherwise be and does call this method. 10466 */ addTextChangedListener(TextWatcher watcher)10467 public void addTextChangedListener(TextWatcher watcher) { 10468 if (mListeners == null) { 10469 mListeners = new ArrayList<TextWatcher>(); 10470 } 10471 10472 mListeners.add(watcher); 10473 } 10474 10475 /** 10476 * Removes the specified TextWatcher from the list of those whose 10477 * methods are called 10478 * whenever this TextView's text changes. 10479 */ removeTextChangedListener(TextWatcher watcher)10480 public void removeTextChangedListener(TextWatcher watcher) { 10481 if (mListeners != null) { 10482 int i = mListeners.indexOf(watcher); 10483 10484 if (i >= 0) { 10485 mListeners.remove(i); 10486 } 10487 } 10488 } 10489 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10490 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10491 if (mListeners != null) { 10492 final ArrayList<TextWatcher> list = mListeners; 10493 final int count = list.size(); 10494 for (int i = 0; i < count; i++) { 10495 list.get(i).beforeTextChanged(text, start, before, after); 10496 } 10497 } 10498 10499 // The spans that are inside or intersect the modified region no longer make sense 10500 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10501 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10502 } 10503 10504 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10505 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10506 if (!(mText instanceof Editable)) return; 10507 Editable text = (Editable) mText; 10508 10509 T[] spans = text.getSpans(start, end, type); 10510 final int length = spans.length; 10511 for (int i = 0; i < length; i++) { 10512 final int spanStart = text.getSpanStart(spans[i]); 10513 final int spanEnd = text.getSpanEnd(spans[i]); 10514 if (spanEnd == start || spanStart == end) break; 10515 text.removeSpan(spans[i]); 10516 } 10517 } 10518 removeAdjacentSuggestionSpans(final int pos)10519 void removeAdjacentSuggestionSpans(final int pos) { 10520 if (!(mText instanceof Editable)) return; 10521 final Editable text = (Editable) mText; 10522 10523 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10524 final int length = spans.length; 10525 for (int i = 0; i < length; i++) { 10526 final int spanStart = text.getSpanStart(spans[i]); 10527 final int spanEnd = text.getSpanEnd(spans[i]); 10528 if (spanEnd == pos || spanStart == pos) { 10529 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10530 text.removeSpan(spans[i]); 10531 } 10532 } 10533 } 10534 } 10535 10536 /** 10537 * Not private so it can be called from an inner class without going 10538 * through a thunk. 10539 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10540 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10541 if (mListeners != null) { 10542 final ArrayList<TextWatcher> list = mListeners; 10543 final int count = list.size(); 10544 for (int i = 0; i < count; i++) { 10545 list.get(i).onTextChanged(text, start, before, after); 10546 } 10547 } 10548 10549 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10550 } 10551 10552 /** 10553 * Not private so it can be called from an inner class without going 10554 * through a thunk. 10555 */ sendAfterTextChanged(Editable text)10556 void sendAfterTextChanged(Editable text) { 10557 if (mListeners != null) { 10558 final ArrayList<TextWatcher> list = mListeners; 10559 final int count = list.size(); 10560 for (int i = 0; i < count; i++) { 10561 list.get(i).afterTextChanged(text); 10562 } 10563 } 10564 10565 notifyListeningManagersAfterTextChanged(); 10566 10567 hideErrorIfUnchanged(); 10568 } 10569 10570 /** 10571 * Notify managers (such as {@link AutofillManager}) that are interested in text changes. 10572 */ notifyListeningManagersAfterTextChanged()10573 private void notifyListeningManagersAfterTextChanged() { 10574 10575 // Autofill 10576 if (isAutofillable()) { 10577 // It is important to not check whether the view is important for autofill 10578 // since the user can trigger autofill manually on not important views. 10579 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10580 if (afm != null) { 10581 if (android.view.autofill.Helper.sVerbose) { 10582 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10583 } 10584 afm.notifyValueChanged(TextView.this); 10585 } 10586 } 10587 } 10588 isAutofillable()10589 private boolean isAutofillable() { 10590 // It is important to not check whether the view is important for autofill 10591 // since the user can trigger autofill manually on not important views. 10592 return getAutofillType() != AUTOFILL_TYPE_NONE; 10593 } 10594 updateAfterEdit()10595 void updateAfterEdit() { 10596 invalidate(); 10597 int curs = getSelectionStart(); 10598 10599 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10600 registerForPreDraw(); 10601 } 10602 10603 checkForResize(); 10604 10605 if (curs >= 0) { 10606 mHighlightPathBogus = true; 10607 if (mEditor != null) mEditor.makeBlink(); 10608 bringPointIntoView(curs); 10609 } 10610 } 10611 10612 /** 10613 * Not private so it can be called from an inner class without going 10614 * through a thunk. 10615 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10616 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10617 sLastCutCopyOrTextChangedTime = 0; 10618 10619 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10620 if (ims == null || ims.mBatchEditNesting == 0) { 10621 updateAfterEdit(); 10622 } 10623 if (ims != null) { 10624 ims.mContentChanged = true; 10625 if (ims.mChangedStart < 0) { 10626 ims.mChangedStart = start; 10627 ims.mChangedEnd = start + before; 10628 } else { 10629 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10630 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10631 } 10632 ims.mChangedDelta += after - before; 10633 } 10634 resetErrorChangedFlag(); 10635 sendOnTextChanged(buffer, start, before, after); 10636 onTextChanged(buffer, start, before, after); 10637 } 10638 10639 /** 10640 * Not private so it can be called from an inner class without going 10641 * through a thunk. 10642 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10643 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10644 // XXX Make the start and end move together if this ends up 10645 // spending too much time invalidating. 10646 10647 boolean selChanged = false; 10648 int newSelStart = -1, newSelEnd = -1; 10649 10650 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10651 10652 if (what == Selection.SELECTION_END) { 10653 selChanged = true; 10654 newSelEnd = newStart; 10655 10656 if (oldStart >= 0 || newStart >= 0) { 10657 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10658 checkForResize(); 10659 registerForPreDraw(); 10660 if (mEditor != null) mEditor.makeBlink(); 10661 } 10662 } 10663 10664 if (what == Selection.SELECTION_START) { 10665 selChanged = true; 10666 newSelStart = newStart; 10667 10668 if (oldStart >= 0 || newStart >= 0) { 10669 int end = Selection.getSelectionEnd(buf); 10670 invalidateCursor(end, oldStart, newStart); 10671 } 10672 } 10673 10674 if (selChanged) { 10675 mHighlightPathBogus = true; 10676 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10677 10678 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10679 if (newSelStart < 0) { 10680 newSelStart = Selection.getSelectionStart(buf); 10681 } 10682 if (newSelEnd < 0) { 10683 newSelEnd = Selection.getSelectionEnd(buf); 10684 } 10685 10686 if (mEditor != null) { 10687 mEditor.refreshTextActionMode(); 10688 if (!hasSelection() 10689 && mEditor.getTextActionMode() == null && hasTransientState()) { 10690 // User generated selection has been removed. 10691 setHasTransientState(false); 10692 } 10693 } 10694 onSelectionChanged(newSelStart, newSelEnd); 10695 } 10696 } 10697 10698 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10699 || what instanceof CharacterStyle) { 10700 if (ims == null || ims.mBatchEditNesting == 0) { 10701 invalidate(); 10702 mHighlightPathBogus = true; 10703 checkForResize(); 10704 } else { 10705 ims.mContentChanged = true; 10706 } 10707 if (mEditor != null) { 10708 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10709 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10710 mEditor.invalidateHandlesAndActionMode(); 10711 } 10712 } 10713 10714 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10715 mHighlightPathBogus = true; 10716 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10717 ims.mSelectionModeChanged = true; 10718 } 10719 10720 if (Selection.getSelectionStart(buf) >= 0) { 10721 if (ims == null || ims.mBatchEditNesting == 0) { 10722 invalidateCursor(); 10723 } else { 10724 ims.mCursorChanged = true; 10725 } 10726 } 10727 } 10728 10729 if (what instanceof ParcelableSpan) { 10730 // If this is a span that can be sent to a remote process, 10731 // the current extract editor would be interested in it. 10732 if (ims != null && ims.mExtractedTextRequest != null) { 10733 if (ims.mBatchEditNesting != 0) { 10734 if (oldStart >= 0) { 10735 if (ims.mChangedStart > oldStart) { 10736 ims.mChangedStart = oldStart; 10737 } 10738 if (ims.mChangedStart > oldEnd) { 10739 ims.mChangedStart = oldEnd; 10740 } 10741 } 10742 if (newStart >= 0) { 10743 if (ims.mChangedStart > newStart) { 10744 ims.mChangedStart = newStart; 10745 } 10746 if (ims.mChangedStart > newEnd) { 10747 ims.mChangedStart = newEnd; 10748 } 10749 } 10750 } else { 10751 if (DEBUG_EXTRACT) { 10752 Log.v(LOG_TAG, "Span change outside of batch: " 10753 + oldStart + "-" + oldEnd + "," 10754 + newStart + "-" + newEnd + " " + what); 10755 } 10756 ims.mContentChanged = true; 10757 } 10758 } 10759 } 10760 10761 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 10762 && what instanceof SpellCheckSpan) { 10763 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 10764 } 10765 } 10766 10767 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10768 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 10769 if (isTemporarilyDetached()) { 10770 // If we are temporarily in the detach state, then do nothing. 10771 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10772 return; 10773 } 10774 10775 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 10776 10777 if (focused) { 10778 if (mSpannable != null) { 10779 MetaKeyKeyListener.resetMetaState(mSpannable); 10780 } 10781 } 10782 10783 startStopMarquee(focused); 10784 10785 if (mTransformation != null) { 10786 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 10787 } 10788 10789 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10790 } 10791 10792 @Override onWindowFocusChanged(boolean hasWindowFocus)10793 public void onWindowFocusChanged(boolean hasWindowFocus) { 10794 super.onWindowFocusChanged(hasWindowFocus); 10795 10796 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 10797 10798 startStopMarquee(hasWindowFocus); 10799 } 10800 10801 @Override onVisibilityChanged(View changedView, int visibility)10802 protected void onVisibilityChanged(View changedView, int visibility) { 10803 super.onVisibilityChanged(changedView, visibility); 10804 if (mEditor != null && visibility != VISIBLE) { 10805 mEditor.hideCursorAndSpanControllers(); 10806 stopTextActionMode(); 10807 } 10808 } 10809 10810 /** 10811 * Use {@link BaseInputConnection#removeComposingSpans 10812 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 10813 * state from this text view. 10814 */ clearComposingText()10815 public void clearComposingText() { 10816 if (mText instanceof Spannable) { 10817 BaseInputConnection.removeComposingSpans(mSpannable); 10818 } 10819 } 10820 10821 @Override setSelected(boolean selected)10822 public void setSelected(boolean selected) { 10823 boolean wasSelected = isSelected(); 10824 10825 super.setSelected(selected); 10826 10827 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10828 if (selected) { 10829 startMarquee(); 10830 } else { 10831 stopMarquee(); 10832 } 10833 } 10834 } 10835 10836 @Override onTouchEvent(MotionEvent event)10837 public boolean onTouchEvent(MotionEvent event) { 10838 final int action = event.getActionMasked(); 10839 if (mEditor != null) { 10840 mEditor.onTouchEvent(event); 10841 10842 if (mEditor.mSelectionModifierCursorController != null 10843 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 10844 return true; 10845 } 10846 } 10847 10848 final boolean superResult = super.onTouchEvent(event); 10849 10850 /* 10851 * Don't handle the release after a long press, because it will move the selection away from 10852 * whatever the menu action was trying to affect. If the long press should have triggered an 10853 * insertion action mode, we can now actually show it. 10854 */ 10855 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 10856 mEditor.mDiscardNextActionUp = false; 10857 10858 if (mEditor.mIsInsertionActionModeStartPending) { 10859 mEditor.startInsertionActionMode(); 10860 mEditor.mIsInsertionActionModeStartPending = false; 10861 } 10862 return superResult; 10863 } 10864 10865 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 10866 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 10867 10868 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 10869 && mText instanceof Spannable && mLayout != null) { 10870 boolean handled = false; 10871 10872 if (mMovement != null) { 10873 handled |= mMovement.onTouchEvent(this, mSpannable, event); 10874 } 10875 10876 final boolean textIsSelectable = isTextSelectable(); 10877 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 10878 // The LinkMovementMethod which should handle taps on links has not been installed 10879 // on non editable text that support text selection. 10880 // We reproduce its behavior here to open links for these. 10881 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 10882 getSelectionEnd(), ClickableSpan.class); 10883 10884 if (links.length > 0) { 10885 links[0].onClick(this); 10886 handled = true; 10887 } 10888 } 10889 10890 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 10891 // Show the IME, except when selecting in read-only text. 10892 final InputMethodManager imm = getInputMethodManager(); 10893 viewClicked(imm); 10894 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10895 imm.showSoftInput(this, 0); 10896 } 10897 10898 // The above condition ensures that the mEditor is not null 10899 mEditor.onTouchUpEvent(event); 10900 10901 handled = true; 10902 } 10903 10904 if (handled) { 10905 return true; 10906 } 10907 } 10908 10909 return superResult; 10910 } 10911 10912 @Override onGenericMotionEvent(MotionEvent event)10913 public boolean onGenericMotionEvent(MotionEvent event) { 10914 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 10915 try { 10916 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 10917 return true; 10918 } 10919 } catch (AbstractMethodError ex) { 10920 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 10921 // Ignore its absence in case third party applications implemented the 10922 // interface directly. 10923 } 10924 } 10925 return super.onGenericMotionEvent(event); 10926 } 10927 10928 @Override onCreateContextMenu(ContextMenu menu)10929 protected void onCreateContextMenu(ContextMenu menu) { 10930 if (mEditor != null) { 10931 mEditor.onCreateContextMenu(menu); 10932 } 10933 } 10934 10935 @Override showContextMenu()10936 public boolean showContextMenu() { 10937 if (mEditor != null) { 10938 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 10939 } 10940 return super.showContextMenu(); 10941 } 10942 10943 @Override showContextMenu(float x, float y)10944 public boolean showContextMenu(float x, float y) { 10945 if (mEditor != null) { 10946 mEditor.setContextMenuAnchor(x, y); 10947 } 10948 return super.showContextMenu(x, y); 10949 } 10950 10951 /** 10952 * @return True iff this TextView contains a text that can be edited, or if this is 10953 * a selectable TextView. 10954 */ 10955 @UnsupportedAppUsage isTextEditable()10956 boolean isTextEditable() { 10957 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 10958 } 10959 10960 /** 10961 * Returns true, only while processing a touch gesture, if the initial 10962 * touch down event caused focus to move to the text view and as a result 10963 * its selection changed. Only valid while processing the touch gesture 10964 * of interest, in an editable text view. 10965 */ didTouchFocusSelect()10966 public boolean didTouchFocusSelect() { 10967 return mEditor != null && mEditor.mTouchFocusSelected; 10968 } 10969 10970 @Override cancelLongPress()10971 public void cancelLongPress() { 10972 super.cancelLongPress(); 10973 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 10974 } 10975 10976 @Override onTrackballEvent(MotionEvent event)10977 public boolean onTrackballEvent(MotionEvent event) { 10978 if (mMovement != null && mSpannable != null && mLayout != null) { 10979 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 10980 return true; 10981 } 10982 } 10983 10984 return super.onTrackballEvent(event); 10985 } 10986 10987 /** 10988 * Sets the Scroller used for producing a scrolling animation 10989 * 10990 * @param s A Scroller instance 10991 */ setScroller(Scroller s)10992 public void setScroller(Scroller s) { 10993 mScroller = s; 10994 } 10995 10996 @Override getLeftFadingEdgeStrength()10997 protected float getLeftFadingEdgeStrength() { 10998 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 10999 final Marquee marquee = mMarquee; 11000 if (marquee.shouldDrawLeftFade()) { 11001 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11002 } else { 11003 return 0.0f; 11004 } 11005 } else if (getLineCount() == 1) { 11006 final float lineLeft = getLayout().getLineLeft(0); 11007 if (lineLeft > mScrollX) return 0.0f; 11008 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11009 } 11010 return super.getLeftFadingEdgeStrength(); 11011 } 11012 11013 @Override getRightFadingEdgeStrength()11014 protected float getRightFadingEdgeStrength() { 11015 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11016 final Marquee marquee = mMarquee; 11017 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11018 } else if (getLineCount() == 1) { 11019 final float rightEdge = mScrollX + 11020 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11021 final float lineRight = getLayout().getLineRight(0); 11022 if (lineRight < rightEdge) return 0.0f; 11023 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11024 } 11025 return super.getRightFadingEdgeStrength(); 11026 } 11027 11028 /** 11029 * Calculates the fading edge strength as the ratio of the distance between two 11030 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11031 * value for the distance calculation. 11032 * 11033 * @param position1 A horizontal position. 11034 * @param position2 A horizontal position. 11035 * @return Fading edge strength between [0.0f, 1.0f]. 11036 */ 11037 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11038 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11039 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11040 if (horizontalFadingEdgeLength == 0) return 0.0f; 11041 final float diff = Math.abs(position1 - position2); 11042 if (diff > horizontalFadingEdgeLength) return 1.0f; 11043 return diff / horizontalFadingEdgeLength; 11044 } 11045 isMarqueeFadeEnabled()11046 private boolean isMarqueeFadeEnabled() { 11047 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11048 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11049 } 11050 11051 @Override computeHorizontalScrollRange()11052 protected int computeHorizontalScrollRange() { 11053 if (mLayout != null) { 11054 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11055 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11056 } 11057 11058 return super.computeHorizontalScrollRange(); 11059 } 11060 11061 @Override computeVerticalScrollRange()11062 protected int computeVerticalScrollRange() { 11063 if (mLayout != null) { 11064 return mLayout.getHeight(); 11065 } 11066 return super.computeVerticalScrollRange(); 11067 } 11068 11069 @Override computeVerticalScrollExtent()11070 protected int computeVerticalScrollExtent() { 11071 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11072 } 11073 11074 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11075 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11076 super.findViewsWithText(outViews, searched, flags); 11077 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11078 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11079 String searchedLowerCase = searched.toString().toLowerCase(); 11080 String textLowerCase = mText.toString().toLowerCase(); 11081 if (textLowerCase.contains(searchedLowerCase)) { 11082 outViews.add(this); 11083 } 11084 } 11085 } 11086 11087 /** 11088 * Type of the text buffer that defines the characteristics of the text such as static, 11089 * styleable, or editable. 11090 */ 11091 public enum BufferType { 11092 NORMAL, SPANNABLE, EDITABLE 11093 } 11094 11095 /** 11096 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11097 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11098 * if TextView_textColor was not set directly. 11099 * 11100 * @removed 11101 */ getTextColors(Context context, TypedArray attrs)11102 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11103 if (attrs == null) { 11104 // Preserve behavior prior to removal of this API. 11105 throw new NullPointerException(); 11106 } 11107 11108 // It's not safe to use this method from apps. The parameter 'attrs' 11109 // must have been obtained using the TextView filter array which is not 11110 // available to the SDK. As such, we grab a default TypedArray with the 11111 // right filter instead here. 11112 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11113 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11114 if (colors == null) { 11115 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11116 if (ap != 0) { 11117 final TypedArray appearance = context.obtainStyledAttributes( 11118 ap, R.styleable.TextAppearance); 11119 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11120 appearance.recycle(); 11121 } 11122 } 11123 a.recycle(); 11124 11125 return colors; 11126 } 11127 11128 /** 11129 * Returns the default color from the TextView_textColor attribute from the 11130 * AttributeSet, if set, or the default color from the 11131 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11132 * TextView_textColor was not set directly. 11133 * 11134 * @removed 11135 */ getTextColor(Context context, TypedArray attrs, int def)11136 public static int getTextColor(Context context, TypedArray attrs, int def) { 11137 final ColorStateList colors = getTextColors(context, attrs); 11138 if (colors == null) { 11139 return def; 11140 } else { 11141 return colors.getDefaultColor(); 11142 } 11143 } 11144 11145 @Override onKeyShortcut(int keyCode, KeyEvent event)11146 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11147 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11148 // Handle Ctrl-only shortcuts. 11149 switch (keyCode) { 11150 case KeyEvent.KEYCODE_A: 11151 if (canSelectText()) { 11152 return onTextContextMenuItem(ID_SELECT_ALL); 11153 } 11154 break; 11155 case KeyEvent.KEYCODE_Z: 11156 if (canUndo()) { 11157 return onTextContextMenuItem(ID_UNDO); 11158 } 11159 break; 11160 case KeyEvent.KEYCODE_X: 11161 if (canCut()) { 11162 return onTextContextMenuItem(ID_CUT); 11163 } 11164 break; 11165 case KeyEvent.KEYCODE_C: 11166 if (canCopy()) { 11167 return onTextContextMenuItem(ID_COPY); 11168 } 11169 break; 11170 case KeyEvent.KEYCODE_V: 11171 if (canPaste()) { 11172 return onTextContextMenuItem(ID_PASTE); 11173 } 11174 break; 11175 } 11176 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11177 // Handle Ctrl-Shift shortcuts. 11178 switch (keyCode) { 11179 case KeyEvent.KEYCODE_Z: 11180 if (canRedo()) { 11181 return onTextContextMenuItem(ID_REDO); 11182 } 11183 break; 11184 case KeyEvent.KEYCODE_V: 11185 if (canPaste()) { 11186 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11187 } 11188 } 11189 } 11190 return super.onKeyShortcut(keyCode, event); 11191 } 11192 11193 /** 11194 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11195 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11196 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11197 * sufficient. 11198 */ canSelectText()11199 boolean canSelectText() { 11200 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11201 } 11202 11203 /** 11204 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11205 * The text must be spannable and the movement method must allow for arbitary selection. 11206 * 11207 * See also {@link #canSelectText()}. 11208 */ textCanBeSelected()11209 boolean textCanBeSelected() { 11210 // prepareCursorController() relies on this method. 11211 // If you change this condition, make sure prepareCursorController is called anywhere 11212 // the value of this condition might be changed. 11213 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11214 return isTextEditable() 11215 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11216 } 11217 11218 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11219 private Locale getTextServicesLocale(boolean allowNullLocale) { 11220 // Start fetching the text services locale asynchronously. 11221 updateTextServicesLocaleAsync(); 11222 // If !allowNullLocale and there is no cached text services locale, just return the default 11223 // locale. 11224 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11225 : mCurrentSpellCheckerLocaleCache; 11226 } 11227 11228 /** 11229 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11230 * this {@link TextView}. 11231 * 11232 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11233 * other apps may need to set this so that the system can user right user's resources and 11234 * services such as input methods and spell checkers.</p> 11235 * 11236 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11237 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11238 * @hide 11239 */ 11240 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11241 public final void setTextOperationUser(@Nullable UserHandle user) { 11242 if (Objects.equals(mTextOperationUser, user)) { 11243 return; 11244 } 11245 if (user != null && !Process.myUserHandle().equals(user)) { 11246 // Just for preventing people from accidentally using this hidden API without 11247 // the required permission. The same permission is also checked in the system server. 11248 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11249 != PackageManager.PERMISSION_GRANTED) { 11250 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11251 + " userId=" + user.getIdentifier() 11252 + " callingUserId" + UserHandle.myUserId()); 11253 } 11254 } 11255 mTextOperationUser = user; 11256 // Invalidate some resources 11257 mCurrentSpellCheckerLocaleCache = null; 11258 if (mEditor != null) { 11259 mEditor.onTextOperationUserChanged(); 11260 } 11261 } 11262 11263 @Nullable getTextServicesManagerForUser()11264 final TextServicesManager getTextServicesManagerForUser() { 11265 return getServiceManagerForUser("android", TextServicesManager.class); 11266 } 11267 11268 @Nullable getClipboardManagerForUser()11269 final ClipboardManager getClipboardManagerForUser() { 11270 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11271 } 11272 11273 @Nullable getTextClassificationManagerForUser()11274 final TextClassificationManager getTextClassificationManagerForUser() { 11275 return getServiceManagerForUser( 11276 getContext().getPackageName(), TextClassificationManager.class); 11277 } 11278 11279 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11280 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11281 if (mTextOperationUser == null) { 11282 return getContext().getSystemService(managerClazz); 11283 } 11284 try { 11285 Context context = getContext().createPackageContextAsUser( 11286 packageName, 0 /* flags */, mTextOperationUser); 11287 return context.getSystemService(managerClazz); 11288 } catch (PackageManager.NameNotFoundException e) { 11289 return null; 11290 } 11291 } 11292 11293 /** 11294 * Starts {@link Activity} as a text-operation user if it is specified with 11295 * {@link #setTextOperationUser(UserHandle)}. 11296 * 11297 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11298 * 11299 * @param intent The description of the activity to start. 11300 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11301 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11302 if (mTextOperationUser != null) { 11303 getContext().startActivityAsUser(intent, mTextOperationUser); 11304 } else { 11305 getContext().startActivity(intent); 11306 } 11307 } 11308 11309 /** 11310 * This is a temporary method. Future versions may support multi-locale text. 11311 * Caveat: This method may not return the latest text services locale, but this should be 11312 * acceptable and it's more important to make this method asynchronous. 11313 * 11314 * @return The locale that should be used for a word iterator 11315 * in this TextView, based on the current spell checker settings, 11316 * the current IME's locale, or the system default locale. 11317 * Please note that a word iterator in this TextView is different from another word iterator 11318 * used by SpellChecker.java of TextView. This method should be used for the former. 11319 * @hide 11320 */ 11321 // TODO: Support multi-locale 11322 // TODO: Update the text services locale immediately after the keyboard locale is switched 11323 // by catching intent of keyboard switch event getTextServicesLocale()11324 public Locale getTextServicesLocale() { 11325 return getTextServicesLocale(false /* allowNullLocale */); 11326 } 11327 11328 /** 11329 * @return {@code true} if this TextView is specialized for showing and interacting with the 11330 * extracted text in a full-screen input method. 11331 * @hide 11332 */ isInExtractedMode()11333 public boolean isInExtractedMode() { 11334 return false; 11335 } 11336 11337 /** 11338 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11339 * auto-size. 11340 */ isAutoSizeEnabled()11341 private boolean isAutoSizeEnabled() { 11342 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11343 } 11344 11345 /** 11346 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11347 * @hide 11348 */ supportsAutoSizeText()11349 protected boolean supportsAutoSizeText() { 11350 return true; 11351 } 11352 11353 /** 11354 * This is a temporary method. Future versions may support multi-locale text. 11355 * Caveat: This method may not return the latest spell checker locale, but this should be 11356 * acceptable and it's more important to make this method asynchronous. 11357 * 11358 * @return The locale that should be used for a spell checker in this TextView, 11359 * based on the current spell checker settings, the current IME's locale, or the system default 11360 * locale. 11361 * @hide 11362 */ getSpellCheckerLocale()11363 public Locale getSpellCheckerLocale() { 11364 return getTextServicesLocale(true /* allowNullLocale */); 11365 } 11366 updateTextServicesLocaleAsync()11367 private void updateTextServicesLocaleAsync() { 11368 // AsyncTask.execute() uses a serial executor which means we don't have 11369 // to lock around updateTextServicesLocaleLocked() to prevent it from 11370 // being executed n times in parallel. 11371 AsyncTask.execute(new Runnable() { 11372 @Override 11373 public void run() { 11374 updateTextServicesLocaleLocked(); 11375 } 11376 }); 11377 } 11378 11379 @UnsupportedAppUsage updateTextServicesLocaleLocked()11380 private void updateTextServicesLocaleLocked() { 11381 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11382 if (textServicesManager == null) { 11383 return; 11384 } 11385 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11386 final Locale locale; 11387 if (subtype != null) { 11388 locale = subtype.getLocaleObject(); 11389 } else { 11390 locale = null; 11391 } 11392 mCurrentSpellCheckerLocaleCache = locale; 11393 } 11394 onLocaleChanged()11395 void onLocaleChanged() { 11396 mEditor.onLocaleChanged(); 11397 } 11398 11399 /** 11400 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11401 * Made available to achieve a consistent behavior. 11402 * @hide 11403 */ getWordIterator()11404 public WordIterator getWordIterator() { 11405 if (mEditor != null) { 11406 return mEditor.getWordIterator(); 11407 } else { 11408 return null; 11409 } 11410 } 11411 11412 /** @hide */ 11413 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11414 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11415 super.onPopulateAccessibilityEventInternal(event); 11416 11417 final CharSequence text = getTextForAccessibility(); 11418 if (!TextUtils.isEmpty(text)) { 11419 event.getText().add(text); 11420 } 11421 } 11422 11423 @Override getAccessibilityClassName()11424 public CharSequence getAccessibilityClassName() { 11425 return TextView.class.getName(); 11426 } 11427 11428 /** @hide */ 11429 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11430 protected void onProvideStructure(@NonNull ViewStructure structure, 11431 @ViewStructureType int viewFor, int flags) { 11432 super.onProvideStructure(structure, viewFor, flags); 11433 11434 final boolean isPassword = hasPasswordTransformationMethod() 11435 || isPasswordInputType(getInputType()); 11436 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11437 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11438 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11439 } 11440 if (mTextId != Resources.ID_NULL) { 11441 try { 11442 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11443 } catch (Resources.NotFoundException e) { 11444 if (android.view.autofill.Helper.sVerbose) { 11445 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11446 + mTextId + ": " + e.getMessage()); 11447 } 11448 } 11449 } 11450 } 11451 11452 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11453 if (mLayout == null) { 11454 assumeLayout(); 11455 } 11456 Layout layout = mLayout; 11457 final int lineCount = layout.getLineCount(); 11458 if (lineCount <= 1) { 11459 // Simple case: this is a single line. 11460 final CharSequence text = getText(); 11461 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11462 structure.setText(text); 11463 } else { 11464 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11465 } 11466 } else { 11467 // Complex case: multi-line, could be scrolled or within a scroll container 11468 // so some lines are not visible. 11469 final int[] tmpCords = new int[2]; 11470 getLocationInWindow(tmpCords); 11471 final int topWindowLocation = tmpCords[1]; 11472 View root = this; 11473 ViewParent viewParent = getParent(); 11474 while (viewParent instanceof View) { 11475 root = (View) viewParent; 11476 viewParent = root.getParent(); 11477 } 11478 final int windowHeight = root.getHeight(); 11479 final int topLine; 11480 final int bottomLine; 11481 if (topWindowLocation >= 0) { 11482 // The top of the view is fully within its window; start text at line 0. 11483 topLine = getLineAtCoordinateUnclamped(0); 11484 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11485 } else { 11486 // The top of hte window has scrolled off the top of the window; figure out 11487 // the starting line for this. 11488 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11489 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11490 } 11491 // We want to return some contextual lines above/below the lines that are 11492 // actually visible. 11493 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11494 if (expandedTopLine < 0) { 11495 expandedTopLine = 0; 11496 } 11497 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11498 if (expandedBottomLine >= lineCount) { 11499 expandedBottomLine = lineCount - 1; 11500 } 11501 11502 // Convert lines into character offsets. 11503 int expandedTopChar = layout.getLineStart(expandedTopLine); 11504 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11505 11506 // Take into account selection -- if there is a selection, we need to expand 11507 // the text we are returning to include that selection. 11508 final int selStart = getSelectionStart(); 11509 final int selEnd = getSelectionEnd(); 11510 if (selStart < selEnd) { 11511 if (selStart < expandedTopChar) { 11512 expandedTopChar = selStart; 11513 } 11514 if (selEnd > expandedBottomChar) { 11515 expandedBottomChar = selEnd; 11516 } 11517 } 11518 11519 // Get the text and trim it to the range we are reporting. 11520 CharSequence text = getText(); 11521 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11522 text = text.subSequence(expandedTopChar, expandedBottomChar); 11523 } 11524 11525 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11526 structure.setText(text); 11527 } else { 11528 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 11529 11530 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11531 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11532 final int baselineOffset = getBaselineOffset(); 11533 for (int i = topLine; i <= bottomLine; i++) { 11534 lineOffsets[i - topLine] = layout.getLineStart(i); 11535 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11536 } 11537 structure.setTextLines(lineOffsets, lineBaselines); 11538 } 11539 } 11540 11541 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) { 11542 // Extract style information that applies to the TextView as a whole. 11543 int style = 0; 11544 int typefaceStyle = getTypefaceStyle(); 11545 if ((typefaceStyle & Typeface.BOLD) != 0) { 11546 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11547 } 11548 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11549 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11550 } 11551 11552 // Global styles can also be set via TextView.setPaintFlags(). 11553 int paintFlags = mTextPaint.getFlags(); 11554 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11555 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11556 } 11557 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11558 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11559 } 11560 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11561 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11562 } 11563 11564 // TextView does not have its own text background color. A background is either part 11565 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11566 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11567 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11568 } 11569 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11570 structure.setMinTextEms(getMinEms()); 11571 structure.setMaxTextEms(getMaxEms()); 11572 int maxLength = -1; 11573 for (InputFilter filter: getFilters()) { 11574 if (filter instanceof InputFilter.LengthFilter) { 11575 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11576 break; 11577 } 11578 } 11579 structure.setMaxTextLength(maxLength); 11580 } 11581 } 11582 structure.setHint(getHint()); 11583 structure.setInputType(getInputType()); 11584 } 11585 canRequestAutofill()11586 boolean canRequestAutofill() { 11587 if (!isAutofillable()) { 11588 return false; 11589 } 11590 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11591 if (afm != null) { 11592 return afm.isEnabled(); 11593 } 11594 return false; 11595 } 11596 requestAutofill()11597 private void requestAutofill() { 11598 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11599 if (afm != null) { 11600 afm.requestAutofill(this); 11601 } 11602 } 11603 11604 @Override autofill(AutofillValue value)11605 public void autofill(AutofillValue value) { 11606 if (!value.isText() || !isTextEditable()) { 11607 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 11608 return; 11609 } 11610 11611 final CharSequence autofilledValue = value.getTextValue(); 11612 11613 // First autofill it... 11614 setText(autofilledValue, mBufferType, true, 0); 11615 11616 // ...then move cursor to the end. 11617 final CharSequence text = getText(); 11618 if ((text instanceof Spannable)) { 11619 Selection.setSelection((Spannable) text, text.length()); 11620 } 11621 } 11622 11623 @Override getAutofillType()11624 public @AutofillType int getAutofillType() { 11625 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11626 } 11627 11628 /** 11629 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11630 * {@code char}s if longer. 11631 * 11632 * @return current text, {@code null} if the text is not editable 11633 * 11634 * @see View#getAutofillValue() 11635 */ 11636 @Override 11637 @Nullable getAutofillValue()11638 public AutofillValue getAutofillValue() { 11639 if (isTextEditable()) { 11640 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 11641 return AutofillValue.forText(text); 11642 } 11643 return null; 11644 } 11645 11646 /** @hide */ 11647 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)11648 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 11649 super.onInitializeAccessibilityEventInternal(event); 11650 11651 final boolean isPassword = hasPasswordTransformationMethod(); 11652 event.setPassword(isPassword); 11653 11654 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 11655 event.setFromIndex(Selection.getSelectionStart(mText)); 11656 event.setToIndex(Selection.getSelectionEnd(mText)); 11657 event.setItemCount(mText.length()); 11658 } 11659 } 11660 11661 /** @hide */ 11662 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11663 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 11664 super.onInitializeAccessibilityNodeInfoInternal(info); 11665 11666 final boolean isPassword = hasPasswordTransformationMethod(); 11667 info.setPassword(isPassword); 11668 info.setText(getTextForAccessibility()); 11669 info.setHintText(mHint); 11670 info.setShowingHintText(isShowingHint()); 11671 11672 if (mBufferType == BufferType.EDITABLE) { 11673 info.setEditable(true); 11674 if (isEnabled()) { 11675 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 11676 } 11677 } 11678 11679 if (mEditor != null) { 11680 info.setInputType(mEditor.mInputType); 11681 11682 if (mEditor.mError != null) { 11683 info.setContentInvalid(true); 11684 info.setError(mEditor.mError); 11685 } 11686 } 11687 11688 if (!TextUtils.isEmpty(mText)) { 11689 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 11690 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 11691 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 11692 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 11693 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 11694 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 11695 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 11696 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 11697 info.setAvailableExtraData( 11698 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); 11699 } 11700 11701 if (isFocused()) { 11702 if (canCopy()) { 11703 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 11704 } 11705 if (canPaste()) { 11706 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 11707 } 11708 if (canCut()) { 11709 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 11710 } 11711 if (canShare()) { 11712 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 11713 ACCESSIBILITY_ACTION_SHARE, 11714 getResources().getString(com.android.internal.R.string.share))); 11715 } 11716 if (canProcessText()) { // also implies mEditor is not null. 11717 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 11718 } 11719 } 11720 11721 // Check for known input filter types. 11722 final int numFilters = mFilters.length; 11723 for (int i = 0; i < numFilters; i++) { 11724 final InputFilter filter = mFilters[i]; 11725 if (filter instanceof InputFilter.LengthFilter) { 11726 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 11727 } 11728 } 11729 11730 if (!isSingleLine()) { 11731 info.setMultiLine(true); 11732 } 11733 } 11734 11735 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11736 public void addExtraDataToAccessibilityNodeInfo( 11737 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 11738 // The only extra data we support requires arguments. 11739 if (arguments == null) { 11740 return; 11741 } 11742 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 11743 int positionInfoStartIndex = arguments.getInt( 11744 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 11745 int positionInfoLength = arguments.getInt( 11746 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 11747 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 11748 || (positionInfoStartIndex >= mText.length())) { 11749 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 11750 return; 11751 } 11752 RectF[] boundingRects = new RectF[positionInfoLength]; 11753 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 11754 populateCharacterBounds(builder, positionInfoStartIndex, 11755 positionInfoStartIndex + positionInfoLength, 11756 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 11757 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 11758 for (int i = 0; i < positionInfoLength; i++) { 11759 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 11760 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 11761 RectF bounds = cursorAnchorInfo 11762 .getCharacterBounds(positionInfoStartIndex + i); 11763 if (bounds != null) { 11764 mapRectFromViewToScreenCoords(bounds, true); 11765 boundingRects[i] = bounds; 11766 } 11767 } 11768 } 11769 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 11770 } 11771 } 11772 11773 /** 11774 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 11775 * 11776 * @param builder The builder to populate 11777 * @param startIndex The starting character index to populate 11778 * @param endIndex The ending character index to populate 11779 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 11780 * content 11781 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 11782 * @hide 11783 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11784 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 11785 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 11786 float viewportToContentVerticalOffset) { 11787 final int minLine = mLayout.getLineForOffset(startIndex); 11788 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 11789 for (int line = minLine; line <= maxLine; ++line) { 11790 final int lineStart = mLayout.getLineStart(line); 11791 final int lineEnd = mLayout.getLineEnd(line); 11792 final int offsetStart = Math.max(lineStart, startIndex); 11793 final int offsetEnd = Math.min(lineEnd, endIndex); 11794 final boolean ltrLine = 11795 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 11796 final float[] widths = new float[offsetEnd - offsetStart]; 11797 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 11798 final float top = mLayout.getLineTop(line); 11799 final float bottom = mLayout.getLineBottom(line); 11800 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 11801 final float charWidth = widths[offset - offsetStart]; 11802 final boolean isRtl = mLayout.isRtlCharAt(offset); 11803 final float primary = mLayout.getPrimaryHorizontal(offset); 11804 final float secondary = mLayout.getSecondaryHorizontal(offset); 11805 // TODO: This doesn't work perfectly for text with custom styles and 11806 // TAB chars. 11807 final float left; 11808 final float right; 11809 if (ltrLine) { 11810 if (isRtl) { 11811 left = secondary - charWidth; 11812 right = secondary; 11813 } else { 11814 left = primary; 11815 right = primary + charWidth; 11816 } 11817 } else { 11818 if (!isRtl) { 11819 left = secondary; 11820 right = secondary + charWidth; 11821 } else { 11822 left = primary - charWidth; 11823 right = primary; 11824 } 11825 } 11826 // TODO: Check top-right and bottom-left as well. 11827 final float localLeft = left + viewportToContentHorizontalOffset; 11828 final float localRight = right + viewportToContentHorizontalOffset; 11829 final float localTop = top + viewportToContentVerticalOffset; 11830 final float localBottom = bottom + viewportToContentVerticalOffset; 11831 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 11832 final boolean isBottomRightVisible = 11833 isPositionVisible(localRight, localBottom); 11834 int characterBoundsFlags = 0; 11835 if (isTopLeftVisible || isBottomRightVisible) { 11836 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 11837 } 11838 if (!isTopLeftVisible || !isBottomRightVisible) { 11839 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 11840 } 11841 if (isRtl) { 11842 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 11843 } 11844 // Here offset is the index in Java chars. 11845 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 11846 localBottom, characterBoundsFlags); 11847 } 11848 } 11849 } 11850 11851 /** 11852 * @hide 11853 */ isPositionVisible(final float positionX, final float positionY)11854 public boolean isPositionVisible(final float positionX, final float positionY) { 11855 synchronized (TEMP_POSITION) { 11856 final float[] position = TEMP_POSITION; 11857 position[0] = positionX; 11858 position[1] = positionY; 11859 View view = this; 11860 11861 while (view != null) { 11862 if (view != this) { 11863 // Local scroll is already taken into account in positionX/Y 11864 position[0] -= view.getScrollX(); 11865 position[1] -= view.getScrollY(); 11866 } 11867 11868 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 11869 || position[1] > view.getHeight()) { 11870 return false; 11871 } 11872 11873 if (!view.getMatrix().isIdentity()) { 11874 view.getMatrix().mapPoints(position); 11875 } 11876 11877 position[0] += view.getLeft(); 11878 position[1] += view.getTop(); 11879 11880 final ViewParent parent = view.getParent(); 11881 if (parent instanceof View) { 11882 view = (View) parent; 11883 } else { 11884 // We've reached the ViewRoot, stop iterating 11885 view = null; 11886 } 11887 } 11888 } 11889 11890 // We've been able to walk up the view hierarchy and the position was never clipped 11891 return true; 11892 } 11893 11894 /** 11895 * Performs an accessibility action after it has been offered to the 11896 * delegate. 11897 * 11898 * @hide 11899 */ 11900 @Override performAccessibilityActionInternal(int action, Bundle arguments)11901 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 11902 if (mEditor != null 11903 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 11904 return true; 11905 } 11906 switch (action) { 11907 case AccessibilityNodeInfo.ACTION_CLICK: { 11908 return performAccessibilityActionClick(arguments); 11909 } 11910 case AccessibilityNodeInfo.ACTION_COPY: { 11911 if (isFocused() && canCopy()) { 11912 if (onTextContextMenuItem(ID_COPY)) { 11913 return true; 11914 } 11915 } 11916 } return false; 11917 case AccessibilityNodeInfo.ACTION_PASTE: { 11918 if (isFocused() && canPaste()) { 11919 if (onTextContextMenuItem(ID_PASTE)) { 11920 return true; 11921 } 11922 } 11923 } return false; 11924 case AccessibilityNodeInfo.ACTION_CUT: { 11925 if (isFocused() && canCut()) { 11926 if (onTextContextMenuItem(ID_CUT)) { 11927 return true; 11928 } 11929 } 11930 } return false; 11931 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 11932 ensureIterableTextForAccessibilitySelectable(); 11933 CharSequence text = getIterableTextForAccessibility(); 11934 if (text == null) { 11935 return false; 11936 } 11937 final int start = (arguments != null) ? arguments.getInt( 11938 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 11939 final int end = (arguments != null) ? arguments.getInt( 11940 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 11941 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 11942 // No arguments clears the selection. 11943 if (start == end && end == -1) { 11944 Selection.removeSelection((Spannable) text); 11945 return true; 11946 } 11947 if (start >= 0 && start <= end && end <= text.length()) { 11948 Selection.setSelection((Spannable) text, start, end); 11949 // Make sure selection mode is engaged. 11950 if (mEditor != null) { 11951 mEditor.startSelectionActionModeAsync(false); 11952 } 11953 return true; 11954 } 11955 } 11956 } return false; 11957 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 11958 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 11959 ensureIterableTextForAccessibilitySelectable(); 11960 return super.performAccessibilityActionInternal(action, arguments); 11961 } 11962 case ACCESSIBILITY_ACTION_SHARE: { 11963 if (isFocused() && canShare()) { 11964 if (onTextContextMenuItem(ID_SHARE)) { 11965 return true; 11966 } 11967 } 11968 } return false; 11969 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 11970 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 11971 return false; 11972 } 11973 CharSequence text = (arguments != null) ? arguments.getCharSequence( 11974 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 11975 setText(text); 11976 if (mText != null) { 11977 int updatedTextLength = mText.length(); 11978 if (updatedTextLength > 0) { 11979 Selection.setSelection(mSpannable, updatedTextLength); 11980 } 11981 } 11982 } return true; 11983 default: { 11984 return super.performAccessibilityActionInternal(action, arguments); 11985 } 11986 } 11987 } 11988 performAccessibilityActionClick(Bundle arguments)11989 private boolean performAccessibilityActionClick(Bundle arguments) { 11990 boolean handled = false; 11991 11992 if (!isEnabled()) { 11993 return false; 11994 } 11995 11996 if (isClickable() || isLongClickable()) { 11997 // Simulate View.onTouchEvent for an ACTION_UP event 11998 if (isFocusable() && !isFocused()) { 11999 requestFocus(); 12000 } 12001 12002 performClick(); 12003 handled = true; 12004 } 12005 12006 // Show the IME, except when selecting in read-only text. 12007 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12008 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12009 final InputMethodManager imm = getInputMethodManager(); 12010 viewClicked(imm); 12011 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12012 handled |= imm.showSoftInput(this, 0); 12013 } 12014 } 12015 12016 return handled; 12017 } 12018 hasSpannableText()12019 private boolean hasSpannableText() { 12020 return mText != null && mText instanceof Spannable; 12021 } 12022 12023 /** @hide */ 12024 @Override sendAccessibilityEventInternal(int eventType)12025 public void sendAccessibilityEventInternal(int eventType) { 12026 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12027 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12028 } 12029 12030 super.sendAccessibilityEventInternal(eventType); 12031 } 12032 12033 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12034 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12035 // Do not send scroll events since first they are not interesting for 12036 // accessibility and second such events a generated too frequently. 12037 // For details see the implementation of bringTextIntoView(). 12038 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12039 return; 12040 } 12041 super.sendAccessibilityEventUnchecked(event); 12042 } 12043 12044 /** 12045 * Returns the text that should be exposed to accessibility services. 12046 * <p> 12047 * This approximates what is displayed visually. If the user has specified 12048 * that accessibility services should speak passwords, this method will 12049 * bypass any password transformation method and return unobscured text. 12050 * 12051 * @return the text that should be exposed to accessibility services, may 12052 * be {@code null} if no text is set 12053 */ 12054 @Nullable 12055 @UnsupportedAppUsage getTextForAccessibility()12056 private CharSequence getTextForAccessibility() { 12057 // If the text is empty, we must be showing the hint text. 12058 if (TextUtils.isEmpty(mText)) { 12059 return mHint; 12060 } 12061 12062 // Otherwise, return whatever text is being displayed. 12063 return TextUtils.trimToParcelableSize(mTransformed); 12064 } 12065 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12066 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12067 int fromIndex, int removedCount, int addedCount) { 12068 AccessibilityEvent event = 12069 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12070 event.setFromIndex(fromIndex); 12071 event.setRemovedCount(removedCount); 12072 event.setAddedCount(addedCount); 12073 event.setBeforeText(beforeText); 12074 sendAccessibilityEventUnchecked(event); 12075 } 12076 getInputMethodManager()12077 private InputMethodManager getInputMethodManager() { 12078 return getContext().getSystemService(InputMethodManager.class); 12079 } 12080 12081 /** 12082 * Returns whether this text view is a current input method target. The 12083 * default implementation just checks with {@link InputMethodManager}. 12084 * @return True if the TextView is a current input method target; false otherwise. 12085 */ isInputMethodTarget()12086 public boolean isInputMethodTarget() { 12087 InputMethodManager imm = getInputMethodManager(); 12088 return imm != null && imm.isActive(this); 12089 } 12090 12091 static final int ID_SELECT_ALL = android.R.id.selectAll; 12092 static final int ID_UNDO = android.R.id.undo; 12093 static final int ID_REDO = android.R.id.redo; 12094 static final int ID_CUT = android.R.id.cut; 12095 static final int ID_COPY = android.R.id.copy; 12096 static final int ID_PASTE = android.R.id.paste; 12097 static final int ID_SHARE = android.R.id.shareText; 12098 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12099 static final int ID_REPLACE = android.R.id.replaceText; 12100 static final int ID_ASSIST = android.R.id.textAssist; 12101 static final int ID_AUTOFILL = android.R.id.autofill; 12102 12103 /** 12104 * Called when a context menu option for the text view is selected. Currently 12105 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12106 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12107 * 12108 * @return true if the context menu item action was performed. 12109 */ onTextContextMenuItem(int id)12110 public boolean onTextContextMenuItem(int id) { 12111 int min = 0; 12112 int max = mText.length(); 12113 12114 if (isFocused()) { 12115 final int selStart = getSelectionStart(); 12116 final int selEnd = getSelectionEnd(); 12117 12118 min = Math.max(0, Math.min(selStart, selEnd)); 12119 max = Math.max(0, Math.max(selStart, selEnd)); 12120 } 12121 12122 switch (id) { 12123 case ID_SELECT_ALL: 12124 final boolean hadSelection = hasSelection(); 12125 selectAllText(); 12126 if (mEditor != null && hadSelection) { 12127 mEditor.invalidateActionModeAsync(); 12128 } 12129 return true; 12130 12131 case ID_UNDO: 12132 if (mEditor != null) { 12133 mEditor.undo(); 12134 } 12135 return true; // Returns true even if nothing was undone. 12136 12137 case ID_REDO: 12138 if (mEditor != null) { 12139 mEditor.redo(); 12140 } 12141 return true; // Returns true even if nothing was undone. 12142 12143 case ID_PASTE: 12144 paste(min, max, true /* withFormatting */); 12145 return true; 12146 12147 case ID_PASTE_AS_PLAIN_TEXT: 12148 paste(min, max, false /* withFormatting */); 12149 return true; 12150 12151 case ID_CUT: 12152 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12153 if (setPrimaryClip(cutData)) { 12154 deleteText_internal(min, max); 12155 } else { 12156 Toast.makeText(getContext(), 12157 com.android.internal.R.string.failed_to_copy_to_clipboard, 12158 Toast.LENGTH_SHORT).show(); 12159 } 12160 return true; 12161 12162 case ID_COPY: 12163 // For link action mode in a non-selectable/non-focusable TextView, 12164 // make sure that we set the appropriate min/max. 12165 final int selStart = getSelectionStart(); 12166 final int selEnd = getSelectionEnd(); 12167 min = Math.max(0, Math.min(selStart, selEnd)); 12168 max = Math.max(0, Math.max(selStart, selEnd)); 12169 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12170 if (setPrimaryClip(copyData)) { 12171 stopTextActionMode(); 12172 } else { 12173 Toast.makeText(getContext(), 12174 com.android.internal.R.string.failed_to_copy_to_clipboard, 12175 Toast.LENGTH_SHORT).show(); 12176 } 12177 return true; 12178 12179 case ID_REPLACE: 12180 if (mEditor != null) { 12181 mEditor.replace(); 12182 } 12183 return true; 12184 12185 case ID_SHARE: 12186 shareSelectedText(); 12187 return true; 12188 12189 case ID_AUTOFILL: 12190 requestAutofill(); 12191 stopTextActionMode(); 12192 return true; 12193 } 12194 return false; 12195 } 12196 12197 @UnsupportedAppUsage getTransformedText(int start, int end)12198 CharSequence getTransformedText(int start, int end) { 12199 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12200 } 12201 12202 @Override performLongClick()12203 public boolean performLongClick() { 12204 boolean handled = false; 12205 boolean performedHapticFeedback = false; 12206 12207 if (mEditor != null) { 12208 mEditor.mIsBeingLongClicked = true; 12209 } 12210 12211 if (super.performLongClick()) { 12212 handled = true; 12213 performedHapticFeedback = true; 12214 } 12215 12216 if (mEditor != null) { 12217 handled |= mEditor.performLongClick(handled); 12218 mEditor.mIsBeingLongClicked = false; 12219 } 12220 12221 if (handled) { 12222 if (!performedHapticFeedback) { 12223 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12224 } 12225 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12226 } else { 12227 MetricsLogger.action( 12228 mContext, 12229 MetricsEvent.TEXT_LONGPRESS, 12230 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12231 } 12232 12233 return handled; 12234 } 12235 12236 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12237 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12238 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12239 if (mEditor != null) { 12240 mEditor.onScrollChanged(); 12241 } 12242 } 12243 12244 /** 12245 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12246 * by the IME or by the spell checker as the user types. This is done by adding 12247 * {@link SuggestionSpan}s to the text. 12248 * 12249 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12250 * user asks for them on these parts of the text. This value depends on the inputType of this 12251 * TextView. 12252 * 12253 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12254 * 12255 * In addition, the type variation must be one of 12256 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12257 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12258 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12259 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12260 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12261 * 12262 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12263 * 12264 * @return true if the suggestions popup window is enabled, based on the inputType. 12265 */ isSuggestionsEnabled()12266 public boolean isSuggestionsEnabled() { 12267 if (mEditor == null) return false; 12268 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12269 return false; 12270 } 12271 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12272 12273 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12274 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12275 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12276 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12277 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12278 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12279 } 12280 12281 /** 12282 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12283 * selection is initiated in this View. 12284 * 12285 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12286 * Paste, Replace and Share actions, depending on what this View supports. 12287 * 12288 * <p>A custom implementation can add new entries in the default menu in its 12289 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12290 * method. The default actions can also be removed from the menu using 12291 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12292 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12293 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12294 * 12295 * <p>Returning false from 12296 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12297 * will prevent the action mode from being started. 12298 * 12299 * <p>Action click events should be handled by the custom implementation of 12300 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12301 * android.view.MenuItem)}. 12302 * 12303 * <p>Note that text selection mode is not started when a TextView receives focus and the 12304 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12305 * that case, to allow for quick replacement. 12306 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12307 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12308 createEditorIfNeeded(); 12309 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12310 } 12311 12312 /** 12313 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12314 * 12315 * @return The current custom selection callback. 12316 */ getCustomSelectionActionModeCallback()12317 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12318 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12319 } 12320 12321 /** 12322 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12323 * insertion is initiated in this View. 12324 * The standard implementation populates the menu with a subset of Select All, 12325 * Paste and Replace actions, depending on what this View supports. 12326 * 12327 * <p>A custom implementation can add new entries in the default menu in its 12328 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12329 * android.view.Menu)} method. The default actions can also be removed from the menu using 12330 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12331 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12332 * 12333 * <p>Returning false from 12334 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12335 * android.view.Menu)} will prevent the action mode from being started.</p> 12336 * 12337 * <p>Action click events should be handled by the custom implementation of 12338 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12339 * android.view.MenuItem)}.</p> 12340 * 12341 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12342 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12343 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12344 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12345 createEditorIfNeeded(); 12346 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12347 } 12348 12349 /** 12350 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12351 * 12352 * @return The current custom insertion callback. 12353 */ getCustomInsertionActionModeCallback()12354 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12355 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12356 } 12357 12358 /** 12359 * Sets the {@link TextClassifier} for this TextView. 12360 */ setTextClassifier(@ullable TextClassifier textClassifier)12361 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12362 mTextClassifier = textClassifier; 12363 } 12364 12365 /** 12366 * Returns the {@link TextClassifier} used by this TextView. 12367 * If no TextClassifier has been set, this TextView uses the default set by the 12368 * {@link TextClassificationManager}. 12369 */ 12370 @NonNull getTextClassifier()12371 public TextClassifier getTextClassifier() { 12372 if (mTextClassifier == null) { 12373 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12374 if (tcm != null) { 12375 return tcm.getTextClassifier(); 12376 } 12377 return TextClassifier.NO_OP; 12378 } 12379 return mTextClassifier; 12380 } 12381 12382 /** 12383 * Returns a session-aware text classifier. 12384 * This method creates one if none already exists or the current one is destroyed. 12385 */ 12386 @NonNull getTextClassificationSession()12387 TextClassifier getTextClassificationSession() { 12388 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12389 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12390 if (tcm != null) { 12391 final String widgetType; 12392 if (isTextEditable()) { 12393 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12394 } else if (isTextSelectable()) { 12395 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12396 } else { 12397 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12398 } 12399 mTextClassificationContext = new TextClassificationContext.Builder( 12400 mContext.getPackageName(), widgetType) 12401 .build(); 12402 if (mTextClassifier != null) { 12403 mTextClassificationSession = tcm.createTextClassificationSession( 12404 mTextClassificationContext, mTextClassifier); 12405 } else { 12406 mTextClassificationSession = tcm.createTextClassificationSession( 12407 mTextClassificationContext); 12408 } 12409 } else { 12410 mTextClassificationSession = TextClassifier.NO_OP; 12411 } 12412 } 12413 return mTextClassificationSession; 12414 } 12415 12416 /** 12417 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12418 * @see #getTextClassificationSession() 12419 */ 12420 @Nullable getTextClassificationContext()12421 TextClassificationContext getTextClassificationContext() { 12422 return mTextClassificationContext; 12423 } 12424 12425 /** 12426 * Returns true if this TextView uses a no-op TextClassifier. 12427 */ usesNoOpTextClassifier()12428 boolean usesNoOpTextClassifier() { 12429 return getTextClassifier() == TextClassifier.NO_OP; 12430 } 12431 12432 12433 /** 12434 * Starts an ActionMode for the specified TextLinkSpan. 12435 * 12436 * @return Whether or not we're attempting to start the action mode. 12437 * @hide 12438 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12439 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12440 Preconditions.checkNotNull(clickedSpan); 12441 12442 if (!(mText instanceof Spanned)) { 12443 return false; 12444 } 12445 12446 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12447 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12448 12449 if (start < 0 || end > mText.length() || start >= end) { 12450 return false; 12451 } 12452 12453 createEditorIfNeeded(); 12454 mEditor.startLinkActionModeAsync(start, end); 12455 return true; 12456 } 12457 12458 /** 12459 * Handles a click on the specified TextLinkSpan. 12460 * 12461 * @return Whether or not the click is being handled. 12462 * @hide 12463 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12464 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12465 Preconditions.checkNotNull(clickedSpan); 12466 if (mText instanceof Spanned) { 12467 final Spanned spanned = (Spanned) mText; 12468 final int start = spanned.getSpanStart(clickedSpan); 12469 final int end = spanned.getSpanEnd(clickedSpan); 12470 if (start >= 0 && end <= mText.length() && start < end) { 12471 final TextClassification.Request request = new TextClassification.Request.Builder( 12472 mText, start, end) 12473 .setDefaultLocales(getTextLocales()) 12474 .build(); 12475 final Supplier<TextClassification> supplier = () -> 12476 getTextClassifier().classifyText(request); 12477 final Consumer<TextClassification> consumer = classification -> { 12478 if (classification != null) { 12479 if (!classification.getActions().isEmpty()) { 12480 try { 12481 classification.getActions().get(0).getActionIntent().send(); 12482 } catch (PendingIntent.CanceledException e) { 12483 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12484 } 12485 } else { 12486 Log.d(LOG_TAG, "No link action to perform"); 12487 } 12488 } else { 12489 // classification == null 12490 Log.d(LOG_TAG, "Timeout while classifying text"); 12491 } 12492 }; 12493 CompletableFuture.supplyAsync(supplier) 12494 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12495 .thenAccept(consumer); 12496 return true; 12497 } 12498 } 12499 return false; 12500 } 12501 12502 /** 12503 * @hide 12504 */ 12505 @UnsupportedAppUsage stopTextActionMode()12506 protected void stopTextActionMode() { 12507 if (mEditor != null) { 12508 mEditor.stopTextActionMode(); 12509 } 12510 } 12511 12512 /** @hide */ hideFloatingToolbar(int durationMs)12513 public void hideFloatingToolbar(int durationMs) { 12514 if (mEditor != null) { 12515 mEditor.hideFloatingToolbar(durationMs); 12516 } 12517 } 12518 canUndo()12519 boolean canUndo() { 12520 return mEditor != null && mEditor.canUndo(); 12521 } 12522 canRedo()12523 boolean canRedo() { 12524 return mEditor != null && mEditor.canRedo(); 12525 } 12526 canCut()12527 boolean canCut() { 12528 if (hasPasswordTransformationMethod()) { 12529 return false; 12530 } 12531 12532 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12533 && mEditor.mKeyListener != null) { 12534 return true; 12535 } 12536 12537 return false; 12538 } 12539 canCopy()12540 boolean canCopy() { 12541 if (hasPasswordTransformationMethod()) { 12542 return false; 12543 } 12544 12545 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12546 return true; 12547 } 12548 12549 return false; 12550 } 12551 canShare()12552 boolean canShare() { 12553 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12554 return false; 12555 } 12556 return canCopy(); 12557 } 12558 isDeviceProvisioned()12559 boolean isDeviceProvisioned() { 12560 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12561 mDeviceProvisionedState = Settings.Global.getInt( 12562 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12563 ? DEVICE_PROVISIONED_YES 12564 : DEVICE_PROVISIONED_NO; 12565 } 12566 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12567 } 12568 12569 @UnsupportedAppUsage canPaste()12570 boolean canPaste() { 12571 return (mText instanceof Editable 12572 && mEditor != null && mEditor.mKeyListener != null 12573 && getSelectionStart() >= 0 12574 && getSelectionEnd() >= 0 12575 && getClipboardManagerForUser().hasPrimaryClip()); 12576 } 12577 canPasteAsPlainText()12578 boolean canPasteAsPlainText() { 12579 if (!canPaste()) { 12580 return false; 12581 } 12582 12583 final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); 12584 final ClipDescription description = clipData.getDescription(); 12585 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 12586 final CharSequence text = clipData.getItemAt(0).getText(); 12587 if (isPlainType && (text instanceof Spanned)) { 12588 Spanned spanned = (Spanned) text; 12589 if (TextUtils.hasStyleSpan(spanned)) { 12590 return true; 12591 } 12592 } 12593 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 12594 } 12595 canProcessText()12596 boolean canProcessText() { 12597 if (getId() == View.NO_ID) { 12598 return false; 12599 } 12600 return canShare(); 12601 } 12602 canSelectAllText()12603 boolean canSelectAllText() { 12604 return canSelectText() && !hasPasswordTransformationMethod() 12605 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 12606 } 12607 selectAllText()12608 boolean selectAllText() { 12609 if (mEditor != null) { 12610 // Hide the toolbar before changing the selection to avoid flickering. 12611 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 12612 } 12613 final int length = mText.length(); 12614 Selection.setSelection(mSpannable, 0, length); 12615 return length > 0; 12616 } 12617 replaceSelectionWithText(CharSequence text)12618 void replaceSelectionWithText(CharSequence text) { 12619 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 12620 } 12621 12622 /** 12623 * Paste clipboard content between min and max positions. 12624 */ paste(int min, int max, boolean withFormatting)12625 private void paste(int min, int max, boolean withFormatting) { 12626 ClipboardManager clipboard = getClipboardManagerForUser(); 12627 ClipData clip = clipboard.getPrimaryClip(); 12628 if (clip != null) { 12629 boolean didFirst = false; 12630 for (int i = 0; i < clip.getItemCount(); i++) { 12631 final CharSequence paste; 12632 if (withFormatting) { 12633 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 12634 } else { 12635 // Get an item as text and remove all spans by toString(). 12636 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 12637 paste = (text instanceof Spanned) ? text.toString() : text; 12638 } 12639 if (paste != null) { 12640 if (!didFirst) { 12641 Selection.setSelection(mSpannable, max); 12642 ((Editable) mText).replace(min, max, paste); 12643 didFirst = true; 12644 } else { 12645 ((Editable) mText).insert(getSelectionEnd(), "\n"); 12646 ((Editable) mText).insert(getSelectionEnd(), paste); 12647 } 12648 } 12649 } 12650 sLastCutCopyOrTextChangedTime = 0; 12651 } 12652 } 12653 shareSelectedText()12654 private void shareSelectedText() { 12655 String selectedText = getSelectedText(); 12656 if (selectedText != null && !selectedText.isEmpty()) { 12657 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 12658 sharingIntent.setType("text/plain"); 12659 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 12660 selectedText = TextUtils.trimToParcelableSize(selectedText); 12661 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 12662 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 12663 Selection.setSelection(mSpannable, getSelectionEnd()); 12664 } 12665 } 12666 12667 @CheckResult setPrimaryClip(ClipData clip)12668 private boolean setPrimaryClip(ClipData clip) { 12669 ClipboardManager clipboard = getClipboardManagerForUser(); 12670 try { 12671 clipboard.setPrimaryClip(clip); 12672 } catch (Throwable t) { 12673 return false; 12674 } 12675 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 12676 return true; 12677 } 12678 12679 /** 12680 * Get the character offset closest to the specified absolute position. A typical use case is to 12681 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 12682 * 12683 * @param x The horizontal absolute position of a point on screen 12684 * @param y The vertical absolute position of a point on screen 12685 * @return the character offset for the character whose position is closest to the specified 12686 * position. Returns -1 if there is no layout. 12687 */ getOffsetForPosition(float x, float y)12688 public int getOffsetForPosition(float x, float y) { 12689 if (getLayout() == null) return -1; 12690 final int line = getLineAtCoordinate(y); 12691 final int offset = getOffsetAtCoordinate(line, x); 12692 return offset; 12693 } 12694 convertToLocalHorizontalCoordinate(float x)12695 float convertToLocalHorizontalCoordinate(float x) { 12696 x -= getTotalPaddingLeft(); 12697 // Clamp the position to inside of the view. 12698 x = Math.max(0.0f, x); 12699 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 12700 x += getScrollX(); 12701 return x; 12702 } 12703 12704 @UnsupportedAppUsage getLineAtCoordinate(float y)12705 int getLineAtCoordinate(float y) { 12706 y -= getTotalPaddingTop(); 12707 // Clamp the position to inside of the view. 12708 y = Math.max(0.0f, y); 12709 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 12710 y += getScrollY(); 12711 return getLayout().getLineForVertical((int) y); 12712 } 12713 getLineAtCoordinateUnclamped(float y)12714 int getLineAtCoordinateUnclamped(float y) { 12715 y -= getTotalPaddingTop(); 12716 y += getScrollY(); 12717 return getLayout().getLineForVertical((int) y); 12718 } 12719 getOffsetAtCoordinate(int line, float x)12720 int getOffsetAtCoordinate(int line, float x) { 12721 x = convertToLocalHorizontalCoordinate(x); 12722 return getLayout().getOffsetForHorizontal(line, x); 12723 } 12724 12725 @Override onDragEvent(DragEvent event)12726 public boolean onDragEvent(DragEvent event) { 12727 switch (event.getAction()) { 12728 case DragEvent.ACTION_DRAG_STARTED: 12729 return mEditor != null && mEditor.hasInsertionController(); 12730 12731 case DragEvent.ACTION_DRAG_ENTERED: 12732 TextView.this.requestFocus(); 12733 return true; 12734 12735 case DragEvent.ACTION_DRAG_LOCATION: 12736 if (mText instanceof Spannable) { 12737 final int offset = getOffsetForPosition(event.getX(), event.getY()); 12738 Selection.setSelection(mSpannable, offset); 12739 } 12740 return true; 12741 12742 case DragEvent.ACTION_DROP: 12743 if (mEditor != null) mEditor.onDrop(event); 12744 return true; 12745 12746 case DragEvent.ACTION_DRAG_ENDED: 12747 case DragEvent.ACTION_DRAG_EXITED: 12748 default: 12749 return true; 12750 } 12751 } 12752 isInBatchEditMode()12753 boolean isInBatchEditMode() { 12754 if (mEditor == null) return false; 12755 final Editor.InputMethodState ims = mEditor.mInputMethodState; 12756 if (ims != null) { 12757 return ims.mBatchEditNesting > 0; 12758 } 12759 return mEditor.mInBatchEditControllers; 12760 } 12761 12762 @Override onRtlPropertiesChanged(int layoutDirection)12763 public void onRtlPropertiesChanged(int layoutDirection) { 12764 super.onRtlPropertiesChanged(layoutDirection); 12765 12766 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 12767 if (mTextDir != newTextDir) { 12768 mTextDir = newTextDir; 12769 if (mLayout != null) { 12770 checkForRelayout(); 12771 } 12772 } 12773 } 12774 12775 /** 12776 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 12777 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 12778 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 12779 * return value may not be the same as the one TextView uses if the View's layout direction is 12780 * not resolved or detached from parent root view. 12781 */ getTextDirectionHeuristic()12782 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 12783 if (hasPasswordTransformationMethod()) { 12784 // passwords fields should be LTR 12785 return TextDirectionHeuristics.LTR; 12786 } 12787 12788 if (mEditor != null 12789 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12790 == EditorInfo.TYPE_CLASS_PHONE) { 12791 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 12792 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 12793 // RTL digits. 12794 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 12795 final String zero = symbols.getDigitStrings()[0]; 12796 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 12797 // direction. 12798 final int firstCodepoint = zero.codePointAt(0); 12799 final byte digitDirection = Character.getDirectionality(firstCodepoint); 12800 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 12801 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 12802 return TextDirectionHeuristics.RTL; 12803 } else { 12804 return TextDirectionHeuristics.LTR; 12805 } 12806 } 12807 12808 // Always need to resolve layout direction first 12809 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 12810 12811 // Now, we can select the heuristic 12812 switch (getTextDirection()) { 12813 default: 12814 case TEXT_DIRECTION_FIRST_STRONG: 12815 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 12816 TextDirectionHeuristics.FIRSTSTRONG_LTR); 12817 case TEXT_DIRECTION_ANY_RTL: 12818 return TextDirectionHeuristics.ANYRTL_LTR; 12819 case TEXT_DIRECTION_LTR: 12820 return TextDirectionHeuristics.LTR; 12821 case TEXT_DIRECTION_RTL: 12822 return TextDirectionHeuristics.RTL; 12823 case TEXT_DIRECTION_LOCALE: 12824 return TextDirectionHeuristics.LOCALE; 12825 case TEXT_DIRECTION_FIRST_STRONG_LTR: 12826 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 12827 case TEXT_DIRECTION_FIRST_STRONG_RTL: 12828 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 12829 } 12830 } 12831 12832 /** 12833 * @hide 12834 */ 12835 @Override onResolveDrawables(int layoutDirection)12836 public void onResolveDrawables(int layoutDirection) { 12837 // No need to resolve twice 12838 if (mLastLayoutDirection == layoutDirection) { 12839 return; 12840 } 12841 mLastLayoutDirection = layoutDirection; 12842 12843 // Resolve drawables 12844 if (mDrawables != null) { 12845 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 12846 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 12847 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 12848 applyCompoundDrawableTint(); 12849 } 12850 } 12851 } 12852 12853 /** 12854 * Prepares a drawable for display by propagating layout direction and 12855 * drawable state. 12856 * 12857 * @param dr the drawable to prepare 12858 */ prepareDrawableForDisplay(@ullable Drawable dr)12859 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 12860 if (dr == null) { 12861 return; 12862 } 12863 12864 dr.setLayoutDirection(getLayoutDirection()); 12865 12866 if (dr.isStateful()) { 12867 dr.setState(getDrawableState()); 12868 dr.jumpToCurrentState(); 12869 } 12870 } 12871 12872 /** 12873 * @hide 12874 */ resetResolvedDrawables()12875 protected void resetResolvedDrawables() { 12876 super.resetResolvedDrawables(); 12877 mLastLayoutDirection = -1; 12878 } 12879 12880 /** 12881 * @hide 12882 */ viewClicked(InputMethodManager imm)12883 protected void viewClicked(InputMethodManager imm) { 12884 if (imm != null) { 12885 imm.viewClicked(this); 12886 } 12887 } 12888 12889 /** 12890 * Deletes the range of text [start, end[. 12891 * @hide 12892 */ 12893 @UnsupportedAppUsage deleteText_internal(int start, int end)12894 protected void deleteText_internal(int start, int end) { 12895 ((Editable) mText).delete(start, end); 12896 } 12897 12898 /** 12899 * Replaces the range of text [start, end[ by replacement text 12900 * @hide 12901 */ replaceText_internal(int start, int end, CharSequence text)12902 protected void replaceText_internal(int start, int end, CharSequence text) { 12903 ((Editable) mText).replace(start, end, text); 12904 } 12905 12906 /** 12907 * Sets a span on the specified range of text 12908 * @hide 12909 */ setSpan_internal(Object span, int start, int end, int flags)12910 protected void setSpan_internal(Object span, int start, int end, int flags) { 12911 ((Editable) mText).setSpan(span, start, end, flags); 12912 } 12913 12914 /** 12915 * Moves the cursor to the specified offset position in text 12916 * @hide 12917 */ setCursorPosition_internal(int start, int end)12918 protected void setCursorPosition_internal(int start, int end) { 12919 Selection.setSelection(((Editable) mText), start, end); 12920 } 12921 12922 /** 12923 * An Editor should be created as soon as any of the editable-specific fields (grouped 12924 * inside the Editor object) is assigned to a non-default value. 12925 * This method will create the Editor if needed. 12926 * 12927 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 12928 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 12929 * Editor for backward compatibility, as soon as one of these fields is assigned. 12930 * 12931 * Also note that for performance reasons, the mEditor is created when needed, but not 12932 * reset when no more edit-specific fields are needed. 12933 */ 12934 @UnsupportedAppUsage createEditorIfNeeded()12935 private void createEditorIfNeeded() { 12936 if (mEditor == null) { 12937 mEditor = new Editor(this); 12938 } 12939 } 12940 12941 /** 12942 * @hide 12943 */ 12944 @Override 12945 @UnsupportedAppUsage getIterableTextForAccessibility()12946 public CharSequence getIterableTextForAccessibility() { 12947 return mText; 12948 } 12949 ensureIterableTextForAccessibilitySelectable()12950 private void ensureIterableTextForAccessibilitySelectable() { 12951 if (!(mText instanceof Spannable)) { 12952 setText(mText, BufferType.SPANNABLE); 12953 } 12954 } 12955 12956 /** 12957 * @hide 12958 */ 12959 @Override getIteratorForGranularity(int granularity)12960 public TextSegmentIterator getIteratorForGranularity(int granularity) { 12961 switch (granularity) { 12962 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 12963 Spannable text = (Spannable) getIterableTextForAccessibility(); 12964 if (!TextUtils.isEmpty(text) && getLayout() != null) { 12965 AccessibilityIterators.LineTextSegmentIterator iterator = 12966 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 12967 iterator.initialize(text, getLayout()); 12968 return iterator; 12969 } 12970 } break; 12971 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 12972 Spannable text = (Spannable) getIterableTextForAccessibility(); 12973 if (!TextUtils.isEmpty(text) && getLayout() != null) { 12974 AccessibilityIterators.PageTextSegmentIterator iterator = 12975 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 12976 iterator.initialize(this); 12977 return iterator; 12978 } 12979 } break; 12980 } 12981 return super.getIteratorForGranularity(granularity); 12982 } 12983 12984 /** 12985 * @hide 12986 */ 12987 @Override getAccessibilitySelectionStart()12988 public int getAccessibilitySelectionStart() { 12989 return getSelectionStart(); 12990 } 12991 12992 /** 12993 * @hide 12994 */ isAccessibilitySelectionExtendable()12995 public boolean isAccessibilitySelectionExtendable() { 12996 return true; 12997 } 12998 12999 /** 13000 * @hide 13001 */ 13002 @Override getAccessibilitySelectionEnd()13003 public int getAccessibilitySelectionEnd() { 13004 return getSelectionEnd(); 13005 } 13006 13007 /** 13008 * @hide 13009 */ 13010 @Override setAccessibilitySelection(int start, int end)13011 public void setAccessibilitySelection(int start, int end) { 13012 if (getAccessibilitySelectionStart() == start 13013 && getAccessibilitySelectionEnd() == end) { 13014 return; 13015 } 13016 CharSequence text = getIterableTextForAccessibility(); 13017 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13018 Selection.setSelection((Spannable) text, start, end); 13019 } else { 13020 Selection.removeSelection((Spannable) text); 13021 } 13022 // Hide all selection controllers used for adjusting selection 13023 // since we are doing so explicitlty by other means and these 13024 // controllers interact with how selection behaves. 13025 if (mEditor != null) { 13026 mEditor.hideCursorAndSpanControllers(); 13027 mEditor.stopTextActionMode(); 13028 } 13029 } 13030 13031 /** @hide */ 13032 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13033 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13034 super.encodeProperties(stream); 13035 13036 TruncateAt ellipsize = getEllipsize(); 13037 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13038 stream.addProperty("text:textSize", getTextSize()); 13039 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13040 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13041 stream.addProperty("text:selectionStart", getSelectionStart()); 13042 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13043 stream.addProperty("text:curTextColor", mCurTextColor); 13044 stream.addProperty("text:text", mText == null ? null : mText.toString()); 13045 stream.addProperty("text:gravity", mGravity); 13046 } 13047 13048 /** 13049 * User interface state that is stored by TextView for implementing 13050 * {@link View#onSaveInstanceState}. 13051 */ 13052 public static class SavedState extends BaseSavedState { 13053 int selStart = -1; 13054 int selEnd = -1; 13055 @UnsupportedAppUsage 13056 CharSequence text; 13057 boolean frozenWithFocus; 13058 CharSequence error; 13059 ParcelableParcel editorState; // Optional state from Editor. 13060 SavedState(Parcelable superState)13061 SavedState(Parcelable superState) { 13062 super(superState); 13063 } 13064 13065 @Override writeToParcel(Parcel out, int flags)13066 public void writeToParcel(Parcel out, int flags) { 13067 super.writeToParcel(out, flags); 13068 out.writeInt(selStart); 13069 out.writeInt(selEnd); 13070 out.writeInt(frozenWithFocus ? 1 : 0); 13071 TextUtils.writeToParcel(text, out, flags); 13072 13073 if (error == null) { 13074 out.writeInt(0); 13075 } else { 13076 out.writeInt(1); 13077 TextUtils.writeToParcel(error, out, flags); 13078 } 13079 13080 if (editorState == null) { 13081 out.writeInt(0); 13082 } else { 13083 out.writeInt(1); 13084 editorState.writeToParcel(out, flags); 13085 } 13086 } 13087 13088 @Override toString()13089 public String toString() { 13090 String str = "TextView.SavedState{" 13091 + Integer.toHexString(System.identityHashCode(this)) 13092 + " start=" + selStart + " end=" + selEnd; 13093 if (text != null) { 13094 str += " text=" + text; 13095 } 13096 return str + "}"; 13097 } 13098 13099 @SuppressWarnings("hiding") 13100 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13101 new Parcelable.Creator<SavedState>() { 13102 public SavedState createFromParcel(Parcel in) { 13103 return new SavedState(in); 13104 } 13105 13106 public SavedState[] newArray(int size) { 13107 return new SavedState[size]; 13108 } 13109 }; 13110 SavedState(Parcel in)13111 private SavedState(Parcel in) { 13112 super(in); 13113 selStart = in.readInt(); 13114 selEnd = in.readInt(); 13115 frozenWithFocus = (in.readInt() != 0); 13116 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13117 13118 if (in.readInt() != 0) { 13119 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13120 } 13121 13122 if (in.readInt() != 0) { 13123 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13124 } 13125 } 13126 } 13127 13128 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13129 private char[] mChars; 13130 private int mStart, mLength; 13131 CharWrapper(char[] chars, int start, int len)13132 public CharWrapper(char[] chars, int start, int len) { 13133 mChars = chars; 13134 mStart = start; 13135 mLength = len; 13136 } 13137 set(char[] chars, int start, int len)13138 /* package */ void set(char[] chars, int start, int len) { 13139 mChars = chars; 13140 mStart = start; 13141 mLength = len; 13142 } 13143 length()13144 public int length() { 13145 return mLength; 13146 } 13147 charAt(int off)13148 public char charAt(int off) { 13149 return mChars[off + mStart]; 13150 } 13151 13152 @Override toString()13153 public String toString() { 13154 return new String(mChars, mStart, mLength); 13155 } 13156 subSequence(int start, int end)13157 public CharSequence subSequence(int start, int end) { 13158 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13159 throw new IndexOutOfBoundsException(start + ", " + end); 13160 } 13161 13162 return new String(mChars, start + mStart, end - start); 13163 } 13164 getChars(int start, int end, char[] buf, int off)13165 public void getChars(int start, int end, char[] buf, int off) { 13166 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13167 throw new IndexOutOfBoundsException(start + ", " + end); 13168 } 13169 13170 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13171 } 13172 13173 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13174 public void drawText(BaseCanvas c, int start, int end, 13175 float x, float y, Paint p) { 13176 c.drawText(mChars, start + mStart, end - start, x, y, p); 13177 } 13178 13179 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13180 public void drawTextRun(BaseCanvas c, int start, int end, 13181 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13182 int count = end - start; 13183 int contextCount = contextEnd - contextStart; 13184 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13185 contextCount, x, y, isRtl, p); 13186 } 13187 measureText(int start, int end, Paint p)13188 public float measureText(int start, int end, Paint p) { 13189 return p.measureText(mChars, start + mStart, end - start); 13190 } 13191 getTextWidths(int start, int end, float[] widths, Paint p)13192 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13193 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13194 } 13195 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13196 public float getTextRunAdvances(int start, int end, int contextStart, 13197 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13198 Paint p) { 13199 int count = end - start; 13200 int contextCount = contextEnd - contextStart; 13201 return p.getTextRunAdvances(mChars, start + mStart, count, 13202 contextStart + mStart, contextCount, isRtl, advances, 13203 advancesIndex); 13204 } 13205 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13206 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13207 int offset, int cursorOpt, Paint p) { 13208 int contextCount = contextEnd - contextStart; 13209 return p.getTextRunCursor(mChars, contextStart + mStart, 13210 contextCount, isRtl, offset + mStart, cursorOpt); 13211 } 13212 } 13213 13214 private static final class Marquee { 13215 // TODO: Add an option to configure this 13216 private static final float MARQUEE_DELTA_MAX = 0.07f; 13217 private static final int MARQUEE_DELAY = 1200; 13218 private static final int MARQUEE_DP_PER_SECOND = 30; 13219 13220 private static final byte MARQUEE_STOPPED = 0x0; 13221 private static final byte MARQUEE_STARTING = 0x1; 13222 private static final byte MARQUEE_RUNNING = 0x2; 13223 13224 private final WeakReference<TextView> mView; 13225 private final Choreographer mChoreographer; 13226 13227 private byte mStatus = MARQUEE_STOPPED; 13228 private final float mPixelsPerMs; 13229 private float mMaxScroll; 13230 private float mMaxFadeScroll; 13231 private float mGhostStart; 13232 private float mGhostOffset; 13233 private float mFadeStop; 13234 private int mRepeatLimit; 13235 13236 private float mScroll; 13237 private long mLastAnimationMs; 13238 Marquee(TextView v)13239 Marquee(TextView v) { 13240 final float density = v.getContext().getResources().getDisplayMetrics().density; 13241 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13242 mView = new WeakReference<TextView>(v); 13243 mChoreographer = Choreographer.getInstance(); 13244 } 13245 13246 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13247 @Override 13248 public void doFrame(long frameTimeNanos) { 13249 tick(); 13250 } 13251 }; 13252 13253 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13254 @Override 13255 public void doFrame(long frameTimeNanos) { 13256 mStatus = MARQUEE_RUNNING; 13257 mLastAnimationMs = mChoreographer.getFrameTime(); 13258 tick(); 13259 } 13260 }; 13261 13262 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13263 @Override 13264 public void doFrame(long frameTimeNanos) { 13265 if (mStatus == MARQUEE_RUNNING) { 13266 if (mRepeatLimit >= 0) { 13267 mRepeatLimit--; 13268 } 13269 start(mRepeatLimit); 13270 } 13271 } 13272 }; 13273 tick()13274 void tick() { 13275 if (mStatus != MARQUEE_RUNNING) { 13276 return; 13277 } 13278 13279 mChoreographer.removeFrameCallback(mTickCallback); 13280 13281 final TextView textView = mView.get(); 13282 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13283 long currentMs = mChoreographer.getFrameTime(); 13284 long deltaMs = currentMs - mLastAnimationMs; 13285 mLastAnimationMs = currentMs; 13286 float deltaPx = deltaMs * mPixelsPerMs; 13287 mScroll += deltaPx; 13288 if (mScroll > mMaxScroll) { 13289 mScroll = mMaxScroll; 13290 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13291 } else { 13292 mChoreographer.postFrameCallback(mTickCallback); 13293 } 13294 textView.invalidate(); 13295 } 13296 } 13297 stop()13298 void stop() { 13299 mStatus = MARQUEE_STOPPED; 13300 mChoreographer.removeFrameCallback(mStartCallback); 13301 mChoreographer.removeFrameCallback(mRestartCallback); 13302 mChoreographer.removeFrameCallback(mTickCallback); 13303 resetScroll(); 13304 } 13305 resetScroll()13306 private void resetScroll() { 13307 mScroll = 0.0f; 13308 final TextView textView = mView.get(); 13309 if (textView != null) textView.invalidate(); 13310 } 13311 start(int repeatLimit)13312 void start(int repeatLimit) { 13313 if (repeatLimit == 0) { 13314 stop(); 13315 return; 13316 } 13317 mRepeatLimit = repeatLimit; 13318 final TextView textView = mView.get(); 13319 if (textView != null && textView.mLayout != null) { 13320 mStatus = MARQUEE_STARTING; 13321 mScroll = 0.0f; 13322 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13323 - textView.getCompoundPaddingRight(); 13324 final float lineWidth = textView.mLayout.getLineWidth(0); 13325 final float gap = textWidth / 3.0f; 13326 mGhostStart = lineWidth - textWidth + gap; 13327 mMaxScroll = mGhostStart + textWidth; 13328 mGhostOffset = lineWidth + gap; 13329 mFadeStop = lineWidth + textWidth / 6.0f; 13330 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13331 13332 textView.invalidate(); 13333 mChoreographer.postFrameCallback(mStartCallback); 13334 } 13335 } 13336 getGhostOffset()13337 float getGhostOffset() { 13338 return mGhostOffset; 13339 } 13340 getScroll()13341 float getScroll() { 13342 return mScroll; 13343 } 13344 getMaxFadeScroll()13345 float getMaxFadeScroll() { 13346 return mMaxFadeScroll; 13347 } 13348 shouldDrawLeftFade()13349 boolean shouldDrawLeftFade() { 13350 return mScroll <= mFadeStop; 13351 } 13352 shouldDrawGhost()13353 boolean shouldDrawGhost() { 13354 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13355 } 13356 isRunning()13357 boolean isRunning() { 13358 return mStatus == MARQUEE_RUNNING; 13359 } 13360 isStopped()13361 boolean isStopped() { 13362 return mStatus == MARQUEE_STOPPED; 13363 } 13364 } 13365 13366 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13367 13368 private CharSequence mBeforeText; 13369 beforeTextChanged(CharSequence buffer, int start, int before, int after)13370 public void beforeTextChanged(CharSequence buffer, int start, 13371 int before, int after) { 13372 if (DEBUG_EXTRACT) { 13373 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13374 + " before=" + before + " after=" + after + ": " + buffer); 13375 } 13376 13377 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13378 mBeforeText = mTransformed.toString(); 13379 } 13380 13381 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13382 } 13383 onTextChanged(CharSequence buffer, int start, int before, int after)13384 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13385 if (DEBUG_EXTRACT) { 13386 Log.v(LOG_TAG, "onTextChanged start=" + start 13387 + " before=" + before + " after=" + after + ": " + buffer); 13388 } 13389 TextView.this.handleTextChanged(buffer, start, before, after); 13390 13391 if (AccessibilityManager.getInstance(mContext).isEnabled() 13392 && (isFocused() || isSelected() && isShown())) { 13393 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13394 mBeforeText = null; 13395 } 13396 } 13397 afterTextChanged(Editable buffer)13398 public void afterTextChanged(Editable buffer) { 13399 if (DEBUG_EXTRACT) { 13400 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13401 } 13402 TextView.this.sendAfterTextChanged(buffer); 13403 13404 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13405 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13406 } 13407 } 13408 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13409 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13410 if (DEBUG_EXTRACT) { 13411 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13412 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13413 } 13414 TextView.this.spanChange(buf, what, s, st, e, en); 13415 } 13416 onSpanAdded(Spannable buf, Object what, int s, int e)13417 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13418 if (DEBUG_EXTRACT) { 13419 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13420 } 13421 TextView.this.spanChange(buf, what, -1, s, -1, e); 13422 } 13423 onSpanRemoved(Spannable buf, Object what, int s, int e)13424 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13425 if (DEBUG_EXTRACT) { 13426 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13427 } 13428 TextView.this.spanChange(buf, what, s, -1, e, -1); 13429 } 13430 } 13431 } 13432