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