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 isPasswordInputType(int inputType)6580 static boolean isPasswordInputType(int inputType) { 6581 final int variation = 6582 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6583 return variation 6584 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6585 || variation 6586 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6587 || variation 6588 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6589 } 6590 isVisiblePasswordInputType(int inputType)6591 private static boolean isVisiblePasswordInputType(int inputType) { 6592 final int variation = 6593 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6594 return variation 6595 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6596 } 6597 6598 /** 6599 * Directly change the content type integer of the text view, without 6600 * modifying any other state. 6601 * @see #setInputType(int) 6602 * @see android.text.InputType 6603 * @attr ref android.R.styleable#TextView_inputType 6604 */ setRawInputType(int type)6605 public void setRawInputType(int type) { 6606 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6607 createEditorIfNeeded(); 6608 mEditor.mInputType = type; 6609 } 6610 6611 /** 6612 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6613 * a {@code Locale} object that can be used to customize key various listeners. 6614 * @see DateKeyListener#getInstance(Locale) 6615 * @see DateTimeKeyListener#getInstance(Locale) 6616 * @see DigitsKeyListener#getInstance(Locale) 6617 * @see TimeKeyListener#getInstance(Locale) 6618 */ 6619 @Nullable getCustomLocaleForKeyListenerOrNull()6620 private Locale getCustomLocaleForKeyListenerOrNull() { 6621 if (!mUseInternationalizedInput) { 6622 // If the application does not target O, stick to the previous behavior. 6623 return null; 6624 } 6625 final LocaleList locales = getImeHintLocales(); 6626 if (locales == null) { 6627 // If the application does not explicitly specify IME hint locale, also stick to the 6628 // previous behavior. 6629 return null; 6630 } 6631 return locales.get(0); 6632 } 6633 6634 @UnsupportedAppUsage setInputType(int type, boolean direct)6635 private void setInputType(int type, boolean direct) { 6636 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6637 KeyListener input; 6638 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6639 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6640 TextKeyListener.Capitalize cap; 6641 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6642 cap = TextKeyListener.Capitalize.CHARACTERS; 6643 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6644 cap = TextKeyListener.Capitalize.WORDS; 6645 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6646 cap = TextKeyListener.Capitalize.SENTENCES; 6647 } else { 6648 cap = TextKeyListener.Capitalize.NONE; 6649 } 6650 input = TextKeyListener.getInstance(autotext, cap); 6651 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6652 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6653 input = DigitsKeyListener.getInstance( 6654 locale, 6655 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6656 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6657 if (locale != null) { 6658 // Override type, if necessary for i18n. 6659 int newType = input.getInputType(); 6660 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6661 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6662 // The class is different from the original class. So we need to override 6663 // 'type'. But we want to keep the password flag if it's there. 6664 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6665 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6666 } 6667 type = newType; 6668 } 6669 } 6670 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6671 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6672 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6673 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6674 input = DateKeyListener.getInstance(locale); 6675 break; 6676 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6677 input = TimeKeyListener.getInstance(locale); 6678 break; 6679 default: 6680 input = DateTimeKeyListener.getInstance(locale); 6681 break; 6682 } 6683 if (mUseInternationalizedInput) { 6684 type = input.getInputType(); // Override type, if necessary for i18n. 6685 } 6686 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6687 input = DialerKeyListener.getInstance(); 6688 } else { 6689 input = TextKeyListener.getInstance(); 6690 } 6691 setRawInputType(type); 6692 mListenerChanged = false; 6693 if (direct) { 6694 createEditorIfNeeded(); 6695 mEditor.mKeyListener = input; 6696 } else { 6697 setKeyListenerOnly(input); 6698 } 6699 } 6700 6701 /** 6702 * Get the type of the editable content. 6703 * 6704 * @see #setInputType(int) 6705 * @see android.text.InputType 6706 */ 6707 @InspectableProperty(flagMapping = { 6708 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6709 @FlagEntry( 6710 name = "text", 6711 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6712 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6713 @FlagEntry( 6714 name = "textUri", 6715 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6716 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6717 @FlagEntry( 6718 name = "textEmailAddress", 6719 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6720 target = InputType.TYPE_CLASS_TEXT 6721 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6722 @FlagEntry( 6723 name = "textEmailSubject", 6724 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6725 target = InputType.TYPE_CLASS_TEXT 6726 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6727 @FlagEntry( 6728 name = "textShortMessage", 6729 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6730 target = InputType.TYPE_CLASS_TEXT 6731 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6732 @FlagEntry( 6733 name = "textLongMessage", 6734 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6735 target = InputType.TYPE_CLASS_TEXT 6736 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6737 @FlagEntry( 6738 name = "textPersonName", 6739 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6740 target = InputType.TYPE_CLASS_TEXT 6741 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6742 @FlagEntry( 6743 name = "textPostalAddress", 6744 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6745 target = InputType.TYPE_CLASS_TEXT 6746 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6747 @FlagEntry( 6748 name = "textPassword", 6749 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6750 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6751 @FlagEntry( 6752 name = "textVisiblePassword", 6753 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6754 target = InputType.TYPE_CLASS_TEXT 6755 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6756 @FlagEntry( 6757 name = "textWebEditText", 6758 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6759 target = InputType.TYPE_CLASS_TEXT 6760 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6761 @FlagEntry( 6762 name = "textFilter", 6763 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6764 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6765 @FlagEntry( 6766 name = "textPhonetic", 6767 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6768 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6769 @FlagEntry( 6770 name = "textWebEmailAddress", 6771 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6772 target = InputType.TYPE_CLASS_TEXT 6773 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6774 @FlagEntry( 6775 name = "textWebPassword", 6776 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6777 target = InputType.TYPE_CLASS_TEXT 6778 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6779 @FlagEntry( 6780 name = "number", 6781 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6782 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6783 @FlagEntry( 6784 name = "numberPassword", 6785 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6786 target = InputType.TYPE_CLASS_NUMBER 6787 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6788 @FlagEntry( 6789 name = "phone", 6790 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6791 target = InputType.TYPE_CLASS_PHONE), 6792 @FlagEntry( 6793 name = "datetime", 6794 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6795 target = InputType.TYPE_CLASS_DATETIME 6796 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6797 @FlagEntry( 6798 name = "date", 6799 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6800 target = InputType.TYPE_CLASS_DATETIME 6801 | InputType.TYPE_DATETIME_VARIATION_DATE), 6802 @FlagEntry( 6803 name = "time", 6804 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6805 target = InputType.TYPE_CLASS_DATETIME 6806 | InputType.TYPE_DATETIME_VARIATION_TIME), 6807 @FlagEntry( 6808 name = "textCapCharacters", 6809 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6810 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6811 @FlagEntry( 6812 name = "textCapWords", 6813 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6814 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6815 @FlagEntry( 6816 name = "textCapSentences", 6817 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6818 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6819 @FlagEntry( 6820 name = "textAutoCorrect", 6821 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6822 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6823 @FlagEntry( 6824 name = "textAutoComplete", 6825 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6826 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6827 @FlagEntry( 6828 name = "textMultiLine", 6829 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6830 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6831 @FlagEntry( 6832 name = "textImeMultiLine", 6833 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6834 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6835 @FlagEntry( 6836 name = "textNoSuggestions", 6837 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6838 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6839 @FlagEntry( 6840 name = "numberSigned", 6841 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6842 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6843 @FlagEntry( 6844 name = "numberDecimal", 6845 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6846 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6847 }) getInputType()6848 public int getInputType() { 6849 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6850 } 6851 6852 /** 6853 * Change the editor type integer associated with the text view, which 6854 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6855 * when it has focus. 6856 * @see #getImeOptions 6857 * @see android.view.inputmethod.EditorInfo 6858 * @attr ref android.R.styleable#TextView_imeOptions 6859 */ setImeOptions(int imeOptions)6860 public void setImeOptions(int imeOptions) { 6861 createEditorIfNeeded(); 6862 mEditor.createInputContentTypeIfNeeded(); 6863 mEditor.mInputContentType.imeOptions = imeOptions; 6864 } 6865 6866 /** 6867 * Get the type of the Input Method Editor (IME). 6868 * @return the type of the IME 6869 * @see #setImeOptions(int) 6870 * @see EditorInfo 6871 */ 6872 @InspectableProperty(flagMapping = { 6873 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 6874 @FlagEntry( 6875 name = "actionUnspecified", 6876 mask = EditorInfo.IME_MASK_ACTION, 6877 target = EditorInfo.IME_ACTION_UNSPECIFIED), 6878 @FlagEntry( 6879 name = "actionNone", 6880 mask = EditorInfo.IME_MASK_ACTION, 6881 target = EditorInfo.IME_ACTION_NONE), 6882 @FlagEntry( 6883 name = "actionGo", 6884 mask = EditorInfo.IME_MASK_ACTION, 6885 target = EditorInfo.IME_ACTION_GO), 6886 @FlagEntry( 6887 name = "actionSearch", 6888 mask = EditorInfo.IME_MASK_ACTION, 6889 target = EditorInfo.IME_ACTION_SEARCH), 6890 @FlagEntry( 6891 name = "actionSend", 6892 mask = EditorInfo.IME_MASK_ACTION, 6893 target = EditorInfo.IME_ACTION_SEND), 6894 @FlagEntry( 6895 name = "actionNext", 6896 mask = EditorInfo.IME_MASK_ACTION, 6897 target = EditorInfo.IME_ACTION_NEXT), 6898 @FlagEntry( 6899 name = "actionDone", 6900 mask = EditorInfo.IME_MASK_ACTION, 6901 target = EditorInfo.IME_ACTION_DONE), 6902 @FlagEntry( 6903 name = "actionPrevious", 6904 mask = EditorInfo.IME_MASK_ACTION, 6905 target = EditorInfo.IME_ACTION_PREVIOUS), 6906 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 6907 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 6908 @FlagEntry( 6909 name = "flagNavigatePrevious", 6910 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 6911 @FlagEntry( 6912 name = "flagNoAccessoryAction", 6913 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 6914 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 6915 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 6916 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 6917 @FlagEntry( 6918 name = "flagNoPersonalizedLearning", 6919 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 6920 }) getImeOptions()6921 public int getImeOptions() { 6922 return mEditor != null && mEditor.mInputContentType != null 6923 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 6924 } 6925 6926 /** 6927 * Change the custom IME action associated with the text view, which 6928 * will be reported to an IME with {@link EditorInfo#actionLabel} 6929 * and {@link EditorInfo#actionId} when it has focus. 6930 * @see #getImeActionLabel 6931 * @see #getImeActionId 6932 * @see android.view.inputmethod.EditorInfo 6933 * @attr ref android.R.styleable#TextView_imeActionLabel 6934 * @attr ref android.R.styleable#TextView_imeActionId 6935 */ setImeActionLabel(CharSequence label, int actionId)6936 public void setImeActionLabel(CharSequence label, int actionId) { 6937 createEditorIfNeeded(); 6938 mEditor.createInputContentTypeIfNeeded(); 6939 mEditor.mInputContentType.imeActionLabel = label; 6940 mEditor.mInputContentType.imeActionId = actionId; 6941 } 6942 6943 /** 6944 * Get the IME action label previous set with {@link #setImeActionLabel}. 6945 * 6946 * @see #setImeActionLabel 6947 * @see android.view.inputmethod.EditorInfo 6948 */ 6949 @InspectableProperty getImeActionLabel()6950 public CharSequence getImeActionLabel() { 6951 return mEditor != null && mEditor.mInputContentType != null 6952 ? mEditor.mInputContentType.imeActionLabel : null; 6953 } 6954 6955 /** 6956 * Get the IME action ID previous set with {@link #setImeActionLabel}. 6957 * 6958 * @see #setImeActionLabel 6959 * @see android.view.inputmethod.EditorInfo 6960 */ 6961 @InspectableProperty getImeActionId()6962 public int getImeActionId() { 6963 return mEditor != null && mEditor.mInputContentType != null 6964 ? mEditor.mInputContentType.imeActionId : 0; 6965 } 6966 6967 /** 6968 * Set a special listener to be called when an action is performed 6969 * on the text view. This will be called when the enter key is pressed, 6970 * or when an action supplied to the IME is selected by the user. Setting 6971 * this means that the normal hard key event will not insert a newline 6972 * into the text view, even if it is multi-line; holding down the ALT 6973 * modifier will, however, allow the user to insert a newline character. 6974 */ setOnEditorActionListener(OnEditorActionListener l)6975 public void setOnEditorActionListener(OnEditorActionListener l) { 6976 createEditorIfNeeded(); 6977 mEditor.createInputContentTypeIfNeeded(); 6978 mEditor.mInputContentType.onEditorActionListener = l; 6979 } 6980 6981 /** 6982 * Called when an attached input method calls 6983 * {@link InputConnection#performEditorAction(int) 6984 * InputConnection.performEditorAction()} 6985 * for this text view. The default implementation will call your action 6986 * listener supplied to {@link #setOnEditorActionListener}, or perform 6987 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 6988 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 6989 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 6990 * EditorInfo.IME_ACTION_DONE}. 6991 * 6992 * <p>For backwards compatibility, if no IME options have been set and the 6993 * text view would not normally advance focus on enter, then 6994 * the NEXT and DONE actions received here will be turned into an enter 6995 * key down/up pair to go through the normal key handling. 6996 * 6997 * @param actionCode The code of the action being performed. 6998 * 6999 * @see #setOnEditorActionListener 7000 */ onEditorAction(int actionCode)7001 public void onEditorAction(int actionCode) { 7002 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7003 if (ict != null) { 7004 if (ict.onEditorActionListener != null) { 7005 if (ict.onEditorActionListener.onEditorAction(this, 7006 actionCode, null)) { 7007 return; 7008 } 7009 } 7010 7011 // This is the handling for some default action. 7012 // Note that for backwards compatibility we don't do this 7013 // default handling if explicit ime options have not been given, 7014 // instead turning this into the normal enter key codes that an 7015 // app may be expecting. 7016 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7017 View v = focusSearch(FOCUS_FORWARD); 7018 if (v != null) { 7019 if (!v.requestFocus(FOCUS_FORWARD)) { 7020 throw new IllegalStateException("focus search returned a view " 7021 + "that wasn't able to take focus!"); 7022 } 7023 } 7024 return; 7025 7026 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7027 View v = focusSearch(FOCUS_BACKWARD); 7028 if (v != null) { 7029 if (!v.requestFocus(FOCUS_BACKWARD)) { 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_DONE) { 7037 InputMethodManager imm = getInputMethodManager(); 7038 if (imm != null && imm.isActive(this)) { 7039 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7040 } 7041 return; 7042 } 7043 } 7044 7045 ViewRootImpl viewRootImpl = getViewRootImpl(); 7046 if (viewRootImpl != null) { 7047 long eventTime = SystemClock.uptimeMillis(); 7048 viewRootImpl.dispatchKeyFromIme( 7049 new KeyEvent(eventTime, eventTime, 7050 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7051 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7052 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7053 | KeyEvent.FLAG_EDITOR_ACTION)); 7054 viewRootImpl.dispatchKeyFromIme( 7055 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7056 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7057 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7058 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7059 | KeyEvent.FLAG_EDITOR_ACTION)); 7060 } 7061 } 7062 7063 /** 7064 * Set the private content type of the text, which is the 7065 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7066 * field that will be filled in when creating an input connection. 7067 * 7068 * @see #getPrivateImeOptions() 7069 * @see EditorInfo#privateImeOptions 7070 * @attr ref android.R.styleable#TextView_privateImeOptions 7071 */ setPrivateImeOptions(String type)7072 public void setPrivateImeOptions(String type) { 7073 createEditorIfNeeded(); 7074 mEditor.createInputContentTypeIfNeeded(); 7075 mEditor.mInputContentType.privateImeOptions = type; 7076 } 7077 7078 /** 7079 * Get the private type of the content. 7080 * 7081 * @see #setPrivateImeOptions(String) 7082 * @see EditorInfo#privateImeOptions 7083 */ 7084 @InspectableProperty getPrivateImeOptions()7085 public String getPrivateImeOptions() { 7086 return mEditor != null && mEditor.mInputContentType != null 7087 ? mEditor.mInputContentType.privateImeOptions : null; 7088 } 7089 7090 /** 7091 * Set the extra input data of the text, which is the 7092 * {@link EditorInfo#extras TextBoxAttribute.extras} 7093 * Bundle that will be filled in when creating an input connection. The 7094 * given integer is the resource identifier of an XML resource holding an 7095 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7096 * 7097 * @see #getInputExtras(boolean) 7098 * @see EditorInfo#extras 7099 * @attr ref android.R.styleable#TextView_editorExtras 7100 */ setInputExtras(@mlRes int xmlResId)7101 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7102 createEditorIfNeeded(); 7103 XmlResourceParser parser = getResources().getXml(xmlResId); 7104 mEditor.createInputContentTypeIfNeeded(); 7105 mEditor.mInputContentType.extras = new Bundle(); 7106 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7107 } 7108 7109 /** 7110 * Retrieve the input extras currently associated with the text view, which 7111 * can be viewed as well as modified. 7112 * 7113 * @param create If true, the extras will be created if they don't already 7114 * exist. Otherwise, null will be returned if none have been created. 7115 * @see #setInputExtras(int) 7116 * @see EditorInfo#extras 7117 * @attr ref android.R.styleable#TextView_editorExtras 7118 */ getInputExtras(boolean create)7119 public Bundle getInputExtras(boolean create) { 7120 if (mEditor == null && !create) return null; 7121 createEditorIfNeeded(); 7122 if (mEditor.mInputContentType == null) { 7123 if (!create) return null; 7124 mEditor.createInputContentTypeIfNeeded(); 7125 } 7126 if (mEditor.mInputContentType.extras == null) { 7127 if (!create) return null; 7128 mEditor.mInputContentType.extras = new Bundle(); 7129 } 7130 return mEditor.mInputContentType.extras; 7131 } 7132 7133 /** 7134 * Change "hint" locales associated with the text view, which will be reported to an IME with 7135 * {@link EditorInfo#hintLocales} when it has focus. 7136 * 7137 * Starting with Android O, this also causes internationalized listeners to be created (or 7138 * change locale) based on the first locale in the input locale list. 7139 * 7140 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7141 * call {@link InputMethodManager#restartInput(View)}.</p> 7142 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7143 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7144 * @see #getImeHintLocales() 7145 * @see android.view.inputmethod.EditorInfo#hintLocales 7146 */ setImeHintLocales(@ullable LocaleList hintLocales)7147 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7148 createEditorIfNeeded(); 7149 mEditor.createInputContentTypeIfNeeded(); 7150 mEditor.mInputContentType.imeHintLocales = hintLocales; 7151 if (mUseInternationalizedInput) { 7152 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7153 } 7154 } 7155 7156 /** 7157 * @return The current languages list "hint". {@code null} when no "hint" is available. 7158 * @see #setImeHintLocales(LocaleList) 7159 * @see android.view.inputmethod.EditorInfo#hintLocales 7160 */ 7161 @Nullable getImeHintLocales()7162 public LocaleList getImeHintLocales() { 7163 if (mEditor == null) { 7164 return null; 7165 } 7166 if (mEditor.mInputContentType == null) { 7167 return null; 7168 } 7169 return mEditor.mInputContentType.imeHintLocales; 7170 } 7171 7172 /** 7173 * Returns the error message that was set to be displayed with 7174 * {@link #setError}, or <code>null</code> if no error was set 7175 * or if it the error was cleared by the widget after user input. 7176 */ getError()7177 public CharSequence getError() { 7178 return mEditor == null ? null : mEditor.mError; 7179 } 7180 7181 /** 7182 * Sets the right-hand compound drawable of the TextView to the "error" 7183 * icon and sets an error message that will be displayed in a popup when 7184 * the TextView has focus. The icon and error message will be reset to 7185 * null when any key events cause changes to the TextView's text. If the 7186 * <code>error</code> is <code>null</code>, the error message and icon 7187 * will be cleared. 7188 */ 7189 @android.view.RemotableViewMethod setError(CharSequence error)7190 public void setError(CharSequence error) { 7191 if (error == null) { 7192 setError(null, null); 7193 } else { 7194 Drawable dr = getContext().getDrawable( 7195 com.android.internal.R.drawable.indicator_input_error); 7196 7197 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7198 setError(error, dr); 7199 } 7200 } 7201 7202 /** 7203 * Sets the right-hand compound drawable of the TextView to the specified 7204 * icon and sets an error message that will be displayed in a popup when 7205 * the TextView has focus. The icon and error message will be reset to 7206 * null when any key events cause changes to the TextView's text. The 7207 * drawable must already have had {@link Drawable#setBounds} set on it. 7208 * If the <code>error</code> is <code>null</code>, the error message will 7209 * be cleared (and you should provide a <code>null</code> icon as well). 7210 */ setError(CharSequence error, Drawable icon)7211 public void setError(CharSequence error, Drawable icon) { 7212 createEditorIfNeeded(); 7213 mEditor.setError(error, icon); 7214 notifyViewAccessibilityStateChangedIfNeeded( 7215 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7216 } 7217 7218 @Override setFrame(int l, int t, int r, int b)7219 protected boolean setFrame(int l, int t, int r, int b) { 7220 boolean result = super.setFrame(l, t, r, b); 7221 7222 if (mEditor != null) mEditor.setFrame(); 7223 7224 restartMarqueeIfNeeded(); 7225 7226 return result; 7227 } 7228 restartMarqueeIfNeeded()7229 private void restartMarqueeIfNeeded() { 7230 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7231 mRestartMarquee = false; 7232 startMarquee(); 7233 } 7234 } 7235 7236 /** 7237 * Sets the list of input filters that will be used if the buffer is 7238 * Editable. Has no effect otherwise. 7239 * 7240 * @attr ref android.R.styleable#TextView_maxLength 7241 */ setFilters(InputFilter[] filters)7242 public void setFilters(InputFilter[] filters) { 7243 if (filters == null) { 7244 throw new IllegalArgumentException(); 7245 } 7246 7247 mFilters = filters; 7248 7249 if (mText instanceof Editable) { 7250 setFilters((Editable) mText, filters); 7251 } 7252 } 7253 7254 /** 7255 * Sets the list of input filters on the specified Editable, 7256 * and includes mInput in the list if it is an InputFilter. 7257 */ setFilters(Editable e, InputFilter[] filters)7258 private void setFilters(Editable e, InputFilter[] filters) { 7259 if (mEditor != null) { 7260 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7261 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7262 int num = 0; 7263 if (undoFilter) num++; 7264 if (keyFilter) num++; 7265 if (num > 0) { 7266 InputFilter[] nf = new InputFilter[filters.length + num]; 7267 7268 System.arraycopy(filters, 0, nf, 0, filters.length); 7269 num = 0; 7270 if (undoFilter) { 7271 nf[filters.length] = mEditor.mUndoInputFilter; 7272 num++; 7273 } 7274 if (keyFilter) { 7275 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7276 } 7277 7278 e.setFilters(nf); 7279 return; 7280 } 7281 } 7282 e.setFilters(filters); 7283 } 7284 7285 /** 7286 * Returns the current list of input filters. 7287 * 7288 * @attr ref android.R.styleable#TextView_maxLength 7289 */ getFilters()7290 public InputFilter[] getFilters() { 7291 return mFilters; 7292 } 7293 7294 ///////////////////////////////////////////////////////////////////////// 7295 getBoxHeight(Layout l)7296 private int getBoxHeight(Layout l) { 7297 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7298 int padding = (l == mHintLayout) 7299 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7300 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7301 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7302 } 7303 7304 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7305 int getVerticalOffset(boolean forceNormal) { 7306 int voffset = 0; 7307 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7308 7309 Layout l = mLayout; 7310 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7311 l = mHintLayout; 7312 } 7313 7314 if (gravity != Gravity.TOP) { 7315 int boxht = getBoxHeight(l); 7316 int textht = l.getHeight(); 7317 7318 if (textht < boxht) { 7319 if (gravity == Gravity.BOTTOM) { 7320 voffset = boxht - textht; 7321 } else { // (gravity == Gravity.CENTER_VERTICAL) 7322 voffset = (boxht - textht) >> 1; 7323 } 7324 } 7325 } 7326 return voffset; 7327 } 7328 getBottomVerticalOffset(boolean forceNormal)7329 private int getBottomVerticalOffset(boolean forceNormal) { 7330 int voffset = 0; 7331 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7332 7333 Layout l = mLayout; 7334 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7335 l = mHintLayout; 7336 } 7337 7338 if (gravity != Gravity.BOTTOM) { 7339 int boxht = getBoxHeight(l); 7340 int textht = l.getHeight(); 7341 7342 if (textht < boxht) { 7343 if (gravity == Gravity.TOP) { 7344 voffset = boxht - textht; 7345 } else { // (gravity == Gravity.CENTER_VERTICAL) 7346 voffset = (boxht - textht) >> 1; 7347 } 7348 } 7349 } 7350 return voffset; 7351 } 7352 invalidateCursorPath()7353 void invalidateCursorPath() { 7354 if (mHighlightPathBogus) { 7355 invalidateCursor(); 7356 } else { 7357 final int horizontalPadding = getCompoundPaddingLeft(); 7358 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7359 7360 if (mEditor.mDrawableForCursor == null) { 7361 synchronized (TEMP_RECTF) { 7362 /* 7363 * The reason for this concern about the thickness of the 7364 * cursor and doing the floor/ceil on the coordinates is that 7365 * some EditTexts (notably textfields in the Browser) have 7366 * anti-aliased text where not all the characters are 7367 * necessarily at integer-multiple locations. This should 7368 * make sure the entire cursor gets invalidated instead of 7369 * sometimes missing half a pixel. 7370 */ 7371 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7372 if (thick < 1.0f) { 7373 thick = 1.0f; 7374 } 7375 7376 thick /= 2.0f; 7377 7378 // mHighlightPath is guaranteed to be non null at that point. 7379 mHighlightPath.computeBounds(TEMP_RECTF, false); 7380 7381 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7382 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7383 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7384 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7385 } 7386 } else { 7387 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7388 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7389 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7390 } 7391 } 7392 } 7393 invalidateCursor()7394 void invalidateCursor() { 7395 int where = getSelectionEnd(); 7396 7397 invalidateCursor(where, where, where); 7398 } 7399 invalidateCursor(int a, int b, int c)7400 private void invalidateCursor(int a, int b, int c) { 7401 if (a >= 0 || b >= 0 || c >= 0) { 7402 int start = Math.min(Math.min(a, b), c); 7403 int end = Math.max(Math.max(a, b), c); 7404 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7405 } 7406 } 7407 7408 /** 7409 * Invalidates the region of text enclosed between the start and end text offsets. 7410 */ invalidateRegion(int start, int end, boolean invalidateCursor)7411 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7412 if (mLayout == null) { 7413 invalidate(); 7414 } else { 7415 int lineStart = mLayout.getLineForOffset(start); 7416 int top = mLayout.getLineTop(lineStart); 7417 7418 // This is ridiculous, but the descent from the line above 7419 // can hang down into the line we really want to redraw, 7420 // so we have to invalidate part of the line above to make 7421 // sure everything that needs to be redrawn really is. 7422 // (But not the whole line above, because that would cause 7423 // the same problem with the descenders on the line above it!) 7424 if (lineStart > 0) { 7425 top -= mLayout.getLineDescent(lineStart - 1); 7426 } 7427 7428 int lineEnd; 7429 7430 if (start == end) { 7431 lineEnd = lineStart; 7432 } else { 7433 lineEnd = mLayout.getLineForOffset(end); 7434 } 7435 7436 int bottom = mLayout.getLineBottom(lineEnd); 7437 7438 // mEditor can be null in case selection is set programmatically. 7439 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7440 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7441 top = Math.min(top, bounds.top); 7442 bottom = Math.max(bottom, bounds.bottom); 7443 } 7444 7445 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7446 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7447 7448 int left, right; 7449 if (lineStart == lineEnd && !invalidateCursor) { 7450 left = (int) mLayout.getPrimaryHorizontal(start); 7451 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7452 left += compoundPaddingLeft; 7453 right += compoundPaddingLeft; 7454 } else { 7455 // Rectangle bounding box when the region spans several lines 7456 left = compoundPaddingLeft; 7457 right = getWidth() - getCompoundPaddingRight(); 7458 } 7459 7460 invalidate(mScrollX + left, verticalPadding + top, 7461 mScrollX + right, verticalPadding + bottom); 7462 } 7463 } 7464 registerForPreDraw()7465 private void registerForPreDraw() { 7466 if (!mPreDrawRegistered) { 7467 getViewTreeObserver().addOnPreDrawListener(this); 7468 mPreDrawRegistered = true; 7469 } 7470 } 7471 unregisterForPreDraw()7472 private void unregisterForPreDraw() { 7473 getViewTreeObserver().removeOnPreDrawListener(this); 7474 mPreDrawRegistered = false; 7475 mPreDrawListenerDetached = false; 7476 } 7477 7478 /** 7479 * {@inheritDoc} 7480 */ 7481 @Override onPreDraw()7482 public boolean onPreDraw() { 7483 if (mLayout == null) { 7484 assumeLayout(); 7485 } 7486 7487 if (mMovement != null) { 7488 /* This code also provides auto-scrolling when a cursor is moved using a 7489 * CursorController (insertion point or selection limits). 7490 * For selection, ensure start or end is visible depending on controller's state. 7491 */ 7492 int curs = getSelectionEnd(); 7493 // Do not create the controller if it is not already created. 7494 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7495 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7496 curs = getSelectionStart(); 7497 } 7498 7499 /* 7500 * TODO: This should really only keep the end in view if 7501 * it already was before the text changed. I'm not sure 7502 * of a good way to tell from here if it was. 7503 */ 7504 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7505 curs = mText.length(); 7506 } 7507 7508 if (curs >= 0) { 7509 bringPointIntoView(curs); 7510 } 7511 } else { 7512 bringTextIntoView(); 7513 } 7514 7515 // This has to be checked here since: 7516 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7517 // a screen rotation) since layout is not yet initialized at that point. 7518 if (mEditor != null && mEditor.mCreatedWithASelection) { 7519 mEditor.refreshTextActionMode(); 7520 mEditor.mCreatedWithASelection = false; 7521 } 7522 7523 unregisterForPreDraw(); 7524 7525 return true; 7526 } 7527 7528 @Override onAttachedToWindow()7529 protected void onAttachedToWindow() { 7530 super.onAttachedToWindow(); 7531 7532 if (mEditor != null) mEditor.onAttachedToWindow(); 7533 7534 if (mPreDrawListenerDetached) { 7535 getViewTreeObserver().addOnPreDrawListener(this); 7536 mPreDrawListenerDetached = false; 7537 } 7538 } 7539 7540 /** @hide */ 7541 @Override onDetachedFromWindowInternal()7542 protected void onDetachedFromWindowInternal() { 7543 if (mPreDrawRegistered) { 7544 getViewTreeObserver().removeOnPreDrawListener(this); 7545 mPreDrawListenerDetached = true; 7546 } 7547 7548 resetResolvedDrawables(); 7549 7550 if (mEditor != null) mEditor.onDetachedFromWindow(); 7551 7552 super.onDetachedFromWindowInternal(); 7553 } 7554 7555 @Override onScreenStateChanged(int screenState)7556 public void onScreenStateChanged(int screenState) { 7557 super.onScreenStateChanged(screenState); 7558 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7559 } 7560 7561 @Override isPaddingOffsetRequired()7562 protected boolean isPaddingOffsetRequired() { 7563 return mShadowRadius != 0 || mDrawables != null; 7564 } 7565 7566 @Override getLeftPaddingOffset()7567 protected int getLeftPaddingOffset() { 7568 return getCompoundPaddingLeft() - mPaddingLeft 7569 + (int) Math.min(0, mShadowDx - mShadowRadius); 7570 } 7571 7572 @Override getTopPaddingOffset()7573 protected int getTopPaddingOffset() { 7574 return (int) Math.min(0, mShadowDy - mShadowRadius); 7575 } 7576 7577 @Override getBottomPaddingOffset()7578 protected int getBottomPaddingOffset() { 7579 return (int) Math.max(0, mShadowDy + mShadowRadius); 7580 } 7581 7582 @Override getRightPaddingOffset()7583 protected int getRightPaddingOffset() { 7584 return -(getCompoundPaddingRight() - mPaddingRight) 7585 + (int) Math.max(0, mShadowDx + mShadowRadius); 7586 } 7587 7588 @Override verifyDrawable(@onNull Drawable who)7589 protected boolean verifyDrawable(@NonNull Drawable who) { 7590 final boolean verified = super.verifyDrawable(who); 7591 if (!verified && mDrawables != null) { 7592 for (Drawable dr : mDrawables.mShowing) { 7593 if (who == dr) { 7594 return true; 7595 } 7596 } 7597 } 7598 return verified; 7599 } 7600 7601 @Override jumpDrawablesToCurrentState()7602 public void jumpDrawablesToCurrentState() { 7603 super.jumpDrawablesToCurrentState(); 7604 if (mDrawables != null) { 7605 for (Drawable dr : mDrawables.mShowing) { 7606 if (dr != null) { 7607 dr.jumpToCurrentState(); 7608 } 7609 } 7610 } 7611 } 7612 7613 @Override invalidateDrawable(@onNull Drawable drawable)7614 public void invalidateDrawable(@NonNull Drawable drawable) { 7615 boolean handled = false; 7616 7617 if (verifyDrawable(drawable)) { 7618 final Rect dirty = drawable.getBounds(); 7619 int scrollX = mScrollX; 7620 int scrollY = mScrollY; 7621 7622 // IMPORTANT: The coordinates below are based on the coordinates computed 7623 // for each compound drawable in onDraw(). Make sure to update each section 7624 // accordingly. 7625 final TextView.Drawables drawables = mDrawables; 7626 if (drawables != null) { 7627 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7628 final int compoundPaddingTop = getCompoundPaddingTop(); 7629 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7630 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7631 7632 scrollX += mPaddingLeft; 7633 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7634 handled = true; 7635 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7636 final int compoundPaddingTop = getCompoundPaddingTop(); 7637 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7638 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7639 7640 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7641 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7642 handled = true; 7643 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7644 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7645 final int compoundPaddingRight = getCompoundPaddingRight(); 7646 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7647 7648 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7649 scrollY += mPaddingTop; 7650 handled = true; 7651 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7652 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7653 final int compoundPaddingRight = getCompoundPaddingRight(); 7654 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7655 7656 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7657 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7658 handled = true; 7659 } 7660 } 7661 7662 if (handled) { 7663 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7664 dirty.right + scrollX, dirty.bottom + scrollY); 7665 } 7666 } 7667 7668 if (!handled) { 7669 super.invalidateDrawable(drawable); 7670 } 7671 } 7672 7673 @Override hasOverlappingRendering()7674 public boolean hasOverlappingRendering() { 7675 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7676 return ((getBackground() != null && getBackground().getCurrent() != null) 7677 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7678 || mShadowColor != 0); 7679 } 7680 7681 /** 7682 * 7683 * Returns the state of the {@code textIsSelectable} flag (See 7684 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7685 * to allow users to select and copy text in a non-editable TextView, the content of an 7686 * {@link EditText} can always be selected, independently of the value of this flag. 7687 * <p> 7688 * 7689 * @return True if the text displayed in this TextView can be selected by the user. 7690 * 7691 * @attr ref android.R.styleable#TextView_textIsSelectable 7692 */ 7693 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7694 public boolean isTextSelectable() { 7695 return mEditor == null ? false : mEditor.mTextIsSelectable; 7696 } 7697 7698 /** 7699 * Sets whether the content of this view is selectable by the user. The default is 7700 * {@code false}, meaning that the content is not selectable. 7701 * <p> 7702 * When you use a TextView to display a useful piece of information to the user (such as a 7703 * contact's address), make it selectable, so that the user can select and copy its 7704 * content. You can also use set the XML attribute 7705 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7706 * <p> 7707 * When you call this method to set the value of {@code textIsSelectable}, it sets 7708 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7709 * and {@code longClickable} to the same value. These flags correspond to the attributes 7710 * {@link android.R.styleable#View_focusable android:focusable}, 7711 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7712 * {@link android.R.styleable#View_clickable android:clickable}, and 7713 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7714 * flags to a state you had set previously, call one or more of the following methods: 7715 * {@link #setFocusable(boolean) setFocusable()}, 7716 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7717 * {@link #setClickable(boolean) setClickable()} or 7718 * {@link #setLongClickable(boolean) setLongClickable()}. 7719 * 7720 * @param selectable Whether the content of this TextView should be selectable. 7721 */ setTextIsSelectable(boolean selectable)7722 public void setTextIsSelectable(boolean selectable) { 7723 if (!selectable && mEditor == null) return; // false is default value with no edit data 7724 7725 createEditorIfNeeded(); 7726 if (mEditor.mTextIsSelectable == selectable) return; 7727 7728 mEditor.mTextIsSelectable = selectable; 7729 setFocusableInTouchMode(selectable); 7730 setFocusable(FOCUSABLE_AUTO); 7731 setClickable(selectable); 7732 setLongClickable(selectable); 7733 7734 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7735 7736 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7737 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7738 7739 // Called by setText above, but safer in case of future code changes 7740 mEditor.prepareCursorControllers(); 7741 } 7742 7743 @Override onCreateDrawableState(int extraSpace)7744 protected int[] onCreateDrawableState(int extraSpace) { 7745 final int[] drawableState; 7746 7747 if (mSingleLine) { 7748 drawableState = super.onCreateDrawableState(extraSpace); 7749 } else { 7750 drawableState = super.onCreateDrawableState(extraSpace + 1); 7751 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7752 } 7753 7754 if (isTextSelectable()) { 7755 // Disable pressed state, which was introduced when TextView was made clickable. 7756 // Prevents text color change. 7757 // setClickable(false) would have a similar effect, but it also disables focus changes 7758 // and long press actions, which are both needed by text selection. 7759 final int length = drawableState.length; 7760 for (int i = 0; i < length; i++) { 7761 if (drawableState[i] == R.attr.state_pressed) { 7762 final int[] nonPressedState = new int[length - 1]; 7763 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7764 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7765 return nonPressedState; 7766 } 7767 } 7768 } 7769 7770 return drawableState; 7771 } 7772 7773 @UnsupportedAppUsage getUpdatedHighlightPath()7774 private Path getUpdatedHighlightPath() { 7775 Path highlight = null; 7776 Paint highlightPaint = mHighlightPaint; 7777 7778 final int selStart = getSelectionStart(); 7779 final int selEnd = getSelectionEnd(); 7780 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7781 if (selStart == selEnd) { 7782 if (mEditor != null && mEditor.shouldRenderCursor()) { 7783 if (mHighlightPathBogus) { 7784 if (mHighlightPath == null) mHighlightPath = new Path(); 7785 mHighlightPath.reset(); 7786 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7787 mEditor.updateCursorPosition(); 7788 mHighlightPathBogus = false; 7789 } 7790 7791 // XXX should pass to skin instead of drawing directly 7792 highlightPaint.setColor(mCurTextColor); 7793 highlightPaint.setStyle(Paint.Style.STROKE); 7794 highlight = mHighlightPath; 7795 } 7796 } else { 7797 if (mHighlightPathBogus) { 7798 if (mHighlightPath == null) mHighlightPath = new Path(); 7799 mHighlightPath.reset(); 7800 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7801 mHighlightPathBogus = false; 7802 } 7803 7804 // XXX should pass to skin instead of drawing directly 7805 highlightPaint.setColor(mHighlightColor); 7806 highlightPaint.setStyle(Paint.Style.FILL); 7807 7808 highlight = mHighlightPath; 7809 } 7810 } 7811 return highlight; 7812 } 7813 7814 /** 7815 * @hide 7816 */ getHorizontalOffsetForDrawables()7817 public int getHorizontalOffsetForDrawables() { 7818 return 0; 7819 } 7820 7821 @Override onDraw(Canvas canvas)7822 protected void onDraw(Canvas canvas) { 7823 restartMarqueeIfNeeded(); 7824 7825 // Draw the background for this view 7826 super.onDraw(canvas); 7827 7828 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7829 final int compoundPaddingTop = getCompoundPaddingTop(); 7830 final int compoundPaddingRight = getCompoundPaddingRight(); 7831 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7832 final int scrollX = mScrollX; 7833 final int scrollY = mScrollY; 7834 final int right = mRight; 7835 final int left = mLeft; 7836 final int bottom = mBottom; 7837 final int top = mTop; 7838 final boolean isLayoutRtl = isLayoutRtl(); 7839 final int offset = getHorizontalOffsetForDrawables(); 7840 final int leftOffset = isLayoutRtl ? 0 : offset; 7841 final int rightOffset = isLayoutRtl ? offset : 0; 7842 7843 final Drawables dr = mDrawables; 7844 if (dr != null) { 7845 /* 7846 * Compound, not extended, because the icon is not clipped 7847 * if the text height is smaller. 7848 */ 7849 7850 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7851 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7852 7853 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7854 // Make sure to update invalidateDrawable() when changing this code. 7855 if (dr.mShowing[Drawables.LEFT] != null) { 7856 canvas.save(); 7857 canvas.translate(scrollX + mPaddingLeft + leftOffset, 7858 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 7859 dr.mShowing[Drawables.LEFT].draw(canvas); 7860 canvas.restore(); 7861 } 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.RIGHT] != null) { 7866 canvas.save(); 7867 canvas.translate(scrollX + right - left - mPaddingRight 7868 - dr.mDrawableSizeRight - rightOffset, 7869 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 7870 dr.mShowing[Drawables.RIGHT].draw(canvas); 7871 canvas.restore(); 7872 } 7873 7874 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7875 // Make sure to update invalidateDrawable() when changing this code. 7876 if (dr.mShowing[Drawables.TOP] != null) { 7877 canvas.save(); 7878 canvas.translate(scrollX + compoundPaddingLeft 7879 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 7880 dr.mShowing[Drawables.TOP].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.BOTTOM] != null) { 7887 canvas.save(); 7888 canvas.translate(scrollX + compoundPaddingLeft 7889 + (hspace - dr.mDrawableWidthBottom) / 2, 7890 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 7891 dr.mShowing[Drawables.BOTTOM].draw(canvas); 7892 canvas.restore(); 7893 } 7894 } 7895 7896 int color = mCurTextColor; 7897 7898 if (mLayout == null) { 7899 assumeLayout(); 7900 } 7901 7902 Layout layout = mLayout; 7903 7904 if (mHint != null && mText.length() == 0) { 7905 if (mHintTextColor != null) { 7906 color = mCurHintTextColor; 7907 } 7908 7909 layout = mHintLayout; 7910 } 7911 7912 mTextPaint.setColor(color); 7913 mTextPaint.drawableState = getDrawableState(); 7914 7915 canvas.save(); 7916 /* Would be faster if we didn't have to do this. Can we chop the 7917 (displayable) text so that we don't need to do this ever? 7918 */ 7919 7920 int extendedPaddingTop = getExtendedPaddingTop(); 7921 int extendedPaddingBottom = getExtendedPaddingBottom(); 7922 7923 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7924 final int maxScrollY = mLayout.getHeight() - vspace; 7925 7926 float clipLeft = compoundPaddingLeft + scrollX; 7927 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 7928 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 7929 float clipBottom = bottom - top + scrollY 7930 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 7931 7932 if (mShadowRadius != 0) { 7933 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 7934 clipRight += Math.max(0, mShadowDx + mShadowRadius); 7935 7936 clipTop += Math.min(0, mShadowDy - mShadowRadius); 7937 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 7938 } 7939 7940 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 7941 7942 int voffsetText = 0; 7943 int voffsetCursor = 0; 7944 7945 // translate in by our padding 7946 /* shortcircuit calling getVerticaOffset() */ 7947 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7948 voffsetText = getVerticalOffset(false); 7949 voffsetCursor = getVerticalOffset(true); 7950 } 7951 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 7952 7953 final int layoutDirection = getLayoutDirection(); 7954 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 7955 if (isMarqueeFadeEnabled()) { 7956 if (!mSingleLine && getLineCount() == 1 && canMarquee() 7957 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 7958 final int width = mRight - mLeft; 7959 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 7960 final float dx = mLayout.getLineRight(0) - (width - padding); 7961 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7962 } 7963 7964 if (mMarquee != null && mMarquee.isRunning()) { 7965 final float dx = -mMarquee.getScroll(); 7966 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7967 } 7968 } 7969 7970 final int cursorOffsetVertical = voffsetCursor - voffsetText; 7971 7972 Path highlight = getUpdatedHighlightPath(); 7973 if (mEditor != null) { 7974 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 7975 } else { 7976 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7977 } 7978 7979 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 7980 final float dx = mMarquee.getGhostOffset(); 7981 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 7982 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 7983 } 7984 7985 canvas.restore(); 7986 } 7987 7988 @Override getFocusedRect(Rect r)7989 public void getFocusedRect(Rect r) { 7990 if (mLayout == null) { 7991 super.getFocusedRect(r); 7992 return; 7993 } 7994 7995 int selEnd = getSelectionEnd(); 7996 if (selEnd < 0) { 7997 super.getFocusedRect(r); 7998 return; 7999 } 8000 8001 int selStart = getSelectionStart(); 8002 if (selStart < 0 || selStart >= selEnd) { 8003 int line = mLayout.getLineForOffset(selEnd); 8004 r.top = mLayout.getLineTop(line); 8005 r.bottom = mLayout.getLineBottom(line); 8006 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8007 r.right = r.left + 4; 8008 } else { 8009 int lineStart = mLayout.getLineForOffset(selStart); 8010 int lineEnd = mLayout.getLineForOffset(selEnd); 8011 r.top = mLayout.getLineTop(lineStart); 8012 r.bottom = mLayout.getLineBottom(lineEnd); 8013 if (lineStart == lineEnd) { 8014 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8015 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8016 } else { 8017 // Selection extends across multiple lines -- make the focused 8018 // rect cover the entire width. 8019 if (mHighlightPathBogus) { 8020 if (mHighlightPath == null) mHighlightPath = new Path(); 8021 mHighlightPath.reset(); 8022 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8023 mHighlightPathBogus = false; 8024 } 8025 synchronized (TEMP_RECTF) { 8026 mHighlightPath.computeBounds(TEMP_RECTF, true); 8027 r.left = (int) TEMP_RECTF.left - 1; 8028 r.right = (int) TEMP_RECTF.right + 1; 8029 } 8030 } 8031 } 8032 8033 // Adjust for padding and gravity. 8034 int paddingLeft = getCompoundPaddingLeft(); 8035 int paddingTop = getExtendedPaddingTop(); 8036 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8037 paddingTop += getVerticalOffset(false); 8038 } 8039 r.offset(paddingLeft, paddingTop); 8040 int paddingBottom = getExtendedPaddingBottom(); 8041 r.bottom += paddingBottom; 8042 } 8043 8044 /** 8045 * Return the number of lines of text, or 0 if the internal Layout has not 8046 * been built. 8047 */ getLineCount()8048 public int getLineCount() { 8049 return mLayout != null ? mLayout.getLineCount() : 0; 8050 } 8051 8052 /** 8053 * Return the baseline for the specified line (0...getLineCount() - 1) 8054 * If bounds is not null, return the top, left, right, bottom extents 8055 * of the specified line in it. If the internal Layout has not been built, 8056 * return 0 and set bounds to (0, 0, 0, 0) 8057 * @param line which line to examine (0..getLineCount() - 1) 8058 * @param bounds Optional. If not null, it returns the extent of the line 8059 * @return the Y-coordinate of the baseline 8060 */ getLineBounds(int line, Rect bounds)8061 public int getLineBounds(int line, Rect bounds) { 8062 if (mLayout == null) { 8063 if (bounds != null) { 8064 bounds.set(0, 0, 0, 0); 8065 } 8066 return 0; 8067 } else { 8068 int baseline = mLayout.getLineBounds(line, bounds); 8069 8070 int voffset = getExtendedPaddingTop(); 8071 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8072 voffset += getVerticalOffset(true); 8073 } 8074 if (bounds != null) { 8075 bounds.offset(getCompoundPaddingLeft(), voffset); 8076 } 8077 return baseline + voffset; 8078 } 8079 } 8080 8081 @Override getBaseline()8082 public int getBaseline() { 8083 if (mLayout == null) { 8084 return super.getBaseline(); 8085 } 8086 8087 return getBaselineOffset() + mLayout.getLineBaseline(0); 8088 } 8089 getBaselineOffset()8090 int getBaselineOffset() { 8091 int voffset = 0; 8092 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8093 voffset = getVerticalOffset(true); 8094 } 8095 8096 if (isLayoutModeOptical(mParent)) { 8097 voffset -= getOpticalInsets().top; 8098 } 8099 8100 return getExtendedPaddingTop() + voffset; 8101 } 8102 8103 /** 8104 * @hide 8105 */ 8106 @Override getFadeTop(boolean offsetRequired)8107 protected int getFadeTop(boolean offsetRequired) { 8108 if (mLayout == null) return 0; 8109 8110 int voffset = 0; 8111 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8112 voffset = getVerticalOffset(true); 8113 } 8114 8115 if (offsetRequired) voffset += getTopPaddingOffset(); 8116 8117 return getExtendedPaddingTop() + voffset; 8118 } 8119 8120 /** 8121 * @hide 8122 */ 8123 @Override getFadeHeight(boolean offsetRequired)8124 protected int getFadeHeight(boolean offsetRequired) { 8125 return mLayout != null ? mLayout.getHeight() : 0; 8126 } 8127 8128 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8129 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8130 if (mSpannable != null && mLinksClickable) { 8131 final float x = event.getX(pointerIndex); 8132 final float y = event.getY(pointerIndex); 8133 final int offset = getOffsetForPosition(x, y); 8134 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8135 ClickableSpan.class); 8136 if (clickables.length > 0) { 8137 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8138 } 8139 } 8140 if (isTextSelectable() || isTextEditable()) { 8141 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8142 } 8143 return super.onResolvePointerIcon(event, pointerIndex); 8144 } 8145 8146 @Override onKeyPreIme(int keyCode, KeyEvent event)8147 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8148 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8149 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8150 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8151 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8152 return true; 8153 } 8154 return super.onKeyPreIme(keyCode, event); 8155 } 8156 8157 /** 8158 * @hide 8159 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8160 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8161 // Do nothing unless mEditor is in text action mode. 8162 if (mEditor == null || mEditor.getTextActionMode() == null) { 8163 return false; 8164 } 8165 8166 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8167 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8168 if (state != null) { 8169 state.startTracking(event, this); 8170 } 8171 return true; 8172 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8173 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8174 if (state != null) { 8175 state.handleUpEvent(event); 8176 } 8177 if (event.isTracking() && !event.isCanceled()) { 8178 stopTextActionMode(); 8179 return true; 8180 } 8181 } 8182 return false; 8183 } 8184 8185 @Override onKeyDown(int keyCode, KeyEvent event)8186 public boolean onKeyDown(int keyCode, KeyEvent event) { 8187 final int which = doKeyDown(keyCode, event, null); 8188 if (which == KEY_EVENT_NOT_HANDLED) { 8189 return super.onKeyDown(keyCode, event); 8190 } 8191 8192 return true; 8193 } 8194 8195 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8196 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8197 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8198 final int which = doKeyDown(keyCode, down, event); 8199 if (which == KEY_EVENT_NOT_HANDLED) { 8200 // Go through default dispatching. 8201 return super.onKeyMultiple(keyCode, repeatCount, event); 8202 } 8203 if (which == KEY_EVENT_HANDLED) { 8204 // Consumed the whole thing. 8205 return true; 8206 } 8207 8208 repeatCount--; 8209 8210 // We are going to dispatch the remaining events to either the input 8211 // or movement method. To do this, we will just send a repeated stream 8212 // of down and up events until we have done the complete repeatCount. 8213 // It would be nice if those interfaces had an onKeyMultiple() method, 8214 // but adding that is a more complicated change. 8215 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8216 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8217 // mEditor and mEditor.mInput are not null from doKeyDown 8218 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8219 while (--repeatCount > 0) { 8220 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8221 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8222 } 8223 hideErrorIfUnchanged(); 8224 8225 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8226 // mMovement is not null from doKeyDown 8227 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8228 while (--repeatCount > 0) { 8229 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8230 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8231 } 8232 } 8233 8234 return true; 8235 } 8236 8237 /** 8238 * Returns true if pressing ENTER in this field advances focus instead 8239 * of inserting the character. This is true mostly in single-line fields, 8240 * but also in mail addresses and subjects which will display on multiple 8241 * lines but where it doesn't make sense to insert newlines. 8242 */ shouldAdvanceFocusOnEnter()8243 private boolean shouldAdvanceFocusOnEnter() { 8244 if (getKeyListener() == null) { 8245 return false; 8246 } 8247 8248 if (mSingleLine) { 8249 return true; 8250 } 8251 8252 if (mEditor != null 8253 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8254 == EditorInfo.TYPE_CLASS_TEXT) { 8255 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8256 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8257 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8258 return true; 8259 } 8260 } 8261 8262 return false; 8263 } 8264 8265 /** 8266 * Returns true if pressing TAB in this field advances focus instead 8267 * of inserting the character. Insert tabs only in multi-line editors. 8268 */ shouldAdvanceFocusOnTab()8269 private boolean shouldAdvanceFocusOnTab() { 8270 if (getKeyListener() != null && !mSingleLine && mEditor != null 8271 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8272 == EditorInfo.TYPE_CLASS_TEXT) { 8273 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8274 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 8275 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 8276 return false; 8277 } 8278 } 8279 return true; 8280 } 8281 isDirectionalNavigationKey(int keyCode)8282 private boolean isDirectionalNavigationKey(int keyCode) { 8283 switch(keyCode) { 8284 case KeyEvent.KEYCODE_DPAD_UP: 8285 case KeyEvent.KEYCODE_DPAD_DOWN: 8286 case KeyEvent.KEYCODE_DPAD_LEFT: 8287 case KeyEvent.KEYCODE_DPAD_RIGHT: 8288 return true; 8289 } 8290 return false; 8291 } 8292 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8293 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8294 if (!isEnabled()) { 8295 return KEY_EVENT_NOT_HANDLED; 8296 } 8297 8298 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8299 // While this shouldn't be necessary because any time we're preventing default movement we 8300 // should be restricting the focus to remain within this view, thus we'll also receive 8301 // the key up event, occasionally key up events will get dropped and we don't want to 8302 // prevent the user from traversing out of this on the next key down. 8303 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8304 mPreventDefaultMovement = false; 8305 } 8306 8307 switch (keyCode) { 8308 case KeyEvent.KEYCODE_ENTER: 8309 if (event.hasNoModifiers()) { 8310 // When mInputContentType is set, we know that we are 8311 // running in a "modern" cupcake environment, so don't need 8312 // to worry about the application trying to capture 8313 // enter key events. 8314 if (mEditor != null && mEditor.mInputContentType != null) { 8315 // If there is an action listener, given them a 8316 // chance to consume the event. 8317 if (mEditor.mInputContentType.onEditorActionListener != null 8318 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8319 this, EditorInfo.IME_NULL, event)) { 8320 mEditor.mInputContentType.enterDown = true; 8321 // We are consuming the enter key for them. 8322 return KEY_EVENT_HANDLED; 8323 } 8324 } 8325 8326 // If our editor should move focus when enter is pressed, or 8327 // this is a generated event from an IME action button, then 8328 // don't let it be inserted into the text. 8329 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8330 || shouldAdvanceFocusOnEnter()) { 8331 if (hasOnClickListeners()) { 8332 return KEY_EVENT_NOT_HANDLED; 8333 } 8334 return KEY_EVENT_HANDLED; 8335 } 8336 } 8337 break; 8338 8339 case KeyEvent.KEYCODE_DPAD_CENTER: 8340 if (event.hasNoModifiers()) { 8341 if (shouldAdvanceFocusOnEnter()) { 8342 return KEY_EVENT_NOT_HANDLED; 8343 } 8344 } 8345 break; 8346 8347 case KeyEvent.KEYCODE_TAB: 8348 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8349 if (shouldAdvanceFocusOnTab()) { 8350 return KEY_EVENT_NOT_HANDLED; 8351 } 8352 } 8353 break; 8354 8355 // Has to be done on key down (and not on key up) to correctly be intercepted. 8356 case KeyEvent.KEYCODE_BACK: 8357 if (mEditor != null && mEditor.getTextActionMode() != null) { 8358 stopTextActionMode(); 8359 return KEY_EVENT_HANDLED; 8360 } 8361 break; 8362 8363 case KeyEvent.KEYCODE_CUT: 8364 if (event.hasNoModifiers() && canCut()) { 8365 if (onTextContextMenuItem(ID_CUT)) { 8366 return KEY_EVENT_HANDLED; 8367 } 8368 } 8369 break; 8370 8371 case KeyEvent.KEYCODE_COPY: 8372 if (event.hasNoModifiers() && canCopy()) { 8373 if (onTextContextMenuItem(ID_COPY)) { 8374 return KEY_EVENT_HANDLED; 8375 } 8376 } 8377 break; 8378 8379 case KeyEvent.KEYCODE_PASTE: 8380 if (event.hasNoModifiers() && canPaste()) { 8381 if (onTextContextMenuItem(ID_PASTE)) { 8382 return KEY_EVENT_HANDLED; 8383 } 8384 } 8385 break; 8386 8387 case KeyEvent.KEYCODE_FORWARD_DEL: 8388 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8389 if (onTextContextMenuItem(ID_CUT)) { 8390 return KEY_EVENT_HANDLED; 8391 } 8392 } 8393 break; 8394 8395 case KeyEvent.KEYCODE_INSERT: 8396 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8397 if (onTextContextMenuItem(ID_COPY)) { 8398 return KEY_EVENT_HANDLED; 8399 } 8400 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8401 if (onTextContextMenuItem(ID_PASTE)) { 8402 return KEY_EVENT_HANDLED; 8403 } 8404 } 8405 break; 8406 } 8407 8408 if (mEditor != null && mEditor.mKeyListener != null) { 8409 boolean doDown = true; 8410 if (otherEvent != null) { 8411 try { 8412 beginBatchEdit(); 8413 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8414 otherEvent); 8415 hideErrorIfUnchanged(); 8416 doDown = false; 8417 if (handled) { 8418 return KEY_EVENT_HANDLED; 8419 } 8420 } catch (AbstractMethodError e) { 8421 // onKeyOther was added after 1.0, so if it isn't 8422 // implemented we need to try to dispatch as a regular down. 8423 } finally { 8424 endBatchEdit(); 8425 } 8426 } 8427 8428 if (doDown) { 8429 beginBatchEdit(); 8430 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8431 keyCode, event); 8432 endBatchEdit(); 8433 hideErrorIfUnchanged(); 8434 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8435 } 8436 } 8437 8438 // bug 650865: sometimes we get a key event before a layout. 8439 // don't try to move around if we don't know the layout. 8440 8441 if (mMovement != null && mLayout != null) { 8442 boolean doDown = true; 8443 if (otherEvent != null) { 8444 try { 8445 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8446 doDown = false; 8447 if (handled) { 8448 return KEY_EVENT_HANDLED; 8449 } 8450 } catch (AbstractMethodError e) { 8451 // onKeyOther was added after 1.0, so if it isn't 8452 // implemented we need to try to dispatch as a regular down. 8453 } 8454 } 8455 if (doDown) { 8456 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8457 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8458 mPreventDefaultMovement = true; 8459 } 8460 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8461 } 8462 } 8463 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8464 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8465 // to move focus with arrows. 8466 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8467 && isDirectionalNavigationKey(keyCode)) { 8468 return KEY_EVENT_HANDLED; 8469 } 8470 } 8471 8472 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8473 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8474 } 8475 8476 /** 8477 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8478 * can be recorded. 8479 * @hide 8480 */ resetErrorChangedFlag()8481 public void resetErrorChangedFlag() { 8482 /* 8483 * Keep track of what the error was before doing the input 8484 * so that if an input filter changed the error, we leave 8485 * that error showing. Otherwise, we take down whatever 8486 * error was showing when the user types something. 8487 */ 8488 if (mEditor != null) mEditor.mErrorWasChanged = false; 8489 } 8490 8491 /** 8492 * @hide 8493 */ hideErrorIfUnchanged()8494 public void hideErrorIfUnchanged() { 8495 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8496 setError(null, null); 8497 } 8498 } 8499 8500 @Override onKeyUp(int keyCode, KeyEvent event)8501 public boolean onKeyUp(int keyCode, KeyEvent event) { 8502 if (!isEnabled()) { 8503 return super.onKeyUp(keyCode, event); 8504 } 8505 8506 if (!KeyEvent.isModifierKey(keyCode)) { 8507 mPreventDefaultMovement = false; 8508 } 8509 8510 switch (keyCode) { 8511 case KeyEvent.KEYCODE_DPAD_CENTER: 8512 if (event.hasNoModifiers()) { 8513 /* 8514 * If there is a click listener, just call through to 8515 * super, which will invoke it. 8516 * 8517 * If there isn't a click listener, try to show the soft 8518 * input method. (It will also 8519 * call performClick(), but that won't do anything in 8520 * this case.) 8521 */ 8522 if (!hasOnClickListeners()) { 8523 if (mMovement != null && mText instanceof Editable 8524 && mLayout != null && onCheckIsTextEditor()) { 8525 InputMethodManager imm = getInputMethodManager(); 8526 viewClicked(imm); 8527 if (imm != null && getShowSoftInputOnFocus()) { 8528 imm.showSoftInput(this, 0); 8529 } 8530 } 8531 } 8532 } 8533 return super.onKeyUp(keyCode, event); 8534 8535 case KeyEvent.KEYCODE_ENTER: 8536 if (event.hasNoModifiers()) { 8537 if (mEditor != null && mEditor.mInputContentType != null 8538 && mEditor.mInputContentType.onEditorActionListener != null 8539 && mEditor.mInputContentType.enterDown) { 8540 mEditor.mInputContentType.enterDown = false; 8541 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8542 this, EditorInfo.IME_NULL, event)) { 8543 return true; 8544 } 8545 } 8546 8547 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8548 || shouldAdvanceFocusOnEnter()) { 8549 /* 8550 * If there is a click listener, just call through to 8551 * super, which will invoke it. 8552 * 8553 * If there isn't a click listener, try to advance focus, 8554 * but still call through to super, which will reset the 8555 * pressed state and longpress state. (It will also 8556 * call performClick(), but that won't do anything in 8557 * this case.) 8558 */ 8559 if (!hasOnClickListeners()) { 8560 View v = focusSearch(FOCUS_DOWN); 8561 8562 if (v != null) { 8563 if (!v.requestFocus(FOCUS_DOWN)) { 8564 throw new IllegalStateException("focus search returned a view " 8565 + "that wasn't able to take focus!"); 8566 } 8567 8568 /* 8569 * Return true because we handled the key; super 8570 * will return false because there was no click 8571 * listener. 8572 */ 8573 super.onKeyUp(keyCode, event); 8574 return true; 8575 } else if ((event.getFlags() 8576 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8577 // No target for next focus, but make sure the IME 8578 // if this came from it. 8579 InputMethodManager imm = getInputMethodManager(); 8580 if (imm != null && imm.isActive(this)) { 8581 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8582 } 8583 } 8584 } 8585 } 8586 return super.onKeyUp(keyCode, event); 8587 } 8588 break; 8589 } 8590 8591 if (mEditor != null && mEditor.mKeyListener != null) { 8592 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8593 return true; 8594 } 8595 } 8596 8597 if (mMovement != null && mLayout != null) { 8598 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8599 return true; 8600 } 8601 } 8602 8603 return super.onKeyUp(keyCode, event); 8604 } 8605 8606 @Override onCheckIsTextEditor()8607 public boolean onCheckIsTextEditor() { 8608 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8609 } 8610 8611 @Override onCreateInputConnection(EditorInfo outAttrs)8612 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8613 if (onCheckIsTextEditor() && isEnabled()) { 8614 mEditor.createInputMethodStateIfNeeded(); 8615 outAttrs.inputType = getInputType(); 8616 if (mEditor.mInputContentType != null) { 8617 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8618 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8619 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8620 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8621 outAttrs.extras = mEditor.mInputContentType.extras; 8622 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8623 } else { 8624 outAttrs.imeOptions = EditorInfo.IME_NULL; 8625 outAttrs.hintLocales = null; 8626 } 8627 if (focusSearch(FOCUS_DOWN) != null) { 8628 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8629 } 8630 if (focusSearch(FOCUS_UP) != null) { 8631 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8632 } 8633 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8634 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8635 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8636 // An action has not been set, but the enter key will move to 8637 // the next focus, so set the action to that. 8638 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8639 } else { 8640 // An action has not been set, and there is no focus to move 8641 // to, so let's just supply a "done" action. 8642 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8643 } 8644 if (!shouldAdvanceFocusOnEnter()) { 8645 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8646 } 8647 } 8648 if (isMultilineInputType(outAttrs.inputType)) { 8649 // Multi-line text editors should always show an enter key. 8650 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8651 } 8652 outAttrs.hintText = mHint; 8653 outAttrs.targetInputMethodUser = mTextOperationUser; 8654 if (mText instanceof Editable) { 8655 InputConnection ic = new EditableInputConnection(this); 8656 outAttrs.initialSelStart = getSelectionStart(); 8657 outAttrs.initialSelEnd = getSelectionEnd(); 8658 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8659 return ic; 8660 } 8661 } 8662 return null; 8663 } 8664 8665 /** 8666 * If this TextView contains editable content, extract a portion of it 8667 * based on the information in <var>request</var> in to <var>outText</var>. 8668 * @return Returns true if the text was successfully extracted, else false. 8669 */ extractText(ExtractedTextRequest request, ExtractedText outText)8670 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8671 createEditorIfNeeded(); 8672 return mEditor.extractText(request, outText); 8673 } 8674 8675 /** 8676 * This is used to remove all style-impacting spans from text before new 8677 * extracted text is being replaced into it, so that we don't have any 8678 * lingering spans applied during the replace. 8679 */ removeParcelableSpans(Spannable spannable, int start, int end)8680 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8681 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8682 int i = spans.length; 8683 while (i > 0) { 8684 i--; 8685 spannable.removeSpan(spans[i]); 8686 } 8687 } 8688 8689 /** 8690 * Apply to this text view the given extracted text, as previously 8691 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8692 */ setExtractedText(ExtractedText text)8693 public void setExtractedText(ExtractedText text) { 8694 Editable content = getEditableText(); 8695 if (text.text != null) { 8696 if (content == null) { 8697 setText(text.text, TextView.BufferType.EDITABLE); 8698 } else { 8699 int start = 0; 8700 int end = content.length(); 8701 8702 if (text.partialStartOffset >= 0) { 8703 final int N = content.length(); 8704 start = text.partialStartOffset; 8705 if (start > N) start = N; 8706 end = text.partialEndOffset; 8707 if (end > N) end = N; 8708 } 8709 8710 removeParcelableSpans(content, start, end); 8711 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8712 if (text.text instanceof Spanned) { 8713 // OK to copy spans only. 8714 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8715 Object.class, content, start); 8716 } 8717 } else { 8718 content.replace(start, end, text.text); 8719 } 8720 } 8721 } 8722 8723 // Now set the selection position... make sure it is in range, to 8724 // avoid crashes. If this is a partial update, it is possible that 8725 // the underlying text may have changed, causing us problems here. 8726 // Also we just don't want to trust clients to do the right thing. 8727 Spannable sp = (Spannable) getText(); 8728 final int N = sp.length(); 8729 int start = text.selectionStart; 8730 if (start < 0) { 8731 start = 0; 8732 } else if (start > N) { 8733 start = N; 8734 } 8735 int end = text.selectionEnd; 8736 if (end < 0) { 8737 end = 0; 8738 } else if (end > N) { 8739 end = N; 8740 } 8741 Selection.setSelection(sp, start, end); 8742 8743 // Finally, update the selection mode. 8744 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8745 MetaKeyKeyListener.startSelecting(this, sp); 8746 } else { 8747 MetaKeyKeyListener.stopSelecting(this, sp); 8748 } 8749 8750 setHintInternal(text.hint); 8751 } 8752 8753 /** 8754 * @hide 8755 */ setExtracting(ExtractedTextRequest req)8756 public void setExtracting(ExtractedTextRequest req) { 8757 if (mEditor.mInputMethodState != null) { 8758 mEditor.mInputMethodState.mExtractedTextRequest = req; 8759 } 8760 // This would stop a possible selection mode, but no such mode is started in case 8761 // extracted mode will start. Some text is selected though, and will trigger an action mode 8762 // in the extracted view. 8763 mEditor.hideCursorAndSpanControllers(); 8764 stopTextActionMode(); 8765 if (mEditor.mSelectionModifierCursorController != null) { 8766 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8767 } 8768 } 8769 8770 /** 8771 * Called by the framework in response to a text completion from 8772 * the current input method, provided by it calling 8773 * {@link InputConnection#commitCompletion 8774 * InputConnection.commitCompletion()}. The default implementation does 8775 * nothing; text views that are supporting auto-completion should override 8776 * this to do their desired behavior. 8777 * 8778 * @param text The auto complete text the user has selected. 8779 */ onCommitCompletion(CompletionInfo text)8780 public void onCommitCompletion(CompletionInfo text) { 8781 // intentionally empty 8782 } 8783 8784 /** 8785 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8786 * dictionary) from the current input method, provided by it calling 8787 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8788 * The default implementation flashes the background of the corrected word to provide 8789 * feedback to the user. 8790 * 8791 * @param info The auto correct info about the text that was corrected. 8792 */ onCommitCorrection(CorrectionInfo info)8793 public void onCommitCorrection(CorrectionInfo info) { 8794 if (mEditor != null) mEditor.onCommitCorrection(info); 8795 } 8796 beginBatchEdit()8797 public void beginBatchEdit() { 8798 if (mEditor != null) mEditor.beginBatchEdit(); 8799 } 8800 endBatchEdit()8801 public void endBatchEdit() { 8802 if (mEditor != null) mEditor.endBatchEdit(); 8803 } 8804 8805 /** 8806 * Called by the framework in response to a request to begin a batch 8807 * of edit operations through a call to link {@link #beginBatchEdit()}. 8808 */ onBeginBatchEdit()8809 public void onBeginBatchEdit() { 8810 // intentionally empty 8811 } 8812 8813 /** 8814 * Called by the framework in response to a request to end a batch 8815 * of edit operations through a call to link {@link #endBatchEdit}. 8816 */ onEndBatchEdit()8817 public void onEndBatchEdit() { 8818 // intentionally empty 8819 } 8820 8821 /** 8822 * Called by the framework in response to a private command from the 8823 * current method, provided by it calling 8824 * {@link InputConnection#performPrivateCommand 8825 * InputConnection.performPrivateCommand()}. 8826 * 8827 * @param action The action name of the command. 8828 * @param data Any additional data for the command. This may be null. 8829 * @return Return true if you handled the command, else false. 8830 */ onPrivateIMECommand(String action, Bundle data)8831 public boolean onPrivateIMECommand(String action, Bundle data) { 8832 return false; 8833 } 8834 8835 /** @hide */ 8836 @VisibleForTesting 8837 @UnsupportedAppUsage nullLayouts()8838 public void nullLayouts() { 8839 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8840 mSavedLayout = (BoringLayout) mLayout; 8841 } 8842 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8843 mSavedHintLayout = (BoringLayout) mHintLayout; 8844 } 8845 8846 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8847 8848 mBoring = mHintBoring = null; 8849 8850 // Since it depends on the value of mLayout 8851 if (mEditor != null) mEditor.prepareCursorControllers(); 8852 } 8853 8854 /** 8855 * Make a new Layout based on the already-measured size of the view, 8856 * on the assumption that it was measured correctly at some point. 8857 */ 8858 @UnsupportedAppUsage assumeLayout()8859 private void assumeLayout() { 8860 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8861 8862 if (width < 1) { 8863 width = 0; 8864 } 8865 8866 int physicalWidth = width; 8867 8868 if (mHorizontallyScrolling) { 8869 width = VERY_WIDE; 8870 } 8871 8872 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 8873 physicalWidth, false); 8874 } 8875 8876 @UnsupportedAppUsage getLayoutAlignment()8877 private Layout.Alignment getLayoutAlignment() { 8878 Layout.Alignment alignment; 8879 switch (getTextAlignment()) { 8880 case TEXT_ALIGNMENT_GRAVITY: 8881 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 8882 case Gravity.START: 8883 alignment = Layout.Alignment.ALIGN_NORMAL; 8884 break; 8885 case Gravity.END: 8886 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8887 break; 8888 case Gravity.LEFT: 8889 alignment = Layout.Alignment.ALIGN_LEFT; 8890 break; 8891 case Gravity.RIGHT: 8892 alignment = Layout.Alignment.ALIGN_RIGHT; 8893 break; 8894 case Gravity.CENTER_HORIZONTAL: 8895 alignment = Layout.Alignment.ALIGN_CENTER; 8896 break; 8897 default: 8898 alignment = Layout.Alignment.ALIGN_NORMAL; 8899 break; 8900 } 8901 break; 8902 case TEXT_ALIGNMENT_TEXT_START: 8903 alignment = Layout.Alignment.ALIGN_NORMAL; 8904 break; 8905 case TEXT_ALIGNMENT_TEXT_END: 8906 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8907 break; 8908 case TEXT_ALIGNMENT_CENTER: 8909 alignment = Layout.Alignment.ALIGN_CENTER; 8910 break; 8911 case TEXT_ALIGNMENT_VIEW_START: 8912 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8913 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8914 break; 8915 case TEXT_ALIGNMENT_VIEW_END: 8916 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8917 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8918 break; 8919 case TEXT_ALIGNMENT_INHERIT: 8920 // This should never happen as we have already resolved the text alignment 8921 // but better safe than sorry so we just fall through 8922 default: 8923 alignment = Layout.Alignment.ALIGN_NORMAL; 8924 break; 8925 } 8926 return alignment; 8927 } 8928 8929 /** 8930 * The width passed in is now the desired layout width, 8931 * not the full view width with padding. 8932 * {@hide} 8933 */ 8934 @VisibleForTesting 8935 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8936 public void makeNewLayout(int wantWidth, int hintWidth, 8937 BoringLayout.Metrics boring, 8938 BoringLayout.Metrics hintBoring, 8939 int ellipsisWidth, boolean bringIntoView) { 8940 stopMarquee(); 8941 8942 // Update "old" cached values 8943 mOldMaximum = mMaximum; 8944 mOldMaxMode = mMaxMode; 8945 8946 mHighlightPathBogus = true; 8947 8948 if (wantWidth < 0) { 8949 wantWidth = 0; 8950 } 8951 if (hintWidth < 0) { 8952 hintWidth = 0; 8953 } 8954 8955 Layout.Alignment alignment = getLayoutAlignment(); 8956 final boolean testDirChange = mSingleLine && mLayout != null 8957 && (alignment == Layout.Alignment.ALIGN_NORMAL 8958 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 8959 int oldDir = 0; 8960 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 8961 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 8962 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 8963 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 8964 TruncateAt effectiveEllipsize = mEllipsize; 8965 if (mEllipsize == TruncateAt.MARQUEE 8966 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8967 effectiveEllipsize = TruncateAt.END_SMALL; 8968 } 8969 8970 if (mTextDir == null) { 8971 mTextDir = getTextDirectionHeuristic(); 8972 } 8973 8974 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 8975 effectiveEllipsize, effectiveEllipsize == mEllipsize); 8976 if (switchEllipsize) { 8977 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 8978 ? TruncateAt.END : TruncateAt.MARQUEE; 8979 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 8980 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 8981 } 8982 8983 shouldEllipsize = mEllipsize != null; 8984 mHintLayout = null; 8985 8986 if (mHint != null) { 8987 if (shouldEllipsize) hintWidth = wantWidth; 8988 8989 if (hintBoring == UNKNOWN_BORING) { 8990 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 8991 mHintBoring); 8992 if (hintBoring != null) { 8993 mHintBoring = hintBoring; 8994 } 8995 } 8996 8997 if (hintBoring != null) { 8998 if (hintBoring.width <= hintWidth 8999 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9000 if (mSavedHintLayout != null) { 9001 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9002 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9003 hintBoring, mIncludePad); 9004 } else { 9005 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9006 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9007 hintBoring, mIncludePad); 9008 } 9009 9010 mSavedHintLayout = (BoringLayout) mHintLayout; 9011 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9012 if (mSavedHintLayout != null) { 9013 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9014 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9015 hintBoring, mIncludePad, mEllipsize, 9016 ellipsisWidth); 9017 } else { 9018 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9019 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9020 hintBoring, mIncludePad, mEllipsize, 9021 ellipsisWidth); 9022 } 9023 } 9024 } 9025 // TODO: code duplication with makeSingleLayout() 9026 if (mHintLayout == null) { 9027 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9028 mHint.length(), mTextPaint, hintWidth) 9029 .setAlignment(alignment) 9030 .setTextDirection(mTextDir) 9031 .setLineSpacing(mSpacingAdd, mSpacingMult) 9032 .setIncludePad(mIncludePad) 9033 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9034 .setBreakStrategy(mBreakStrategy) 9035 .setHyphenationFrequency(mHyphenationFrequency) 9036 .setJustificationMode(mJustificationMode) 9037 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9038 if (shouldEllipsize) { 9039 builder.setEllipsize(mEllipsize) 9040 .setEllipsizedWidth(ellipsisWidth); 9041 } 9042 mHintLayout = builder.build(); 9043 } 9044 } 9045 9046 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9047 registerForPreDraw(); 9048 } 9049 9050 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9051 if (!compressText(ellipsisWidth)) { 9052 final int height = mLayoutParams.height; 9053 // If the size of the view does not depend on the size of the text, try to 9054 // start the marquee immediately 9055 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9056 startMarquee(); 9057 } else { 9058 // Defer the start of the marquee until we know our width (see setFrame()) 9059 mRestartMarquee = true; 9060 } 9061 } 9062 } 9063 9064 // CursorControllers need a non-null mLayout 9065 if (mEditor != null) mEditor.prepareCursorControllers(); 9066 } 9067 9068 /** 9069 * Returns true if DynamicLayout is required 9070 * 9071 * @hide 9072 */ 9073 @VisibleForTesting useDynamicLayout()9074 public boolean useDynamicLayout() { 9075 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9076 } 9077 9078 /** 9079 * @hide 9080 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9081 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9082 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9083 boolean useSaved) { 9084 Layout result = null; 9085 if (useDynamicLayout()) { 9086 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9087 wantWidth) 9088 .setDisplayText(mTransformed) 9089 .setAlignment(alignment) 9090 .setTextDirection(mTextDir) 9091 .setLineSpacing(mSpacingAdd, mSpacingMult) 9092 .setIncludePad(mIncludePad) 9093 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9094 .setBreakStrategy(mBreakStrategy) 9095 .setHyphenationFrequency(mHyphenationFrequency) 9096 .setJustificationMode(mJustificationMode) 9097 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9098 .setEllipsizedWidth(ellipsisWidth); 9099 result = builder.build(); 9100 } else { 9101 if (boring == UNKNOWN_BORING) { 9102 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9103 if (boring != null) { 9104 mBoring = boring; 9105 } 9106 } 9107 9108 if (boring != null) { 9109 if (boring.width <= wantWidth 9110 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9111 if (useSaved && mSavedLayout != null) { 9112 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9113 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9114 boring, mIncludePad); 9115 } else { 9116 result = BoringLayout.make(mTransformed, mTextPaint, 9117 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9118 boring, mIncludePad); 9119 } 9120 9121 if (useSaved) { 9122 mSavedLayout = (BoringLayout) result; 9123 } 9124 } else if (shouldEllipsize && boring.width <= wantWidth) { 9125 if (useSaved && mSavedLayout != null) { 9126 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9127 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9128 boring, mIncludePad, effectiveEllipsize, 9129 ellipsisWidth); 9130 } else { 9131 result = BoringLayout.make(mTransformed, mTextPaint, 9132 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9133 boring, mIncludePad, effectiveEllipsize, 9134 ellipsisWidth); 9135 } 9136 } 9137 } 9138 } 9139 if (result == null) { 9140 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9141 0, mTransformed.length(), mTextPaint, wantWidth) 9142 .setAlignment(alignment) 9143 .setTextDirection(mTextDir) 9144 .setLineSpacing(mSpacingAdd, mSpacingMult) 9145 .setIncludePad(mIncludePad) 9146 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9147 .setBreakStrategy(mBreakStrategy) 9148 .setHyphenationFrequency(mHyphenationFrequency) 9149 .setJustificationMode(mJustificationMode) 9150 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9151 if (shouldEllipsize) { 9152 builder.setEllipsize(effectiveEllipsize) 9153 .setEllipsizedWidth(ellipsisWidth); 9154 } 9155 result = builder.build(); 9156 } 9157 return result; 9158 } 9159 9160 @UnsupportedAppUsage compressText(float width)9161 private boolean compressText(float width) { 9162 if (isHardwareAccelerated()) return false; 9163 9164 // Only compress the text if it hasn't been compressed by the previous pass 9165 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9166 && mTextPaint.getTextScaleX() == 1.0f) { 9167 final float textWidth = mLayout.getLineWidth(0); 9168 final float overflow = (textWidth + 1.0f - width) / width; 9169 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9170 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9171 post(new Runnable() { 9172 public void run() { 9173 requestLayout(); 9174 } 9175 }); 9176 return true; 9177 } 9178 } 9179 9180 return false; 9181 } 9182 desired(Layout layout)9183 private static int desired(Layout layout) { 9184 int n = layout.getLineCount(); 9185 CharSequence text = layout.getText(); 9186 float max = 0; 9187 9188 // if any line was wrapped, we can't use it. 9189 // but it's ok for the last line not to have a newline 9190 9191 for (int i = 0; i < n - 1; i++) { 9192 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9193 return -1; 9194 } 9195 } 9196 9197 for (int i = 0; i < n; i++) { 9198 max = Math.max(max, layout.getLineWidth(i)); 9199 } 9200 9201 return (int) Math.ceil(max); 9202 } 9203 9204 /** 9205 * Set whether the TextView includes extra top and bottom padding to make 9206 * room for accents that go above the normal ascent and descent. 9207 * The default is true. 9208 * 9209 * @see #getIncludeFontPadding() 9210 * 9211 * @attr ref android.R.styleable#TextView_includeFontPadding 9212 */ setIncludeFontPadding(boolean includepad)9213 public void setIncludeFontPadding(boolean includepad) { 9214 if (mIncludePad != includepad) { 9215 mIncludePad = includepad; 9216 9217 if (mLayout != null) { 9218 nullLayouts(); 9219 requestLayout(); 9220 invalidate(); 9221 } 9222 } 9223 } 9224 9225 /** 9226 * Gets whether the TextView includes extra top and bottom padding to make 9227 * room for accents that go above the normal ascent and descent. 9228 * 9229 * @see #setIncludeFontPadding(boolean) 9230 * 9231 * @attr ref android.R.styleable#TextView_includeFontPadding 9232 */ 9233 @InspectableProperty getIncludeFontPadding()9234 public boolean getIncludeFontPadding() { 9235 return mIncludePad; 9236 } 9237 9238 /** @hide */ 9239 @VisibleForTesting 9240 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9241 9242 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9243 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9244 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9245 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9246 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9247 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9248 9249 int width; 9250 int height; 9251 9252 BoringLayout.Metrics boring = UNKNOWN_BORING; 9253 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9254 9255 if (mTextDir == null) { 9256 mTextDir = getTextDirectionHeuristic(); 9257 } 9258 9259 int des = -1; 9260 boolean fromexisting = false; 9261 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9262 ? (float) widthSize : Float.MAX_VALUE; 9263 9264 if (widthMode == MeasureSpec.EXACTLY) { 9265 // Parent has told us how big to be. So be it. 9266 width = widthSize; 9267 } else { 9268 if (mLayout != null && mEllipsize == null) { 9269 des = desired(mLayout); 9270 } 9271 9272 if (des < 0) { 9273 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9274 if (boring != null) { 9275 mBoring = boring; 9276 } 9277 } else { 9278 fromexisting = true; 9279 } 9280 9281 if (boring == null || boring == UNKNOWN_BORING) { 9282 if (des < 0) { 9283 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9284 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9285 } 9286 width = des; 9287 } else { 9288 width = boring.width; 9289 } 9290 9291 final Drawables dr = mDrawables; 9292 if (dr != null) { 9293 width = Math.max(width, dr.mDrawableWidthTop); 9294 width = Math.max(width, dr.mDrawableWidthBottom); 9295 } 9296 9297 if (mHint != null) { 9298 int hintDes = -1; 9299 int hintWidth; 9300 9301 if (mHintLayout != null && mEllipsize == null) { 9302 hintDes = desired(mHintLayout); 9303 } 9304 9305 if (hintDes < 0) { 9306 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9307 if (hintBoring != null) { 9308 mHintBoring = hintBoring; 9309 } 9310 } 9311 9312 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9313 if (hintDes < 0) { 9314 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9315 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9316 } 9317 hintWidth = hintDes; 9318 } else { 9319 hintWidth = hintBoring.width; 9320 } 9321 9322 if (hintWidth > width) { 9323 width = hintWidth; 9324 } 9325 } 9326 9327 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9328 9329 if (mMaxWidthMode == EMS) { 9330 width = Math.min(width, mMaxWidth * getLineHeight()); 9331 } else { 9332 width = Math.min(width, mMaxWidth); 9333 } 9334 9335 if (mMinWidthMode == EMS) { 9336 width = Math.max(width, mMinWidth * getLineHeight()); 9337 } else { 9338 width = Math.max(width, mMinWidth); 9339 } 9340 9341 // Check against our minimum width 9342 width = Math.max(width, getSuggestedMinimumWidth()); 9343 9344 if (widthMode == MeasureSpec.AT_MOST) { 9345 width = Math.min(widthSize, width); 9346 } 9347 } 9348 9349 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9350 int unpaddedWidth = want; 9351 9352 if (mHorizontallyScrolling) want = VERY_WIDE; 9353 9354 int hintWant = want; 9355 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9356 9357 if (mLayout == null) { 9358 makeNewLayout(want, hintWant, boring, hintBoring, 9359 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9360 } else { 9361 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9362 || (mLayout.getEllipsizedWidth() 9363 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9364 9365 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9366 && (want > mLayout.getWidth()) 9367 && (mLayout instanceof BoringLayout 9368 || (fromexisting && des >= 0 && des <= want)); 9369 9370 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9371 9372 if (layoutChanged || maximumChanged) { 9373 if (!maximumChanged && widthChanged) { 9374 mLayout.increaseWidthTo(want); 9375 } else { 9376 makeNewLayout(want, hintWant, boring, hintBoring, 9377 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9378 } 9379 } else { 9380 // Nothing has changed 9381 } 9382 } 9383 9384 if (heightMode == MeasureSpec.EXACTLY) { 9385 // Parent has told us how big to be. So be it. 9386 height = heightSize; 9387 mDesiredHeightAtMeasure = -1; 9388 } else { 9389 int desired = getDesiredHeight(); 9390 9391 height = desired; 9392 mDesiredHeightAtMeasure = desired; 9393 9394 if (heightMode == MeasureSpec.AT_MOST) { 9395 height = Math.min(desired, heightSize); 9396 } 9397 } 9398 9399 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9400 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9401 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9402 } 9403 9404 /* 9405 * We didn't let makeNewLayout() register to bring the cursor into view, 9406 * so do it here if there is any possibility that it is needed. 9407 */ 9408 if (mMovement != null 9409 || mLayout.getWidth() > unpaddedWidth 9410 || mLayout.getHeight() > unpaddedHeight) { 9411 registerForPreDraw(); 9412 } else { 9413 scrollTo(0, 0); 9414 } 9415 9416 setMeasuredDimension(width, height); 9417 } 9418 9419 /** 9420 * Automatically computes and sets the text size. 9421 */ autoSizeText()9422 private void autoSizeText() { 9423 if (!isAutoSizeEnabled()) { 9424 return; 9425 } 9426 9427 if (mNeedsAutoSizeText) { 9428 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9429 return; 9430 } 9431 9432 final int availableWidth = mHorizontallyScrolling 9433 ? VERY_WIDE 9434 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9435 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9436 - getExtendedPaddingTop(); 9437 9438 if (availableWidth <= 0 || availableHeight <= 0) { 9439 return; 9440 } 9441 9442 synchronized (TEMP_RECTF) { 9443 TEMP_RECTF.setEmpty(); 9444 TEMP_RECTF.right = availableWidth; 9445 TEMP_RECTF.bottom = availableHeight; 9446 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9447 9448 if (optimalTextSize != getTextSize()) { 9449 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9450 false /* shouldRequestLayout */); 9451 9452 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9453 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9454 false /* bringIntoView */); 9455 } 9456 } 9457 } 9458 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9459 // after the next layout pass should set this to false. 9460 mNeedsAutoSizeText = true; 9461 } 9462 9463 /** 9464 * Performs a binary search to find the largest text size that will still fit within the size 9465 * available to this view. 9466 */ findLargestTextSizeWhichFits(RectF availableSpace)9467 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9468 final int sizesCount = mAutoSizeTextSizesInPx.length; 9469 if (sizesCount == 0) { 9470 throw new IllegalStateException("No available text sizes to choose from."); 9471 } 9472 9473 int bestSizeIndex = 0; 9474 int lowIndex = bestSizeIndex + 1; 9475 int highIndex = sizesCount - 1; 9476 int sizeToTryIndex; 9477 while (lowIndex <= highIndex) { 9478 sizeToTryIndex = (lowIndex + highIndex) / 2; 9479 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9480 bestSizeIndex = lowIndex; 9481 lowIndex = sizeToTryIndex + 1; 9482 } else { 9483 highIndex = sizeToTryIndex - 1; 9484 bestSizeIndex = highIndex; 9485 } 9486 } 9487 9488 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9489 } 9490 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9491 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9492 final CharSequence text = mTransformed != null 9493 ? mTransformed 9494 : getText(); 9495 final int maxLines = getMaxLines(); 9496 if (mTempTextPaint == null) { 9497 mTempTextPaint = new TextPaint(); 9498 } else { 9499 mTempTextPaint.reset(); 9500 } 9501 mTempTextPaint.set(getPaint()); 9502 mTempTextPaint.setTextSize(suggestedSizeInPx); 9503 9504 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9505 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9506 9507 layoutBuilder.setAlignment(getLayoutAlignment()) 9508 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9509 .setIncludePad(getIncludeFontPadding()) 9510 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9511 .setBreakStrategy(getBreakStrategy()) 9512 .setHyphenationFrequency(getHyphenationFrequency()) 9513 .setJustificationMode(getJustificationMode()) 9514 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9515 .setTextDirection(getTextDirectionHeuristic()); 9516 9517 final StaticLayout layout = layoutBuilder.build(); 9518 9519 // Lines overflow. 9520 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9521 return false; 9522 } 9523 9524 // Height overflow. 9525 if (layout.getHeight() > availableSpace.bottom) { 9526 return false; 9527 } 9528 9529 return true; 9530 } 9531 getDesiredHeight()9532 private int getDesiredHeight() { 9533 return Math.max( 9534 getDesiredHeight(mLayout, true), 9535 getDesiredHeight(mHintLayout, mEllipsize != null)); 9536 } 9537 getDesiredHeight(Layout layout, boolean cap)9538 private int getDesiredHeight(Layout layout, boolean cap) { 9539 if (layout == null) { 9540 return 0; 9541 } 9542 9543 /* 9544 * Don't cap the hint to a certain number of lines. 9545 * (Do cap it, though, if we have a maximum pixel height.) 9546 */ 9547 int desired = layout.getHeight(cap); 9548 9549 final Drawables dr = mDrawables; 9550 if (dr != null) { 9551 desired = Math.max(desired, dr.mDrawableHeightLeft); 9552 desired = Math.max(desired, dr.mDrawableHeightRight); 9553 } 9554 9555 int linecount = layout.getLineCount(); 9556 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9557 desired += padding; 9558 9559 if (mMaxMode != LINES) { 9560 desired = Math.min(desired, mMaximum); 9561 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9562 || layout instanceof BoringLayout)) { 9563 desired = layout.getLineTop(mMaximum); 9564 9565 if (dr != null) { 9566 desired = Math.max(desired, dr.mDrawableHeightLeft); 9567 desired = Math.max(desired, dr.mDrawableHeightRight); 9568 } 9569 9570 desired += padding; 9571 linecount = mMaximum; 9572 } 9573 9574 if (mMinMode == LINES) { 9575 if (linecount < mMinimum) { 9576 desired += getLineHeight() * (mMinimum - linecount); 9577 } 9578 } else { 9579 desired = Math.max(desired, mMinimum); 9580 } 9581 9582 // Check against our minimum height 9583 desired = Math.max(desired, getSuggestedMinimumHeight()); 9584 9585 return desired; 9586 } 9587 9588 /** 9589 * Check whether a change to the existing text layout requires a 9590 * new view layout. 9591 */ checkForResize()9592 private void checkForResize() { 9593 boolean sizeChanged = false; 9594 9595 if (mLayout != null) { 9596 // Check if our width changed 9597 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9598 sizeChanged = true; 9599 invalidate(); 9600 } 9601 9602 // Check if our height changed 9603 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9604 int desiredHeight = getDesiredHeight(); 9605 9606 if (desiredHeight != this.getHeight()) { 9607 sizeChanged = true; 9608 } 9609 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9610 if (mDesiredHeightAtMeasure >= 0) { 9611 int desiredHeight = getDesiredHeight(); 9612 9613 if (desiredHeight != mDesiredHeightAtMeasure) { 9614 sizeChanged = true; 9615 } 9616 } 9617 } 9618 } 9619 9620 if (sizeChanged) { 9621 requestLayout(); 9622 // caller will have already invalidated 9623 } 9624 } 9625 9626 /** 9627 * Check whether entirely new text requires a new view layout 9628 * or merely a new text layout. 9629 */ 9630 @UnsupportedAppUsage checkForRelayout()9631 private void checkForRelayout() { 9632 // If we have a fixed width, we can just swap in a new text layout 9633 // if the text height stays the same or if the view height is fixed. 9634 9635 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9636 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9637 && (mHint == null || mHintLayout != null) 9638 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9639 // Static width, so try making a new text layout. 9640 9641 int oldht = mLayout.getHeight(); 9642 int want = mLayout.getWidth(); 9643 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9644 9645 /* 9646 * No need to bring the text into view, since the size is not 9647 * changing (unless we do the requestLayout(), in which case it 9648 * will happen at measure). 9649 */ 9650 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9651 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9652 false); 9653 9654 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9655 // In a fixed-height view, so use our new text layout. 9656 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9657 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9658 autoSizeText(); 9659 invalidate(); 9660 return; 9661 } 9662 9663 // Dynamic height, but height has stayed the same, 9664 // so use our new text layout. 9665 if (mLayout.getHeight() == oldht 9666 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9667 autoSizeText(); 9668 invalidate(); 9669 return; 9670 } 9671 } 9672 9673 // We lose: the height has changed and we have a dynamic height. 9674 // Request a new view layout using our new text layout. 9675 requestLayout(); 9676 invalidate(); 9677 } else { 9678 // Dynamic width, so we have no choice but to request a new 9679 // view layout with a new text layout. 9680 nullLayouts(); 9681 requestLayout(); 9682 invalidate(); 9683 } 9684 } 9685 9686 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9687 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9688 super.onLayout(changed, left, top, right, bottom); 9689 if (mDeferScroll >= 0) { 9690 int curs = mDeferScroll; 9691 mDeferScroll = -1; 9692 bringPointIntoView(Math.min(curs, mText.length())); 9693 } 9694 // Call auto-size after the width and height have been calculated. 9695 autoSizeText(); 9696 } 9697 isShowingHint()9698 private boolean isShowingHint() { 9699 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9700 } 9701 9702 /** 9703 * Returns true if anything changed. 9704 */ 9705 @UnsupportedAppUsage bringTextIntoView()9706 private boolean bringTextIntoView() { 9707 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9708 int line = 0; 9709 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9710 line = layout.getLineCount() - 1; 9711 } 9712 9713 Layout.Alignment a = layout.getParagraphAlignment(line); 9714 int dir = layout.getParagraphDirection(line); 9715 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9716 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9717 int ht = layout.getHeight(); 9718 9719 int scrollx, scrolly; 9720 9721 // Convert to left, center, or right alignment. 9722 if (a == Layout.Alignment.ALIGN_NORMAL) { 9723 a = dir == Layout.DIR_LEFT_TO_RIGHT 9724 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9725 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9726 a = dir == Layout.DIR_LEFT_TO_RIGHT 9727 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9728 } 9729 9730 if (a == Layout.Alignment.ALIGN_CENTER) { 9731 /* 9732 * Keep centered if possible, or, if it is too wide to fit, 9733 * keep leading edge in view. 9734 */ 9735 9736 int left = (int) Math.floor(layout.getLineLeft(line)); 9737 int right = (int) Math.ceil(layout.getLineRight(line)); 9738 9739 if (right - left < hspace) { 9740 scrollx = (right + left) / 2 - hspace / 2; 9741 } else { 9742 if (dir < 0) { 9743 scrollx = right - hspace; 9744 } else { 9745 scrollx = left; 9746 } 9747 } 9748 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9749 int right = (int) Math.ceil(layout.getLineRight(line)); 9750 scrollx = right - hspace; 9751 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9752 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9753 } 9754 9755 if (ht < vspace) { 9756 scrolly = 0; 9757 } else { 9758 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9759 scrolly = ht - vspace; 9760 } else { 9761 scrolly = 0; 9762 } 9763 } 9764 9765 if (scrollx != mScrollX || scrolly != mScrollY) { 9766 scrollTo(scrollx, scrolly); 9767 return true; 9768 } else { 9769 return false; 9770 } 9771 } 9772 9773 /** 9774 * Move the point, specified by the offset, into the view if it is needed. 9775 * This has to be called after layout. Returns true if anything changed. 9776 */ bringPointIntoView(int offset)9777 public boolean bringPointIntoView(int offset) { 9778 if (isLayoutRequested()) { 9779 mDeferScroll = offset; 9780 return false; 9781 } 9782 boolean changed = false; 9783 9784 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9785 9786 if (layout == null) return changed; 9787 9788 int line = layout.getLineForOffset(offset); 9789 9790 int grav; 9791 9792 switch (layout.getParagraphAlignment(line)) { 9793 case ALIGN_LEFT: 9794 grav = 1; 9795 break; 9796 case ALIGN_RIGHT: 9797 grav = -1; 9798 break; 9799 case ALIGN_NORMAL: 9800 grav = layout.getParagraphDirection(line); 9801 break; 9802 case ALIGN_OPPOSITE: 9803 grav = -layout.getParagraphDirection(line); 9804 break; 9805 case ALIGN_CENTER: 9806 default: 9807 grav = 0; 9808 break; 9809 } 9810 9811 // We only want to clamp the cursor to fit within the layout width 9812 // in left-to-right modes, because in a right to left alignment, 9813 // we want to scroll to keep the line-right on the screen, as other 9814 // lines are likely to have text flush with the right margin, which 9815 // we want to keep visible. 9816 // A better long-term solution would probably be to measure both 9817 // the full line and a blank-trimmed version, and, for example, use 9818 // the latter measurement for centering and right alignment, but for 9819 // the time being we only implement the cursor clamping in left to 9820 // right where it is most likely to be annoying. 9821 final boolean clamped = grav > 0; 9822 // FIXME: Is it okay to truncate this, or should we round? 9823 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9824 final int top = layout.getLineTop(line); 9825 final int bottom = layout.getLineTop(line + 1); 9826 9827 int left = (int) Math.floor(layout.getLineLeft(line)); 9828 int right = (int) Math.ceil(layout.getLineRight(line)); 9829 int ht = layout.getHeight(); 9830 9831 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9832 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9833 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9834 // If cursor has been clamped, make sure we don't scroll. 9835 right = Math.max(x, left + hspace); 9836 } 9837 9838 int hslack = (bottom - top) / 2; 9839 int vslack = hslack; 9840 9841 if (vslack > vspace / 4) { 9842 vslack = vspace / 4; 9843 } 9844 if (hslack > hspace / 4) { 9845 hslack = hspace / 4; 9846 } 9847 9848 int hs = mScrollX; 9849 int vs = mScrollY; 9850 9851 if (top - vs < vslack) { 9852 vs = top - vslack; 9853 } 9854 if (bottom - vs > vspace - vslack) { 9855 vs = bottom - (vspace - vslack); 9856 } 9857 if (ht - vs < vspace) { 9858 vs = ht - vspace; 9859 } 9860 if (0 - vs > 0) { 9861 vs = 0; 9862 } 9863 9864 if (grav != 0) { 9865 if (x - hs < hslack) { 9866 hs = x - hslack; 9867 } 9868 if (x - hs > hspace - hslack) { 9869 hs = x - (hspace - hslack); 9870 } 9871 } 9872 9873 if (grav < 0) { 9874 if (left - hs > 0) { 9875 hs = left; 9876 } 9877 if (right - hs < hspace) { 9878 hs = right - hspace; 9879 } 9880 } else if (grav > 0) { 9881 if (right - hs < hspace) { 9882 hs = right - hspace; 9883 } 9884 if (left - hs > 0) { 9885 hs = left; 9886 } 9887 } else /* grav == 0 */ { 9888 if (right - left <= hspace) { 9889 /* 9890 * If the entire text fits, center it exactly. 9891 */ 9892 hs = left - (hspace - (right - left)) / 2; 9893 } else if (x > right - hslack) { 9894 /* 9895 * If we are near the right edge, keep the right edge 9896 * at the edge of the view. 9897 */ 9898 hs = right - hspace; 9899 } else if (x < left + hslack) { 9900 /* 9901 * If we are near the left edge, keep the left edge 9902 * at the edge of the view. 9903 */ 9904 hs = left; 9905 } else if (left > hs) { 9906 /* 9907 * Is there whitespace visible at the left? Fix it if so. 9908 */ 9909 hs = left; 9910 } else if (right < hs + hspace) { 9911 /* 9912 * Is there whitespace visible at the right? Fix it if so. 9913 */ 9914 hs = right - hspace; 9915 } else { 9916 /* 9917 * Otherwise, float as needed. 9918 */ 9919 if (x - hs < hslack) { 9920 hs = x - hslack; 9921 } 9922 if (x - hs > hspace - hslack) { 9923 hs = x - (hspace - hslack); 9924 } 9925 } 9926 } 9927 9928 if (hs != mScrollX || vs != mScrollY) { 9929 if (mScroller == null) { 9930 scrollTo(hs, vs); 9931 } else { 9932 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 9933 int dx = hs - mScrollX; 9934 int dy = vs - mScrollY; 9935 9936 if (duration > ANIMATED_SCROLL_GAP) { 9937 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 9938 awakenScrollBars(mScroller.getDuration()); 9939 invalidate(); 9940 } else { 9941 if (!mScroller.isFinished()) { 9942 mScroller.abortAnimation(); 9943 } 9944 9945 scrollBy(dx, dy); 9946 } 9947 9948 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 9949 } 9950 9951 changed = true; 9952 } 9953 9954 if (isFocused()) { 9955 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 9956 // requestRectangleOnScreen() is in terms of content coordinates. 9957 9958 // The offsets here are to ensure the rectangle we are using is 9959 // within our view bounds, in case the cursor is on the far left 9960 // or right. If it isn't withing the bounds, then this request 9961 // will be ignored. 9962 if (mTempRect == null) mTempRect = new Rect(); 9963 mTempRect.set(x - 2, top, x + 2, bottom); 9964 getInterestingRect(mTempRect, line); 9965 mTempRect.offset(mScrollX, mScrollY); 9966 9967 if (requestRectangleOnScreen(mTempRect)) { 9968 changed = true; 9969 } 9970 } 9971 9972 return changed; 9973 } 9974 9975 /** 9976 * Move the cursor, if needed, so that it is at an offset that is visible 9977 * to the user. This will not move the cursor if it represents more than 9978 * one character (a selection range). This will only work if the 9979 * TextView contains spannable text; otherwise it will do nothing. 9980 * 9981 * @return True if the cursor was actually moved, false otherwise. 9982 */ moveCursorToVisibleOffset()9983 public boolean moveCursorToVisibleOffset() { 9984 if (!(mText instanceof Spannable)) { 9985 return false; 9986 } 9987 int start = getSelectionStart(); 9988 int end = getSelectionEnd(); 9989 if (start != end) { 9990 return false; 9991 } 9992 9993 // First: make sure the line is visible on screen: 9994 9995 int line = mLayout.getLineForOffset(start); 9996 9997 final int top = mLayout.getLineTop(line); 9998 final int bottom = mLayout.getLineTop(line + 1); 9999 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10000 int vslack = (bottom - top) / 2; 10001 if (vslack > vspace / 4) { 10002 vslack = vspace / 4; 10003 } 10004 final int vs = mScrollY; 10005 10006 if (top < (vs + vslack)) { 10007 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10008 } else if (bottom > (vspace + vs - vslack)) { 10009 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10010 } 10011 10012 // Next: make sure the character is visible on screen: 10013 10014 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10015 final int hs = mScrollX; 10016 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10017 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10018 10019 // line might contain bidirectional text 10020 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10021 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10022 10023 int newStart = start; 10024 if (newStart < lowChar) { 10025 newStart = lowChar; 10026 } else if (newStart > highChar) { 10027 newStart = highChar; 10028 } 10029 10030 if (newStart != start) { 10031 Selection.setSelection(mSpannable, newStart); 10032 return true; 10033 } 10034 10035 return false; 10036 } 10037 10038 @Override computeScroll()10039 public void computeScroll() { 10040 if (mScroller != null) { 10041 if (mScroller.computeScrollOffset()) { 10042 mScrollX = mScroller.getCurrX(); 10043 mScrollY = mScroller.getCurrY(); 10044 invalidateParentCaches(); 10045 postInvalidate(); // So we draw again 10046 } 10047 } 10048 } 10049 getInterestingRect(Rect r, int line)10050 private void getInterestingRect(Rect r, int line) { 10051 convertFromViewportToContentCoordinates(r); 10052 10053 // Rectangle can can be expanded on first and last line to take 10054 // padding into account. 10055 // TODO Take left/right padding into account too? 10056 if (line == 0) r.top -= getExtendedPaddingTop(); 10057 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10058 } 10059 convertFromViewportToContentCoordinates(Rect r)10060 private void convertFromViewportToContentCoordinates(Rect r) { 10061 final int horizontalOffset = viewportToContentHorizontalOffset(); 10062 r.left += horizontalOffset; 10063 r.right += horizontalOffset; 10064 10065 final int verticalOffset = viewportToContentVerticalOffset(); 10066 r.top += verticalOffset; 10067 r.bottom += verticalOffset; 10068 } 10069 viewportToContentHorizontalOffset()10070 int viewportToContentHorizontalOffset() { 10071 return getCompoundPaddingLeft() - mScrollX; 10072 } 10073 10074 @UnsupportedAppUsage viewportToContentVerticalOffset()10075 int viewportToContentVerticalOffset() { 10076 int offset = getExtendedPaddingTop() - mScrollY; 10077 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10078 offset += getVerticalOffset(false); 10079 } 10080 return offset; 10081 } 10082 10083 @Override debug(int depth)10084 public void debug(int depth) { 10085 super.debug(depth); 10086 10087 String output = debugIndent(depth); 10088 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10089 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10090 + "} "; 10091 10092 if (mText != null) { 10093 10094 output += "mText=\"" + mText + "\" "; 10095 if (mLayout != null) { 10096 output += "mLayout width=" + mLayout.getWidth() 10097 + " height=" + mLayout.getHeight(); 10098 } 10099 } else { 10100 output += "mText=NULL"; 10101 } 10102 Log.d(VIEW_LOG_TAG, output); 10103 } 10104 10105 /** 10106 * Convenience for {@link Selection#getSelectionStart}. 10107 */ 10108 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10109 public int getSelectionStart() { 10110 return Selection.getSelectionStart(getText()); 10111 } 10112 10113 /** 10114 * Convenience for {@link Selection#getSelectionEnd}. 10115 */ 10116 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10117 public int getSelectionEnd() { 10118 return Selection.getSelectionEnd(getText()); 10119 } 10120 10121 /** 10122 * Return true iff there is a selection of nonzero length inside this text view. 10123 */ hasSelection()10124 public boolean hasSelection() { 10125 final int selectionStart = getSelectionStart(); 10126 final int selectionEnd = getSelectionEnd(); 10127 10128 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10129 } 10130 getSelectedText()10131 String getSelectedText() { 10132 if (!hasSelection()) { 10133 return null; 10134 } 10135 10136 final int start = getSelectionStart(); 10137 final int end = getSelectionEnd(); 10138 return String.valueOf( 10139 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10140 } 10141 10142 /** 10143 * Sets the properties of this field (lines, horizontally scrolling, 10144 * transformation method) to be for a single-line input. 10145 * 10146 * @attr ref android.R.styleable#TextView_singleLine 10147 */ setSingleLine()10148 public void setSingleLine() { 10149 setSingleLine(true); 10150 } 10151 10152 /** 10153 * Sets the properties of this field to transform input to ALL CAPS 10154 * display. This may use a "small caps" formatting if available. 10155 * This setting will be ignored if this field is editable or selectable. 10156 * 10157 * This call replaces the current transformation method. Disabling this 10158 * will not necessarily restore the previous behavior from before this 10159 * was enabled. 10160 * 10161 * @see #setTransformationMethod(TransformationMethod) 10162 * @attr ref android.R.styleable#TextView_textAllCaps 10163 */ setAllCaps(boolean allCaps)10164 public void setAllCaps(boolean allCaps) { 10165 if (allCaps) { 10166 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10167 } else { 10168 setTransformationMethod(null); 10169 } 10170 } 10171 10172 /** 10173 * 10174 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10175 * @return Whether the current transformation method is for ALL CAPS. 10176 * 10177 * @see #setAllCaps(boolean) 10178 * @see #setTransformationMethod(TransformationMethod) 10179 */ 10180 @InspectableProperty(name = "textAllCaps") isAllCaps()10181 public boolean isAllCaps() { 10182 final TransformationMethod method = getTransformationMethod(); 10183 return method != null && method instanceof AllCapsTransformationMethod; 10184 } 10185 10186 /** 10187 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10188 * transformation method) to be for a single-line input; if false, restores these to the default 10189 * conditions. 10190 * 10191 * Note that the default conditions are not necessarily those that were in effect prior this 10192 * method, and you may want to reset these properties to your custom values. 10193 * 10194 * @attr ref android.R.styleable#TextView_singleLine 10195 */ 10196 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10197 public void setSingleLine(boolean singleLine) { 10198 // Could be used, but may break backward compatibility. 10199 // if (mSingleLine == singleLine) return; 10200 setInputTypeSingleLine(singleLine); 10201 applySingleLine(singleLine, true, true); 10202 } 10203 10204 /** 10205 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10206 * @param singleLine 10207 */ setInputTypeSingleLine(boolean singleLine)10208 private void setInputTypeSingleLine(boolean singleLine) { 10209 if (mEditor != null 10210 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10211 == EditorInfo.TYPE_CLASS_TEXT) { 10212 if (singleLine) { 10213 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10214 } else { 10215 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10216 } 10217 } 10218 } 10219 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10220 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10221 boolean changeMaxLines) { 10222 mSingleLine = singleLine; 10223 if (singleLine) { 10224 setLines(1); 10225 setHorizontallyScrolling(true); 10226 if (applyTransformation) { 10227 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10228 } 10229 } else { 10230 if (changeMaxLines) { 10231 setMaxLines(Integer.MAX_VALUE); 10232 } 10233 setHorizontallyScrolling(false); 10234 if (applyTransformation) { 10235 setTransformationMethod(null); 10236 } 10237 } 10238 } 10239 10240 /** 10241 * Causes words in the text that are longer than the view's width 10242 * to be ellipsized instead of broken in the middle. You may also 10243 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10244 * to constrain the text to a single line. Use <code>null</code> 10245 * to turn off ellipsizing. 10246 * 10247 * If {@link #setMaxLines} has been used to set two or more lines, 10248 * only {@link android.text.TextUtils.TruncateAt#END} and 10249 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10250 * (other ellipsizing types will not do anything). 10251 * 10252 * @attr ref android.R.styleable#TextView_ellipsize 10253 */ setEllipsize(TextUtils.TruncateAt where)10254 public void setEllipsize(TextUtils.TruncateAt where) { 10255 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10256 if (mEllipsize != where) { 10257 mEllipsize = where; 10258 10259 if (mLayout != null) { 10260 nullLayouts(); 10261 requestLayout(); 10262 invalidate(); 10263 } 10264 } 10265 } 10266 10267 /** 10268 * Sets how many times to repeat the marquee animation. Only applied if the 10269 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10270 * 10271 * @see #getMarqueeRepeatLimit() 10272 * 10273 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10274 */ setMarqueeRepeatLimit(int marqueeLimit)10275 public void setMarqueeRepeatLimit(int marqueeLimit) { 10276 mMarqueeRepeatLimit = marqueeLimit; 10277 } 10278 10279 /** 10280 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10281 * TextView has marquee enabled. 10282 * 10283 * @return the number of times the marquee animation is repeated. -1 if the animation 10284 * repeats indefinitely 10285 * 10286 * @see #setMarqueeRepeatLimit(int) 10287 * 10288 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10289 */ 10290 @InspectableProperty getMarqueeRepeatLimit()10291 public int getMarqueeRepeatLimit() { 10292 return mMarqueeRepeatLimit; 10293 } 10294 10295 /** 10296 * Returns where, if anywhere, words that are longer than the view 10297 * is wide should be ellipsized. 10298 */ 10299 @InspectableProperty 10300 @ViewDebug.ExportedProperty getEllipsize()10301 public TextUtils.TruncateAt getEllipsize() { 10302 return mEllipsize; 10303 } 10304 10305 /** 10306 * Set the TextView so that when it takes focus, all the text is 10307 * selected. 10308 * 10309 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10310 */ 10311 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10312 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10313 createEditorIfNeeded(); 10314 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10315 10316 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10317 setText(mText, BufferType.SPANNABLE); 10318 } 10319 } 10320 10321 /** 10322 * Set whether the cursor is visible. The default is true. Note that this property only 10323 * makes sense for editable TextView. 10324 * 10325 * @see #isCursorVisible() 10326 * 10327 * @attr ref android.R.styleable#TextView_cursorVisible 10328 */ 10329 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10330 public void setCursorVisible(boolean visible) { 10331 if (visible && mEditor == null) return; // visible is the default value with no edit data 10332 createEditorIfNeeded(); 10333 if (mEditor.mCursorVisible != visible) { 10334 mEditor.mCursorVisible = visible; 10335 invalidate(); 10336 10337 mEditor.makeBlink(); 10338 10339 // InsertionPointCursorController depends on mCursorVisible 10340 mEditor.prepareCursorControllers(); 10341 } 10342 } 10343 10344 /** 10345 * @return whether or not the cursor is visible (assuming this TextView is editable) 10346 * 10347 * @see #setCursorVisible(boolean) 10348 * 10349 * @attr ref android.R.styleable#TextView_cursorVisible 10350 */ 10351 @InspectableProperty isCursorVisible()10352 public boolean isCursorVisible() { 10353 // true is the default value 10354 return mEditor == null ? true : mEditor.mCursorVisible; 10355 } 10356 canMarquee()10357 private boolean canMarquee() { 10358 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10359 return width > 0 && (mLayout.getLineWidth(0) > width 10360 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10361 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10362 } 10363 10364 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10365 private void startMarquee() { 10366 // Do not ellipsize EditText 10367 if (getKeyListener() != null) return; 10368 10369 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10370 return; 10371 } 10372 10373 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10374 && getLineCount() == 1 && canMarquee()) { 10375 10376 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10377 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10378 final Layout tmp = mLayout; 10379 mLayout = mSavedMarqueeModeLayout; 10380 mSavedMarqueeModeLayout = tmp; 10381 setHorizontalFadingEdgeEnabled(true); 10382 requestLayout(); 10383 invalidate(); 10384 } 10385 10386 if (mMarquee == null) mMarquee = new Marquee(this); 10387 mMarquee.start(mMarqueeRepeatLimit); 10388 } 10389 } 10390 stopMarquee()10391 private void stopMarquee() { 10392 if (mMarquee != null && !mMarquee.isStopped()) { 10393 mMarquee.stop(); 10394 } 10395 10396 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10397 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10398 final Layout tmp = mSavedMarqueeModeLayout; 10399 mSavedMarqueeModeLayout = mLayout; 10400 mLayout = tmp; 10401 setHorizontalFadingEdgeEnabled(false); 10402 requestLayout(); 10403 invalidate(); 10404 } 10405 } 10406 10407 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10408 private void startStopMarquee(boolean start) { 10409 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10410 if (start) { 10411 startMarquee(); 10412 } else { 10413 stopMarquee(); 10414 } 10415 } 10416 } 10417 10418 /** 10419 * This method is called when the text is changed, in case any subclasses 10420 * would like to know. 10421 * 10422 * Within <code>text</code>, the <code>lengthAfter</code> characters 10423 * beginning at <code>start</code> have just replaced old text that had 10424 * length <code>lengthBefore</code>. It is an error to attempt to make 10425 * changes to <code>text</code> from this callback. 10426 * 10427 * @param text The text the TextView is displaying 10428 * @param start The offset of the start of the range of the text that was 10429 * modified 10430 * @param lengthBefore The length of the former text that has been replaced 10431 * @param lengthAfter The length of the replacement modified text 10432 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10433 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10434 // intentionally empty, template pattern method can be overridden by subclasses 10435 } 10436 10437 /** 10438 * This method is called when the selection has changed, in case any 10439 * subclasses would like to know. 10440 * 10441 * @param selStart The new selection start location. 10442 * @param selEnd The new selection end location. 10443 */ onSelectionChanged(int selStart, int selEnd)10444 protected void onSelectionChanged(int selStart, int selEnd) { 10445 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10446 } 10447 10448 /** 10449 * Adds a TextWatcher to the list of those whose methods are called 10450 * whenever this TextView's text changes. 10451 * <p> 10452 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10453 * not called after {@link #setText} calls. Now, doing {@link #setText} 10454 * if there are any text changed listeners forces the buffer type to 10455 * Editable if it would not otherwise be and does call this method. 10456 */ addTextChangedListener(TextWatcher watcher)10457 public void addTextChangedListener(TextWatcher watcher) { 10458 if (mListeners == null) { 10459 mListeners = new ArrayList<TextWatcher>(); 10460 } 10461 10462 mListeners.add(watcher); 10463 } 10464 10465 /** 10466 * Removes the specified TextWatcher from the list of those whose 10467 * methods are called 10468 * whenever this TextView's text changes. 10469 */ removeTextChangedListener(TextWatcher watcher)10470 public void removeTextChangedListener(TextWatcher watcher) { 10471 if (mListeners != null) { 10472 int i = mListeners.indexOf(watcher); 10473 10474 if (i >= 0) { 10475 mListeners.remove(i); 10476 } 10477 } 10478 } 10479 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10480 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10481 if (mListeners != null) { 10482 final ArrayList<TextWatcher> list = mListeners; 10483 final int count = list.size(); 10484 for (int i = 0; i < count; i++) { 10485 list.get(i).beforeTextChanged(text, start, before, after); 10486 } 10487 } 10488 10489 // The spans that are inside or intersect the modified region no longer make sense 10490 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10491 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10492 } 10493 10494 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10495 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10496 if (!(mText instanceof Editable)) return; 10497 Editable text = (Editable) mText; 10498 10499 T[] spans = text.getSpans(start, end, type); 10500 final int length = spans.length; 10501 for (int i = 0; i < length; i++) { 10502 final int spanStart = text.getSpanStart(spans[i]); 10503 final int spanEnd = text.getSpanEnd(spans[i]); 10504 if (spanEnd == start || spanStart == end) break; 10505 text.removeSpan(spans[i]); 10506 } 10507 } 10508 removeAdjacentSuggestionSpans(final int pos)10509 void removeAdjacentSuggestionSpans(final int pos) { 10510 if (!(mText instanceof Editable)) return; 10511 final Editable text = (Editable) mText; 10512 10513 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10514 final int length = spans.length; 10515 for (int i = 0; i < length; i++) { 10516 final int spanStart = text.getSpanStart(spans[i]); 10517 final int spanEnd = text.getSpanEnd(spans[i]); 10518 if (spanEnd == pos || spanStart == pos) { 10519 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10520 text.removeSpan(spans[i]); 10521 } 10522 } 10523 } 10524 } 10525 10526 /** 10527 * Not private so it can be called from an inner class without going 10528 * through a thunk. 10529 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10530 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10531 if (mListeners != null) { 10532 final ArrayList<TextWatcher> list = mListeners; 10533 final int count = list.size(); 10534 for (int i = 0; i < count; i++) { 10535 list.get(i).onTextChanged(text, start, before, after); 10536 } 10537 } 10538 10539 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10540 } 10541 10542 /** 10543 * Not private so it can be called from an inner class without going 10544 * through a thunk. 10545 */ sendAfterTextChanged(Editable text)10546 void sendAfterTextChanged(Editable text) { 10547 if (mListeners != null) { 10548 final ArrayList<TextWatcher> list = mListeners; 10549 final int count = list.size(); 10550 for (int i = 0; i < count; i++) { 10551 list.get(i).afterTextChanged(text); 10552 } 10553 } 10554 10555 notifyListeningManagersAfterTextChanged(); 10556 10557 hideErrorIfUnchanged(); 10558 } 10559 10560 /** 10561 * Notify managers (such as {@link AutofillManager}) that are interested in text changes. 10562 */ notifyListeningManagersAfterTextChanged()10563 private void notifyListeningManagersAfterTextChanged() { 10564 10565 // Autofill 10566 if (isAutofillable()) { 10567 // It is important to not check whether the view is important for autofill 10568 // since the user can trigger autofill manually on not important views. 10569 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10570 if (afm != null) { 10571 if (android.view.autofill.Helper.sVerbose) { 10572 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10573 } 10574 afm.notifyValueChanged(TextView.this); 10575 } 10576 } 10577 } 10578 isAutofillable()10579 private boolean isAutofillable() { 10580 // It is important to not check whether the view is important for autofill 10581 // since the user can trigger autofill manually on not important views. 10582 return getAutofillType() != AUTOFILL_TYPE_NONE; 10583 } 10584 updateAfterEdit()10585 void updateAfterEdit() { 10586 invalidate(); 10587 int curs = getSelectionStart(); 10588 10589 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10590 registerForPreDraw(); 10591 } 10592 10593 checkForResize(); 10594 10595 if (curs >= 0) { 10596 mHighlightPathBogus = true; 10597 if (mEditor != null) mEditor.makeBlink(); 10598 bringPointIntoView(curs); 10599 } 10600 } 10601 10602 /** 10603 * Not private so it can be called from an inner class without going 10604 * through a thunk. 10605 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10606 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10607 sLastCutCopyOrTextChangedTime = 0; 10608 10609 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10610 if (ims == null || ims.mBatchEditNesting == 0) { 10611 updateAfterEdit(); 10612 } 10613 if (ims != null) { 10614 ims.mContentChanged = true; 10615 if (ims.mChangedStart < 0) { 10616 ims.mChangedStart = start; 10617 ims.mChangedEnd = start + before; 10618 } else { 10619 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10620 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10621 } 10622 ims.mChangedDelta += after - before; 10623 } 10624 resetErrorChangedFlag(); 10625 sendOnTextChanged(buffer, start, before, after); 10626 onTextChanged(buffer, start, before, after); 10627 } 10628 10629 /** 10630 * Not private so it can be called from an inner class without going 10631 * through a thunk. 10632 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10633 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10634 // XXX Make the start and end move together if this ends up 10635 // spending too much time invalidating. 10636 10637 boolean selChanged = false; 10638 int newSelStart = -1, newSelEnd = -1; 10639 10640 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10641 10642 if (what == Selection.SELECTION_END) { 10643 selChanged = true; 10644 newSelEnd = newStart; 10645 10646 if (oldStart >= 0 || newStart >= 0) { 10647 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10648 checkForResize(); 10649 registerForPreDraw(); 10650 if (mEditor != null) mEditor.makeBlink(); 10651 } 10652 } 10653 10654 if (what == Selection.SELECTION_START) { 10655 selChanged = true; 10656 newSelStart = newStart; 10657 10658 if (oldStart >= 0 || newStart >= 0) { 10659 int end = Selection.getSelectionEnd(buf); 10660 invalidateCursor(end, oldStart, newStart); 10661 } 10662 } 10663 10664 if (selChanged) { 10665 mHighlightPathBogus = true; 10666 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10667 10668 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10669 if (newSelStart < 0) { 10670 newSelStart = Selection.getSelectionStart(buf); 10671 } 10672 if (newSelEnd < 0) { 10673 newSelEnd = Selection.getSelectionEnd(buf); 10674 } 10675 10676 if (mEditor != null) { 10677 mEditor.refreshTextActionMode(); 10678 if (!hasSelection() 10679 && mEditor.getTextActionMode() == null && hasTransientState()) { 10680 // User generated selection has been removed. 10681 setHasTransientState(false); 10682 } 10683 } 10684 onSelectionChanged(newSelStart, newSelEnd); 10685 } 10686 } 10687 10688 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10689 || what instanceof CharacterStyle) { 10690 if (ims == null || ims.mBatchEditNesting == 0) { 10691 invalidate(); 10692 mHighlightPathBogus = true; 10693 checkForResize(); 10694 } else { 10695 ims.mContentChanged = true; 10696 } 10697 if (mEditor != null) { 10698 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10699 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10700 mEditor.invalidateHandlesAndActionMode(); 10701 } 10702 } 10703 10704 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10705 mHighlightPathBogus = true; 10706 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10707 ims.mSelectionModeChanged = true; 10708 } 10709 10710 if (Selection.getSelectionStart(buf) >= 0) { 10711 if (ims == null || ims.mBatchEditNesting == 0) { 10712 invalidateCursor(); 10713 } else { 10714 ims.mCursorChanged = true; 10715 } 10716 } 10717 } 10718 10719 if (what instanceof ParcelableSpan) { 10720 // If this is a span that can be sent to a remote process, 10721 // the current extract editor would be interested in it. 10722 if (ims != null && ims.mExtractedTextRequest != null) { 10723 if (ims.mBatchEditNesting != 0) { 10724 if (oldStart >= 0) { 10725 if (ims.mChangedStart > oldStart) { 10726 ims.mChangedStart = oldStart; 10727 } 10728 if (ims.mChangedStart > oldEnd) { 10729 ims.mChangedStart = oldEnd; 10730 } 10731 } 10732 if (newStart >= 0) { 10733 if (ims.mChangedStart > newStart) { 10734 ims.mChangedStart = newStart; 10735 } 10736 if (ims.mChangedStart > newEnd) { 10737 ims.mChangedStart = newEnd; 10738 } 10739 } 10740 } else { 10741 if (DEBUG_EXTRACT) { 10742 Log.v(LOG_TAG, "Span change outside of batch: " 10743 + oldStart + "-" + oldEnd + "," 10744 + newStart + "-" + newEnd + " " + what); 10745 } 10746 ims.mContentChanged = true; 10747 } 10748 } 10749 } 10750 10751 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 10752 && what instanceof SpellCheckSpan) { 10753 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 10754 } 10755 } 10756 10757 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10758 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 10759 if (isTemporarilyDetached()) { 10760 // If we are temporarily in the detach state, then do nothing. 10761 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10762 return; 10763 } 10764 10765 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 10766 10767 if (focused) { 10768 if (mSpannable != null) { 10769 MetaKeyKeyListener.resetMetaState(mSpannable); 10770 } 10771 } 10772 10773 startStopMarquee(focused); 10774 10775 if (mTransformation != null) { 10776 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 10777 } 10778 10779 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10780 } 10781 10782 @Override onWindowFocusChanged(boolean hasWindowFocus)10783 public void onWindowFocusChanged(boolean hasWindowFocus) { 10784 super.onWindowFocusChanged(hasWindowFocus); 10785 10786 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 10787 10788 startStopMarquee(hasWindowFocus); 10789 } 10790 10791 @Override onVisibilityChanged(View changedView, int visibility)10792 protected void onVisibilityChanged(View changedView, int visibility) { 10793 super.onVisibilityChanged(changedView, visibility); 10794 if (mEditor != null && visibility != VISIBLE) { 10795 mEditor.hideCursorAndSpanControllers(); 10796 stopTextActionMode(); 10797 } 10798 } 10799 10800 /** 10801 * Use {@link BaseInputConnection#removeComposingSpans 10802 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 10803 * state from this text view. 10804 */ clearComposingText()10805 public void clearComposingText() { 10806 if (mText instanceof Spannable) { 10807 BaseInputConnection.removeComposingSpans(mSpannable); 10808 } 10809 } 10810 10811 @Override setSelected(boolean selected)10812 public void setSelected(boolean selected) { 10813 boolean wasSelected = isSelected(); 10814 10815 super.setSelected(selected); 10816 10817 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10818 if (selected) { 10819 startMarquee(); 10820 } else { 10821 stopMarquee(); 10822 } 10823 } 10824 } 10825 10826 @Override onTouchEvent(MotionEvent event)10827 public boolean onTouchEvent(MotionEvent event) { 10828 final int action = event.getActionMasked(); 10829 if (mEditor != null) { 10830 mEditor.onTouchEvent(event); 10831 10832 if (mEditor.mSelectionModifierCursorController != null 10833 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 10834 return true; 10835 } 10836 } 10837 10838 final boolean superResult = super.onTouchEvent(event); 10839 10840 /* 10841 * Don't handle the release after a long press, because it will move the selection away from 10842 * whatever the menu action was trying to affect. If the long press should have triggered an 10843 * insertion action mode, we can now actually show it. 10844 */ 10845 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 10846 mEditor.mDiscardNextActionUp = false; 10847 10848 if (mEditor.mIsInsertionActionModeStartPending) { 10849 mEditor.startInsertionActionMode(); 10850 mEditor.mIsInsertionActionModeStartPending = false; 10851 } 10852 return superResult; 10853 } 10854 10855 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 10856 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 10857 10858 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 10859 && mText instanceof Spannable && mLayout != null) { 10860 boolean handled = false; 10861 10862 if (mMovement != null) { 10863 handled |= mMovement.onTouchEvent(this, mSpannable, event); 10864 } 10865 10866 final boolean textIsSelectable = isTextSelectable(); 10867 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 10868 // The LinkMovementMethod which should handle taps on links has not been installed 10869 // on non editable text that support text selection. 10870 // We reproduce its behavior here to open links for these. 10871 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 10872 getSelectionEnd(), ClickableSpan.class); 10873 10874 if (links.length > 0) { 10875 links[0].onClick(this); 10876 handled = true; 10877 } 10878 } 10879 10880 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 10881 // Show the IME, except when selecting in read-only text. 10882 final InputMethodManager imm = getInputMethodManager(); 10883 viewClicked(imm); 10884 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10885 imm.showSoftInput(this, 0); 10886 } 10887 10888 // The above condition ensures that the mEditor is not null 10889 mEditor.onTouchUpEvent(event); 10890 10891 handled = true; 10892 } 10893 10894 if (handled) { 10895 return true; 10896 } 10897 } 10898 10899 return superResult; 10900 } 10901 10902 @Override onGenericMotionEvent(MotionEvent event)10903 public boolean onGenericMotionEvent(MotionEvent event) { 10904 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 10905 try { 10906 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 10907 return true; 10908 } 10909 } catch (AbstractMethodError ex) { 10910 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 10911 // Ignore its absence in case third party applications implemented the 10912 // interface directly. 10913 } 10914 } 10915 return super.onGenericMotionEvent(event); 10916 } 10917 10918 @Override onCreateContextMenu(ContextMenu menu)10919 protected void onCreateContextMenu(ContextMenu menu) { 10920 if (mEditor != null) { 10921 mEditor.onCreateContextMenu(menu); 10922 } 10923 } 10924 10925 @Override showContextMenu()10926 public boolean showContextMenu() { 10927 if (mEditor != null) { 10928 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 10929 } 10930 return super.showContextMenu(); 10931 } 10932 10933 @Override showContextMenu(float x, float y)10934 public boolean showContextMenu(float x, float y) { 10935 if (mEditor != null) { 10936 mEditor.setContextMenuAnchor(x, y); 10937 } 10938 return super.showContextMenu(x, y); 10939 } 10940 10941 /** 10942 * @return True iff this TextView contains a text that can be edited, or if this is 10943 * a selectable TextView. 10944 */ 10945 @UnsupportedAppUsage isTextEditable()10946 boolean isTextEditable() { 10947 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 10948 } 10949 10950 /** 10951 * Returns true, only while processing a touch gesture, if the initial 10952 * touch down event caused focus to move to the text view and as a result 10953 * its selection changed. Only valid while processing the touch gesture 10954 * of interest, in an editable text view. 10955 */ didTouchFocusSelect()10956 public boolean didTouchFocusSelect() { 10957 return mEditor != null && mEditor.mTouchFocusSelected; 10958 } 10959 10960 @Override cancelLongPress()10961 public void cancelLongPress() { 10962 super.cancelLongPress(); 10963 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 10964 } 10965 10966 @Override onTrackballEvent(MotionEvent event)10967 public boolean onTrackballEvent(MotionEvent event) { 10968 if (mMovement != null && mSpannable != null && mLayout != null) { 10969 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 10970 return true; 10971 } 10972 } 10973 10974 return super.onTrackballEvent(event); 10975 } 10976 10977 /** 10978 * Sets the Scroller used for producing a scrolling animation 10979 * 10980 * @param s A Scroller instance 10981 */ setScroller(Scroller s)10982 public void setScroller(Scroller s) { 10983 mScroller = s; 10984 } 10985 10986 @Override getLeftFadingEdgeStrength()10987 protected float getLeftFadingEdgeStrength() { 10988 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 10989 final Marquee marquee = mMarquee; 10990 if (marquee.shouldDrawLeftFade()) { 10991 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 10992 } else { 10993 return 0.0f; 10994 } 10995 } else if (getLineCount() == 1) { 10996 final float lineLeft = getLayout().getLineLeft(0); 10997 if (lineLeft > mScrollX) return 0.0f; 10998 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 10999 } 11000 return super.getLeftFadingEdgeStrength(); 11001 } 11002 11003 @Override getRightFadingEdgeStrength()11004 protected float getRightFadingEdgeStrength() { 11005 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11006 final Marquee marquee = mMarquee; 11007 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11008 } else if (getLineCount() == 1) { 11009 final float rightEdge = mScrollX + 11010 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11011 final float lineRight = getLayout().getLineRight(0); 11012 if (lineRight < rightEdge) return 0.0f; 11013 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11014 } 11015 return super.getRightFadingEdgeStrength(); 11016 } 11017 11018 /** 11019 * Calculates the fading edge strength as the ratio of the distance between two 11020 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11021 * value for the distance calculation. 11022 * 11023 * @param position1 A horizontal position. 11024 * @param position2 A horizontal position. 11025 * @return Fading edge strength between [0.0f, 1.0f]. 11026 */ 11027 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11028 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11029 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11030 if (horizontalFadingEdgeLength == 0) return 0.0f; 11031 final float diff = Math.abs(position1 - position2); 11032 if (diff > horizontalFadingEdgeLength) return 1.0f; 11033 return diff / horizontalFadingEdgeLength; 11034 } 11035 isMarqueeFadeEnabled()11036 private boolean isMarqueeFadeEnabled() { 11037 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11038 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11039 } 11040 11041 @Override computeHorizontalScrollRange()11042 protected int computeHorizontalScrollRange() { 11043 if (mLayout != null) { 11044 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11045 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11046 } 11047 11048 return super.computeHorizontalScrollRange(); 11049 } 11050 11051 @Override computeVerticalScrollRange()11052 protected int computeVerticalScrollRange() { 11053 if (mLayout != null) { 11054 return mLayout.getHeight(); 11055 } 11056 return super.computeVerticalScrollRange(); 11057 } 11058 11059 @Override computeVerticalScrollExtent()11060 protected int computeVerticalScrollExtent() { 11061 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11062 } 11063 11064 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11065 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11066 super.findViewsWithText(outViews, searched, flags); 11067 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11068 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11069 String searchedLowerCase = searched.toString().toLowerCase(); 11070 String textLowerCase = mText.toString().toLowerCase(); 11071 if (textLowerCase.contains(searchedLowerCase)) { 11072 outViews.add(this); 11073 } 11074 } 11075 } 11076 11077 /** 11078 * Type of the text buffer that defines the characteristics of the text such as static, 11079 * styleable, or editable. 11080 */ 11081 public enum BufferType { 11082 NORMAL, SPANNABLE, EDITABLE 11083 } 11084 11085 /** 11086 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11087 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11088 * if TextView_textColor was not set directly. 11089 * 11090 * @removed 11091 */ getTextColors(Context context, TypedArray attrs)11092 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11093 if (attrs == null) { 11094 // Preserve behavior prior to removal of this API. 11095 throw new NullPointerException(); 11096 } 11097 11098 // It's not safe to use this method from apps. The parameter 'attrs' 11099 // must have been obtained using the TextView filter array which is not 11100 // available to the SDK. As such, we grab a default TypedArray with the 11101 // right filter instead here. 11102 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11103 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11104 if (colors == null) { 11105 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11106 if (ap != 0) { 11107 final TypedArray appearance = context.obtainStyledAttributes( 11108 ap, R.styleable.TextAppearance); 11109 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11110 appearance.recycle(); 11111 } 11112 } 11113 a.recycle(); 11114 11115 return colors; 11116 } 11117 11118 /** 11119 * Returns the default color from the TextView_textColor attribute from the 11120 * AttributeSet, if set, or the default color from the 11121 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11122 * TextView_textColor was not set directly. 11123 * 11124 * @removed 11125 */ getTextColor(Context context, TypedArray attrs, int def)11126 public static int getTextColor(Context context, TypedArray attrs, int def) { 11127 final ColorStateList colors = getTextColors(context, attrs); 11128 if (colors == null) { 11129 return def; 11130 } else { 11131 return colors.getDefaultColor(); 11132 } 11133 } 11134 11135 @Override onKeyShortcut(int keyCode, KeyEvent event)11136 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11137 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11138 // Handle Ctrl-only shortcuts. 11139 switch (keyCode) { 11140 case KeyEvent.KEYCODE_A: 11141 if (canSelectText()) { 11142 return onTextContextMenuItem(ID_SELECT_ALL); 11143 } 11144 break; 11145 case KeyEvent.KEYCODE_Z: 11146 if (canUndo()) { 11147 return onTextContextMenuItem(ID_UNDO); 11148 } 11149 break; 11150 case KeyEvent.KEYCODE_X: 11151 if (canCut()) { 11152 return onTextContextMenuItem(ID_CUT); 11153 } 11154 break; 11155 case KeyEvent.KEYCODE_C: 11156 if (canCopy()) { 11157 return onTextContextMenuItem(ID_COPY); 11158 } 11159 break; 11160 case KeyEvent.KEYCODE_V: 11161 if (canPaste()) { 11162 return onTextContextMenuItem(ID_PASTE); 11163 } 11164 break; 11165 } 11166 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11167 // Handle Ctrl-Shift shortcuts. 11168 switch (keyCode) { 11169 case KeyEvent.KEYCODE_Z: 11170 if (canRedo()) { 11171 return onTextContextMenuItem(ID_REDO); 11172 } 11173 break; 11174 case KeyEvent.KEYCODE_V: 11175 if (canPaste()) { 11176 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11177 } 11178 } 11179 } 11180 return super.onKeyShortcut(keyCode, event); 11181 } 11182 11183 /** 11184 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11185 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11186 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11187 * sufficient. 11188 */ canSelectText()11189 boolean canSelectText() { 11190 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11191 } 11192 11193 /** 11194 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11195 * The text must be spannable and the movement method must allow for arbitary selection. 11196 * 11197 * See also {@link #canSelectText()}. 11198 */ textCanBeSelected()11199 boolean textCanBeSelected() { 11200 // prepareCursorController() relies on this method. 11201 // If you change this condition, make sure prepareCursorController is called anywhere 11202 // the value of this condition might be changed. 11203 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11204 return isTextEditable() 11205 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11206 } 11207 11208 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11209 private Locale getTextServicesLocale(boolean allowNullLocale) { 11210 // Start fetching the text services locale asynchronously. 11211 updateTextServicesLocaleAsync(); 11212 // If !allowNullLocale and there is no cached text services locale, just return the default 11213 // locale. 11214 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11215 : mCurrentSpellCheckerLocaleCache; 11216 } 11217 11218 /** 11219 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11220 * this {@link TextView}. 11221 * 11222 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11223 * other apps may need to set this so that the system can user right user's resources and 11224 * services such as input methods and spell checkers.</p> 11225 * 11226 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11227 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11228 * @hide 11229 */ 11230 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11231 public final void setTextOperationUser(@Nullable UserHandle user) { 11232 if (Objects.equals(mTextOperationUser, user)) { 11233 return; 11234 } 11235 if (user != null && !Process.myUserHandle().equals(user)) { 11236 // Just for preventing people from accidentally using this hidden API without 11237 // the required permission. The same permission is also checked in the system server. 11238 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11239 != PackageManager.PERMISSION_GRANTED) { 11240 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11241 + " userId=" + user.getIdentifier() 11242 + " callingUserId" + UserHandle.myUserId()); 11243 } 11244 } 11245 mTextOperationUser = user; 11246 // Invalidate some resources 11247 mCurrentSpellCheckerLocaleCache = null; 11248 if (mEditor != null) { 11249 mEditor.onTextOperationUserChanged(); 11250 } 11251 } 11252 11253 @Nullable getTextServicesManagerForUser()11254 final TextServicesManager getTextServicesManagerForUser() { 11255 return getServiceManagerForUser("android", TextServicesManager.class); 11256 } 11257 11258 @Nullable getClipboardManagerForUser()11259 final ClipboardManager getClipboardManagerForUser() { 11260 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11261 } 11262 11263 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11264 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11265 if (mTextOperationUser == null) { 11266 return getContext().getSystemService(managerClazz); 11267 } 11268 try { 11269 Context context = getContext().createPackageContextAsUser( 11270 packageName, 0 /* flags */, mTextOperationUser); 11271 return context.getSystemService(managerClazz); 11272 } catch (PackageManager.NameNotFoundException e) { 11273 return null; 11274 } 11275 } 11276 11277 /** 11278 * Starts {@link Activity} as a text-operation user if it is specified with 11279 * {@link #setTextOperationUser(UserHandle)}. 11280 * 11281 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11282 * 11283 * @param intent The description of the activity to start. 11284 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11285 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11286 if (mTextOperationUser != null) { 11287 getContext().startActivityAsUser(intent, mTextOperationUser); 11288 } else { 11289 getContext().startActivity(intent); 11290 } 11291 } 11292 11293 /** 11294 * This is a temporary method. Future versions may support multi-locale text. 11295 * Caveat: This method may not return the latest text services locale, but this should be 11296 * acceptable and it's more important to make this method asynchronous. 11297 * 11298 * @return The locale that should be used for a word iterator 11299 * in this TextView, based on the current spell checker settings, 11300 * the current IME's locale, or the system default locale. 11301 * Please note that a word iterator in this TextView is different from another word iterator 11302 * used by SpellChecker.java of TextView. This method should be used for the former. 11303 * @hide 11304 */ 11305 // TODO: Support multi-locale 11306 // TODO: Update the text services locale immediately after the keyboard locale is switched 11307 // by catching intent of keyboard switch event getTextServicesLocale()11308 public Locale getTextServicesLocale() { 11309 return getTextServicesLocale(false /* allowNullLocale */); 11310 } 11311 11312 /** 11313 * @return {@code true} if this TextView is specialized for showing and interacting with the 11314 * extracted text in a full-screen input method. 11315 * @hide 11316 */ isInExtractedMode()11317 public boolean isInExtractedMode() { 11318 return false; 11319 } 11320 11321 /** 11322 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11323 * auto-size. 11324 */ isAutoSizeEnabled()11325 private boolean isAutoSizeEnabled() { 11326 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11327 } 11328 11329 /** 11330 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11331 * @hide 11332 */ supportsAutoSizeText()11333 protected boolean supportsAutoSizeText() { 11334 return true; 11335 } 11336 11337 /** 11338 * This is a temporary method. Future versions may support multi-locale text. 11339 * Caveat: This method may not return the latest spell checker locale, but this should be 11340 * acceptable and it's more important to make this method asynchronous. 11341 * 11342 * @return The locale that should be used for a spell checker in this TextView, 11343 * based on the current spell checker settings, the current IME's locale, or the system default 11344 * locale. 11345 * @hide 11346 */ getSpellCheckerLocale()11347 public Locale getSpellCheckerLocale() { 11348 return getTextServicesLocale(true /* allowNullLocale */); 11349 } 11350 updateTextServicesLocaleAsync()11351 private void updateTextServicesLocaleAsync() { 11352 // AsyncTask.execute() uses a serial executor which means we don't have 11353 // to lock around updateTextServicesLocaleLocked() to prevent it from 11354 // being executed n times in parallel. 11355 AsyncTask.execute(new Runnable() { 11356 @Override 11357 public void run() { 11358 updateTextServicesLocaleLocked(); 11359 } 11360 }); 11361 } 11362 11363 @UnsupportedAppUsage updateTextServicesLocaleLocked()11364 private void updateTextServicesLocaleLocked() { 11365 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11366 if (textServicesManager == null) { 11367 return; 11368 } 11369 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11370 final Locale locale; 11371 if (subtype != null) { 11372 locale = subtype.getLocaleObject(); 11373 } else { 11374 locale = null; 11375 } 11376 mCurrentSpellCheckerLocaleCache = locale; 11377 } 11378 onLocaleChanged()11379 void onLocaleChanged() { 11380 mEditor.onLocaleChanged(); 11381 } 11382 11383 /** 11384 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11385 * Made available to achieve a consistent behavior. 11386 * @hide 11387 */ getWordIterator()11388 public WordIterator getWordIterator() { 11389 if (mEditor != null) { 11390 return mEditor.getWordIterator(); 11391 } else { 11392 return null; 11393 } 11394 } 11395 11396 /** @hide */ 11397 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11398 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11399 super.onPopulateAccessibilityEventInternal(event); 11400 11401 final CharSequence text = getTextForAccessibility(); 11402 if (!TextUtils.isEmpty(text)) { 11403 event.getText().add(text); 11404 } 11405 } 11406 11407 @Override getAccessibilityClassName()11408 public CharSequence getAccessibilityClassName() { 11409 return TextView.class.getName(); 11410 } 11411 11412 /** @hide */ 11413 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11414 protected void onProvideStructure(@NonNull ViewStructure structure, 11415 @ViewStructureType int viewFor, int flags) { 11416 super.onProvideStructure(structure, viewFor, flags); 11417 11418 final boolean isPassword = hasPasswordTransformationMethod() 11419 || isPasswordInputType(getInputType()); 11420 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11421 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11422 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11423 } 11424 if (mTextId != Resources.ID_NULL) { 11425 try { 11426 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11427 } catch (Resources.NotFoundException e) { 11428 if (android.view.autofill.Helper.sVerbose) { 11429 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11430 + mTextId + ": " + e.getMessage()); 11431 } 11432 } 11433 } 11434 } 11435 11436 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11437 if (mLayout == null) { 11438 assumeLayout(); 11439 } 11440 Layout layout = mLayout; 11441 final int lineCount = layout.getLineCount(); 11442 if (lineCount <= 1) { 11443 // Simple case: this is a single line. 11444 final CharSequence text = getText(); 11445 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11446 structure.setText(text); 11447 } else { 11448 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11449 } 11450 } else { 11451 // Complex case: multi-line, could be scrolled or within a scroll container 11452 // so some lines are not visible. 11453 final int[] tmpCords = new int[2]; 11454 getLocationInWindow(tmpCords); 11455 final int topWindowLocation = tmpCords[1]; 11456 View root = this; 11457 ViewParent viewParent = getParent(); 11458 while (viewParent instanceof View) { 11459 root = (View) viewParent; 11460 viewParent = root.getParent(); 11461 } 11462 final int windowHeight = root.getHeight(); 11463 final int topLine; 11464 final int bottomLine; 11465 if (topWindowLocation >= 0) { 11466 // The top of the view is fully within its window; start text at line 0. 11467 topLine = getLineAtCoordinateUnclamped(0); 11468 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11469 } else { 11470 // The top of hte window has scrolled off the top of the window; figure out 11471 // the starting line for this. 11472 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11473 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11474 } 11475 // We want to return some contextual lines above/below the lines that are 11476 // actually visible. 11477 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11478 if (expandedTopLine < 0) { 11479 expandedTopLine = 0; 11480 } 11481 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11482 if (expandedBottomLine >= lineCount) { 11483 expandedBottomLine = lineCount - 1; 11484 } 11485 11486 // Convert lines into character offsets. 11487 int expandedTopChar = layout.getLineStart(expandedTopLine); 11488 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11489 11490 // Take into account selection -- if there is a selection, we need to expand 11491 // the text we are returning to include that selection. 11492 final int selStart = getSelectionStart(); 11493 final int selEnd = getSelectionEnd(); 11494 if (selStart < selEnd) { 11495 if (selStart < expandedTopChar) { 11496 expandedTopChar = selStart; 11497 } 11498 if (selEnd > expandedBottomChar) { 11499 expandedBottomChar = selEnd; 11500 } 11501 } 11502 11503 // Get the text and trim it to the range we are reporting. 11504 CharSequence text = getText(); 11505 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11506 text = text.subSequence(expandedTopChar, expandedBottomChar); 11507 } 11508 11509 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11510 structure.setText(text); 11511 } else { 11512 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 11513 11514 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11515 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11516 final int baselineOffset = getBaselineOffset(); 11517 for (int i = topLine; i <= bottomLine; i++) { 11518 lineOffsets[i - topLine] = layout.getLineStart(i); 11519 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11520 } 11521 structure.setTextLines(lineOffsets, lineBaselines); 11522 } 11523 } 11524 11525 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) { 11526 // Extract style information that applies to the TextView as a whole. 11527 int style = 0; 11528 int typefaceStyle = getTypefaceStyle(); 11529 if ((typefaceStyle & Typeface.BOLD) != 0) { 11530 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11531 } 11532 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11533 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11534 } 11535 11536 // Global styles can also be set via TextView.setPaintFlags(). 11537 int paintFlags = mTextPaint.getFlags(); 11538 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11539 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11540 } 11541 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11542 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11543 } 11544 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11545 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11546 } 11547 11548 // TextView does not have its own text background color. A background is either part 11549 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11550 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11551 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11552 } 11553 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11554 structure.setMinTextEms(getMinEms()); 11555 structure.setMaxTextEms(getMaxEms()); 11556 int maxLength = -1; 11557 for (InputFilter filter: getFilters()) { 11558 if (filter instanceof InputFilter.LengthFilter) { 11559 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11560 break; 11561 } 11562 } 11563 structure.setMaxTextLength(maxLength); 11564 } 11565 } 11566 structure.setHint(getHint()); 11567 structure.setInputType(getInputType()); 11568 } 11569 canRequestAutofill()11570 boolean canRequestAutofill() { 11571 if (!isAutofillable()) { 11572 return false; 11573 } 11574 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11575 if (afm != null) { 11576 return afm.isEnabled(); 11577 } 11578 return false; 11579 } 11580 requestAutofill()11581 private void requestAutofill() { 11582 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11583 if (afm != null) { 11584 afm.requestAutofill(this); 11585 } 11586 } 11587 11588 @Override autofill(AutofillValue value)11589 public void autofill(AutofillValue value) { 11590 if (!value.isText() || !isTextEditable()) { 11591 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 11592 return; 11593 } 11594 11595 final CharSequence autofilledValue = value.getTextValue(); 11596 11597 // First autofill it... 11598 setText(autofilledValue, mBufferType, true, 0); 11599 11600 // ...then move cursor to the end. 11601 final CharSequence text = getText(); 11602 if ((text instanceof Spannable)) { 11603 Selection.setSelection((Spannable) text, text.length()); 11604 } 11605 } 11606 11607 @Override getAutofillType()11608 public @AutofillType int getAutofillType() { 11609 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11610 } 11611 11612 /** 11613 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11614 * {@code char}s if longer. 11615 * 11616 * @return current text, {@code null} if the text is not editable 11617 * 11618 * @see View#getAutofillValue() 11619 */ 11620 @Override 11621 @Nullable getAutofillValue()11622 public AutofillValue getAutofillValue() { 11623 if (isTextEditable()) { 11624 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 11625 return AutofillValue.forText(text); 11626 } 11627 return null; 11628 } 11629 11630 /** @hide */ 11631 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)11632 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 11633 super.onInitializeAccessibilityEventInternal(event); 11634 11635 final boolean isPassword = hasPasswordTransformationMethod(); 11636 event.setPassword(isPassword); 11637 11638 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 11639 event.setFromIndex(Selection.getSelectionStart(mText)); 11640 event.setToIndex(Selection.getSelectionEnd(mText)); 11641 event.setItemCount(mText.length()); 11642 } 11643 } 11644 11645 /** @hide */ 11646 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11647 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 11648 super.onInitializeAccessibilityNodeInfoInternal(info); 11649 11650 final boolean isPassword = hasPasswordTransformationMethod(); 11651 info.setPassword(isPassword); 11652 info.setText(getTextForAccessibility()); 11653 info.setHintText(mHint); 11654 info.setShowingHintText(isShowingHint()); 11655 11656 if (mBufferType == BufferType.EDITABLE) { 11657 info.setEditable(true); 11658 if (isEnabled()) { 11659 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 11660 } 11661 } 11662 11663 if (mEditor != null) { 11664 info.setInputType(mEditor.mInputType); 11665 11666 if (mEditor.mError != null) { 11667 info.setContentInvalid(true); 11668 info.setError(mEditor.mError); 11669 } 11670 } 11671 11672 if (!TextUtils.isEmpty(mText)) { 11673 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 11674 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 11675 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 11676 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 11677 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 11678 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 11679 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 11680 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 11681 info.setAvailableExtraData( 11682 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); 11683 } 11684 11685 if (isFocused()) { 11686 if (canCopy()) { 11687 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 11688 } 11689 if (canPaste()) { 11690 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 11691 } 11692 if (canCut()) { 11693 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 11694 } 11695 if (canShare()) { 11696 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 11697 ACCESSIBILITY_ACTION_SHARE, 11698 getResources().getString(com.android.internal.R.string.share))); 11699 } 11700 if (canProcessText()) { // also implies mEditor is not null. 11701 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 11702 } 11703 } 11704 11705 // Check for known input filter types. 11706 final int numFilters = mFilters.length; 11707 for (int i = 0; i < numFilters; i++) { 11708 final InputFilter filter = mFilters[i]; 11709 if (filter instanceof InputFilter.LengthFilter) { 11710 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 11711 } 11712 } 11713 11714 if (!isSingleLine()) { 11715 info.setMultiLine(true); 11716 } 11717 } 11718 11719 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11720 public void addExtraDataToAccessibilityNodeInfo( 11721 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 11722 // The only extra data we support requires arguments. 11723 if (arguments == null) { 11724 return; 11725 } 11726 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 11727 int positionInfoStartIndex = arguments.getInt( 11728 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 11729 int positionInfoLength = arguments.getInt( 11730 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 11731 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 11732 || (positionInfoStartIndex >= mText.length())) { 11733 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 11734 return; 11735 } 11736 RectF[] boundingRects = new RectF[positionInfoLength]; 11737 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 11738 populateCharacterBounds(builder, positionInfoStartIndex, 11739 positionInfoStartIndex + positionInfoLength, 11740 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 11741 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 11742 for (int i = 0; i < positionInfoLength; i++) { 11743 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 11744 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 11745 RectF bounds = cursorAnchorInfo 11746 .getCharacterBounds(positionInfoStartIndex + i); 11747 if (bounds != null) { 11748 mapRectFromViewToScreenCoords(bounds, true); 11749 boundingRects[i] = bounds; 11750 } 11751 } 11752 } 11753 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 11754 } 11755 } 11756 11757 /** 11758 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 11759 * 11760 * @param builder The builder to populate 11761 * @param startIndex The starting character index to populate 11762 * @param endIndex The ending character index to populate 11763 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 11764 * content 11765 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 11766 * @hide 11767 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11768 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 11769 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 11770 float viewportToContentVerticalOffset) { 11771 final int minLine = mLayout.getLineForOffset(startIndex); 11772 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 11773 for (int line = minLine; line <= maxLine; ++line) { 11774 final int lineStart = mLayout.getLineStart(line); 11775 final int lineEnd = mLayout.getLineEnd(line); 11776 final int offsetStart = Math.max(lineStart, startIndex); 11777 final int offsetEnd = Math.min(lineEnd, endIndex); 11778 final boolean ltrLine = 11779 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 11780 final float[] widths = new float[offsetEnd - offsetStart]; 11781 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 11782 final float top = mLayout.getLineTop(line); 11783 final float bottom = mLayout.getLineBottom(line); 11784 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 11785 final float charWidth = widths[offset - offsetStart]; 11786 final boolean isRtl = mLayout.isRtlCharAt(offset); 11787 final float primary = mLayout.getPrimaryHorizontal(offset); 11788 final float secondary = mLayout.getSecondaryHorizontal(offset); 11789 // TODO: This doesn't work perfectly for text with custom styles and 11790 // TAB chars. 11791 final float left; 11792 final float right; 11793 if (ltrLine) { 11794 if (isRtl) { 11795 left = secondary - charWidth; 11796 right = secondary; 11797 } else { 11798 left = primary; 11799 right = primary + charWidth; 11800 } 11801 } else { 11802 if (!isRtl) { 11803 left = secondary; 11804 right = secondary + charWidth; 11805 } else { 11806 left = primary - charWidth; 11807 right = primary; 11808 } 11809 } 11810 // TODO: Check top-right and bottom-left as well. 11811 final float localLeft = left + viewportToContentHorizontalOffset; 11812 final float localRight = right + viewportToContentHorizontalOffset; 11813 final float localTop = top + viewportToContentVerticalOffset; 11814 final float localBottom = bottom + viewportToContentVerticalOffset; 11815 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 11816 final boolean isBottomRightVisible = 11817 isPositionVisible(localRight, localBottom); 11818 int characterBoundsFlags = 0; 11819 if (isTopLeftVisible || isBottomRightVisible) { 11820 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 11821 } 11822 if (!isTopLeftVisible || !isBottomRightVisible) { 11823 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 11824 } 11825 if (isRtl) { 11826 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 11827 } 11828 // Here offset is the index in Java chars. 11829 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 11830 localBottom, characterBoundsFlags); 11831 } 11832 } 11833 } 11834 11835 /** 11836 * @hide 11837 */ isPositionVisible(final float positionX, final float positionY)11838 public boolean isPositionVisible(final float positionX, final float positionY) { 11839 synchronized (TEMP_POSITION) { 11840 final float[] position = TEMP_POSITION; 11841 position[0] = positionX; 11842 position[1] = positionY; 11843 View view = this; 11844 11845 while (view != null) { 11846 if (view != this) { 11847 // Local scroll is already taken into account in positionX/Y 11848 position[0] -= view.getScrollX(); 11849 position[1] -= view.getScrollY(); 11850 } 11851 11852 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 11853 || position[1] > view.getHeight()) { 11854 return false; 11855 } 11856 11857 if (!view.getMatrix().isIdentity()) { 11858 view.getMatrix().mapPoints(position); 11859 } 11860 11861 position[0] += view.getLeft(); 11862 position[1] += view.getTop(); 11863 11864 final ViewParent parent = view.getParent(); 11865 if (parent instanceof View) { 11866 view = (View) parent; 11867 } else { 11868 // We've reached the ViewRoot, stop iterating 11869 view = null; 11870 } 11871 } 11872 } 11873 11874 // We've been able to walk up the view hierarchy and the position was never clipped 11875 return true; 11876 } 11877 11878 /** 11879 * Performs an accessibility action after it has been offered to the 11880 * delegate. 11881 * 11882 * @hide 11883 */ 11884 @Override performAccessibilityActionInternal(int action, Bundle arguments)11885 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 11886 if (mEditor != null 11887 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 11888 return true; 11889 } 11890 switch (action) { 11891 case AccessibilityNodeInfo.ACTION_CLICK: { 11892 return performAccessibilityActionClick(arguments); 11893 } 11894 case AccessibilityNodeInfo.ACTION_COPY: { 11895 if (isFocused() && canCopy()) { 11896 if (onTextContextMenuItem(ID_COPY)) { 11897 return true; 11898 } 11899 } 11900 } return false; 11901 case AccessibilityNodeInfo.ACTION_PASTE: { 11902 if (isFocused() && canPaste()) { 11903 if (onTextContextMenuItem(ID_PASTE)) { 11904 return true; 11905 } 11906 } 11907 } return false; 11908 case AccessibilityNodeInfo.ACTION_CUT: { 11909 if (isFocused() && canCut()) { 11910 if (onTextContextMenuItem(ID_CUT)) { 11911 return true; 11912 } 11913 } 11914 } return false; 11915 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 11916 ensureIterableTextForAccessibilitySelectable(); 11917 CharSequence text = getIterableTextForAccessibility(); 11918 if (text == null) { 11919 return false; 11920 } 11921 final int start = (arguments != null) ? arguments.getInt( 11922 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 11923 final int end = (arguments != null) ? arguments.getInt( 11924 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 11925 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 11926 // No arguments clears the selection. 11927 if (start == end && end == -1) { 11928 Selection.removeSelection((Spannable) text); 11929 return true; 11930 } 11931 if (start >= 0 && start <= end && end <= text.length()) { 11932 Selection.setSelection((Spannable) text, start, end); 11933 // Make sure selection mode is engaged. 11934 if (mEditor != null) { 11935 mEditor.startSelectionActionModeAsync(false); 11936 } 11937 return true; 11938 } 11939 } 11940 } return false; 11941 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 11942 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 11943 ensureIterableTextForAccessibilitySelectable(); 11944 return super.performAccessibilityActionInternal(action, arguments); 11945 } 11946 case ACCESSIBILITY_ACTION_SHARE: { 11947 if (isFocused() && canShare()) { 11948 if (onTextContextMenuItem(ID_SHARE)) { 11949 return true; 11950 } 11951 } 11952 } return false; 11953 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 11954 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 11955 return false; 11956 } 11957 CharSequence text = (arguments != null) ? arguments.getCharSequence( 11958 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 11959 setText(text); 11960 if (mText != null) { 11961 int updatedTextLength = mText.length(); 11962 if (updatedTextLength > 0) { 11963 Selection.setSelection(mSpannable, updatedTextLength); 11964 } 11965 } 11966 } return true; 11967 default: { 11968 return super.performAccessibilityActionInternal(action, arguments); 11969 } 11970 } 11971 } 11972 performAccessibilityActionClick(Bundle arguments)11973 private boolean performAccessibilityActionClick(Bundle arguments) { 11974 boolean handled = false; 11975 11976 if (!isEnabled()) { 11977 return false; 11978 } 11979 11980 if (isClickable() || isLongClickable()) { 11981 // Simulate View.onTouchEvent for an ACTION_UP event 11982 if (isFocusable() && !isFocused()) { 11983 requestFocus(); 11984 } 11985 11986 performClick(); 11987 handled = true; 11988 } 11989 11990 // Show the IME, except when selecting in read-only text. 11991 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 11992 && (isTextEditable() || isTextSelectable()) && isFocused()) { 11993 final InputMethodManager imm = getInputMethodManager(); 11994 viewClicked(imm); 11995 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 11996 handled |= imm.showSoftInput(this, 0); 11997 } 11998 } 11999 12000 return handled; 12001 } 12002 hasSpannableText()12003 private boolean hasSpannableText() { 12004 return mText != null && mText instanceof Spannable; 12005 } 12006 12007 /** @hide */ 12008 @Override sendAccessibilityEventInternal(int eventType)12009 public void sendAccessibilityEventInternal(int eventType) { 12010 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12011 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12012 } 12013 12014 super.sendAccessibilityEventInternal(eventType); 12015 } 12016 12017 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12018 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12019 // Do not send scroll events since first they are not interesting for 12020 // accessibility and second such events a generated too frequently. 12021 // For details see the implementation of bringTextIntoView(). 12022 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12023 return; 12024 } 12025 super.sendAccessibilityEventUnchecked(event); 12026 } 12027 12028 /** 12029 * Returns the text that should be exposed to accessibility services. 12030 * <p> 12031 * This approximates what is displayed visually. If the user has specified 12032 * that accessibility services should speak passwords, this method will 12033 * bypass any password transformation method and return unobscured text. 12034 * 12035 * @return the text that should be exposed to accessibility services, may 12036 * be {@code null} if no text is set 12037 */ 12038 @Nullable 12039 @UnsupportedAppUsage getTextForAccessibility()12040 private CharSequence getTextForAccessibility() { 12041 // If the text is empty, we must be showing the hint text. 12042 if (TextUtils.isEmpty(mText)) { 12043 return mHint; 12044 } 12045 12046 // Otherwise, return whatever text is being displayed. 12047 return TextUtils.trimToParcelableSize(mTransformed); 12048 } 12049 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12050 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12051 int fromIndex, int removedCount, int addedCount) { 12052 AccessibilityEvent event = 12053 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12054 event.setFromIndex(fromIndex); 12055 event.setRemovedCount(removedCount); 12056 event.setAddedCount(addedCount); 12057 event.setBeforeText(beforeText); 12058 sendAccessibilityEventUnchecked(event); 12059 } 12060 getInputMethodManager()12061 private InputMethodManager getInputMethodManager() { 12062 return getContext().getSystemService(InputMethodManager.class); 12063 } 12064 12065 /** 12066 * Returns whether this text view is a current input method target. The 12067 * default implementation just checks with {@link InputMethodManager}. 12068 * @return True if the TextView is a current input method target; false otherwise. 12069 */ isInputMethodTarget()12070 public boolean isInputMethodTarget() { 12071 InputMethodManager imm = getInputMethodManager(); 12072 return imm != null && imm.isActive(this); 12073 } 12074 12075 static final int ID_SELECT_ALL = android.R.id.selectAll; 12076 static final int ID_UNDO = android.R.id.undo; 12077 static final int ID_REDO = android.R.id.redo; 12078 static final int ID_CUT = android.R.id.cut; 12079 static final int ID_COPY = android.R.id.copy; 12080 static final int ID_PASTE = android.R.id.paste; 12081 static final int ID_SHARE = android.R.id.shareText; 12082 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12083 static final int ID_REPLACE = android.R.id.replaceText; 12084 static final int ID_ASSIST = android.R.id.textAssist; 12085 static final int ID_AUTOFILL = android.R.id.autofill; 12086 12087 /** 12088 * Called when a context menu option for the text view is selected. Currently 12089 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12090 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12091 * 12092 * @return true if the context menu item action was performed. 12093 */ onTextContextMenuItem(int id)12094 public boolean onTextContextMenuItem(int id) { 12095 int min = 0; 12096 int max = mText.length(); 12097 12098 if (isFocused()) { 12099 final int selStart = getSelectionStart(); 12100 final int selEnd = getSelectionEnd(); 12101 12102 min = Math.max(0, Math.min(selStart, selEnd)); 12103 max = Math.max(0, Math.max(selStart, selEnd)); 12104 } 12105 12106 switch (id) { 12107 case ID_SELECT_ALL: 12108 final boolean hadSelection = hasSelection(); 12109 selectAllText(); 12110 if (mEditor != null && hadSelection) { 12111 mEditor.invalidateActionModeAsync(); 12112 } 12113 return true; 12114 12115 case ID_UNDO: 12116 if (mEditor != null) { 12117 mEditor.undo(); 12118 } 12119 return true; // Returns true even if nothing was undone. 12120 12121 case ID_REDO: 12122 if (mEditor != null) { 12123 mEditor.redo(); 12124 } 12125 return true; // Returns true even if nothing was undone. 12126 12127 case ID_PASTE: 12128 paste(min, max, true /* withFormatting */); 12129 return true; 12130 12131 case ID_PASTE_AS_PLAIN_TEXT: 12132 paste(min, max, false /* withFormatting */); 12133 return true; 12134 12135 case ID_CUT: 12136 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12137 if (setPrimaryClip(cutData)) { 12138 deleteText_internal(min, max); 12139 } else { 12140 Toast.makeText(getContext(), 12141 com.android.internal.R.string.failed_to_copy_to_clipboard, 12142 Toast.LENGTH_SHORT).show(); 12143 } 12144 return true; 12145 12146 case ID_COPY: 12147 // For link action mode in a non-selectable/non-focusable TextView, 12148 // make sure that we set the appropriate min/max. 12149 final int selStart = getSelectionStart(); 12150 final int selEnd = getSelectionEnd(); 12151 min = Math.max(0, Math.min(selStart, selEnd)); 12152 max = Math.max(0, Math.max(selStart, selEnd)); 12153 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12154 if (setPrimaryClip(copyData)) { 12155 stopTextActionMode(); 12156 } else { 12157 Toast.makeText(getContext(), 12158 com.android.internal.R.string.failed_to_copy_to_clipboard, 12159 Toast.LENGTH_SHORT).show(); 12160 } 12161 return true; 12162 12163 case ID_REPLACE: 12164 if (mEditor != null) { 12165 mEditor.replace(); 12166 } 12167 return true; 12168 12169 case ID_SHARE: 12170 shareSelectedText(); 12171 return true; 12172 12173 case ID_AUTOFILL: 12174 requestAutofill(); 12175 stopTextActionMode(); 12176 return true; 12177 } 12178 return false; 12179 } 12180 12181 @UnsupportedAppUsage getTransformedText(int start, int end)12182 CharSequence getTransformedText(int start, int end) { 12183 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12184 } 12185 12186 @Override performLongClick()12187 public boolean performLongClick() { 12188 boolean handled = false; 12189 boolean performedHapticFeedback = false; 12190 12191 if (mEditor != null) { 12192 mEditor.mIsBeingLongClicked = true; 12193 } 12194 12195 if (super.performLongClick()) { 12196 handled = true; 12197 performedHapticFeedback = true; 12198 } 12199 12200 if (mEditor != null) { 12201 handled |= mEditor.performLongClick(handled); 12202 mEditor.mIsBeingLongClicked = false; 12203 } 12204 12205 if (handled) { 12206 if (!performedHapticFeedback) { 12207 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12208 } 12209 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12210 } else { 12211 MetricsLogger.action( 12212 mContext, 12213 MetricsEvent.TEXT_LONGPRESS, 12214 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12215 } 12216 12217 return handled; 12218 } 12219 12220 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12221 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12222 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12223 if (mEditor != null) { 12224 mEditor.onScrollChanged(); 12225 } 12226 } 12227 12228 /** 12229 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12230 * by the IME or by the spell checker as the user types. This is done by adding 12231 * {@link SuggestionSpan}s to the text. 12232 * 12233 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12234 * user asks for them on these parts of the text. This value depends on the inputType of this 12235 * TextView. 12236 * 12237 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12238 * 12239 * In addition, the type variation must be one of 12240 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12241 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12242 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12243 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12244 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12245 * 12246 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12247 * 12248 * @return true if the suggestions popup window is enabled, based on the inputType. 12249 */ isSuggestionsEnabled()12250 public boolean isSuggestionsEnabled() { 12251 if (mEditor == null) return false; 12252 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12253 return false; 12254 } 12255 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12256 12257 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12258 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12259 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12260 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12261 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12262 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12263 } 12264 12265 /** 12266 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12267 * selection is initiated in this View. 12268 * 12269 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12270 * Paste, Replace and Share actions, depending on what this View supports. 12271 * 12272 * <p>A custom implementation can add new entries in the default menu in its 12273 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12274 * method. The default actions can also be removed from the menu using 12275 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12276 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12277 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12278 * 12279 * <p>Returning false from 12280 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12281 * will prevent the action mode from being started. 12282 * 12283 * <p>Action click events should be handled by the custom implementation of 12284 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12285 * android.view.MenuItem)}. 12286 * 12287 * <p>Note that text selection mode is not started when a TextView receives focus and the 12288 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12289 * that case, to allow for quick replacement. 12290 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12291 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12292 createEditorIfNeeded(); 12293 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12294 } 12295 12296 /** 12297 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12298 * 12299 * @return The current custom selection callback. 12300 */ getCustomSelectionActionModeCallback()12301 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12302 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12303 } 12304 12305 /** 12306 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12307 * insertion is initiated in this View. 12308 * The standard implementation populates the menu with a subset of Select All, 12309 * Paste and Replace actions, depending on what this View supports. 12310 * 12311 * <p>A custom implementation can add new entries in the default menu in its 12312 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12313 * android.view.Menu)} method. The default actions can also be removed from the menu using 12314 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12315 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12316 * 12317 * <p>Returning false from 12318 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12319 * android.view.Menu)} will prevent the action mode from being started.</p> 12320 * 12321 * <p>Action click events should be handled by the custom implementation of 12322 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12323 * android.view.MenuItem)}.</p> 12324 * 12325 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12326 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12327 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12328 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12329 createEditorIfNeeded(); 12330 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12331 } 12332 12333 /** 12334 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12335 * 12336 * @return The current custom insertion callback. 12337 */ getCustomInsertionActionModeCallback()12338 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12339 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12340 } 12341 12342 /** 12343 * Sets the {@link TextClassifier} for this TextView. 12344 */ setTextClassifier(@ullable TextClassifier textClassifier)12345 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12346 mTextClassifier = textClassifier; 12347 } 12348 12349 /** 12350 * Returns the {@link TextClassifier} used by this TextView. 12351 * If no TextClassifier has been set, this TextView uses the default set by the 12352 * {@link TextClassificationManager}. 12353 */ 12354 @NonNull getTextClassifier()12355 public TextClassifier getTextClassifier() { 12356 if (mTextClassifier == null) { 12357 final TextClassificationManager tcm = 12358 mContext.getSystemService(TextClassificationManager.class); 12359 if (tcm != null) { 12360 return tcm.getTextClassifier(); 12361 } 12362 return TextClassifier.NO_OP; 12363 } 12364 return mTextClassifier; 12365 } 12366 12367 /** 12368 * Returns a session-aware text classifier. 12369 * This method creates one if none already exists or the current one is destroyed. 12370 */ 12371 @NonNull getTextClassificationSession()12372 TextClassifier getTextClassificationSession() { 12373 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12374 final TextClassificationManager tcm = 12375 mContext.getSystemService(TextClassificationManager.class); 12376 if (tcm != null) { 12377 final String widgetType; 12378 if (isTextEditable()) { 12379 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12380 } else if (isTextSelectable()) { 12381 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12382 } else { 12383 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12384 } 12385 mTextClassificationContext = new TextClassificationContext.Builder( 12386 mContext.getPackageName(), widgetType) 12387 .build(); 12388 if (mTextClassifier != null) { 12389 mTextClassificationSession = tcm.createTextClassificationSession( 12390 mTextClassificationContext, mTextClassifier); 12391 } else { 12392 mTextClassificationSession = tcm.createTextClassificationSession( 12393 mTextClassificationContext); 12394 } 12395 } else { 12396 mTextClassificationSession = TextClassifier.NO_OP; 12397 } 12398 } 12399 return mTextClassificationSession; 12400 } 12401 12402 /** 12403 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12404 * @see #getTextClassificationSession() 12405 */ 12406 @Nullable getTextClassificationContext()12407 TextClassificationContext getTextClassificationContext() { 12408 return mTextClassificationContext; 12409 } 12410 12411 /** 12412 * Returns true if this TextView uses a no-op TextClassifier. 12413 */ usesNoOpTextClassifier()12414 boolean usesNoOpTextClassifier() { 12415 return getTextClassifier() == TextClassifier.NO_OP; 12416 } 12417 12418 12419 /** 12420 * Starts an ActionMode for the specified TextLinkSpan. 12421 * 12422 * @return Whether or not we're attempting to start the action mode. 12423 * @hide 12424 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12425 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12426 Preconditions.checkNotNull(clickedSpan); 12427 12428 if (!(mText instanceof Spanned)) { 12429 return false; 12430 } 12431 12432 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12433 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12434 12435 if (start < 0 || end > mText.length() || start >= end) { 12436 return false; 12437 } 12438 12439 createEditorIfNeeded(); 12440 mEditor.startLinkActionModeAsync(start, end); 12441 return true; 12442 } 12443 12444 /** 12445 * Handles a click on the specified TextLinkSpan. 12446 * 12447 * @return Whether or not the click is being handled. 12448 * @hide 12449 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12450 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12451 Preconditions.checkNotNull(clickedSpan); 12452 if (mText instanceof Spanned) { 12453 final Spanned spanned = (Spanned) mText; 12454 final int start = spanned.getSpanStart(clickedSpan); 12455 final int end = spanned.getSpanEnd(clickedSpan); 12456 if (start >= 0 && end <= mText.length() && start < end) { 12457 final TextClassification.Request request = new TextClassification.Request.Builder( 12458 mText, start, end) 12459 .setDefaultLocales(getTextLocales()) 12460 .build(); 12461 final Supplier<TextClassification> supplier = () -> 12462 getTextClassifier().classifyText(request); 12463 final Consumer<TextClassification> consumer = classification -> { 12464 if (classification != null) { 12465 if (!classification.getActions().isEmpty()) { 12466 try { 12467 classification.getActions().get(0).getActionIntent().send(); 12468 } catch (PendingIntent.CanceledException e) { 12469 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12470 } 12471 } else { 12472 Log.d(LOG_TAG, "No link action to perform"); 12473 } 12474 } else { 12475 // classification == null 12476 Log.d(LOG_TAG, "Timeout while classifying text"); 12477 } 12478 }; 12479 CompletableFuture.supplyAsync(supplier) 12480 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12481 .thenAccept(consumer); 12482 return true; 12483 } 12484 } 12485 return false; 12486 } 12487 12488 /** 12489 * @hide 12490 */ 12491 @UnsupportedAppUsage stopTextActionMode()12492 protected void stopTextActionMode() { 12493 if (mEditor != null) { 12494 mEditor.stopTextActionMode(); 12495 } 12496 } 12497 12498 /** @hide */ hideFloatingToolbar(int durationMs)12499 public void hideFloatingToolbar(int durationMs) { 12500 if (mEditor != null) { 12501 mEditor.hideFloatingToolbar(durationMs); 12502 } 12503 } 12504 canUndo()12505 boolean canUndo() { 12506 return mEditor != null && mEditor.canUndo(); 12507 } 12508 canRedo()12509 boolean canRedo() { 12510 return mEditor != null && mEditor.canRedo(); 12511 } 12512 canCut()12513 boolean canCut() { 12514 if (hasPasswordTransformationMethod()) { 12515 return false; 12516 } 12517 12518 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12519 && mEditor.mKeyListener != null) { 12520 return true; 12521 } 12522 12523 return false; 12524 } 12525 canCopy()12526 boolean canCopy() { 12527 if (hasPasswordTransformationMethod()) { 12528 return false; 12529 } 12530 12531 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12532 return true; 12533 } 12534 12535 return false; 12536 } 12537 canShare()12538 boolean canShare() { 12539 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12540 return false; 12541 } 12542 return canCopy(); 12543 } 12544 isDeviceProvisioned()12545 boolean isDeviceProvisioned() { 12546 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12547 mDeviceProvisionedState = Settings.Global.getInt( 12548 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12549 ? DEVICE_PROVISIONED_YES 12550 : DEVICE_PROVISIONED_NO; 12551 } 12552 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12553 } 12554 12555 @UnsupportedAppUsage canPaste()12556 boolean canPaste() { 12557 return (mText instanceof Editable 12558 && mEditor != null && mEditor.mKeyListener != null 12559 && getSelectionStart() >= 0 12560 && getSelectionEnd() >= 0 12561 && getClipboardManagerForUser().hasPrimaryClip()); 12562 } 12563 canPasteAsPlainText()12564 boolean canPasteAsPlainText() { 12565 if (!canPaste()) { 12566 return false; 12567 } 12568 12569 final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); 12570 final ClipDescription description = clipData.getDescription(); 12571 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 12572 final CharSequence text = clipData.getItemAt(0).getText(); 12573 if (isPlainType && (text instanceof Spanned)) { 12574 Spanned spanned = (Spanned) text; 12575 if (TextUtils.hasStyleSpan(spanned)) { 12576 return true; 12577 } 12578 } 12579 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 12580 } 12581 canProcessText()12582 boolean canProcessText() { 12583 if (getId() == View.NO_ID) { 12584 return false; 12585 } 12586 return canShare(); 12587 } 12588 canSelectAllText()12589 boolean canSelectAllText() { 12590 return canSelectText() && !hasPasswordTransformationMethod() 12591 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 12592 } 12593 selectAllText()12594 boolean selectAllText() { 12595 if (mEditor != null) { 12596 // Hide the toolbar before changing the selection to avoid flickering. 12597 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 12598 } 12599 final int length = mText.length(); 12600 Selection.setSelection(mSpannable, 0, length); 12601 return length > 0; 12602 } 12603 replaceSelectionWithText(CharSequence text)12604 void replaceSelectionWithText(CharSequence text) { 12605 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 12606 } 12607 12608 /** 12609 * Paste clipboard content between min and max positions. 12610 */ paste(int min, int max, boolean withFormatting)12611 private void paste(int min, int max, boolean withFormatting) { 12612 ClipboardManager clipboard = getClipboardManagerForUser(); 12613 ClipData clip = clipboard.getPrimaryClip(); 12614 if (clip != null) { 12615 boolean didFirst = false; 12616 for (int i = 0; i < clip.getItemCount(); i++) { 12617 final CharSequence paste; 12618 if (withFormatting) { 12619 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 12620 } else { 12621 // Get an item as text and remove all spans by toString(). 12622 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 12623 paste = (text instanceof Spanned) ? text.toString() : text; 12624 } 12625 if (paste != null) { 12626 if (!didFirst) { 12627 Selection.setSelection(mSpannable, max); 12628 ((Editable) mText).replace(min, max, paste); 12629 didFirst = true; 12630 } else { 12631 ((Editable) mText).insert(getSelectionEnd(), "\n"); 12632 ((Editable) mText).insert(getSelectionEnd(), paste); 12633 } 12634 } 12635 } 12636 sLastCutCopyOrTextChangedTime = 0; 12637 } 12638 } 12639 shareSelectedText()12640 private void shareSelectedText() { 12641 String selectedText = getSelectedText(); 12642 if (selectedText != null && !selectedText.isEmpty()) { 12643 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 12644 sharingIntent.setType("text/plain"); 12645 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 12646 selectedText = TextUtils.trimToParcelableSize(selectedText); 12647 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 12648 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 12649 Selection.setSelection(mSpannable, getSelectionEnd()); 12650 } 12651 } 12652 12653 @CheckResult setPrimaryClip(ClipData clip)12654 private boolean setPrimaryClip(ClipData clip) { 12655 ClipboardManager clipboard = getClipboardManagerForUser(); 12656 try { 12657 clipboard.setPrimaryClip(clip); 12658 } catch (Throwable t) { 12659 return false; 12660 } 12661 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 12662 return true; 12663 } 12664 12665 /** 12666 * Get the character offset closest to the specified absolute position. A typical use case is to 12667 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 12668 * 12669 * @param x The horizontal absolute position of a point on screen 12670 * @param y The vertical absolute position of a point on screen 12671 * @return the character offset for the character whose position is closest to the specified 12672 * position. Returns -1 if there is no layout. 12673 */ getOffsetForPosition(float x, float y)12674 public int getOffsetForPosition(float x, float y) { 12675 if (getLayout() == null) return -1; 12676 final int line = getLineAtCoordinate(y); 12677 final int offset = getOffsetAtCoordinate(line, x); 12678 return offset; 12679 } 12680 convertToLocalHorizontalCoordinate(float x)12681 float convertToLocalHorizontalCoordinate(float x) { 12682 x -= getTotalPaddingLeft(); 12683 // Clamp the position to inside of the view. 12684 x = Math.max(0.0f, x); 12685 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 12686 x += getScrollX(); 12687 return x; 12688 } 12689 12690 @UnsupportedAppUsage getLineAtCoordinate(float y)12691 int getLineAtCoordinate(float y) { 12692 y -= getTotalPaddingTop(); 12693 // Clamp the position to inside of the view. 12694 y = Math.max(0.0f, y); 12695 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 12696 y += getScrollY(); 12697 return getLayout().getLineForVertical((int) y); 12698 } 12699 getLineAtCoordinateUnclamped(float y)12700 int getLineAtCoordinateUnclamped(float y) { 12701 y -= getTotalPaddingTop(); 12702 y += getScrollY(); 12703 return getLayout().getLineForVertical((int) y); 12704 } 12705 getOffsetAtCoordinate(int line, float x)12706 int getOffsetAtCoordinate(int line, float x) { 12707 x = convertToLocalHorizontalCoordinate(x); 12708 return getLayout().getOffsetForHorizontal(line, x); 12709 } 12710 12711 @Override onDragEvent(DragEvent event)12712 public boolean onDragEvent(DragEvent event) { 12713 switch (event.getAction()) { 12714 case DragEvent.ACTION_DRAG_STARTED: 12715 return mEditor != null && mEditor.hasInsertionController(); 12716 12717 case DragEvent.ACTION_DRAG_ENTERED: 12718 TextView.this.requestFocus(); 12719 return true; 12720 12721 case DragEvent.ACTION_DRAG_LOCATION: 12722 if (mText instanceof Spannable) { 12723 final int offset = getOffsetForPosition(event.getX(), event.getY()); 12724 Selection.setSelection(mSpannable, offset); 12725 } 12726 return true; 12727 12728 case DragEvent.ACTION_DROP: 12729 if (mEditor != null) mEditor.onDrop(event); 12730 return true; 12731 12732 case DragEvent.ACTION_DRAG_ENDED: 12733 case DragEvent.ACTION_DRAG_EXITED: 12734 default: 12735 return true; 12736 } 12737 } 12738 isInBatchEditMode()12739 boolean isInBatchEditMode() { 12740 if (mEditor == null) return false; 12741 final Editor.InputMethodState ims = mEditor.mInputMethodState; 12742 if (ims != null) { 12743 return ims.mBatchEditNesting > 0; 12744 } 12745 return mEditor.mInBatchEditControllers; 12746 } 12747 12748 @Override onRtlPropertiesChanged(int layoutDirection)12749 public void onRtlPropertiesChanged(int layoutDirection) { 12750 super.onRtlPropertiesChanged(layoutDirection); 12751 12752 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 12753 if (mTextDir != newTextDir) { 12754 mTextDir = newTextDir; 12755 if (mLayout != null) { 12756 checkForRelayout(); 12757 } 12758 } 12759 } 12760 12761 /** 12762 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 12763 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 12764 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 12765 * return value may not be the same as the one TextView uses if the View's layout direction is 12766 * not resolved or detached from parent root view. 12767 */ getTextDirectionHeuristic()12768 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 12769 if (hasPasswordTransformationMethod()) { 12770 // passwords fields should be LTR 12771 return TextDirectionHeuristics.LTR; 12772 } 12773 12774 if (mEditor != null 12775 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12776 == EditorInfo.TYPE_CLASS_PHONE) { 12777 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 12778 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 12779 // RTL digits. 12780 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 12781 final String zero = symbols.getDigitStrings()[0]; 12782 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 12783 // direction. 12784 final int firstCodepoint = zero.codePointAt(0); 12785 final byte digitDirection = Character.getDirectionality(firstCodepoint); 12786 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 12787 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 12788 return TextDirectionHeuristics.RTL; 12789 } else { 12790 return TextDirectionHeuristics.LTR; 12791 } 12792 } 12793 12794 // Always need to resolve layout direction first 12795 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 12796 12797 // Now, we can select the heuristic 12798 switch (getTextDirection()) { 12799 default: 12800 case TEXT_DIRECTION_FIRST_STRONG: 12801 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 12802 TextDirectionHeuristics.FIRSTSTRONG_LTR); 12803 case TEXT_DIRECTION_ANY_RTL: 12804 return TextDirectionHeuristics.ANYRTL_LTR; 12805 case TEXT_DIRECTION_LTR: 12806 return TextDirectionHeuristics.LTR; 12807 case TEXT_DIRECTION_RTL: 12808 return TextDirectionHeuristics.RTL; 12809 case TEXT_DIRECTION_LOCALE: 12810 return TextDirectionHeuristics.LOCALE; 12811 case TEXT_DIRECTION_FIRST_STRONG_LTR: 12812 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 12813 case TEXT_DIRECTION_FIRST_STRONG_RTL: 12814 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 12815 } 12816 } 12817 12818 /** 12819 * @hide 12820 */ 12821 @Override onResolveDrawables(int layoutDirection)12822 public void onResolveDrawables(int layoutDirection) { 12823 // No need to resolve twice 12824 if (mLastLayoutDirection == layoutDirection) { 12825 return; 12826 } 12827 mLastLayoutDirection = layoutDirection; 12828 12829 // Resolve drawables 12830 if (mDrawables != null) { 12831 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 12832 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 12833 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 12834 applyCompoundDrawableTint(); 12835 } 12836 } 12837 } 12838 12839 /** 12840 * Prepares a drawable for display by propagating layout direction and 12841 * drawable state. 12842 * 12843 * @param dr the drawable to prepare 12844 */ prepareDrawableForDisplay(@ullable Drawable dr)12845 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 12846 if (dr == null) { 12847 return; 12848 } 12849 12850 dr.setLayoutDirection(getLayoutDirection()); 12851 12852 if (dr.isStateful()) { 12853 dr.setState(getDrawableState()); 12854 dr.jumpToCurrentState(); 12855 } 12856 } 12857 12858 /** 12859 * @hide 12860 */ resetResolvedDrawables()12861 protected void resetResolvedDrawables() { 12862 super.resetResolvedDrawables(); 12863 mLastLayoutDirection = -1; 12864 } 12865 12866 /** 12867 * @hide 12868 */ viewClicked(InputMethodManager imm)12869 protected void viewClicked(InputMethodManager imm) { 12870 if (imm != null) { 12871 imm.viewClicked(this); 12872 } 12873 } 12874 12875 /** 12876 * Deletes the range of text [start, end[. 12877 * @hide 12878 */ 12879 @UnsupportedAppUsage deleteText_internal(int start, int end)12880 protected void deleteText_internal(int start, int end) { 12881 ((Editable) mText).delete(start, end); 12882 } 12883 12884 /** 12885 * Replaces the range of text [start, end[ by replacement text 12886 * @hide 12887 */ replaceText_internal(int start, int end, CharSequence text)12888 protected void replaceText_internal(int start, int end, CharSequence text) { 12889 ((Editable) mText).replace(start, end, text); 12890 } 12891 12892 /** 12893 * Sets a span on the specified range of text 12894 * @hide 12895 */ setSpan_internal(Object span, int start, int end, int flags)12896 protected void setSpan_internal(Object span, int start, int end, int flags) { 12897 ((Editable) mText).setSpan(span, start, end, flags); 12898 } 12899 12900 /** 12901 * Moves the cursor to the specified offset position in text 12902 * @hide 12903 */ setCursorPosition_internal(int start, int end)12904 protected void setCursorPosition_internal(int start, int end) { 12905 Selection.setSelection(((Editable) mText), start, end); 12906 } 12907 12908 /** 12909 * An Editor should be created as soon as any of the editable-specific fields (grouped 12910 * inside the Editor object) is assigned to a non-default value. 12911 * This method will create the Editor if needed. 12912 * 12913 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 12914 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 12915 * Editor for backward compatibility, as soon as one of these fields is assigned. 12916 * 12917 * Also note that for performance reasons, the mEditor is created when needed, but not 12918 * reset when no more edit-specific fields are needed. 12919 */ 12920 @UnsupportedAppUsage createEditorIfNeeded()12921 private void createEditorIfNeeded() { 12922 if (mEditor == null) { 12923 mEditor = new Editor(this); 12924 } 12925 } 12926 12927 /** 12928 * @hide 12929 */ 12930 @Override 12931 @UnsupportedAppUsage getIterableTextForAccessibility()12932 public CharSequence getIterableTextForAccessibility() { 12933 return mText; 12934 } 12935 ensureIterableTextForAccessibilitySelectable()12936 private void ensureIterableTextForAccessibilitySelectable() { 12937 if (!(mText instanceof Spannable)) { 12938 setText(mText, BufferType.SPANNABLE); 12939 } 12940 } 12941 12942 /** 12943 * @hide 12944 */ 12945 @Override getIteratorForGranularity(int granularity)12946 public TextSegmentIterator getIteratorForGranularity(int granularity) { 12947 switch (granularity) { 12948 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 12949 Spannable text = (Spannable) getIterableTextForAccessibility(); 12950 if (!TextUtils.isEmpty(text) && getLayout() != null) { 12951 AccessibilityIterators.LineTextSegmentIterator iterator = 12952 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 12953 iterator.initialize(text, getLayout()); 12954 return iterator; 12955 } 12956 } break; 12957 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 12958 Spannable text = (Spannable) getIterableTextForAccessibility(); 12959 if (!TextUtils.isEmpty(text) && getLayout() != null) { 12960 AccessibilityIterators.PageTextSegmentIterator iterator = 12961 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 12962 iterator.initialize(this); 12963 return iterator; 12964 } 12965 } break; 12966 } 12967 return super.getIteratorForGranularity(granularity); 12968 } 12969 12970 /** 12971 * @hide 12972 */ 12973 @Override getAccessibilitySelectionStart()12974 public int getAccessibilitySelectionStart() { 12975 return getSelectionStart(); 12976 } 12977 12978 /** 12979 * @hide 12980 */ isAccessibilitySelectionExtendable()12981 public boolean isAccessibilitySelectionExtendable() { 12982 return true; 12983 } 12984 12985 /** 12986 * @hide 12987 */ 12988 @Override getAccessibilitySelectionEnd()12989 public int getAccessibilitySelectionEnd() { 12990 return getSelectionEnd(); 12991 } 12992 12993 /** 12994 * @hide 12995 */ 12996 @Override setAccessibilitySelection(int start, int end)12997 public void setAccessibilitySelection(int start, int end) { 12998 if (getAccessibilitySelectionStart() == start 12999 && getAccessibilitySelectionEnd() == end) { 13000 return; 13001 } 13002 CharSequence text = getIterableTextForAccessibility(); 13003 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13004 Selection.setSelection((Spannable) text, start, end); 13005 } else { 13006 Selection.removeSelection((Spannable) text); 13007 } 13008 // Hide all selection controllers used for adjusting selection 13009 // since we are doing so explicitlty by other means and these 13010 // controllers interact with how selection behaves. 13011 if (mEditor != null) { 13012 mEditor.hideCursorAndSpanControllers(); 13013 mEditor.stopTextActionMode(); 13014 } 13015 } 13016 13017 /** @hide */ 13018 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13019 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13020 super.encodeProperties(stream); 13021 13022 TruncateAt ellipsize = getEllipsize(); 13023 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13024 stream.addProperty("text:textSize", getTextSize()); 13025 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13026 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13027 stream.addProperty("text:selectionStart", getSelectionStart()); 13028 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13029 stream.addProperty("text:curTextColor", mCurTextColor); 13030 stream.addProperty("text:text", mText == null ? null : mText.toString()); 13031 stream.addProperty("text:gravity", mGravity); 13032 } 13033 13034 /** 13035 * User interface state that is stored by TextView for implementing 13036 * {@link View#onSaveInstanceState}. 13037 */ 13038 public static class SavedState extends BaseSavedState { 13039 int selStart = -1; 13040 int selEnd = -1; 13041 @UnsupportedAppUsage 13042 CharSequence text; 13043 boolean frozenWithFocus; 13044 CharSequence error; 13045 ParcelableParcel editorState; // Optional state from Editor. 13046 SavedState(Parcelable superState)13047 SavedState(Parcelable superState) { 13048 super(superState); 13049 } 13050 13051 @Override writeToParcel(Parcel out, int flags)13052 public void writeToParcel(Parcel out, int flags) { 13053 super.writeToParcel(out, flags); 13054 out.writeInt(selStart); 13055 out.writeInt(selEnd); 13056 out.writeInt(frozenWithFocus ? 1 : 0); 13057 TextUtils.writeToParcel(text, out, flags); 13058 13059 if (error == null) { 13060 out.writeInt(0); 13061 } else { 13062 out.writeInt(1); 13063 TextUtils.writeToParcel(error, out, flags); 13064 } 13065 13066 if (editorState == null) { 13067 out.writeInt(0); 13068 } else { 13069 out.writeInt(1); 13070 editorState.writeToParcel(out, flags); 13071 } 13072 } 13073 13074 @Override toString()13075 public String toString() { 13076 String str = "TextView.SavedState{" 13077 + Integer.toHexString(System.identityHashCode(this)) 13078 + " start=" + selStart + " end=" + selEnd; 13079 if (text != null) { 13080 str += " text=" + text; 13081 } 13082 return str + "}"; 13083 } 13084 13085 @SuppressWarnings("hiding") 13086 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13087 new Parcelable.Creator<SavedState>() { 13088 public SavedState createFromParcel(Parcel in) { 13089 return new SavedState(in); 13090 } 13091 13092 public SavedState[] newArray(int size) { 13093 return new SavedState[size]; 13094 } 13095 }; 13096 SavedState(Parcel in)13097 private SavedState(Parcel in) { 13098 super(in); 13099 selStart = in.readInt(); 13100 selEnd = in.readInt(); 13101 frozenWithFocus = (in.readInt() != 0); 13102 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13103 13104 if (in.readInt() != 0) { 13105 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13106 } 13107 13108 if (in.readInt() != 0) { 13109 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13110 } 13111 } 13112 } 13113 13114 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13115 private char[] mChars; 13116 private int mStart, mLength; 13117 CharWrapper(char[] chars, int start, int len)13118 public CharWrapper(char[] chars, int start, int len) { 13119 mChars = chars; 13120 mStart = start; 13121 mLength = len; 13122 } 13123 set(char[] chars, int start, int len)13124 /* package */ void set(char[] chars, int start, int len) { 13125 mChars = chars; 13126 mStart = start; 13127 mLength = len; 13128 } 13129 length()13130 public int length() { 13131 return mLength; 13132 } 13133 charAt(int off)13134 public char charAt(int off) { 13135 return mChars[off + mStart]; 13136 } 13137 13138 @Override toString()13139 public String toString() { 13140 return new String(mChars, mStart, mLength); 13141 } 13142 subSequence(int start, int end)13143 public CharSequence subSequence(int start, int end) { 13144 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13145 throw new IndexOutOfBoundsException(start + ", " + end); 13146 } 13147 13148 return new String(mChars, start + mStart, end - start); 13149 } 13150 getChars(int start, int end, char[] buf, int off)13151 public void getChars(int start, int end, char[] buf, int off) { 13152 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13153 throw new IndexOutOfBoundsException(start + ", " + end); 13154 } 13155 13156 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13157 } 13158 13159 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13160 public void drawText(BaseCanvas c, int start, int end, 13161 float x, float y, Paint p) { 13162 c.drawText(mChars, start + mStart, end - start, x, y, p); 13163 } 13164 13165 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13166 public void drawTextRun(BaseCanvas c, int start, int end, 13167 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13168 int count = end - start; 13169 int contextCount = contextEnd - contextStart; 13170 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13171 contextCount, x, y, isRtl, p); 13172 } 13173 measureText(int start, int end, Paint p)13174 public float measureText(int start, int end, Paint p) { 13175 return p.measureText(mChars, start + mStart, end - start); 13176 } 13177 getTextWidths(int start, int end, float[] widths, Paint p)13178 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13179 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13180 } 13181 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13182 public float getTextRunAdvances(int start, int end, int contextStart, 13183 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13184 Paint p) { 13185 int count = end - start; 13186 int contextCount = contextEnd - contextStart; 13187 return p.getTextRunAdvances(mChars, start + mStart, count, 13188 contextStart + mStart, contextCount, isRtl, advances, 13189 advancesIndex); 13190 } 13191 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13192 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13193 int offset, int cursorOpt, Paint p) { 13194 int contextCount = contextEnd - contextStart; 13195 return p.getTextRunCursor(mChars, contextStart + mStart, 13196 contextCount, isRtl, offset + mStart, cursorOpt); 13197 } 13198 } 13199 13200 private static final class Marquee { 13201 // TODO: Add an option to configure this 13202 private static final float MARQUEE_DELTA_MAX = 0.07f; 13203 private static final int MARQUEE_DELAY = 1200; 13204 private static final int MARQUEE_DP_PER_SECOND = 30; 13205 13206 private static final byte MARQUEE_STOPPED = 0x0; 13207 private static final byte MARQUEE_STARTING = 0x1; 13208 private static final byte MARQUEE_RUNNING = 0x2; 13209 13210 private final WeakReference<TextView> mView; 13211 private final Choreographer mChoreographer; 13212 13213 private byte mStatus = MARQUEE_STOPPED; 13214 private final float mPixelsPerMs; 13215 private float mMaxScroll; 13216 private float mMaxFadeScroll; 13217 private float mGhostStart; 13218 private float mGhostOffset; 13219 private float mFadeStop; 13220 private int mRepeatLimit; 13221 13222 private float mScroll; 13223 private long mLastAnimationMs; 13224 Marquee(TextView v)13225 Marquee(TextView v) { 13226 final float density = v.getContext().getResources().getDisplayMetrics().density; 13227 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13228 mView = new WeakReference<TextView>(v); 13229 mChoreographer = Choreographer.getInstance(); 13230 } 13231 13232 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13233 @Override 13234 public void doFrame(long frameTimeNanos) { 13235 tick(); 13236 } 13237 }; 13238 13239 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13240 @Override 13241 public void doFrame(long frameTimeNanos) { 13242 mStatus = MARQUEE_RUNNING; 13243 mLastAnimationMs = mChoreographer.getFrameTime(); 13244 tick(); 13245 } 13246 }; 13247 13248 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13249 @Override 13250 public void doFrame(long frameTimeNanos) { 13251 if (mStatus == MARQUEE_RUNNING) { 13252 if (mRepeatLimit >= 0) { 13253 mRepeatLimit--; 13254 } 13255 start(mRepeatLimit); 13256 } 13257 } 13258 }; 13259 tick()13260 void tick() { 13261 if (mStatus != MARQUEE_RUNNING) { 13262 return; 13263 } 13264 13265 mChoreographer.removeFrameCallback(mTickCallback); 13266 13267 final TextView textView = mView.get(); 13268 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13269 long currentMs = mChoreographer.getFrameTime(); 13270 long deltaMs = currentMs - mLastAnimationMs; 13271 mLastAnimationMs = currentMs; 13272 float deltaPx = deltaMs * mPixelsPerMs; 13273 mScroll += deltaPx; 13274 if (mScroll > mMaxScroll) { 13275 mScroll = mMaxScroll; 13276 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13277 } else { 13278 mChoreographer.postFrameCallback(mTickCallback); 13279 } 13280 textView.invalidate(); 13281 } 13282 } 13283 stop()13284 void stop() { 13285 mStatus = MARQUEE_STOPPED; 13286 mChoreographer.removeFrameCallback(mStartCallback); 13287 mChoreographer.removeFrameCallback(mRestartCallback); 13288 mChoreographer.removeFrameCallback(mTickCallback); 13289 resetScroll(); 13290 } 13291 resetScroll()13292 private void resetScroll() { 13293 mScroll = 0.0f; 13294 final TextView textView = mView.get(); 13295 if (textView != null) textView.invalidate(); 13296 } 13297 start(int repeatLimit)13298 void start(int repeatLimit) { 13299 if (repeatLimit == 0) { 13300 stop(); 13301 return; 13302 } 13303 mRepeatLimit = repeatLimit; 13304 final TextView textView = mView.get(); 13305 if (textView != null && textView.mLayout != null) { 13306 mStatus = MARQUEE_STARTING; 13307 mScroll = 0.0f; 13308 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13309 - textView.getCompoundPaddingRight(); 13310 final float lineWidth = textView.mLayout.getLineWidth(0); 13311 final float gap = textWidth / 3.0f; 13312 mGhostStart = lineWidth - textWidth + gap; 13313 mMaxScroll = mGhostStart + textWidth; 13314 mGhostOffset = lineWidth + gap; 13315 mFadeStop = lineWidth + textWidth / 6.0f; 13316 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13317 13318 textView.invalidate(); 13319 mChoreographer.postFrameCallback(mStartCallback); 13320 } 13321 } 13322 getGhostOffset()13323 float getGhostOffset() { 13324 return mGhostOffset; 13325 } 13326 getScroll()13327 float getScroll() { 13328 return mScroll; 13329 } 13330 getMaxFadeScroll()13331 float getMaxFadeScroll() { 13332 return mMaxFadeScroll; 13333 } 13334 shouldDrawLeftFade()13335 boolean shouldDrawLeftFade() { 13336 return mScroll <= mFadeStop; 13337 } 13338 shouldDrawGhost()13339 boolean shouldDrawGhost() { 13340 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13341 } 13342 isRunning()13343 boolean isRunning() { 13344 return mStatus == MARQUEE_RUNNING; 13345 } 13346 isStopped()13347 boolean isStopped() { 13348 return mStatus == MARQUEE_STOPPED; 13349 } 13350 } 13351 13352 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13353 13354 private CharSequence mBeforeText; 13355 beforeTextChanged(CharSequence buffer, int start, int before, int after)13356 public void beforeTextChanged(CharSequence buffer, int start, 13357 int before, int after) { 13358 if (DEBUG_EXTRACT) { 13359 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13360 + " before=" + before + " after=" + after + ": " + buffer); 13361 } 13362 13363 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13364 mBeforeText = mTransformed.toString(); 13365 } 13366 13367 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13368 } 13369 onTextChanged(CharSequence buffer, int start, int before, int after)13370 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13371 if (DEBUG_EXTRACT) { 13372 Log.v(LOG_TAG, "onTextChanged start=" + start 13373 + " before=" + before + " after=" + after + ": " + buffer); 13374 } 13375 TextView.this.handleTextChanged(buffer, start, before, after); 13376 13377 if (AccessibilityManager.getInstance(mContext).isEnabled() 13378 && (isFocused() || isSelected() && isShown())) { 13379 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13380 mBeforeText = null; 13381 } 13382 } 13383 afterTextChanged(Editable buffer)13384 public void afterTextChanged(Editable buffer) { 13385 if (DEBUG_EXTRACT) { 13386 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13387 } 13388 TextView.this.sendAfterTextChanged(buffer); 13389 13390 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13391 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13392 } 13393 } 13394 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13395 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13396 if (DEBUG_EXTRACT) { 13397 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13398 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13399 } 13400 TextView.this.spanChange(buf, what, s, st, e, en); 13401 } 13402 onSpanAdded(Spannable buf, Object what, int s, int e)13403 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13404 if (DEBUG_EXTRACT) { 13405 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13406 } 13407 TextView.this.spanChange(buf, what, -1, s, -1, e); 13408 } 13409 onSpanRemoved(Spannable buf, Object what, int s, int e)13410 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13411 if (DEBUG_EXTRACT) { 13412 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13413 } 13414 TextView.this.spanChange(buf, what, s, -1, e, -1); 13415 } 13416 } 13417 } 13418