1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; 21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 23 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 24 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 25 26 import android.R; 27 import android.annotation.CallSuper; 28 import android.annotation.CheckResult; 29 import android.annotation.ColorInt; 30 import android.annotation.DrawableRes; 31 import android.annotation.FloatRange; 32 import android.annotation.IntDef; 33 import android.annotation.IntRange; 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.Px; 37 import android.annotation.RequiresPermission; 38 import android.annotation.Size; 39 import android.annotation.StringRes; 40 import android.annotation.StyleRes; 41 import android.annotation.XmlRes; 42 import android.app.Activity; 43 import android.app.PendingIntent; 44 import android.app.assist.AssistStructure; 45 import android.compat.annotation.UnsupportedAppUsage; 46 import android.content.ClipData; 47 import android.content.ClipDescription; 48 import android.content.ClipboardManager; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.UndoManager; 52 import android.content.pm.PackageManager; 53 import android.content.res.ColorStateList; 54 import android.content.res.CompatibilityInfo; 55 import android.content.res.Configuration; 56 import android.content.res.Resources; 57 import android.content.res.TypedArray; 58 import android.content.res.XmlResourceParser; 59 import android.graphics.BaseCanvas; 60 import android.graphics.BlendMode; 61 import android.graphics.Canvas; 62 import android.graphics.Insets; 63 import android.graphics.Paint; 64 import android.graphics.Paint.FontMetricsInt; 65 import android.graphics.Path; 66 import android.graphics.PorterDuff; 67 import android.graphics.Rect; 68 import android.graphics.RectF; 69 import android.graphics.Typeface; 70 import android.graphics.drawable.Drawable; 71 import android.graphics.fonts.FontStyle; 72 import android.graphics.fonts.FontVariationAxis; 73 import android.icu.text.DecimalFormatSymbols; 74 import android.os.AsyncTask; 75 import android.os.Build; 76 import android.os.Build.VERSION_CODES; 77 import android.os.Bundle; 78 import android.os.LocaleList; 79 import android.os.Parcel; 80 import android.os.Parcelable; 81 import android.os.ParcelableParcel; 82 import android.os.Process; 83 import android.os.SystemClock; 84 import android.os.UserHandle; 85 import android.provider.Settings; 86 import android.text.BoringLayout; 87 import android.text.DynamicLayout; 88 import android.text.Editable; 89 import android.text.GetChars; 90 import android.text.GraphicsOperations; 91 import android.text.InputFilter; 92 import android.text.InputType; 93 import android.text.Layout; 94 import android.text.ParcelableSpan; 95 import android.text.PrecomputedText; 96 import android.text.Selection; 97 import android.text.SpanWatcher; 98 import android.text.Spannable; 99 import android.text.SpannableStringBuilder; 100 import android.text.Spanned; 101 import android.text.SpannedString; 102 import android.text.StaticLayout; 103 import android.text.TextDirectionHeuristic; 104 import android.text.TextDirectionHeuristics; 105 import android.text.TextPaint; 106 import android.text.TextUtils; 107 import android.text.TextUtils.TruncateAt; 108 import android.text.TextWatcher; 109 import android.text.method.AllCapsTransformationMethod; 110 import android.text.method.ArrowKeyMovementMethod; 111 import android.text.method.DateKeyListener; 112 import android.text.method.DateTimeKeyListener; 113 import android.text.method.DialerKeyListener; 114 import android.text.method.DigitsKeyListener; 115 import android.text.method.KeyListener; 116 import android.text.method.LinkMovementMethod; 117 import android.text.method.MetaKeyKeyListener; 118 import android.text.method.MovementMethod; 119 import android.text.method.PasswordTransformationMethod; 120 import android.text.method.SingleLineTransformationMethod; 121 import android.text.method.TextKeyListener; 122 import android.text.method.TimeKeyListener; 123 import android.text.method.TransformationMethod; 124 import android.text.method.TransformationMethod2; 125 import android.text.method.WordIterator; 126 import android.text.style.CharacterStyle; 127 import android.text.style.ClickableSpan; 128 import android.text.style.ParagraphStyle; 129 import android.text.style.SpellCheckSpan; 130 import android.text.style.SuggestionSpan; 131 import android.text.style.URLSpan; 132 import android.text.style.UpdateAppearance; 133 import android.text.util.Linkify; 134 import android.util.AttributeSet; 135 import android.util.DisplayMetrics; 136 import android.util.IntArray; 137 import android.util.Log; 138 import android.util.SparseIntArray; 139 import android.util.TypedValue; 140 import android.view.AccessibilityIterators.TextSegmentIterator; 141 import android.view.ActionMode; 142 import android.view.Choreographer; 143 import android.view.ContextMenu; 144 import android.view.DragEvent; 145 import android.view.Gravity; 146 import android.view.HapticFeedbackConstants; 147 import android.view.InputDevice; 148 import android.view.KeyCharacterMap; 149 import android.view.KeyEvent; 150 import android.view.MotionEvent; 151 import android.view.PointerIcon; 152 import android.view.View; 153 import android.view.ViewConfiguration; 154 import android.view.ViewDebug; 155 import android.view.ViewGroup.LayoutParams; 156 import android.view.ViewHierarchyEncoder; 157 import android.view.ViewParent; 158 import android.view.ViewRootImpl; 159 import android.view.ViewStructure; 160 import android.view.ViewTreeObserver; 161 import android.view.accessibility.AccessibilityEvent; 162 import android.view.accessibility.AccessibilityManager; 163 import android.view.accessibility.AccessibilityNodeInfo; 164 import android.view.animation.AnimationUtils; 165 import android.view.autofill.AutofillManager; 166 import android.view.autofill.AutofillValue; 167 import android.view.contentcapture.ContentCaptureManager; 168 import android.view.contentcapture.ContentCaptureSession; 169 import android.view.inputmethod.BaseInputConnection; 170 import android.view.inputmethod.CompletionInfo; 171 import android.view.inputmethod.CorrectionInfo; 172 import android.view.inputmethod.CursorAnchorInfo; 173 import android.view.inputmethod.EditorInfo; 174 import android.view.inputmethod.ExtractedText; 175 import android.view.inputmethod.ExtractedTextRequest; 176 import android.view.inputmethod.InputConnection; 177 import android.view.inputmethod.InputMethodManager; 178 import android.view.inspector.InspectableProperty; 179 import android.view.inspector.InspectableProperty.EnumEntry; 180 import android.view.inspector.InspectableProperty.FlagEntry; 181 import android.view.textclassifier.TextClassification; 182 import android.view.textclassifier.TextClassificationContext; 183 import android.view.textclassifier.TextClassificationManager; 184 import android.view.textclassifier.TextClassifier; 185 import android.view.textclassifier.TextLinks; 186 import android.view.textservice.SpellCheckerSubtype; 187 import android.view.textservice.TextServicesManager; 188 import android.widget.RemoteViews.RemoteView; 189 190 import com.android.internal.annotations.VisibleForTesting; 191 import com.android.internal.logging.MetricsLogger; 192 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 193 import com.android.internal.util.FastMath; 194 import com.android.internal.util.Preconditions; 195 import com.android.internal.widget.EditableInputConnection; 196 197 import libcore.util.EmptyArray; 198 199 import org.xmlpull.v1.XmlPullParserException; 200 201 import java.io.IOException; 202 import java.lang.annotation.Retention; 203 import java.lang.annotation.RetentionPolicy; 204 import java.lang.ref.WeakReference; 205 import java.util.ArrayList; 206 import java.util.Arrays; 207 import java.util.Locale; 208 import java.util.Objects; 209 import java.util.concurrent.CompletableFuture; 210 import java.util.concurrent.TimeUnit; 211 import java.util.function.Consumer; 212 import java.util.function.Supplier; 213 214 /** 215 * A user interface element that displays text to the user. 216 * To provide user-editable text, see {@link EditText}. 217 * <p> 218 * The following code sample shows a typical use, with an XML layout 219 * and code to modify the contents of the text view: 220 * </p> 221 222 * <pre> 223 * <LinearLayout 224 xmlns:android="http://schemas.android.com/apk/res/android" 225 android:layout_width="match_parent" 226 android:layout_height="match_parent"> 227 * <TextView 228 * android:id="@+id/text_view_id" 229 * android:layout_height="wrap_content" 230 * android:layout_width="wrap_content" 231 * android:text="@string/hello" /> 232 * </LinearLayout> 233 * </pre> 234 * <p> 235 * This code sample demonstrates how to modify the contents of the text view 236 * defined in the previous XML layout: 237 * </p> 238 * <pre> 239 * public class MainActivity extends Activity { 240 * 241 * protected void onCreate(Bundle savedInstanceState) { 242 * super.onCreate(savedInstanceState); 243 * setContentView(R.layout.activity_main); 244 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 245 * helloTextView.setText(R.string.user_greeting); 246 * } 247 * } 248 * </pre> 249 * <p> 250 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 251 * </p> 252 * <p> 253 * <b>XML attributes</b> 254 * <p> 255 * See {@link android.R.styleable#TextView TextView Attributes}, 256 * {@link android.R.styleable#View View Attributes} 257 * 258 * @attr ref android.R.styleable#TextView_text 259 * @attr ref android.R.styleable#TextView_bufferType 260 * @attr ref android.R.styleable#TextView_hint 261 * @attr ref android.R.styleable#TextView_textColor 262 * @attr ref android.R.styleable#TextView_textColorHighlight 263 * @attr ref android.R.styleable#TextView_textColorHint 264 * @attr ref android.R.styleable#TextView_textAppearance 265 * @attr ref android.R.styleable#TextView_textColorLink 266 * @attr ref android.R.styleable#TextView_textFontWeight 267 * @attr ref android.R.styleable#TextView_textSize 268 * @attr ref android.R.styleable#TextView_textScaleX 269 * @attr ref android.R.styleable#TextView_fontFamily 270 * @attr ref android.R.styleable#TextView_typeface 271 * @attr ref android.R.styleable#TextView_textStyle 272 * @attr ref android.R.styleable#TextView_cursorVisible 273 * @attr ref android.R.styleable#TextView_maxLines 274 * @attr ref android.R.styleable#TextView_maxHeight 275 * @attr ref android.R.styleable#TextView_lines 276 * @attr ref android.R.styleable#TextView_height 277 * @attr ref android.R.styleable#TextView_minLines 278 * @attr ref android.R.styleable#TextView_minHeight 279 * @attr ref android.R.styleable#TextView_maxEms 280 * @attr ref android.R.styleable#TextView_maxWidth 281 * @attr ref android.R.styleable#TextView_ems 282 * @attr ref android.R.styleable#TextView_width 283 * @attr ref android.R.styleable#TextView_minEms 284 * @attr ref android.R.styleable#TextView_minWidth 285 * @attr ref android.R.styleable#TextView_gravity 286 * @attr ref android.R.styleable#TextView_scrollHorizontally 287 * @attr ref android.R.styleable#TextView_password 288 * @attr ref android.R.styleable#TextView_singleLine 289 * @attr ref android.R.styleable#TextView_selectAllOnFocus 290 * @attr ref android.R.styleable#TextView_includeFontPadding 291 * @attr ref android.R.styleable#TextView_maxLength 292 * @attr ref android.R.styleable#TextView_shadowColor 293 * @attr ref android.R.styleable#TextView_shadowDx 294 * @attr ref android.R.styleable#TextView_shadowDy 295 * @attr ref android.R.styleable#TextView_shadowRadius 296 * @attr ref android.R.styleable#TextView_autoLink 297 * @attr ref android.R.styleable#TextView_linksClickable 298 * @attr ref android.R.styleable#TextView_numeric 299 * @attr ref android.R.styleable#TextView_digits 300 * @attr ref android.R.styleable#TextView_phoneNumber 301 * @attr ref android.R.styleable#TextView_inputMethod 302 * @attr ref android.R.styleable#TextView_capitalize 303 * @attr ref android.R.styleable#TextView_autoText 304 * @attr ref android.R.styleable#TextView_editable 305 * @attr ref android.R.styleable#TextView_freezesText 306 * @attr ref android.R.styleable#TextView_ellipsize 307 * @attr ref android.R.styleable#TextView_drawableTop 308 * @attr ref android.R.styleable#TextView_drawableBottom 309 * @attr ref android.R.styleable#TextView_drawableRight 310 * @attr ref android.R.styleable#TextView_drawableLeft 311 * @attr ref android.R.styleable#TextView_drawableStart 312 * @attr ref android.R.styleable#TextView_drawableEnd 313 * @attr ref android.R.styleable#TextView_drawablePadding 314 * @attr ref android.R.styleable#TextView_drawableTint 315 * @attr ref android.R.styleable#TextView_drawableTintMode 316 * @attr ref android.R.styleable#TextView_lineSpacingExtra 317 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 318 * @attr ref android.R.styleable#TextView_justificationMode 319 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 320 * @attr ref android.R.styleable#TextView_inputType 321 * @attr ref android.R.styleable#TextView_imeOptions 322 * @attr ref android.R.styleable#TextView_privateImeOptions 323 * @attr ref android.R.styleable#TextView_imeActionLabel 324 * @attr ref android.R.styleable#TextView_imeActionId 325 * @attr ref android.R.styleable#TextView_editorExtras 326 * @attr ref android.R.styleable#TextView_elegantTextHeight 327 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 328 * @attr ref android.R.styleable#TextView_letterSpacing 329 * @attr ref android.R.styleable#TextView_fontFeatureSettings 330 * @attr ref android.R.styleable#TextView_fontVariationSettings 331 * @attr ref android.R.styleable#TextView_breakStrategy 332 * @attr ref android.R.styleable#TextView_hyphenationFrequency 333 * @attr ref android.R.styleable#TextView_autoSizeTextType 334 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 335 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 336 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 337 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 338 * @attr ref android.R.styleable#TextView_textCursorDrawable 339 * @attr ref android.R.styleable#TextView_textSelectHandle 340 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 341 * @attr ref android.R.styleable#TextView_textSelectHandleRight 342 * @attr ref android.R.styleable#TextView_allowUndo 343 * @attr ref android.R.styleable#TextView_enabled 344 */ 345 @RemoteView 346 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 347 static final String LOG_TAG = "TextView"; 348 static final boolean DEBUG_EXTRACT = false; 349 static final boolean DEBUG_CURSOR = false; 350 351 private static final float[] TEMP_POSITION = new float[2]; 352 353 // Enum for the "typeface" XML parameter. 354 // TODO: How can we get this from the XML instead of hardcoding it here? 355 /** @hide */ 356 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 357 @Retention(RetentionPolicy.SOURCE) 358 public @interface XMLTypefaceAttr{} 359 private static final int DEFAULT_TYPEFACE = -1; 360 private static final int SANS = 1; 361 private static final int SERIF = 2; 362 private static final int MONOSPACE = 3; 363 364 // Enum for the "ellipsize" XML parameter. 365 private static final int ELLIPSIZE_NOT_SET = -1; 366 private static final int ELLIPSIZE_NONE = 0; 367 private static final int ELLIPSIZE_START = 1; 368 private static final int ELLIPSIZE_MIDDLE = 2; 369 private static final int ELLIPSIZE_END = 3; 370 private static final int ELLIPSIZE_MARQUEE = 4; 371 372 // Bitfield for the "numeric" XML parameter. 373 // TODO: How can we get this from the XML instead of hardcoding it here? 374 private static final int SIGNED = 2; 375 private static final int DECIMAL = 4; 376 377 /** 378 * Draw marquee text with fading edges as usual 379 */ 380 private static final int MARQUEE_FADE_NORMAL = 0; 381 382 /** 383 * Draw marquee text as ellipsize end while inactive instead of with the fade. 384 * (Useful for devices where the fade can be expensive if overdone) 385 */ 386 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 387 388 /** 389 * Draw marquee text with fading edges because it is currently active/animating. 390 */ 391 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 392 393 @UnsupportedAppUsage 394 private static final int LINES = 1; 395 private static final int EMS = LINES; 396 private static final int PIXELS = 2; 397 398 private static final RectF TEMP_RECTF = new RectF(); 399 400 /** @hide */ 401 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 402 private static final int ANIMATED_SCROLL_GAP = 250; 403 404 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 405 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 406 407 private static final int CHANGE_WATCHER_PRIORITY = 100; 408 409 // New state used to change background based on whether this TextView is multiline. 410 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 411 412 // Accessibility action to share selected text. 413 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 414 415 /** 416 * @hide 417 */ 418 // Accessibility action start id for "process text" actions. 419 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 420 421 /** 422 * @hide 423 */ 424 static final int PROCESS_TEXT_REQUEST_CODE = 100; 425 426 /** 427 * Return code of {@link #doKeyDown}. 428 */ 429 private static final int KEY_EVENT_NOT_HANDLED = 0; 430 private static final int KEY_EVENT_HANDLED = -1; 431 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 432 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 433 434 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 435 436 // System wide time for last cut, copy or text changed action. 437 static long sLastCutCopyOrTextChangedTime; 438 439 private ColorStateList mTextColor; 440 private ColorStateList mHintTextColor; 441 private ColorStateList mLinkTextColor; 442 @ViewDebug.ExportedProperty(category = "text") 443 444 /** 445 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 446 */ 447 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 448 private int mCurTextColor; 449 450 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 451 private int mCurHintTextColor; 452 private boolean mFreezesText; 453 454 @UnsupportedAppUsage 455 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 456 @UnsupportedAppUsage 457 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 458 459 @UnsupportedAppUsage 460 private float mShadowRadius; 461 @UnsupportedAppUsage 462 private float mShadowDx; 463 @UnsupportedAppUsage 464 private float mShadowDy; 465 private int mShadowColor; 466 467 private boolean mPreDrawRegistered; 468 private boolean mPreDrawListenerDetached; 469 470 private TextClassifier mTextClassifier; 471 private TextClassifier mTextClassificationSession; 472 private TextClassificationContext mTextClassificationContext; 473 474 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 475 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 476 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 477 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 478 // into by the user holding the movement key down) then we shouldn't prevent the focus from 479 // changing. 480 private boolean mPreventDefaultMovement; 481 482 private TextUtils.TruncateAt mEllipsize; 483 484 static class Drawables { 485 static final int LEFT = 0; 486 static final int TOP = 1; 487 static final int RIGHT = 2; 488 static final int BOTTOM = 3; 489 490 static final int DRAWABLE_NONE = -1; 491 static final int DRAWABLE_RIGHT = 0; 492 static final int DRAWABLE_LEFT = 1; 493 494 final Rect mCompoundRect = new Rect(); 495 496 final Drawable[] mShowing = new Drawable[4]; 497 498 ColorStateList mTintList; 499 BlendMode mBlendMode; 500 boolean mHasTint; 501 boolean mHasTintMode; 502 503 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 504 Drawable mDrawableLeftInitial, mDrawableRightInitial; 505 506 boolean mIsRtlCompatibilityMode; 507 boolean mOverride; 508 509 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 510 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 511 512 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 513 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 514 515 int mDrawablePadding; 516 517 int mDrawableSaved = DRAWABLE_NONE; 518 Drawables(Context context)519 public Drawables(Context context) { 520 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 521 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 522 || !context.getApplicationInfo().hasRtlSupport(); 523 mOverride = false; 524 } 525 526 /** 527 * @return {@code true} if this object contains metadata that needs to 528 * be retained, {@code false} otherwise 529 */ 530 public boolean hasMetadata() { 531 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 532 } 533 534 /** 535 * Updates the list of displayed drawables to account for the current 536 * layout direction. 537 * 538 * @param layoutDirection the current layout direction 539 * @return {@code true} if the displayed drawables changed 540 */ 541 public boolean resolveWithLayoutDirection(int layoutDirection) { 542 final Drawable previousLeft = mShowing[Drawables.LEFT]; 543 final Drawable previousRight = mShowing[Drawables.RIGHT]; 544 545 // First reset "left" and "right" drawables to their initial values 546 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 547 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 548 549 if (mIsRtlCompatibilityMode) { 550 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 551 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 552 mShowing[Drawables.LEFT] = mDrawableStart; 553 mDrawableSizeLeft = mDrawableSizeStart; 554 mDrawableHeightLeft = mDrawableHeightStart; 555 } 556 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 557 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 558 mShowing[Drawables.RIGHT] = mDrawableEnd; 559 mDrawableSizeRight = mDrawableSizeEnd; 560 mDrawableHeightRight = mDrawableHeightEnd; 561 } 562 } else { 563 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 564 // drawable if and only if they have been defined 565 switch(layoutDirection) { 566 case LAYOUT_DIRECTION_RTL: 567 if (mOverride) { 568 mShowing[Drawables.RIGHT] = mDrawableStart; 569 mDrawableSizeRight = mDrawableSizeStart; 570 mDrawableHeightRight = mDrawableHeightStart; 571 572 mShowing[Drawables.LEFT] = mDrawableEnd; 573 mDrawableSizeLeft = mDrawableSizeEnd; 574 mDrawableHeightLeft = mDrawableHeightEnd; 575 } 576 break; 577 578 case LAYOUT_DIRECTION_LTR: 579 default: 580 if (mOverride) { 581 mShowing[Drawables.LEFT] = mDrawableStart; 582 mDrawableSizeLeft = mDrawableSizeStart; 583 mDrawableHeightLeft = mDrawableHeightStart; 584 585 mShowing[Drawables.RIGHT] = mDrawableEnd; 586 mDrawableSizeRight = mDrawableSizeEnd; 587 mDrawableHeightRight = mDrawableHeightEnd; 588 } 589 break; 590 } 591 } 592 593 applyErrorDrawableIfNeeded(layoutDirection); 594 595 return mShowing[Drawables.LEFT] != previousLeft 596 || mShowing[Drawables.RIGHT] != previousRight; 597 } 598 599 public void setErrorDrawable(Drawable dr, TextView tv) { 600 if (mDrawableError != dr && mDrawableError != null) { 601 mDrawableError.setCallback(null); 602 } 603 mDrawableError = dr; 604 605 if (mDrawableError != null) { 606 final Rect compoundRect = mCompoundRect; 607 final int[] state = tv.getDrawableState(); 608 609 mDrawableError.setState(state); 610 mDrawableError.copyBounds(compoundRect); 611 mDrawableError.setCallback(tv); 612 mDrawableSizeError = compoundRect.width(); 613 mDrawableHeightError = compoundRect.height(); 614 } else { 615 mDrawableSizeError = mDrawableHeightError = 0; 616 } 617 } 618 619 private void applyErrorDrawableIfNeeded(int layoutDirection) { 620 // first restore the initial state if needed 621 switch (mDrawableSaved) { 622 case DRAWABLE_LEFT: 623 mShowing[Drawables.LEFT] = mDrawableTemp; 624 mDrawableSizeLeft = mDrawableSizeTemp; 625 mDrawableHeightLeft = mDrawableHeightTemp; 626 break; 627 case DRAWABLE_RIGHT: 628 mShowing[Drawables.RIGHT] = mDrawableTemp; 629 mDrawableSizeRight = mDrawableSizeTemp; 630 mDrawableHeightRight = mDrawableHeightTemp; 631 break; 632 case DRAWABLE_NONE: 633 default: 634 } 635 // then, if needed, assign the Error drawable to the correct location 636 if (mDrawableError != null) { 637 switch(layoutDirection) { 638 case LAYOUT_DIRECTION_RTL: 639 mDrawableSaved = DRAWABLE_LEFT; 640 641 mDrawableTemp = mShowing[Drawables.LEFT]; 642 mDrawableSizeTemp = mDrawableSizeLeft; 643 mDrawableHeightTemp = mDrawableHeightLeft; 644 645 mShowing[Drawables.LEFT] = mDrawableError; 646 mDrawableSizeLeft = mDrawableSizeError; 647 mDrawableHeightLeft = mDrawableHeightError; 648 break; 649 case LAYOUT_DIRECTION_LTR: 650 default: 651 mDrawableSaved = DRAWABLE_RIGHT; 652 653 mDrawableTemp = mShowing[Drawables.RIGHT]; 654 mDrawableSizeTemp = mDrawableSizeRight; 655 mDrawableHeightTemp = mDrawableHeightRight; 656 657 mShowing[Drawables.RIGHT] = mDrawableError; 658 mDrawableSizeRight = mDrawableSizeError; 659 mDrawableHeightRight = mDrawableHeightError; 660 break; 661 } 662 } 663 } 664 } 665 666 @UnsupportedAppUsage 667 Drawables mDrawables; 668 669 @UnsupportedAppUsage 670 private CharWrapper mCharWrapper; 671 672 @UnsupportedAppUsage(trackingBug = 124050217) 673 private Marquee mMarquee; 674 @UnsupportedAppUsage 675 private boolean mRestartMarquee; 676 677 private int mMarqueeRepeatLimit = 3; 678 679 private int mLastLayoutDirection = -1; 680 681 /** 682 * On some devices the fading edges add a performance penalty if used 683 * extensively in the same layout. This mode indicates how the marquee 684 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 685 */ 686 @UnsupportedAppUsage 687 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 688 689 /** 690 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 691 * the layout that should be used when the mode switches. 692 */ 693 @UnsupportedAppUsage 694 private Layout mSavedMarqueeModeLayout; 695 696 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 697 @ViewDebug.ExportedProperty(category = "text") 698 @UnsupportedAppUsage 699 private @Nullable CharSequence mText; 700 private @Nullable Spannable mSpannable; 701 private @Nullable PrecomputedText mPrecomputed; 702 703 @UnsupportedAppUsage 704 private CharSequence mTransformed; 705 @UnsupportedAppUsage 706 private BufferType mBufferType = BufferType.NORMAL; 707 708 private CharSequence mHint; 709 @UnsupportedAppUsage 710 private Layout mHintLayout; 711 712 private MovementMethod mMovement; 713 714 private TransformationMethod mTransformation; 715 @UnsupportedAppUsage 716 private boolean mAllowTransformationLengthChange; 717 @UnsupportedAppUsage 718 private ChangeWatcher mChangeWatcher; 719 720 @UnsupportedAppUsage(trackingBug = 123769451) 721 private ArrayList<TextWatcher> mListeners; 722 723 // display attributes 724 @UnsupportedAppUsage 725 private final TextPaint mTextPaint; 726 @UnsupportedAppUsage 727 private boolean mUserSetTextScaleX; 728 @UnsupportedAppUsage 729 private Layout mLayout; 730 private boolean mLocalesChanged = false; 731 private int mTextSizeUnit = -1; 732 733 // True if setKeyListener() has been explicitly called 734 private boolean mListenerChanged = false; 735 // True if internationalized input should be used for numbers and date and time. 736 private final boolean mUseInternationalizedInput; 737 // True if fallback fonts that end up getting used should be allowed to affect line spacing. 738 /* package */ boolean mUseFallbackLineSpacing; 739 740 @ViewDebug.ExportedProperty(category = "text") 741 @UnsupportedAppUsage 742 private int mGravity = Gravity.TOP | Gravity.START; 743 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 744 private boolean mHorizontallyScrolling; 745 746 private int mAutoLinkMask; 747 private boolean mLinksClickable = true; 748 749 @UnsupportedAppUsage 750 private float mSpacingMult = 1.0f; 751 @UnsupportedAppUsage 752 private float mSpacingAdd = 0.0f; 753 754 private int mBreakStrategy; 755 private int mHyphenationFrequency; 756 private int mJustificationMode; 757 758 @UnsupportedAppUsage 759 private int mMaximum = Integer.MAX_VALUE; 760 @UnsupportedAppUsage 761 private int mMaxMode = LINES; 762 @UnsupportedAppUsage 763 private int mMinimum = 0; 764 @UnsupportedAppUsage 765 private int mMinMode = LINES; 766 767 @UnsupportedAppUsage 768 private int mOldMaximum = mMaximum; 769 @UnsupportedAppUsage 770 private int mOldMaxMode = mMaxMode; 771 772 @UnsupportedAppUsage 773 private int mMaxWidth = Integer.MAX_VALUE; 774 @UnsupportedAppUsage 775 private int mMaxWidthMode = PIXELS; 776 @UnsupportedAppUsage 777 private int mMinWidth = 0; 778 @UnsupportedAppUsage 779 private int mMinWidthMode = PIXELS; 780 781 @UnsupportedAppUsage 782 private boolean mSingleLine; 783 @UnsupportedAppUsage 784 private int mDesiredHeightAtMeasure = -1; 785 @UnsupportedAppUsage 786 private boolean mIncludePad = true; 787 private int mDeferScroll = -1; 788 789 // tmp primitives, so we don't alloc them on each draw 790 private Rect mTempRect; 791 private long mLastScroll; 792 private Scroller mScroller; 793 private TextPaint mTempTextPaint; 794 795 @UnsupportedAppUsage 796 private BoringLayout.Metrics mBoring; 797 @UnsupportedAppUsage 798 private BoringLayout.Metrics mHintBoring; 799 @UnsupportedAppUsage 800 private BoringLayout mSavedLayout; 801 @UnsupportedAppUsage 802 private BoringLayout mSavedHintLayout; 803 804 @UnsupportedAppUsage 805 private TextDirectionHeuristic mTextDir; 806 807 private InputFilter[] mFilters = NO_FILTERS; 808 809 /** 810 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 811 * the same as {@link Process#myUserHandle()}. 812 * 813 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 814 * other apps may need to set this so that the system can use right user's resources and 815 * services such as input methods and spell checkers.</p> 816 * 817 * @see #setTextOperationUser(UserHandle) 818 */ 819 @Nullable 820 private UserHandle mTextOperationUser; 821 822 private volatile Locale mCurrentSpellCheckerLocaleCache; 823 824 // It is possible to have a selection even when mEditor is null (programmatically set, like when 825 // a link is pressed). These highlight-related fields do not go in mEditor. 826 @UnsupportedAppUsage 827 int mHighlightColor = 0x6633B5E5; 828 private Path mHighlightPath; 829 @UnsupportedAppUsage 830 private final Paint mHighlightPaint; 831 @UnsupportedAppUsage 832 private boolean mHighlightPathBogus = true; 833 834 // Although these fields are specific to editable text, they are not added to Editor because 835 // they are defined by the TextView's style and are theme-dependent. 836 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 837 int mCursorDrawableRes; 838 private Drawable mCursorDrawable; 839 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 840 // by removing it, but we would break apps targeting <= P that use it by reflection. 841 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 842 int mTextSelectHandleLeftRes; 843 private Drawable mTextSelectHandleLeft; 844 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 845 // by removing it, but we would break apps targeting <= P that use it by reflection. 846 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 847 int mTextSelectHandleRightRes; 848 private Drawable mTextSelectHandleRight; 849 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 850 // by removing it, but we would break apps targeting <= P that use it by reflection. 851 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 852 int mTextSelectHandleRes; 853 private Drawable mTextSelectHandle; 854 int mTextEditSuggestionItemLayout; 855 int mTextEditSuggestionContainerLayout; 856 int mTextEditSuggestionHighlightStyle; 857 858 private static final int NO_POINTER_ID = -1; 859 /** 860 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 861 * TextView and the handle views which are rendered on popup windows. 862 */ 863 private int mPrimePointerId = NO_POINTER_ID; 864 865 /** 866 * Whether the prime pointer is from the event delivered to selection handle or insertion 867 * handle. 868 */ 869 private boolean mIsPrimePointerFromHandleView; 870 871 /** 872 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 873 * See {@link #createEditorIfNeeded()}. 874 */ 875 @UnsupportedAppUsage 876 private Editor mEditor; 877 878 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 879 private static final int DEVICE_PROVISIONED_NO = 1; 880 private static final int DEVICE_PROVISIONED_YES = 2; 881 882 /** 883 * Some special options such as sharing selected text should only be shown if the device 884 * is provisioned. Only check the provisioned state once for a given view instance. 885 */ 886 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 887 888 /** 889 * The TextView does not auto-size text (default). 890 */ 891 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 892 893 /** 894 * The TextView scales text size both horizontally and vertically to fit within the 895 * container. 896 */ 897 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 898 899 /** @hide */ 900 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 901 AUTO_SIZE_TEXT_TYPE_NONE, 902 AUTO_SIZE_TEXT_TYPE_UNIFORM 903 }) 904 @Retention(RetentionPolicy.SOURCE) 905 public @interface AutoSizeTextType {} 906 // Default minimum size for auto-sizing text in scaled pixels. 907 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 908 // Default maximum size for auto-sizing text in scaled pixels. 909 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 910 // Default value for the step size in pixels. 911 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 912 // Use this to specify that any of the auto-size configuration int values have not been set. 913 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 914 // Auto-size text type. 915 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 916 // Specify if auto-size text is needed. 917 private boolean mNeedsAutoSizeText = false; 918 // Step size for auto-sizing in pixels. 919 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 920 // Minimum text size for auto-sizing in pixels. 921 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 922 // Maximum text size for auto-sizing in pixels. 923 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 924 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 925 // when auto-sizing text. 926 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 927 // Specifies whether auto-size should use the provided auto size steps set or if it should 928 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 929 // mAutoSizeStepGranularityInPx. 930 private boolean mHasPresetAutoSizeValues = false; 931 932 // Autofill-related attributes 933 // 934 // Indicates whether the text was set statically or dynamically, so it can be used to 935 // sanitize autofill requests. 936 private boolean mTextSetFromXmlOrResourceId = false; 937 // Resource id used to set the text. 938 private @StringRes int mTextId = Resources.ID_NULL; 939 // Resource id used to set the hint. 940 private @StringRes int mHintId = Resources.ID_NULL; 941 // 942 // End of autofill-related attributes 943 944 /** 945 * Kick-start the font cache for the zygote process (to pay the cost of 946 * initializing freetype for our default font only once). 947 * @hide 948 */ 949 public static void preloadFontCache() { 950 Paint p = new Paint(); 951 p.setAntiAlias(true); 952 // Ensure that the Typeface is loaded here. 953 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 954 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 955 // since Paint.measureText can not be called without Typeface static initializer. 956 p.setTypeface(Typeface.DEFAULT); 957 // We don't care about the result, just the side-effect of measuring. 958 p.measureText("H"); 959 } 960 961 /** 962 * Interface definition for a callback to be invoked when an action is 963 * performed on the editor. 964 */ 965 public interface OnEditorActionListener { 966 /** 967 * Called when an action is being performed. 968 * 969 * @param v The view that was clicked. 970 * @param actionId Identifier of the action. This will be either the 971 * identifier you supplied, or {@link EditorInfo#IME_NULL 972 * EditorInfo.IME_NULL} if being called due to the enter key 973 * being pressed. 974 * @param event If triggered by an enter key, this is the event; 975 * otherwise, this is null. 976 * @return Return true if you have consumed the action, else false. 977 */ 978 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 979 } 980 981 public TextView(Context context) { 982 this(context, null); 983 } 984 985 public TextView(Context context, @Nullable AttributeSet attrs) { 986 this(context, attrs, com.android.internal.R.attr.textViewStyle); 987 } 988 989 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 990 this(context, attrs, defStyleAttr, 0); 991 } 992 993 @SuppressWarnings("deprecation") 994 public TextView( 995 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 996 super(context, attrs, defStyleAttr, defStyleRes); 997 998 // TextView is important by default, unless app developer overrode attribute. 999 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1000 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1001 } 1002 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1003 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1004 } 1005 1006 setTextInternal(""); 1007 1008 final Resources res = getResources(); 1009 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1010 1011 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1012 mTextPaint.density = res.getDisplayMetrics().density; 1013 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1014 1015 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1016 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1017 1018 mMovement = getDefaultMovementMethod(); 1019 1020 mTransformation = null; 1021 1022 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1023 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1024 attributes.mTextSize = 15; 1025 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1026 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1027 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1028 1029 final Resources.Theme theme = context.getTheme(); 1030 1031 /* 1032 * Look the appearance up without checking first if it exists because 1033 * almost every TextView has one and it greatly simplifies the logic 1034 * to be able to parse the appearance first and then let specific tags 1035 * for this View override it. 1036 */ 1037 TypedArray a = theme.obtainStyledAttributes(attrs, 1038 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1039 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1040 attrs, a, defStyleAttr, defStyleRes); 1041 TypedArray appearance = null; 1042 int ap = a.getResourceId( 1043 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1044 a.recycle(); 1045 if (ap != -1) { 1046 appearance = theme.obtainStyledAttributes( 1047 ap, com.android.internal.R.styleable.TextAppearance); 1048 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1049 null, appearance, 0, ap); 1050 } 1051 if (appearance != null) { 1052 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1053 attributes.mFontFamilyExplicit = false; 1054 appearance.recycle(); 1055 } 1056 1057 boolean editable = getDefaultEditable(); 1058 CharSequence inputMethod = null; 1059 int numeric = 0; 1060 CharSequence digits = null; 1061 boolean phone = false; 1062 boolean autotext = false; 1063 int autocap = -1; 1064 int buffertype = 0; 1065 boolean selectallonfocus = false; 1066 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1067 drawableBottom = null, drawableStart = null, drawableEnd = null; 1068 ColorStateList drawableTint = null; 1069 BlendMode drawableTintMode = null; 1070 int drawablePadding = 0; 1071 int ellipsize = ELLIPSIZE_NOT_SET; 1072 boolean singleLine = false; 1073 int maxlength = -1; 1074 CharSequence text = ""; 1075 CharSequence hint = null; 1076 boolean password = false; 1077 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1078 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1079 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1080 int inputType = EditorInfo.TYPE_NULL; 1081 a = theme.obtainStyledAttributes( 1082 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1083 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1084 defStyleAttr, defStyleRes); 1085 int firstBaselineToTopHeight = -1; 1086 int lastBaselineToBottomHeight = -1; 1087 int lineHeight = -1; 1088 1089 readTextAppearance(context, a, attributes, true /* styleArray */); 1090 1091 int n = a.getIndexCount(); 1092 1093 // Must set id in a temporary variable because it will be reset by setText() 1094 boolean textIsSetFromXml = false; 1095 for (int i = 0; i < n; i++) { 1096 int attr = a.getIndex(i); 1097 1098 switch (attr) { 1099 case com.android.internal.R.styleable.TextView_editable: 1100 editable = a.getBoolean(attr, editable); 1101 break; 1102 1103 case com.android.internal.R.styleable.TextView_inputMethod: 1104 inputMethod = a.getText(attr); 1105 break; 1106 1107 case com.android.internal.R.styleable.TextView_numeric: 1108 numeric = a.getInt(attr, numeric); 1109 break; 1110 1111 case com.android.internal.R.styleable.TextView_digits: 1112 digits = a.getText(attr); 1113 break; 1114 1115 case com.android.internal.R.styleable.TextView_phoneNumber: 1116 phone = a.getBoolean(attr, phone); 1117 break; 1118 1119 case com.android.internal.R.styleable.TextView_autoText: 1120 autotext = a.getBoolean(attr, autotext); 1121 break; 1122 1123 case com.android.internal.R.styleable.TextView_capitalize: 1124 autocap = a.getInt(attr, autocap); 1125 break; 1126 1127 case com.android.internal.R.styleable.TextView_bufferType: 1128 buffertype = a.getInt(attr, buffertype); 1129 break; 1130 1131 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1132 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1133 break; 1134 1135 case com.android.internal.R.styleable.TextView_autoLink: 1136 mAutoLinkMask = a.getInt(attr, 0); 1137 break; 1138 1139 case com.android.internal.R.styleable.TextView_linksClickable: 1140 mLinksClickable = a.getBoolean(attr, true); 1141 break; 1142 1143 case com.android.internal.R.styleable.TextView_drawableLeft: 1144 drawableLeft = a.getDrawable(attr); 1145 break; 1146 1147 case com.android.internal.R.styleable.TextView_drawableTop: 1148 drawableTop = a.getDrawable(attr); 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_drawableRight: 1152 drawableRight = a.getDrawable(attr); 1153 break; 1154 1155 case com.android.internal.R.styleable.TextView_drawableBottom: 1156 drawableBottom = a.getDrawable(attr); 1157 break; 1158 1159 case com.android.internal.R.styleable.TextView_drawableStart: 1160 drawableStart = a.getDrawable(attr); 1161 break; 1162 1163 case com.android.internal.R.styleable.TextView_drawableEnd: 1164 drawableEnd = a.getDrawable(attr); 1165 break; 1166 1167 case com.android.internal.R.styleable.TextView_drawableTint: 1168 drawableTint = a.getColorStateList(attr); 1169 break; 1170 1171 case com.android.internal.R.styleable.TextView_drawableTintMode: 1172 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1173 drawableTintMode); 1174 break; 1175 1176 case com.android.internal.R.styleable.TextView_drawablePadding: 1177 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1178 break; 1179 1180 case com.android.internal.R.styleable.TextView_maxLines: 1181 setMaxLines(a.getInt(attr, -1)); 1182 break; 1183 1184 case com.android.internal.R.styleable.TextView_maxHeight: 1185 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1186 break; 1187 1188 case com.android.internal.R.styleable.TextView_lines: 1189 setLines(a.getInt(attr, -1)); 1190 break; 1191 1192 case com.android.internal.R.styleable.TextView_height: 1193 setHeight(a.getDimensionPixelSize(attr, -1)); 1194 break; 1195 1196 case com.android.internal.R.styleable.TextView_minLines: 1197 setMinLines(a.getInt(attr, -1)); 1198 break; 1199 1200 case com.android.internal.R.styleable.TextView_minHeight: 1201 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1202 break; 1203 1204 case com.android.internal.R.styleable.TextView_maxEms: 1205 setMaxEms(a.getInt(attr, -1)); 1206 break; 1207 1208 case com.android.internal.R.styleable.TextView_maxWidth: 1209 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1210 break; 1211 1212 case com.android.internal.R.styleable.TextView_ems: 1213 setEms(a.getInt(attr, -1)); 1214 break; 1215 1216 case com.android.internal.R.styleable.TextView_width: 1217 setWidth(a.getDimensionPixelSize(attr, -1)); 1218 break; 1219 1220 case com.android.internal.R.styleable.TextView_minEms: 1221 setMinEms(a.getInt(attr, -1)); 1222 break; 1223 1224 case com.android.internal.R.styleable.TextView_minWidth: 1225 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1226 break; 1227 1228 case com.android.internal.R.styleable.TextView_gravity: 1229 setGravity(a.getInt(attr, -1)); 1230 break; 1231 1232 case com.android.internal.R.styleable.TextView_hint: 1233 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1234 hint = a.getText(attr); 1235 break; 1236 1237 case com.android.internal.R.styleable.TextView_text: 1238 textIsSetFromXml = true; 1239 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1240 text = a.getText(attr); 1241 break; 1242 1243 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1244 if (a.getBoolean(attr, false)) { 1245 setHorizontallyScrolling(true); 1246 } 1247 break; 1248 1249 case com.android.internal.R.styleable.TextView_singleLine: 1250 singleLine = a.getBoolean(attr, singleLine); 1251 break; 1252 1253 case com.android.internal.R.styleable.TextView_ellipsize: 1254 ellipsize = a.getInt(attr, ellipsize); 1255 break; 1256 1257 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1258 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1259 break; 1260 1261 case com.android.internal.R.styleable.TextView_includeFontPadding: 1262 if (!a.getBoolean(attr, true)) { 1263 setIncludeFontPadding(false); 1264 } 1265 break; 1266 1267 case com.android.internal.R.styleable.TextView_cursorVisible: 1268 if (!a.getBoolean(attr, true)) { 1269 setCursorVisible(false); 1270 } 1271 break; 1272 1273 case com.android.internal.R.styleable.TextView_maxLength: 1274 maxlength = a.getInt(attr, -1); 1275 break; 1276 1277 case com.android.internal.R.styleable.TextView_textScaleX: 1278 setTextScaleX(a.getFloat(attr, 1.0f)); 1279 break; 1280 1281 case com.android.internal.R.styleable.TextView_freezesText: 1282 mFreezesText = a.getBoolean(attr, false); 1283 break; 1284 1285 case com.android.internal.R.styleable.TextView_enabled: 1286 setEnabled(a.getBoolean(attr, isEnabled())); 1287 break; 1288 1289 case com.android.internal.R.styleable.TextView_password: 1290 password = a.getBoolean(attr, password); 1291 break; 1292 1293 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1294 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1295 break; 1296 1297 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1298 mSpacingMult = a.getFloat(attr, mSpacingMult); 1299 break; 1300 1301 case com.android.internal.R.styleable.TextView_inputType: 1302 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1303 break; 1304 1305 case com.android.internal.R.styleable.TextView_allowUndo: 1306 createEditorIfNeeded(); 1307 mEditor.mAllowUndo = a.getBoolean(attr, true); 1308 break; 1309 1310 case com.android.internal.R.styleable.TextView_imeOptions: 1311 createEditorIfNeeded(); 1312 mEditor.createInputContentTypeIfNeeded(); 1313 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1314 mEditor.mInputContentType.imeOptions); 1315 break; 1316 1317 case com.android.internal.R.styleable.TextView_imeActionLabel: 1318 createEditorIfNeeded(); 1319 mEditor.createInputContentTypeIfNeeded(); 1320 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1321 break; 1322 1323 case com.android.internal.R.styleable.TextView_imeActionId: 1324 createEditorIfNeeded(); 1325 mEditor.createInputContentTypeIfNeeded(); 1326 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1327 mEditor.mInputContentType.imeActionId); 1328 break; 1329 1330 case com.android.internal.R.styleable.TextView_privateImeOptions: 1331 setPrivateImeOptions(a.getString(attr)); 1332 break; 1333 1334 case com.android.internal.R.styleable.TextView_editorExtras: 1335 try { 1336 setInputExtras(a.getResourceId(attr, 0)); 1337 } catch (XmlPullParserException e) { 1338 Log.w(LOG_TAG, "Failure reading input extras", e); 1339 } catch (IOException e) { 1340 Log.w(LOG_TAG, "Failure reading input extras", e); 1341 } 1342 break; 1343 1344 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1345 mCursorDrawableRes = a.getResourceId(attr, 0); 1346 break; 1347 1348 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1349 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1350 break; 1351 1352 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1353 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1354 break; 1355 1356 case com.android.internal.R.styleable.TextView_textSelectHandle: 1357 mTextSelectHandleRes = a.getResourceId(attr, 0); 1358 break; 1359 1360 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1361 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1362 break; 1363 1364 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1365 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1366 break; 1367 1368 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1369 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1370 break; 1371 1372 case com.android.internal.R.styleable.TextView_textIsSelectable: 1373 setTextIsSelectable(a.getBoolean(attr, false)); 1374 break; 1375 1376 case com.android.internal.R.styleable.TextView_breakStrategy: 1377 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1378 break; 1379 1380 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1381 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1382 break; 1383 1384 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1385 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1386 break; 1387 1388 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1389 autoSizeStepGranularityInPx = a.getDimension(attr, 1390 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1391 break; 1392 1393 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1394 autoSizeMinTextSizeInPx = a.getDimension(attr, 1395 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1396 break; 1397 1398 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1399 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1400 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1401 break; 1402 1403 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1404 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1405 if (autoSizeStepSizeArrayResId > 0) { 1406 final TypedArray autoSizePresetTextSizes = a.getResources() 1407 .obtainTypedArray(autoSizeStepSizeArrayResId); 1408 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1409 autoSizePresetTextSizes.recycle(); 1410 } 1411 break; 1412 case com.android.internal.R.styleable.TextView_justificationMode: 1413 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1414 break; 1415 1416 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1417 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1418 break; 1419 1420 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1421 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1422 break; 1423 1424 case com.android.internal.R.styleable.TextView_lineHeight: 1425 lineHeight = a.getDimensionPixelSize(attr, -1); 1426 break; 1427 } 1428 } 1429 1430 a.recycle(); 1431 1432 BufferType bufferType = BufferType.EDITABLE; 1433 1434 final int variation = 1435 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1436 final boolean passwordInputType = variation 1437 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1438 final boolean webPasswordInputType = variation 1439 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1440 final boolean numberPasswordInputType = variation 1441 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1442 1443 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1444 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1445 mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; 1446 1447 if (inputMethod != null) { 1448 Class<?> c; 1449 1450 try { 1451 c = Class.forName(inputMethod.toString()); 1452 } catch (ClassNotFoundException ex) { 1453 throw new RuntimeException(ex); 1454 } 1455 1456 try { 1457 createEditorIfNeeded(); 1458 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1459 } catch (InstantiationException ex) { 1460 throw new RuntimeException(ex); 1461 } catch (IllegalAccessException ex) { 1462 throw new RuntimeException(ex); 1463 } 1464 try { 1465 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1466 ? inputType 1467 : mEditor.mKeyListener.getInputType(); 1468 } catch (IncompatibleClassChangeError e) { 1469 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1470 } 1471 } else if (digits != null) { 1472 createEditorIfNeeded(); 1473 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1474 // If no input type was specified, we will default to generic 1475 // text, since we can't tell the IME about the set of digits 1476 // that was selected. 1477 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1478 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1479 } else if (inputType != EditorInfo.TYPE_NULL) { 1480 setInputType(inputType, true); 1481 // If set, the input type overrides what was set using the deprecated singleLine flag. 1482 singleLine = !isMultilineInputType(inputType); 1483 } else if (phone) { 1484 createEditorIfNeeded(); 1485 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1486 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1487 } else if (numeric != 0) { 1488 createEditorIfNeeded(); 1489 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1490 null, // locale 1491 (numeric & SIGNED) != 0, 1492 (numeric & DECIMAL) != 0); 1493 inputType = mEditor.mKeyListener.getInputType(); 1494 mEditor.mInputType = inputType; 1495 } else if (autotext || autocap != -1) { 1496 TextKeyListener.Capitalize cap; 1497 1498 inputType = EditorInfo.TYPE_CLASS_TEXT; 1499 1500 switch (autocap) { 1501 case 1: 1502 cap = TextKeyListener.Capitalize.SENTENCES; 1503 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1504 break; 1505 1506 case 2: 1507 cap = TextKeyListener.Capitalize.WORDS; 1508 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1509 break; 1510 1511 case 3: 1512 cap = TextKeyListener.Capitalize.CHARACTERS; 1513 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1514 break; 1515 1516 default: 1517 cap = TextKeyListener.Capitalize.NONE; 1518 break; 1519 } 1520 1521 createEditorIfNeeded(); 1522 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1523 mEditor.mInputType = inputType; 1524 } else if (editable) { 1525 createEditorIfNeeded(); 1526 mEditor.mKeyListener = TextKeyListener.getInstance(); 1527 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1528 } else if (isTextSelectable()) { 1529 // Prevent text changes from keyboard. 1530 if (mEditor != null) { 1531 mEditor.mKeyListener = null; 1532 mEditor.mInputType = EditorInfo.TYPE_NULL; 1533 } 1534 bufferType = BufferType.SPANNABLE; 1535 // So that selection can be changed using arrow keys and touch is handled. 1536 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1537 } else { 1538 if (mEditor != null) mEditor.mKeyListener = null; 1539 1540 switch (buffertype) { 1541 case 0: 1542 bufferType = BufferType.NORMAL; 1543 break; 1544 case 1: 1545 bufferType = BufferType.SPANNABLE; 1546 break; 1547 case 2: 1548 bufferType = BufferType.EDITABLE; 1549 break; 1550 } 1551 } 1552 1553 if (mEditor != null) { 1554 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1555 numberPasswordInputType); 1556 } 1557 1558 if (selectallonfocus) { 1559 createEditorIfNeeded(); 1560 mEditor.mSelectAllOnFocus = true; 1561 1562 if (bufferType == BufferType.NORMAL) { 1563 bufferType = BufferType.SPANNABLE; 1564 } 1565 } 1566 1567 // Set up the tint (if needed) before setting the drawables so that it 1568 // gets applied correctly. 1569 if (drawableTint != null || drawableTintMode != null) { 1570 if (mDrawables == null) { 1571 mDrawables = new Drawables(context); 1572 } 1573 if (drawableTint != null) { 1574 mDrawables.mTintList = drawableTint; 1575 mDrawables.mHasTint = true; 1576 } 1577 if (drawableTintMode != null) { 1578 mDrawables.mBlendMode = drawableTintMode; 1579 mDrawables.mHasTintMode = true; 1580 } 1581 } 1582 1583 // This call will save the initial left/right drawables 1584 setCompoundDrawablesWithIntrinsicBounds( 1585 drawableLeft, drawableTop, drawableRight, drawableBottom); 1586 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1587 setCompoundDrawablePadding(drawablePadding); 1588 1589 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1590 // of lines of height are unchanged for multi-line TextViews. 1591 setInputTypeSingleLine(singleLine); 1592 applySingleLine(singleLine, singleLine, singleLine); 1593 1594 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1595 ellipsize = ELLIPSIZE_END; 1596 } 1597 1598 switch (ellipsize) { 1599 case ELLIPSIZE_START: 1600 setEllipsize(TextUtils.TruncateAt.START); 1601 break; 1602 case ELLIPSIZE_MIDDLE: 1603 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1604 break; 1605 case ELLIPSIZE_END: 1606 setEllipsize(TextUtils.TruncateAt.END); 1607 break; 1608 case ELLIPSIZE_MARQUEE: 1609 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1610 setHorizontalFadingEdgeEnabled(true); 1611 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1612 } else { 1613 setHorizontalFadingEdgeEnabled(false); 1614 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1615 } 1616 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1617 break; 1618 } 1619 1620 final boolean isPassword = password || passwordInputType || webPasswordInputType 1621 || numberPasswordInputType; 1622 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1623 && (mEditor.mInputType 1624 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1625 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1626 if (isMonospaceEnforced) { 1627 attributes.mTypefaceIndex = MONOSPACE; 1628 } 1629 1630 applyTextAppearance(attributes); 1631 1632 if (isPassword) { 1633 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1634 } 1635 1636 if (maxlength >= 0) { 1637 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1638 } else { 1639 setFilters(NO_FILTERS); 1640 } 1641 1642 setText(text, bufferType); 1643 if (mText == null) { 1644 mText = ""; 1645 } 1646 if (mTransformed == null) { 1647 mTransformed = ""; 1648 } 1649 1650 if (textIsSetFromXml) { 1651 mTextSetFromXmlOrResourceId = true; 1652 } 1653 1654 if (hint != null) setHint(hint); 1655 1656 /* 1657 * Views are not normally clickable unless specified to be. 1658 * However, TextViews that have input or movement methods *are* 1659 * clickable by default. By setting clickable here, we implicitly set focusable as well 1660 * if not overridden by the developer. 1661 */ 1662 a = context.obtainStyledAttributes( 1663 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1664 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1665 boolean clickable = canInputOrMove || isClickable(); 1666 boolean longClickable = canInputOrMove || isLongClickable(); 1667 int focusable = getFocusable(); 1668 1669 n = a.getIndexCount(); 1670 for (int i = 0; i < n; i++) { 1671 int attr = a.getIndex(i); 1672 1673 switch (attr) { 1674 case com.android.internal.R.styleable.View_focusable: 1675 TypedValue val = new TypedValue(); 1676 if (a.getValue(attr, val)) { 1677 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1678 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1679 : val.data; 1680 } 1681 break; 1682 1683 case com.android.internal.R.styleable.View_clickable: 1684 clickable = a.getBoolean(attr, clickable); 1685 break; 1686 1687 case com.android.internal.R.styleable.View_longClickable: 1688 longClickable = a.getBoolean(attr, longClickable); 1689 break; 1690 } 1691 } 1692 a.recycle(); 1693 1694 // Some apps were relying on the undefined behavior of focusable winning over 1695 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1696 // when starting with EditText and setting only focusable=false). To keep those apps from 1697 // breaking, re-apply the focusable attribute here. 1698 if (focusable != getFocusable()) { 1699 setFocusable(focusable); 1700 } 1701 setClickable(clickable); 1702 setLongClickable(longClickable); 1703 1704 if (mEditor != null) mEditor.prepareCursorControllers(); 1705 1706 // If not explicitly specified this view is important for accessibility. 1707 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1708 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1709 } 1710 1711 if (supportsAutoSizeText()) { 1712 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1713 // If uniform auto-size has been specified but preset values have not been set then 1714 // replace the auto-size configuration values that have not been specified with the 1715 // defaults. 1716 if (!mHasPresetAutoSizeValues) { 1717 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1718 1719 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1720 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1721 TypedValue.COMPLEX_UNIT_SP, 1722 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1723 displayMetrics); 1724 } 1725 1726 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1727 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1728 TypedValue.COMPLEX_UNIT_SP, 1729 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1730 displayMetrics); 1731 } 1732 1733 if (autoSizeStepGranularityInPx 1734 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1735 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1736 } 1737 1738 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1739 autoSizeMaxTextSizeInPx, 1740 autoSizeStepGranularityInPx); 1741 } 1742 1743 setupAutoSizeText(); 1744 } 1745 } else { 1746 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1747 } 1748 1749 if (firstBaselineToTopHeight >= 0) { 1750 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1751 } 1752 if (lastBaselineToBottomHeight >= 0) { 1753 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1754 } 1755 if (lineHeight >= 0) { 1756 setLineHeight(lineHeight); 1757 } 1758 } 1759 1760 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1761 private void setTextInternal(@Nullable CharSequence text) { 1762 mText = text; 1763 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1764 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1765 } 1766 1767 /** 1768 * Specify whether this widget should automatically scale the text to try to perfectly fit 1769 * within the layout bounds by using the default auto-size configuration. 1770 * 1771 * @param autoSizeTextType the type of auto-size. Must be one of 1772 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1773 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1774 * 1775 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1776 * 1777 * @attr ref android.R.styleable#TextView_autoSizeTextType 1778 * 1779 * @see #getAutoSizeTextType() 1780 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1781 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1782 if (supportsAutoSizeText()) { 1783 switch (autoSizeTextType) { 1784 case AUTO_SIZE_TEXT_TYPE_NONE: 1785 clearAutoSizeConfiguration(); 1786 break; 1787 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1788 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1789 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1790 TypedValue.COMPLEX_UNIT_SP, 1791 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1792 displayMetrics); 1793 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1794 TypedValue.COMPLEX_UNIT_SP, 1795 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1796 displayMetrics); 1797 1798 validateAndSetAutoSizeTextTypeUniformConfiguration( 1799 autoSizeMinTextSizeInPx, 1800 autoSizeMaxTextSizeInPx, 1801 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1802 if (setupAutoSizeText()) { 1803 autoSizeText(); 1804 invalidate(); 1805 } 1806 break; 1807 default: 1808 throw new IllegalArgumentException( 1809 "Unknown auto-size text type: " + autoSizeTextType); 1810 } 1811 } 1812 } 1813 1814 /** 1815 * Specify whether this widget should automatically scale the text to try to perfectly fit 1816 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1817 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1818 * 1819 * @param autoSizeMinTextSize the minimum text size available for auto-size 1820 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1821 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1822 * the minimum and maximum text size in order to build the set of 1823 * text sizes the system uses to choose from when auto-sizing 1824 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1825 * possible dimension units 1826 * 1827 * @throws IllegalArgumentException if any of the configuration params are invalid. 1828 * 1829 * @attr ref android.R.styleable#TextView_autoSizeTextType 1830 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1831 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1832 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1833 * 1834 * @see #setAutoSizeTextTypeWithDefaults(int) 1835 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1836 * @see #getAutoSizeMinTextSize() 1837 * @see #getAutoSizeMaxTextSize() 1838 * @see #getAutoSizeStepGranularity() 1839 * @see #getAutoSizeTextAvailableSizes() 1840 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1841 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1842 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1843 if (supportsAutoSizeText()) { 1844 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1845 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1846 unit, autoSizeMinTextSize, displayMetrics); 1847 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1848 unit, autoSizeMaxTextSize, displayMetrics); 1849 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1850 unit, autoSizeStepGranularity, displayMetrics); 1851 1852 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1853 autoSizeMaxTextSizeInPx, 1854 autoSizeStepGranularityInPx); 1855 1856 if (setupAutoSizeText()) { 1857 autoSizeText(); 1858 invalidate(); 1859 } 1860 } 1861 } 1862 1863 /** 1864 * Specify whether this widget should automatically scale the text to try to perfectly fit 1865 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1866 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1867 * 1868 * @param presetSizes an {@code int} array of sizes in pixels 1869 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1870 * the possible dimension units 1871 * 1872 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1873 * 1874 * @attr ref android.R.styleable#TextView_autoSizeTextType 1875 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1876 * 1877 * @see #setAutoSizeTextTypeWithDefaults(int) 1878 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1879 * @see #getAutoSizeMinTextSize() 1880 * @see #getAutoSizeMaxTextSize() 1881 * @see #getAutoSizeTextAvailableSizes() 1882 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1883 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1884 if (supportsAutoSizeText()) { 1885 final int presetSizesLength = presetSizes.length; 1886 if (presetSizesLength > 0) { 1887 int[] presetSizesInPx = new int[presetSizesLength]; 1888 1889 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1890 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1891 } else { 1892 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1893 // Convert all to sizes to pixels. 1894 for (int i = 0; i < presetSizesLength; i++) { 1895 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 1896 presetSizes[i], displayMetrics)); 1897 } 1898 } 1899 1900 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1901 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1902 throw new IllegalArgumentException("None of the preset sizes is valid: " 1903 + Arrays.toString(presetSizes)); 1904 } 1905 } else { 1906 mHasPresetAutoSizeValues = false; 1907 } 1908 1909 if (setupAutoSizeText()) { 1910 autoSizeText(); 1911 invalidate(); 1912 } 1913 } 1914 } 1915 1916 /** 1917 * Returns the type of auto-size set for this widget. 1918 * 1919 * @return an {@code int} corresponding to one of the auto-size types: 1920 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1921 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1922 * 1923 * @attr ref android.R.styleable#TextView_autoSizeTextType 1924 * 1925 * @see #setAutoSizeTextTypeWithDefaults(int) 1926 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1927 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1928 */ 1929 @InspectableProperty(enumMapping = { 1930 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 1931 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 1932 }) 1933 @AutoSizeTextType getAutoSizeTextType()1934 public int getAutoSizeTextType() { 1935 return mAutoSizeTextType; 1936 } 1937 1938 /** 1939 * @return the current auto-size step granularity in pixels. 1940 * 1941 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1942 * 1943 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1944 */ 1945 @InspectableProperty getAutoSizeStepGranularity()1946 public int getAutoSizeStepGranularity() { 1947 return Math.round(mAutoSizeStepGranularityInPx); 1948 } 1949 1950 /** 1951 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 1952 * if auto-size has not been configured this function returns {@code -1}. 1953 * 1954 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1955 * 1956 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1957 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1958 */ 1959 @InspectableProperty getAutoSizeMinTextSize()1960 public int getAutoSizeMinTextSize() { 1961 return Math.round(mAutoSizeMinTextSizeInPx); 1962 } 1963 1964 /** 1965 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 1966 * if auto-size has not been configured this function returns {@code -1}. 1967 * 1968 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1969 * 1970 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1971 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1972 */ 1973 @InspectableProperty getAutoSizeMaxTextSize()1974 public int getAutoSizeMaxTextSize() { 1975 return Math.round(mAutoSizeMaxTextSizeInPx); 1976 } 1977 1978 /** 1979 * @return the current auto-size {@code int} sizes array (in pixels). 1980 * 1981 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1982 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1983 */ getAutoSizeTextAvailableSizes()1984 public int[] getAutoSizeTextAvailableSizes() { 1985 return mAutoSizeTextSizesInPx; 1986 } 1987 setupAutoSizeUniformPresetSizes(TypedArray textSizes)1988 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 1989 final int textSizesLength = textSizes.length(); 1990 final int[] parsedSizes = new int[textSizesLength]; 1991 1992 if (textSizesLength > 0) { 1993 for (int i = 0; i < textSizesLength; i++) { 1994 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 1995 } 1996 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 1997 setupAutoSizeUniformPresetSizesConfiguration(); 1998 } 1999 } 2000 setupAutoSizeUniformPresetSizesConfiguration()2001 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2002 final int sizesLength = mAutoSizeTextSizesInPx.length; 2003 mHasPresetAutoSizeValues = sizesLength > 0; 2004 if (mHasPresetAutoSizeValues) { 2005 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2006 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2007 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2008 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2009 } 2010 return mHasPresetAutoSizeValues; 2011 } 2012 2013 /** 2014 * If all params are valid then save the auto-size configuration. 2015 * 2016 * @throws IllegalArgumentException if any of the params are invalid 2017 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2018 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2019 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2020 // First validate. 2021 if (autoSizeMinTextSizeInPx <= 0) { 2022 throw new IllegalArgumentException("Minimum auto-size text size (" 2023 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2024 } 2025 2026 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2027 throw new IllegalArgumentException("Maximum auto-size text size (" 2028 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2029 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2030 } 2031 2032 if (autoSizeStepGranularityInPx <= 0) { 2033 throw new IllegalArgumentException("The auto-size step granularity (" 2034 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2035 } 2036 2037 // All good, persist the configuration. 2038 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2039 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2040 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2041 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2042 mHasPresetAutoSizeValues = false; 2043 } 2044 clearAutoSizeConfiguration()2045 private void clearAutoSizeConfiguration() { 2046 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2047 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2048 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2049 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2050 mAutoSizeTextSizesInPx = EmptyArray.INT; 2051 mNeedsAutoSizeText = false; 2052 } 2053 2054 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2055 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2056 final int presetValuesLength = presetValues.length; 2057 if (presetValuesLength == 0) { 2058 return presetValues; 2059 } 2060 Arrays.sort(presetValues); 2061 2062 final IntArray uniqueValidSizes = new IntArray(); 2063 for (int i = 0; i < presetValuesLength; i++) { 2064 final int currentPresetValue = presetValues[i]; 2065 2066 if (currentPresetValue > 0 2067 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2068 uniqueValidSizes.add(currentPresetValue); 2069 } 2070 } 2071 2072 return presetValuesLength == uniqueValidSizes.size() 2073 ? presetValues 2074 : uniqueValidSizes.toArray(); 2075 } 2076 setupAutoSizeText()2077 private boolean setupAutoSizeText() { 2078 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2079 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2080 // not have a predefined set of sizes or if the current sizes array is empty. 2081 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2082 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2083 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2084 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2085 for (int i = 0; i < autoSizeValuesLength; i++) { 2086 autoSizeTextSizesInPx[i] = Math.round( 2087 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2088 } 2089 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2090 } 2091 2092 mNeedsAutoSizeText = true; 2093 } else { 2094 mNeedsAutoSizeText = false; 2095 } 2096 2097 return mNeedsAutoSizeText; 2098 } 2099 parseDimensionArray(TypedArray dimens)2100 private int[] parseDimensionArray(TypedArray dimens) { 2101 if (dimens == null) { 2102 return null; 2103 } 2104 int[] result = new int[dimens.length()]; 2105 for (int i = 0; i < result.length; i++) { 2106 result[i] = dimens.getDimensionPixelSize(i, 0); 2107 } 2108 return result; 2109 } 2110 2111 /** 2112 * @hide 2113 */ 2114 @Override onActivityResult(int requestCode, int resultCode, Intent data)2115 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2116 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2117 if (resultCode == Activity.RESULT_OK && data != null) { 2118 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2119 if (result != null) { 2120 if (isTextEditable()) { 2121 replaceSelectionWithText(result); 2122 if (mEditor != null) { 2123 mEditor.refreshTextActionMode(); 2124 } 2125 } else { 2126 if (result.length() > 0) { 2127 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2128 .show(); 2129 } 2130 } 2131 } 2132 } else if (mSpannable != null) { 2133 // Reset the selection. 2134 Selection.setSelection(mSpannable, getSelectionEnd()); 2135 } 2136 } 2137 } 2138 2139 /** 2140 * Sets the Typeface taking into account the given attributes. 2141 * 2142 * @param typeface a typeface 2143 * @param familyName family name string, e.g. "serif" 2144 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2145 * @param style a typeface style 2146 * @param weight a weight value for the Typeface or -1 if not specified. 2147 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2148 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2149 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2150 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2151 if (typeface == null && familyName != null) { 2152 // Lookup normal Typeface from system font map. 2153 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2154 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2155 } else if (typeface != null) { 2156 resolveStyleAndSetTypeface(typeface, style, weight); 2157 } else { // both typeface and familyName is null. 2158 switch (typefaceIndex) { 2159 case SANS: 2160 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2161 break; 2162 case SERIF: 2163 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2164 break; 2165 case MONOSPACE: 2166 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2167 break; 2168 case DEFAULT_TYPEFACE: 2169 default: 2170 resolveStyleAndSetTypeface(null, style, weight); 2171 break; 2172 } 2173 } 2174 } 2175 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2176 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2177 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2178 if (weight >= 0) { 2179 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2180 final boolean italic = (style & Typeface.ITALIC) != 0; 2181 setTypeface(Typeface.create(typeface, weight, italic)); 2182 } else { 2183 setTypeface(typeface, style); 2184 } 2185 } 2186 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2187 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2188 boolean hasRelativeDrawables = (start != null) || (end != null); 2189 if (hasRelativeDrawables) { 2190 Drawables dr = mDrawables; 2191 if (dr == null) { 2192 mDrawables = dr = new Drawables(getContext()); 2193 } 2194 mDrawables.mOverride = true; 2195 final Rect compoundRect = dr.mCompoundRect; 2196 int[] state = getDrawableState(); 2197 if (start != null) { 2198 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2199 start.setState(state); 2200 start.copyBounds(compoundRect); 2201 start.setCallback(this); 2202 2203 dr.mDrawableStart = start; 2204 dr.mDrawableSizeStart = compoundRect.width(); 2205 dr.mDrawableHeightStart = compoundRect.height(); 2206 } else { 2207 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2208 } 2209 if (end != null) { 2210 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2211 end.setState(state); 2212 end.copyBounds(compoundRect); 2213 end.setCallback(this); 2214 2215 dr.mDrawableEnd = end; 2216 dr.mDrawableSizeEnd = compoundRect.width(); 2217 dr.mDrawableHeightEnd = compoundRect.height(); 2218 } else { 2219 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2220 } 2221 resetResolvedDrawables(); 2222 resolveDrawables(); 2223 applyCompoundDrawableTint(); 2224 } 2225 } 2226 2227 @android.view.RemotableViewMethod 2228 @Override setEnabled(boolean enabled)2229 public void setEnabled(boolean enabled) { 2230 if (enabled == isEnabled()) { 2231 return; 2232 } 2233 2234 if (!enabled) { 2235 // Hide the soft input if the currently active TextView is disabled 2236 InputMethodManager imm = getInputMethodManager(); 2237 if (imm != null && imm.isActive(this)) { 2238 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2239 } 2240 } 2241 2242 super.setEnabled(enabled); 2243 2244 if (enabled) { 2245 // Make sure IME is updated with current editor info. 2246 InputMethodManager imm = getInputMethodManager(); 2247 if (imm != null) imm.restartInput(this); 2248 } 2249 2250 // Will change text color 2251 if (mEditor != null) { 2252 mEditor.invalidateTextDisplayList(); 2253 mEditor.prepareCursorControllers(); 2254 2255 // start or stop the cursor blinking as appropriate 2256 mEditor.makeBlink(); 2257 } 2258 } 2259 2260 /** 2261 * Sets the typeface and style in which the text should be displayed, 2262 * and turns on the fake bold and italic bits in the Paint if the 2263 * Typeface that you provided does not have all the bits in the 2264 * style that you specified. 2265 * 2266 * @attr ref android.R.styleable#TextView_typeface 2267 * @attr ref android.R.styleable#TextView_textStyle 2268 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2269 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2270 if (style > 0) { 2271 if (tf == null) { 2272 tf = Typeface.defaultFromStyle(style); 2273 } else { 2274 tf = Typeface.create(tf, style); 2275 } 2276 2277 setTypeface(tf); 2278 // now compute what (if any) algorithmic styling is needed 2279 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2280 int need = style & ~typefaceStyle; 2281 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2282 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2283 } else { 2284 mTextPaint.setFakeBoldText(false); 2285 mTextPaint.setTextSkewX(0); 2286 setTypeface(tf); 2287 } 2288 } 2289 2290 /** 2291 * Subclasses override this to specify that they have a KeyListener 2292 * by default even if not specifically called for in the XML options. 2293 */ getDefaultEditable()2294 protected boolean getDefaultEditable() { 2295 return false; 2296 } 2297 2298 /** 2299 * Subclasses override this to specify a default movement method. 2300 */ getDefaultMovementMethod()2301 protected MovementMethod getDefaultMovementMethod() { 2302 return null; 2303 } 2304 2305 /** 2306 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2307 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2308 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2309 * the return value from this method to Spannable or Editable, respectively. 2310 * 2311 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2312 * should make your own copy first.</p> 2313 * 2314 * @return The text displayed by the text view. 2315 * @attr ref android.R.styleable#TextView_text 2316 */ 2317 @ViewDebug.CapturedViewProperty 2318 @InspectableProperty getText()2319 public CharSequence getText() { 2320 return mText; 2321 } 2322 2323 /** 2324 * Returns the length, in characters, of the text managed by this TextView 2325 * @return The length of the text managed by the TextView in characters. 2326 */ length()2327 public int length() { 2328 return mText.length(); 2329 } 2330 2331 /** 2332 * Return the text that TextView is displaying as an Editable object. If the text is not 2333 * editable, null is returned. 2334 * 2335 * @see #getText 2336 */ getEditableText()2337 public Editable getEditableText() { 2338 return (mText instanceof Editable) ? (Editable) mText : null; 2339 } 2340 2341 /** 2342 * @hide 2343 */ 2344 @VisibleForTesting getTransformed()2345 public CharSequence getTransformed() { 2346 return mTransformed; 2347 } 2348 2349 /** 2350 * Gets the vertical distance between lines of text, in pixels. 2351 * Note that markup within the text can cause individual lines 2352 * to be taller or shorter than this height, and the layout may 2353 * contain additional first-or last-line padding. 2354 * @return The height of one standard line in pixels. 2355 */ 2356 @InspectableProperty getLineHeight()2357 public int getLineHeight() { 2358 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2359 } 2360 2361 /** 2362 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2363 * This value can be null if the text or width has recently changed. 2364 * @return The Layout that is currently being used to display the text. 2365 */ getLayout()2366 public final Layout getLayout() { 2367 return mLayout; 2368 } 2369 2370 /** 2371 * @return the {@link android.text.Layout} that is currently being used to 2372 * display the hint text. This can be null. 2373 */ 2374 @UnsupportedAppUsage getHintLayout()2375 final Layout getHintLayout() { 2376 return mHintLayout; 2377 } 2378 2379 /** 2380 * Retrieve the {@link android.content.UndoManager} that is currently associated 2381 * with this TextView. By default there is no associated UndoManager, so null 2382 * is returned. One can be associated with the TextView through 2383 * {@link #setUndoManager(android.content.UndoManager, String)} 2384 * 2385 * @hide 2386 */ getUndoManager()2387 public final UndoManager getUndoManager() { 2388 // TODO: Consider supporting a global undo manager. 2389 throw new UnsupportedOperationException("not implemented"); 2390 } 2391 2392 2393 /** 2394 * @hide 2395 */ 2396 @VisibleForTesting getEditorForTesting()2397 public final Editor getEditorForTesting() { 2398 return mEditor; 2399 } 2400 2401 /** 2402 * Associate an {@link android.content.UndoManager} with this TextView. Once 2403 * done, all edit operations on the TextView will result in appropriate 2404 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2405 * stack. 2406 * 2407 * @param undoManager The {@link android.content.UndoManager} to associate with 2408 * this TextView, or null to clear any existing association. 2409 * @param tag String tag identifying this particular TextView owner in the 2410 * UndoManager. This is used to keep the correct association with the 2411 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2412 * 2413 * @hide 2414 */ setUndoManager(UndoManager undoManager, String tag)2415 public final void setUndoManager(UndoManager undoManager, String tag) { 2416 // TODO: Consider supporting a global undo manager. An implementation will need to: 2417 // * createEditorIfNeeded() 2418 // * Promote to BufferType.EDITABLE if needed. 2419 // * Update the UndoManager and UndoOwner. 2420 // Likewise it will need to be able to restore the default UndoManager. 2421 throw new UnsupportedOperationException("not implemented"); 2422 } 2423 2424 /** 2425 * Gets the current {@link KeyListener} for the TextView. 2426 * This will frequently be null for non-EditText TextViews. 2427 * @return the current key listener for this TextView. 2428 * 2429 * @attr ref android.R.styleable#TextView_numeric 2430 * @attr ref android.R.styleable#TextView_digits 2431 * @attr ref android.R.styleable#TextView_phoneNumber 2432 * @attr ref android.R.styleable#TextView_inputMethod 2433 * @attr ref android.R.styleable#TextView_capitalize 2434 * @attr ref android.R.styleable#TextView_autoText 2435 */ getKeyListener()2436 public final KeyListener getKeyListener() { 2437 return mEditor == null ? null : mEditor.mKeyListener; 2438 } 2439 2440 /** 2441 * Sets the key listener to be used with this TextView. This can be null 2442 * to disallow user input. Note that this method has significant and 2443 * subtle interactions with soft keyboards and other input method: 2444 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2445 * for important details. Calling this method will replace the current 2446 * content type of the text view with the content type returned by the 2447 * key listener. 2448 * <p> 2449 * Be warned that if you want a TextView with a key listener or movement 2450 * method not to be focusable, or if you want a TextView without a 2451 * key listener or movement method to be focusable, you must call 2452 * {@link #setFocusable} again after calling this to get the focusability 2453 * back the way you want it. 2454 * 2455 * @attr ref android.R.styleable#TextView_numeric 2456 * @attr ref android.R.styleable#TextView_digits 2457 * @attr ref android.R.styleable#TextView_phoneNumber 2458 * @attr ref android.R.styleable#TextView_inputMethod 2459 * @attr ref android.R.styleable#TextView_capitalize 2460 * @attr ref android.R.styleable#TextView_autoText 2461 */ setKeyListener(KeyListener input)2462 public void setKeyListener(KeyListener input) { 2463 mListenerChanged = true; 2464 setKeyListenerOnly(input); 2465 fixFocusableAndClickableSettings(); 2466 2467 if (input != null) { 2468 createEditorIfNeeded(); 2469 setInputTypeFromEditor(); 2470 } else { 2471 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2472 } 2473 2474 InputMethodManager imm = getInputMethodManager(); 2475 if (imm != null) imm.restartInput(this); 2476 } 2477 setInputTypeFromEditor()2478 private void setInputTypeFromEditor() { 2479 try { 2480 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2481 } catch (IncompatibleClassChangeError e) { 2482 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2483 } 2484 // Change inputType, without affecting transformation. 2485 // No need to applySingleLine since mSingleLine is unchanged. 2486 setInputTypeSingleLine(mSingleLine); 2487 } 2488 setKeyListenerOnly(KeyListener input)2489 private void setKeyListenerOnly(KeyListener input) { 2490 if (mEditor == null && input == null) return; // null is the default value 2491 2492 createEditorIfNeeded(); 2493 if (mEditor.mKeyListener != input) { 2494 mEditor.mKeyListener = input; 2495 if (input != null && !(mText instanceof Editable)) { 2496 setText(mText); 2497 } 2498 2499 setFilters((Editable) mText, mFilters); 2500 } 2501 } 2502 2503 /** 2504 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2505 * which provides positioning, scrolling, and text selection functionality. 2506 * This will frequently be null for non-EditText TextViews. 2507 * @return the movement method being used for this TextView. 2508 * @see android.text.method.MovementMethod 2509 */ getMovementMethod()2510 public final MovementMethod getMovementMethod() { 2511 return mMovement; 2512 } 2513 2514 /** 2515 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2516 * for this TextView. This can be null to disallow using the arrow keys to move the 2517 * cursor or scroll the view. 2518 * <p> 2519 * Be warned that if you want a TextView with a key listener or movement 2520 * method not to be focusable, or if you want a TextView without a 2521 * key listener or movement method to be focusable, you must call 2522 * {@link #setFocusable} again after calling this to get the focusability 2523 * back the way you want it. 2524 */ setMovementMethod(MovementMethod movement)2525 public final void setMovementMethod(MovementMethod movement) { 2526 if (mMovement != movement) { 2527 mMovement = movement; 2528 2529 if (movement != null && mSpannable == null) { 2530 setText(mText); 2531 } 2532 2533 fixFocusableAndClickableSettings(); 2534 2535 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2536 // mMovement 2537 if (mEditor != null) mEditor.prepareCursorControllers(); 2538 } 2539 } 2540 fixFocusableAndClickableSettings()2541 private void fixFocusableAndClickableSettings() { 2542 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2543 setFocusable(FOCUSABLE); 2544 setClickable(true); 2545 setLongClickable(true); 2546 } else { 2547 setFocusable(FOCUSABLE_AUTO); 2548 setClickable(false); 2549 setLongClickable(false); 2550 } 2551 } 2552 2553 /** 2554 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2555 * This is frequently null, except for single-line and password fields. 2556 * @return the current transformation method for this TextView. 2557 * 2558 * @attr ref android.R.styleable#TextView_password 2559 * @attr ref android.R.styleable#TextView_singleLine 2560 */ getTransformationMethod()2561 public final TransformationMethod getTransformationMethod() { 2562 return mTransformation; 2563 } 2564 2565 /** 2566 * Sets the transformation that is applied to the text that this 2567 * TextView is displaying. 2568 * 2569 * @attr ref android.R.styleable#TextView_password 2570 * @attr ref android.R.styleable#TextView_singleLine 2571 */ setTransformationMethod(TransformationMethod method)2572 public final void setTransformationMethod(TransformationMethod method) { 2573 if (method == mTransformation) { 2574 // Avoid the setText() below if the transformation is 2575 // the same. 2576 return; 2577 } 2578 if (mTransformation != null) { 2579 if (mSpannable != null) { 2580 mSpannable.removeSpan(mTransformation); 2581 } 2582 } 2583 2584 mTransformation = method; 2585 2586 if (method instanceof TransformationMethod2) { 2587 TransformationMethod2 method2 = (TransformationMethod2) method; 2588 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2589 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2590 } else { 2591 mAllowTransformationLengthChange = false; 2592 } 2593 2594 setText(mText); 2595 2596 if (hasPasswordTransformationMethod()) { 2597 notifyViewAccessibilityStateChangedIfNeeded( 2598 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2599 } 2600 2601 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2602 // getTextDirectionHeuristic, needs reset 2603 mTextDir = getTextDirectionHeuristic(); 2604 } 2605 2606 /** 2607 * Returns the top padding of the view, plus space for the top 2608 * Drawable if any. 2609 */ getCompoundPaddingTop()2610 public int getCompoundPaddingTop() { 2611 final Drawables dr = mDrawables; 2612 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2613 return mPaddingTop; 2614 } else { 2615 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2616 } 2617 } 2618 2619 /** 2620 * Returns the bottom padding of the view, plus space for the bottom 2621 * Drawable if any. 2622 */ getCompoundPaddingBottom()2623 public int getCompoundPaddingBottom() { 2624 final Drawables dr = mDrawables; 2625 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2626 return mPaddingBottom; 2627 } else { 2628 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2629 } 2630 } 2631 2632 /** 2633 * Returns the left padding of the view, plus space for the left 2634 * Drawable if any. 2635 */ getCompoundPaddingLeft()2636 public int getCompoundPaddingLeft() { 2637 final Drawables dr = mDrawables; 2638 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2639 return mPaddingLeft; 2640 } else { 2641 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2642 } 2643 } 2644 2645 /** 2646 * Returns the right padding of the view, plus space for the right 2647 * Drawable if any. 2648 */ getCompoundPaddingRight()2649 public int getCompoundPaddingRight() { 2650 final Drawables dr = mDrawables; 2651 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2652 return mPaddingRight; 2653 } else { 2654 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2655 } 2656 } 2657 2658 /** 2659 * Returns the start padding of the view, plus space for the start 2660 * Drawable if any. 2661 */ getCompoundPaddingStart()2662 public int getCompoundPaddingStart() { 2663 resolveDrawables(); 2664 switch(getLayoutDirection()) { 2665 default: 2666 case LAYOUT_DIRECTION_LTR: 2667 return getCompoundPaddingLeft(); 2668 case LAYOUT_DIRECTION_RTL: 2669 return getCompoundPaddingRight(); 2670 } 2671 } 2672 2673 /** 2674 * Returns the end padding of the view, plus space for the end 2675 * Drawable if any. 2676 */ getCompoundPaddingEnd()2677 public int getCompoundPaddingEnd() { 2678 resolveDrawables(); 2679 switch(getLayoutDirection()) { 2680 default: 2681 case LAYOUT_DIRECTION_LTR: 2682 return getCompoundPaddingRight(); 2683 case LAYOUT_DIRECTION_RTL: 2684 return getCompoundPaddingLeft(); 2685 } 2686 } 2687 2688 /** 2689 * Returns the extended top padding of the view, including both the 2690 * top Drawable if any and any extra space to keep more than maxLines 2691 * of text from showing. It is only valid to call this after measuring. 2692 */ getExtendedPaddingTop()2693 public int getExtendedPaddingTop() { 2694 if (mMaxMode != LINES) { 2695 return getCompoundPaddingTop(); 2696 } 2697 2698 if (mLayout == null) { 2699 assumeLayout(); 2700 } 2701 2702 if (mLayout.getLineCount() <= mMaximum) { 2703 return getCompoundPaddingTop(); 2704 } 2705 2706 int top = getCompoundPaddingTop(); 2707 int bottom = getCompoundPaddingBottom(); 2708 int viewht = getHeight() - top - bottom; 2709 int layoutht = mLayout.getLineTop(mMaximum); 2710 2711 if (layoutht >= viewht) { 2712 return top; 2713 } 2714 2715 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2716 if (gravity == Gravity.TOP) { 2717 return top; 2718 } else if (gravity == Gravity.BOTTOM) { 2719 return top + viewht - layoutht; 2720 } else { // (gravity == Gravity.CENTER_VERTICAL) 2721 return top + (viewht - layoutht) / 2; 2722 } 2723 } 2724 2725 /** 2726 * Returns the extended bottom padding of the view, including both the 2727 * bottom Drawable if any and any extra space to keep more than maxLines 2728 * of text from showing. It is only valid to call this after measuring. 2729 */ getExtendedPaddingBottom()2730 public int getExtendedPaddingBottom() { 2731 if (mMaxMode != LINES) { 2732 return getCompoundPaddingBottom(); 2733 } 2734 2735 if (mLayout == null) { 2736 assumeLayout(); 2737 } 2738 2739 if (mLayout.getLineCount() <= mMaximum) { 2740 return getCompoundPaddingBottom(); 2741 } 2742 2743 int top = getCompoundPaddingTop(); 2744 int bottom = getCompoundPaddingBottom(); 2745 int viewht = getHeight() - top - bottom; 2746 int layoutht = mLayout.getLineTop(mMaximum); 2747 2748 if (layoutht >= viewht) { 2749 return bottom; 2750 } 2751 2752 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2753 if (gravity == Gravity.TOP) { 2754 return bottom + viewht - layoutht; 2755 } else if (gravity == Gravity.BOTTOM) { 2756 return bottom; 2757 } else { // (gravity == Gravity.CENTER_VERTICAL) 2758 return bottom + (viewht - layoutht) / 2; 2759 } 2760 } 2761 2762 /** 2763 * Returns the total left padding of the view, including the left 2764 * Drawable if any. 2765 */ getTotalPaddingLeft()2766 public int getTotalPaddingLeft() { 2767 return getCompoundPaddingLeft(); 2768 } 2769 2770 /** 2771 * Returns the total right padding of the view, including the right 2772 * Drawable if any. 2773 */ getTotalPaddingRight()2774 public int getTotalPaddingRight() { 2775 return getCompoundPaddingRight(); 2776 } 2777 2778 /** 2779 * Returns the total start padding of the view, including the start 2780 * Drawable if any. 2781 */ getTotalPaddingStart()2782 public int getTotalPaddingStart() { 2783 return getCompoundPaddingStart(); 2784 } 2785 2786 /** 2787 * Returns the total end padding of the view, including the end 2788 * Drawable if any. 2789 */ getTotalPaddingEnd()2790 public int getTotalPaddingEnd() { 2791 return getCompoundPaddingEnd(); 2792 } 2793 2794 /** 2795 * Returns the total top padding of the view, including the top 2796 * Drawable if any, the extra space to keep more than maxLines 2797 * from showing, and the vertical offset for gravity, if any. 2798 */ getTotalPaddingTop()2799 public int getTotalPaddingTop() { 2800 return getExtendedPaddingTop() + getVerticalOffset(true); 2801 } 2802 2803 /** 2804 * Returns the total bottom padding of the view, including the bottom 2805 * Drawable if any, the extra space to keep more than maxLines 2806 * from showing, and the vertical offset for gravity, if any. 2807 */ getTotalPaddingBottom()2808 public int getTotalPaddingBottom() { 2809 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2810 } 2811 2812 /** 2813 * Sets the Drawables (if any) to appear to the left of, above, to the 2814 * right of, and below the text. Use {@code null} if you do not want a 2815 * Drawable there. The Drawables must already have had 2816 * {@link Drawable#setBounds} called. 2817 * <p> 2818 * Calling this method will overwrite any Drawables previously set using 2819 * {@link #setCompoundDrawablesRelative} or related methods. 2820 * 2821 * @attr ref android.R.styleable#TextView_drawableLeft 2822 * @attr ref android.R.styleable#TextView_drawableTop 2823 * @attr ref android.R.styleable#TextView_drawableRight 2824 * @attr ref android.R.styleable#TextView_drawableBottom 2825 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2826 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2827 @Nullable Drawable right, @Nullable Drawable bottom) { 2828 Drawables dr = mDrawables; 2829 2830 // We're switching to absolute, discard relative. 2831 if (dr != null) { 2832 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2833 dr.mDrawableStart = null; 2834 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2835 dr.mDrawableEnd = null; 2836 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2837 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2838 } 2839 2840 final boolean drawables = left != null || top != null || right != null || bottom != null; 2841 if (!drawables) { 2842 // Clearing drawables... can we free the data structure? 2843 if (dr != null) { 2844 if (!dr.hasMetadata()) { 2845 mDrawables = null; 2846 } else { 2847 // We need to retain the last set padding, so just clear 2848 // out all of the fields in the existing structure. 2849 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2850 if (dr.mShowing[i] != null) { 2851 dr.mShowing[i].setCallback(null); 2852 } 2853 dr.mShowing[i] = null; 2854 } 2855 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2856 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2857 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2858 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2859 } 2860 } 2861 } else { 2862 if (dr == null) { 2863 mDrawables = dr = new Drawables(getContext()); 2864 } 2865 2866 mDrawables.mOverride = false; 2867 2868 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2869 dr.mShowing[Drawables.LEFT].setCallback(null); 2870 } 2871 dr.mShowing[Drawables.LEFT] = left; 2872 2873 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2874 dr.mShowing[Drawables.TOP].setCallback(null); 2875 } 2876 dr.mShowing[Drawables.TOP] = top; 2877 2878 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2879 dr.mShowing[Drawables.RIGHT].setCallback(null); 2880 } 2881 dr.mShowing[Drawables.RIGHT] = right; 2882 2883 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2884 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2885 } 2886 dr.mShowing[Drawables.BOTTOM] = bottom; 2887 2888 final Rect compoundRect = dr.mCompoundRect; 2889 int[] state; 2890 2891 state = getDrawableState(); 2892 2893 if (left != null) { 2894 left.setState(state); 2895 left.copyBounds(compoundRect); 2896 left.setCallback(this); 2897 dr.mDrawableSizeLeft = compoundRect.width(); 2898 dr.mDrawableHeightLeft = compoundRect.height(); 2899 } else { 2900 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2901 } 2902 2903 if (right != null) { 2904 right.setState(state); 2905 right.copyBounds(compoundRect); 2906 right.setCallback(this); 2907 dr.mDrawableSizeRight = compoundRect.width(); 2908 dr.mDrawableHeightRight = compoundRect.height(); 2909 } else { 2910 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2911 } 2912 2913 if (top != null) { 2914 top.setState(state); 2915 top.copyBounds(compoundRect); 2916 top.setCallback(this); 2917 dr.mDrawableSizeTop = compoundRect.height(); 2918 dr.mDrawableWidthTop = compoundRect.width(); 2919 } else { 2920 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2921 } 2922 2923 if (bottom != null) { 2924 bottom.setState(state); 2925 bottom.copyBounds(compoundRect); 2926 bottom.setCallback(this); 2927 dr.mDrawableSizeBottom = compoundRect.height(); 2928 dr.mDrawableWidthBottom = compoundRect.width(); 2929 } else { 2930 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2931 } 2932 } 2933 2934 // Save initial left/right drawables 2935 if (dr != null) { 2936 dr.mDrawableLeftInitial = left; 2937 dr.mDrawableRightInitial = right; 2938 } 2939 2940 resetResolvedDrawables(); 2941 resolveDrawables(); 2942 applyCompoundDrawableTint(); 2943 invalidate(); 2944 requestLayout(); 2945 } 2946 2947 /** 2948 * Sets the Drawables (if any) to appear to the left of, above, to the 2949 * right of, and below the text. Use 0 if you do not want a Drawable there. 2950 * The Drawables' bounds will be set to their intrinsic bounds. 2951 * <p> 2952 * Calling this method will overwrite any Drawables previously set using 2953 * {@link #setCompoundDrawablesRelative} or related methods. 2954 * 2955 * @param left Resource identifier of the left Drawable. 2956 * @param top Resource identifier of the top Drawable. 2957 * @param right Resource identifier of the right Drawable. 2958 * @param bottom Resource identifier of the bottom Drawable. 2959 * 2960 * @attr ref android.R.styleable#TextView_drawableLeft 2961 * @attr ref android.R.styleable#TextView_drawableTop 2962 * @attr ref android.R.styleable#TextView_drawableRight 2963 * @attr ref android.R.styleable#TextView_drawableBottom 2964 */ 2965 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2966 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 2967 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 2968 final Context context = getContext(); 2969 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2970 top != 0 ? context.getDrawable(top) : null, 2971 right != 0 ? context.getDrawable(right) : null, 2972 bottom != 0 ? context.getDrawable(bottom) : null); 2973 } 2974 2975 /** 2976 * Sets the Drawables (if any) to appear to the left of, above, to the 2977 * right of, and below the text. Use {@code null} if you do not want a 2978 * Drawable there. The Drawables' bounds will be set to their intrinsic 2979 * bounds. 2980 * <p> 2981 * Calling this method will overwrite any Drawables previously set using 2982 * {@link #setCompoundDrawablesRelative} or related methods. 2983 * 2984 * @attr ref android.R.styleable#TextView_drawableLeft 2985 * @attr ref android.R.styleable#TextView_drawableTop 2986 * @attr ref android.R.styleable#TextView_drawableRight 2987 * @attr ref android.R.styleable#TextView_drawableBottom 2988 */ 2989 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2990 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 2991 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 2992 2993 if (left != null) { 2994 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 2995 } 2996 if (right != null) { 2997 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 2998 } 2999 if (top != null) { 3000 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3001 } 3002 if (bottom != null) { 3003 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3004 } 3005 setCompoundDrawables(left, top, right, bottom); 3006 } 3007 3008 /** 3009 * Sets the Drawables (if any) to appear to the start of, above, to the end 3010 * of, and below the text. Use {@code null} if you do not want a Drawable 3011 * there. The Drawables must already have had {@link Drawable#setBounds} 3012 * called. 3013 * <p> 3014 * Calling this method will overwrite any Drawables previously set using 3015 * {@link #setCompoundDrawables} or related methods. 3016 * 3017 * @attr ref android.R.styleable#TextView_drawableStart 3018 * @attr ref android.R.styleable#TextView_drawableTop 3019 * @attr ref android.R.styleable#TextView_drawableEnd 3020 * @attr ref android.R.styleable#TextView_drawableBottom 3021 */ 3022 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3023 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3024 @Nullable Drawable end, @Nullable Drawable bottom) { 3025 Drawables dr = mDrawables; 3026 3027 // We're switching to relative, discard absolute. 3028 if (dr != null) { 3029 if (dr.mShowing[Drawables.LEFT] != null) { 3030 dr.mShowing[Drawables.LEFT].setCallback(null); 3031 } 3032 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3033 if (dr.mShowing[Drawables.RIGHT] != null) { 3034 dr.mShowing[Drawables.RIGHT].setCallback(null); 3035 } 3036 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3037 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3038 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3039 } 3040 3041 final boolean drawables = start != null || top != null 3042 || end != null || bottom != null; 3043 3044 if (!drawables) { 3045 // Clearing drawables... can we free the data structure? 3046 if (dr != null) { 3047 if (!dr.hasMetadata()) { 3048 mDrawables = null; 3049 } else { 3050 // We need to retain the last set padding, so just clear 3051 // out all of the fields in the existing structure. 3052 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3053 dr.mDrawableStart = null; 3054 if (dr.mShowing[Drawables.TOP] != null) { 3055 dr.mShowing[Drawables.TOP].setCallback(null); 3056 } 3057 dr.mShowing[Drawables.TOP] = null; 3058 if (dr.mDrawableEnd != null) { 3059 dr.mDrawableEnd.setCallback(null); 3060 } 3061 dr.mDrawableEnd = null; 3062 if (dr.mShowing[Drawables.BOTTOM] != null) { 3063 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3064 } 3065 dr.mShowing[Drawables.BOTTOM] = null; 3066 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3067 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3068 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3069 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3070 } 3071 } 3072 } else { 3073 if (dr == null) { 3074 mDrawables = dr = new Drawables(getContext()); 3075 } 3076 3077 mDrawables.mOverride = true; 3078 3079 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3080 dr.mDrawableStart.setCallback(null); 3081 } 3082 dr.mDrawableStart = start; 3083 3084 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3085 dr.mShowing[Drawables.TOP].setCallback(null); 3086 } 3087 dr.mShowing[Drawables.TOP] = top; 3088 3089 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3090 dr.mDrawableEnd.setCallback(null); 3091 } 3092 dr.mDrawableEnd = end; 3093 3094 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3095 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3096 } 3097 dr.mShowing[Drawables.BOTTOM] = bottom; 3098 3099 final Rect compoundRect = dr.mCompoundRect; 3100 int[] state; 3101 3102 state = getDrawableState(); 3103 3104 if (start != null) { 3105 start.setState(state); 3106 start.copyBounds(compoundRect); 3107 start.setCallback(this); 3108 dr.mDrawableSizeStart = compoundRect.width(); 3109 dr.mDrawableHeightStart = compoundRect.height(); 3110 } else { 3111 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3112 } 3113 3114 if (end != null) { 3115 end.setState(state); 3116 end.copyBounds(compoundRect); 3117 end.setCallback(this); 3118 dr.mDrawableSizeEnd = compoundRect.width(); 3119 dr.mDrawableHeightEnd = compoundRect.height(); 3120 } else { 3121 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3122 } 3123 3124 if (top != null) { 3125 top.setState(state); 3126 top.copyBounds(compoundRect); 3127 top.setCallback(this); 3128 dr.mDrawableSizeTop = compoundRect.height(); 3129 dr.mDrawableWidthTop = compoundRect.width(); 3130 } else { 3131 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3132 } 3133 3134 if (bottom != null) { 3135 bottom.setState(state); 3136 bottom.copyBounds(compoundRect); 3137 bottom.setCallback(this); 3138 dr.mDrawableSizeBottom = compoundRect.height(); 3139 dr.mDrawableWidthBottom = compoundRect.width(); 3140 } else { 3141 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3142 } 3143 } 3144 3145 resetResolvedDrawables(); 3146 resolveDrawables(); 3147 invalidate(); 3148 requestLayout(); 3149 } 3150 3151 /** 3152 * Sets the Drawables (if any) to appear to the start of, above, to the end 3153 * of, and below the text. Use 0 if you do not want a Drawable there. The 3154 * Drawables' bounds will be set to their intrinsic bounds. 3155 * <p> 3156 * Calling this method will overwrite any Drawables previously set using 3157 * {@link #setCompoundDrawables} or related methods. 3158 * 3159 * @param start Resource identifier of the start Drawable. 3160 * @param top Resource identifier of the top Drawable. 3161 * @param end Resource identifier of the end Drawable. 3162 * @param bottom Resource identifier of the bottom Drawable. 3163 * 3164 * @attr ref android.R.styleable#TextView_drawableStart 3165 * @attr ref android.R.styleable#TextView_drawableTop 3166 * @attr ref android.R.styleable#TextView_drawableEnd 3167 * @attr ref android.R.styleable#TextView_drawableBottom 3168 */ 3169 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3170 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3171 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3172 final Context context = getContext(); 3173 setCompoundDrawablesRelativeWithIntrinsicBounds( 3174 start != 0 ? context.getDrawable(start) : null, 3175 top != 0 ? context.getDrawable(top) : null, 3176 end != 0 ? context.getDrawable(end) : null, 3177 bottom != 0 ? context.getDrawable(bottom) : null); 3178 } 3179 3180 /** 3181 * Sets the Drawables (if any) to appear to the start of, above, to the end 3182 * of, and below the text. Use {@code null} if you do not want a Drawable 3183 * there. The Drawables' bounds will be set to their intrinsic bounds. 3184 * <p> 3185 * Calling this method will overwrite any Drawables previously set using 3186 * {@link #setCompoundDrawables} or related methods. 3187 * 3188 * @attr ref android.R.styleable#TextView_drawableStart 3189 * @attr ref android.R.styleable#TextView_drawableTop 3190 * @attr ref android.R.styleable#TextView_drawableEnd 3191 * @attr ref android.R.styleable#TextView_drawableBottom 3192 */ 3193 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3194 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3195 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3196 3197 if (start != null) { 3198 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3199 } 3200 if (end != null) { 3201 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3202 } 3203 if (top != null) { 3204 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3205 } 3206 if (bottom != null) { 3207 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3208 } 3209 setCompoundDrawablesRelative(start, top, end, bottom); 3210 } 3211 3212 /** 3213 * Returns drawables for the left, top, right, and bottom borders. 3214 * 3215 * @attr ref android.R.styleable#TextView_drawableLeft 3216 * @attr ref android.R.styleable#TextView_drawableTop 3217 * @attr ref android.R.styleable#TextView_drawableRight 3218 * @attr ref android.R.styleable#TextView_drawableBottom 3219 */ 3220 @NonNull getCompoundDrawables()3221 public Drawable[] getCompoundDrawables() { 3222 final Drawables dr = mDrawables; 3223 if (dr != null) { 3224 return dr.mShowing.clone(); 3225 } else { 3226 return new Drawable[] { null, null, null, null }; 3227 } 3228 } 3229 3230 /** 3231 * Returns drawables for the start, top, end, and bottom borders. 3232 * 3233 * @attr ref android.R.styleable#TextView_drawableStart 3234 * @attr ref android.R.styleable#TextView_drawableTop 3235 * @attr ref android.R.styleable#TextView_drawableEnd 3236 * @attr ref android.R.styleable#TextView_drawableBottom 3237 */ 3238 @NonNull getCompoundDrawablesRelative()3239 public Drawable[] getCompoundDrawablesRelative() { 3240 final Drawables dr = mDrawables; 3241 if (dr != null) { 3242 return new Drawable[] { 3243 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3244 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3245 }; 3246 } else { 3247 return new Drawable[] { null, null, null, null }; 3248 } 3249 } 3250 3251 /** 3252 * Sets the size of the padding between the compound drawables and 3253 * the text. 3254 * 3255 * @attr ref android.R.styleable#TextView_drawablePadding 3256 */ 3257 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3258 public void setCompoundDrawablePadding(int pad) { 3259 Drawables dr = mDrawables; 3260 if (pad == 0) { 3261 if (dr != null) { 3262 dr.mDrawablePadding = pad; 3263 } 3264 } else { 3265 if (dr == null) { 3266 mDrawables = dr = new Drawables(getContext()); 3267 } 3268 dr.mDrawablePadding = pad; 3269 } 3270 3271 invalidate(); 3272 requestLayout(); 3273 } 3274 3275 /** 3276 * Returns the padding between the compound drawables and the text. 3277 * 3278 * @attr ref android.R.styleable#TextView_drawablePadding 3279 */ 3280 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3281 public int getCompoundDrawablePadding() { 3282 final Drawables dr = mDrawables; 3283 return dr != null ? dr.mDrawablePadding : 0; 3284 } 3285 3286 /** 3287 * Applies a tint to the compound drawables. Does not modify the 3288 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3289 * <p> 3290 * Subsequent calls to 3291 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3292 * and related methods will automatically mutate the drawables and apply 3293 * the specified tint and tint mode using 3294 * {@link Drawable#setTintList(ColorStateList)}. 3295 * 3296 * @param tint the tint to apply, may be {@code null} to clear tint 3297 * 3298 * @attr ref android.R.styleable#TextView_drawableTint 3299 * @see #getCompoundDrawableTintList() 3300 * @see Drawable#setTintList(ColorStateList) 3301 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3302 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3303 if (mDrawables == null) { 3304 mDrawables = new Drawables(getContext()); 3305 } 3306 mDrawables.mTintList = tint; 3307 mDrawables.mHasTint = true; 3308 3309 applyCompoundDrawableTint(); 3310 } 3311 3312 /** 3313 * @return the tint applied to the compound drawables 3314 * @attr ref android.R.styleable#TextView_drawableTint 3315 * @see #setCompoundDrawableTintList(ColorStateList) 3316 */ 3317 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3318 public ColorStateList getCompoundDrawableTintList() { 3319 return mDrawables != null ? mDrawables.mTintList : null; 3320 } 3321 3322 /** 3323 * Specifies the blending mode used to apply the tint specified by 3324 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3325 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3326 * 3327 * @param tintMode the blending mode used to apply the tint, may be 3328 * {@code null} to clear tint 3329 * @attr ref android.R.styleable#TextView_drawableTintMode 3330 * @see #setCompoundDrawableTintList(ColorStateList) 3331 * @see Drawable#setTintMode(PorterDuff.Mode) 3332 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3333 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3334 setCompoundDrawableTintBlendMode(tintMode != null 3335 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3336 } 3337 3338 /** 3339 * Specifies the blending mode used to apply the tint specified by 3340 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3341 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3342 * 3343 * @param blendMode the blending mode used to apply the tint, may be 3344 * {@code null} to clear tint 3345 * @attr ref android.R.styleable#TextView_drawableTintMode 3346 * @see #setCompoundDrawableTintList(ColorStateList) 3347 * @see Drawable#setTintBlendMode(BlendMode) 3348 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3349 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3350 if (mDrawables == null) { 3351 mDrawables = new Drawables(getContext()); 3352 } 3353 mDrawables.mBlendMode = blendMode; 3354 mDrawables.mHasTintMode = true; 3355 3356 applyCompoundDrawableTint(); 3357 } 3358 3359 /** 3360 * Returns the blending mode used to apply the tint to the compound 3361 * drawables, if specified. 3362 * 3363 * @return the blending mode used to apply the tint to the compound 3364 * drawables 3365 * @attr ref android.R.styleable#TextView_drawableTintMode 3366 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3367 * 3368 */ 3369 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3370 public PorterDuff.Mode getCompoundDrawableTintMode() { 3371 BlendMode mode = getCompoundDrawableTintBlendMode(); 3372 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3373 } 3374 3375 /** 3376 * Returns the blending mode used to apply the tint to the compound 3377 * drawables, if specified. 3378 * 3379 * @return the blending mode used to apply the tint to the compound 3380 * drawables 3381 * @attr ref android.R.styleable#TextView_drawableTintMode 3382 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3383 */ 3384 @InspectableProperty(name = "drawableBlendMode", 3385 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3386 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3387 return mDrawables != null ? mDrawables.mBlendMode : null; 3388 } 3389 applyCompoundDrawableTint()3390 private void applyCompoundDrawableTint() { 3391 if (mDrawables == null) { 3392 return; 3393 } 3394 3395 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3396 final ColorStateList tintList = mDrawables.mTintList; 3397 final BlendMode blendMode = mDrawables.mBlendMode; 3398 final boolean hasTint = mDrawables.mHasTint; 3399 final boolean hasTintMode = mDrawables.mHasTintMode; 3400 final int[] state = getDrawableState(); 3401 3402 for (Drawable dr : mDrawables.mShowing) { 3403 if (dr == null) { 3404 continue; 3405 } 3406 3407 if (dr == mDrawables.mDrawableError) { 3408 // From a developer's perspective, the error drawable isn't 3409 // a compound drawable. Don't apply the generic compound 3410 // drawable tint to it. 3411 continue; 3412 } 3413 3414 dr.mutate(); 3415 3416 if (hasTint) { 3417 dr.setTintList(tintList); 3418 } 3419 3420 if (hasTintMode) { 3421 dr.setTintBlendMode(blendMode); 3422 } 3423 3424 // The drawable (or one of its children) may not have been 3425 // stateful before applying the tint, so let's try again. 3426 if (dr.isStateful()) { 3427 dr.setState(state); 3428 } 3429 } 3430 } 3431 } 3432 3433 /** 3434 * @inheritDoc 3435 * 3436 * @see #setFirstBaselineToTopHeight(int) 3437 * @see #setLastBaselineToBottomHeight(int) 3438 */ 3439 @Override setPadding(int left, int top, int right, int bottom)3440 public void setPadding(int left, int top, int right, int bottom) { 3441 if (left != mPaddingLeft 3442 || right != mPaddingRight 3443 || top != mPaddingTop 3444 || bottom != mPaddingBottom) { 3445 nullLayouts(); 3446 } 3447 3448 // the super call will requestLayout() 3449 super.setPadding(left, top, right, bottom); 3450 invalidate(); 3451 } 3452 3453 /** 3454 * @inheritDoc 3455 * 3456 * @see #setFirstBaselineToTopHeight(int) 3457 * @see #setLastBaselineToBottomHeight(int) 3458 */ 3459 @Override setPaddingRelative(int start, int top, int end, int bottom)3460 public void setPaddingRelative(int start, int top, int end, int bottom) { 3461 if (start != getPaddingStart() 3462 || end != getPaddingEnd() 3463 || top != mPaddingTop 3464 || bottom != mPaddingBottom) { 3465 nullLayouts(); 3466 } 3467 3468 // the super call will requestLayout() 3469 super.setPaddingRelative(start, top, end, bottom); 3470 invalidate(); 3471 } 3472 3473 /** 3474 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3475 * the distance between the top of the TextView and first line's baseline. 3476 * <p> 3477 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3478 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3479 * 3480 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3481 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3482 * Moreover since this function sets the top padding, if the height of the TextView is less than 3483 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3484 * down and bottom will be clipped. 3485 * 3486 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3487 * in pixels 3488 * 3489 * @see #getFirstBaselineToTopHeight() 3490 * @see #setLastBaselineToBottomHeight(int) 3491 * @see #setPadding(int, int, int, int) 3492 * @see #setPaddingRelative(int, int, int, int) 3493 * 3494 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3495 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3496 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3497 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3498 3499 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3500 final int fontMetricsTop; 3501 if (getIncludeFontPadding()) { 3502 fontMetricsTop = fontMetrics.top; 3503 } else { 3504 fontMetricsTop = fontMetrics.ascent; 3505 } 3506 3507 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3508 // in settings). At the moment, we don't. 3509 3510 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3511 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3512 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3513 } 3514 } 3515 3516 /** 3517 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3518 * the distance between the bottom of the TextView and the last line's baseline. 3519 * <p> 3520 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3521 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3522 * 3523 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3524 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3525 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3526 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3527 * clipped. 3528 * 3529 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3530 * in pixels 3531 * 3532 * @see #getLastBaselineToBottomHeight() 3533 * @see #setFirstBaselineToTopHeight(int) 3534 * @see #setPadding(int, int, int, int) 3535 * @see #setPaddingRelative(int, int, int, int) 3536 * 3537 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3538 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3539 public void setLastBaselineToBottomHeight( 3540 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3541 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3542 3543 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3544 final int fontMetricsBottom; 3545 if (getIncludeFontPadding()) { 3546 fontMetricsBottom = fontMetrics.bottom; 3547 } else { 3548 fontMetricsBottom = fontMetrics.descent; 3549 } 3550 3551 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3552 // in settings). At the moment, we don't. 3553 3554 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3555 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3556 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3557 } 3558 } 3559 3560 /** 3561 * Returns the distance between the first text baseline and the top of this TextView. 3562 * 3563 * @see #setFirstBaselineToTopHeight(int) 3564 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3565 */ 3566 @InspectableProperty getFirstBaselineToTopHeight()3567 public int getFirstBaselineToTopHeight() { 3568 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3569 } 3570 3571 /** 3572 * Returns the distance between the last text baseline and the bottom of this TextView. 3573 * 3574 * @see #setLastBaselineToBottomHeight(int) 3575 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3576 */ 3577 @InspectableProperty getLastBaselineToBottomHeight()3578 public int getLastBaselineToBottomHeight() { 3579 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3580 } 3581 3582 /** 3583 * Gets the autolink mask of the text. 3584 * 3585 * See {@link Linkify#ALL} and peers for possible values. 3586 * 3587 * @attr ref android.R.styleable#TextView_autoLink 3588 */ 3589 @InspectableProperty(name = "autoLink", flagMapping = { 3590 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3591 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3592 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3593 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3594 }) getAutoLinkMask()3595 public final int getAutoLinkMask() { 3596 return mAutoLinkMask; 3597 } 3598 3599 /** 3600 * Sets the Drawable corresponding to the selection handle used for 3601 * positioning the cursor within text. The Drawable defaults to the value 3602 * of the textSelectHandle attribute. 3603 * Note that any change applied to the handle Drawable will not be visible 3604 * until the handle is hidden and then drawn again. 3605 * 3606 * @see #setTextSelectHandle(int) 3607 * @attr ref android.R.styleable#TextView_textSelectHandle 3608 */ 3609 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3610 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3611 Preconditions.checkNotNull(textSelectHandle, 3612 "The text select handle should not be null."); 3613 mTextSelectHandle = textSelectHandle; 3614 mTextSelectHandleRes = 0; 3615 if (mEditor != null) { 3616 mEditor.loadHandleDrawables(true /* overwrite */); 3617 } 3618 } 3619 3620 /** 3621 * Sets the Drawable corresponding to the selection handle used for 3622 * positioning the cursor within text. The Drawable defaults to the value 3623 * of the textSelectHandle attribute. 3624 * Note that any change applied to the handle Drawable will not be visible 3625 * until the handle is hidden and then drawn again. 3626 * 3627 * @see #setTextSelectHandle(Drawable) 3628 * @attr ref android.R.styleable#TextView_textSelectHandle 3629 */ 3630 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3631 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3632 Preconditions.checkArgument(textSelectHandle != 0, 3633 "The text select handle should be a valid drawable resource id."); 3634 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3635 } 3636 3637 /** 3638 * Returns the Drawable corresponding to the selection handle used 3639 * for positioning the cursor within text. 3640 * Note that any change applied to the handle Drawable will not be visible 3641 * until the handle is hidden and then drawn again. 3642 * 3643 * @return the text select handle drawable 3644 * 3645 * @see #setTextSelectHandle(Drawable) 3646 * @see #setTextSelectHandle(int) 3647 * @attr ref android.R.styleable#TextView_textSelectHandle 3648 */ getTextSelectHandle()3649 @Nullable public Drawable getTextSelectHandle() { 3650 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3651 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3652 } 3653 return mTextSelectHandle; 3654 } 3655 3656 /** 3657 * Sets the Drawable corresponding to the left handle used 3658 * for selecting text. The Drawable defaults to the value of the 3659 * textSelectHandleLeft attribute. 3660 * Note that any change applied to the handle Drawable will not be visible 3661 * until the handle is hidden and then drawn again. 3662 * 3663 * @see #setTextSelectHandleLeft(int) 3664 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3665 */ 3666 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3667 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3668 Preconditions.checkNotNull(textSelectHandleLeft, 3669 "The left text select handle should not be null."); 3670 mTextSelectHandleLeft = textSelectHandleLeft; 3671 mTextSelectHandleLeftRes = 0; 3672 if (mEditor != null) { 3673 mEditor.loadHandleDrawables(true /* overwrite */); 3674 } 3675 } 3676 3677 /** 3678 * Sets the Drawable corresponding to the left handle used 3679 * for selecting text. The Drawable defaults to the value of the 3680 * textSelectHandleLeft attribute. 3681 * Note that any change applied to the handle Drawable will not be visible 3682 * until the handle is hidden and then drawn again. 3683 * 3684 * @see #setTextSelectHandleLeft(Drawable) 3685 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3686 */ 3687 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3688 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3689 Preconditions.checkArgument(textSelectHandleLeft != 0, 3690 "The text select left handle should be a valid drawable resource id."); 3691 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3692 } 3693 3694 /** 3695 * Returns the Drawable corresponding to the left handle used 3696 * for selecting text. 3697 * Note that any change applied to the handle Drawable will not be visible 3698 * until the handle is hidden and then drawn again. 3699 * 3700 * @return the left text selection handle drawable 3701 * 3702 * @see #setTextSelectHandleLeft(Drawable) 3703 * @see #setTextSelectHandleLeft(int) 3704 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3705 */ getTextSelectHandleLeft()3706 @Nullable public Drawable getTextSelectHandleLeft() { 3707 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3708 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3709 } 3710 return mTextSelectHandleLeft; 3711 } 3712 3713 /** 3714 * Sets the Drawable corresponding to the right handle used 3715 * for selecting text. The Drawable defaults to the value of the 3716 * textSelectHandleRight attribute. 3717 * Note that any change applied to the handle Drawable will not be visible 3718 * until the handle is hidden and then drawn again. 3719 * 3720 * @see #setTextSelectHandleRight(int) 3721 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3722 */ 3723 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3724 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3725 Preconditions.checkNotNull(textSelectHandleRight, 3726 "The right text select handle should not be null."); 3727 mTextSelectHandleRight = textSelectHandleRight; 3728 mTextSelectHandleRightRes = 0; 3729 if (mEditor != null) { 3730 mEditor.loadHandleDrawables(true /* overwrite */); 3731 } 3732 } 3733 3734 /** 3735 * Sets the Drawable corresponding to the right handle used 3736 * for selecting text. The Drawable defaults to the value of the 3737 * textSelectHandleRight attribute. 3738 * Note that any change applied to the handle Drawable will not be visible 3739 * until the handle is hidden and then drawn again. 3740 * 3741 * @see #setTextSelectHandleRight(Drawable) 3742 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3743 */ 3744 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3745 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3746 Preconditions.checkArgument(textSelectHandleRight != 0, 3747 "The text select right handle should be a valid drawable resource id."); 3748 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3749 } 3750 3751 /** 3752 * Returns the Drawable corresponding to the right handle used 3753 * for selecting text. 3754 * Note that any change applied to the handle Drawable will not be visible 3755 * until the handle is hidden and then drawn again. 3756 * 3757 * @return the right text selection handle drawable 3758 * 3759 * @see #setTextSelectHandleRight(Drawable) 3760 * @see #setTextSelectHandleRight(int) 3761 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3762 */ getTextSelectHandleRight()3763 @Nullable public Drawable getTextSelectHandleRight() { 3764 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3765 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3766 } 3767 return mTextSelectHandleRight; 3768 } 3769 3770 /** 3771 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3772 * value of the textCursorDrawable attribute. 3773 * Note that any change applied to the cursor Drawable will not be visible 3774 * until the cursor is hidden and then drawn again. 3775 * 3776 * @see #setTextCursorDrawable(int) 3777 * @attr ref android.R.styleable#TextView_textCursorDrawable 3778 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3779 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3780 mCursorDrawable = textCursorDrawable; 3781 mCursorDrawableRes = 0; 3782 if (mEditor != null) { 3783 mEditor.loadCursorDrawable(); 3784 } 3785 } 3786 3787 /** 3788 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3789 * value of the textCursorDrawable attribute. 3790 * Note that any change applied to the cursor Drawable will not be visible 3791 * until the cursor is hidden and then drawn again. 3792 * 3793 * @see #setTextCursorDrawable(Drawable) 3794 * @attr ref android.R.styleable#TextView_textCursorDrawable 3795 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3796 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3797 setTextCursorDrawable( 3798 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3799 } 3800 3801 /** 3802 * Returns the Drawable corresponding to the text cursor. 3803 * Note that any change applied to the cursor Drawable will not be visible 3804 * until the cursor is hidden and then drawn again. 3805 * 3806 * @return the text cursor drawable 3807 * 3808 * @see #setTextCursorDrawable(Drawable) 3809 * @see #setTextCursorDrawable(int) 3810 * @attr ref android.R.styleable#TextView_textCursorDrawable 3811 */ getTextCursorDrawable()3812 @Nullable public Drawable getTextCursorDrawable() { 3813 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3814 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3815 } 3816 return mCursorDrawable; 3817 } 3818 3819 /** 3820 * Sets the text appearance from the specified style resource. 3821 * <p> 3822 * Use a framework-defined {@code TextAppearance} style like 3823 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3824 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3825 * set of attributes that can be used in a custom style. 3826 * 3827 * @param resId the resource identifier of the style to apply 3828 * @attr ref android.R.styleable#TextView_textAppearance 3829 */ 3830 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3831 public void setTextAppearance(@StyleRes int resId) { 3832 setTextAppearance(mContext, resId); 3833 } 3834 3835 /** 3836 * Sets the text color, size, style, hint color, and highlight color 3837 * from the specified TextAppearance resource. 3838 * 3839 * @deprecated Use {@link #setTextAppearance(int)} instead. 3840 */ 3841 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3842 public void setTextAppearance(Context context, @StyleRes int resId) { 3843 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3844 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3845 readTextAppearance(context, ta, attributes, false /* styleArray */); 3846 ta.recycle(); 3847 applyTextAppearance(attributes); 3848 } 3849 3850 /** 3851 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3852 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3853 */ 3854 private static class TextAppearanceAttributes { 3855 int mTextColorHighlight = 0; 3856 ColorStateList mTextColor = null; 3857 ColorStateList mTextColorHint = null; 3858 ColorStateList mTextColorLink = null; 3859 int mTextSize = -1; 3860 int mTextSizeUnit = -1; 3861 LocaleList mTextLocales = null; 3862 String mFontFamily = null; 3863 Typeface mFontTypeface = null; 3864 boolean mFontFamilyExplicit = false; 3865 int mTypefaceIndex = -1; 3866 int mTextStyle = 0; 3867 int mFontWeight = -1; 3868 boolean mAllCaps = false; 3869 int mShadowColor = 0; 3870 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3871 boolean mHasElegant = false; 3872 boolean mElegant = false; 3873 boolean mHasFallbackLineSpacing = false; 3874 boolean mFallbackLineSpacing = false; 3875 boolean mHasLetterSpacing = false; 3876 float mLetterSpacing = 0; 3877 String mFontFeatureSettings = null; 3878 String mFontVariationSettings = null; 3879 3880 @Override toString()3881 public String toString() { 3882 return "TextAppearanceAttributes {\n" 3883 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 3884 + " mTextColor:" + mTextColor + "\n" 3885 + " mTextColorHint:" + mTextColorHint + "\n" 3886 + " mTextColorLink:" + mTextColorLink + "\n" 3887 + " mTextSize:" + mTextSize + "\n" 3888 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 3889 + " mTextLocales:" + mTextLocales + "\n" 3890 + " mFontFamily:" + mFontFamily + "\n" 3891 + " mFontTypeface:" + mFontTypeface + "\n" 3892 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 3893 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 3894 + " mTextStyle:" + mTextStyle + "\n" 3895 + " mFontWeight:" + mFontWeight + "\n" 3896 + " mAllCaps:" + mAllCaps + "\n" 3897 + " mShadowColor:" + mShadowColor + "\n" 3898 + " mShadowDx:" + mShadowDx + "\n" 3899 + " mShadowDy:" + mShadowDy + "\n" 3900 + " mShadowRadius:" + mShadowRadius + "\n" 3901 + " mHasElegant:" + mHasElegant + "\n" 3902 + " mElegant:" + mElegant + "\n" 3903 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 3904 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 3905 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 3906 + " mLetterSpacing:" + mLetterSpacing + "\n" 3907 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 3908 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 3909 + "}"; 3910 } 3911 } 3912 3913 // Maps styleable attributes that exist both in TextView style and TextAppearance. 3914 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 3915 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3916 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 3917 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3918 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 3919 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3920 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 3921 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3922 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 3923 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3924 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 3925 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3926 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 3927 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3928 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 3929 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3930 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 3931 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3932 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 3933 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3934 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 3935 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3936 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 3937 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3938 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 3939 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3940 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 3941 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3942 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 3943 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3944 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 3945 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3946 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 3947 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3948 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 3949 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3950 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 3951 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3952 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 3953 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3954 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 3955 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 3956 } 3957 3958 /** 3959 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 3960 * set. If the TypedArray contains a value that was already set in the given attributes, that 3961 * will be overridden. 3962 * 3963 * @param context The Context to be used 3964 * @param appearance The TypedArray to read properties from 3965 * @param attributes the TextAppearanceAttributes to fill in 3966 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 3967 * what attribute indexes will be used to read the properties. 3968 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3969 private void readTextAppearance(Context context, TypedArray appearance, 3970 TextAppearanceAttributes attributes, boolean styleArray) { 3971 final int n = appearance.getIndexCount(); 3972 for (int i = 0; i < n; i++) { 3973 final int attr = appearance.getIndex(i); 3974 int index = attr; 3975 // Translate style array index ids to TextAppearance ids. 3976 if (styleArray) { 3977 index = sAppearanceValues.get(attr, -1); 3978 if (index == -1) { 3979 // This value is not part of a Text Appearance and should be ignored. 3980 continue; 3981 } 3982 } 3983 switch (index) { 3984 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 3985 attributes.mTextColorHighlight = 3986 appearance.getColor(attr, attributes.mTextColorHighlight); 3987 break; 3988 case com.android.internal.R.styleable.TextAppearance_textColor: 3989 attributes.mTextColor = appearance.getColorStateList(attr); 3990 break; 3991 case com.android.internal.R.styleable.TextAppearance_textColorHint: 3992 attributes.mTextColorHint = appearance.getColorStateList(attr); 3993 break; 3994 case com.android.internal.R.styleable.TextAppearance_textColorLink: 3995 attributes.mTextColorLink = appearance.getColorStateList(attr); 3996 break; 3997 case com.android.internal.R.styleable.TextAppearance_textSize: 3998 attributes.mTextSize = 3999 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4000 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4001 break; 4002 case com.android.internal.R.styleable.TextAppearance_textLocale: 4003 final String localeString = appearance.getString(attr); 4004 if (localeString != null) { 4005 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4006 if (!localeList.isEmpty()) { 4007 attributes.mTextLocales = localeList; 4008 } 4009 } 4010 break; 4011 case com.android.internal.R.styleable.TextAppearance_typeface: 4012 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4013 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4014 attributes.mFontFamily = null; 4015 } 4016 break; 4017 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4018 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4019 try { 4020 attributes.mFontTypeface = appearance.getFont(attr); 4021 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4022 // Expected if it is not a font resource. 4023 } 4024 } 4025 if (attributes.mFontTypeface == null) { 4026 attributes.mFontFamily = appearance.getString(attr); 4027 } 4028 attributes.mFontFamilyExplicit = true; 4029 break; 4030 case com.android.internal.R.styleable.TextAppearance_textStyle: 4031 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4032 break; 4033 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4034 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4035 break; 4036 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4037 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4038 break; 4039 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4040 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4041 break; 4042 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4043 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4044 break; 4045 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4046 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4047 break; 4048 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4049 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4050 break; 4051 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4052 attributes.mHasElegant = true; 4053 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4054 break; 4055 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4056 attributes.mHasFallbackLineSpacing = true; 4057 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4058 attributes.mFallbackLineSpacing); 4059 break; 4060 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4061 attributes.mHasLetterSpacing = true; 4062 attributes.mLetterSpacing = 4063 appearance.getFloat(attr, attributes.mLetterSpacing); 4064 break; 4065 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4066 attributes.mFontFeatureSettings = appearance.getString(attr); 4067 break; 4068 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4069 attributes.mFontVariationSettings = appearance.getString(attr); 4070 break; 4071 default: 4072 } 4073 } 4074 } 4075 applyTextAppearance(TextAppearanceAttributes attributes)4076 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4077 if (attributes.mTextColor != null) { 4078 setTextColor(attributes.mTextColor); 4079 } 4080 4081 if (attributes.mTextColorHint != null) { 4082 setHintTextColor(attributes.mTextColorHint); 4083 } 4084 4085 if (attributes.mTextColorLink != null) { 4086 setLinkTextColor(attributes.mTextColorLink); 4087 } 4088 4089 if (attributes.mTextColorHighlight != 0) { 4090 setHighlightColor(attributes.mTextColorHighlight); 4091 } 4092 4093 if (attributes.mTextSize != -1) { 4094 mTextSizeUnit = attributes.mTextSizeUnit; 4095 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4096 } 4097 4098 if (attributes.mTextLocales != null) { 4099 setTextLocales(attributes.mTextLocales); 4100 } 4101 4102 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4103 attributes.mFontFamily = null; 4104 } 4105 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4106 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4107 4108 if (attributes.mShadowColor != 0) { 4109 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4110 attributes.mShadowColor); 4111 } 4112 4113 if (attributes.mAllCaps) { 4114 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4115 } 4116 4117 if (attributes.mHasElegant) { 4118 setElegantTextHeight(attributes.mElegant); 4119 } 4120 4121 if (attributes.mHasFallbackLineSpacing) { 4122 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4123 } 4124 4125 if (attributes.mHasLetterSpacing) { 4126 setLetterSpacing(attributes.mLetterSpacing); 4127 } 4128 4129 if (attributes.mFontFeatureSettings != null) { 4130 setFontFeatureSettings(attributes.mFontFeatureSettings); 4131 } 4132 4133 if (attributes.mFontVariationSettings != null) { 4134 setFontVariationSettings(attributes.mFontVariationSettings); 4135 } 4136 } 4137 4138 /** 4139 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4140 * the first member of {@link #getTextLocales()}. 4141 * @return the default primary {@link Locale} of the text in this TextView. 4142 */ 4143 @NonNull getTextLocale()4144 public Locale getTextLocale() { 4145 return mTextPaint.getTextLocale(); 4146 } 4147 4148 /** 4149 * Get the default {@link LocaleList} of the text in this TextView. 4150 * @return the default {@link LocaleList} of the text in this TextView. 4151 */ 4152 @NonNull @Size(min = 1) getTextLocales()4153 public LocaleList getTextLocales() { 4154 return mTextPaint.getTextLocales(); 4155 } 4156 changeListenerLocaleTo(@ullable Locale locale)4157 private void changeListenerLocaleTo(@Nullable Locale locale) { 4158 if (mListenerChanged) { 4159 // If a listener has been explicitly set, don't change it. We may break something. 4160 return; 4161 } 4162 // The following null check is not absolutely necessary since all calling points of 4163 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4164 // here in case others would want to call this method in the future. 4165 if (mEditor != null) { 4166 KeyListener listener = mEditor.mKeyListener; 4167 if (listener instanceof DigitsKeyListener) { 4168 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4169 } else if (listener instanceof DateKeyListener) { 4170 listener = DateKeyListener.getInstance(locale); 4171 } else if (listener instanceof TimeKeyListener) { 4172 listener = TimeKeyListener.getInstance(locale); 4173 } else if (listener instanceof DateTimeKeyListener) { 4174 listener = DateTimeKeyListener.getInstance(locale); 4175 } else { 4176 return; 4177 } 4178 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4179 setKeyListenerOnly(listener); 4180 setInputTypeFromEditor(); 4181 if (wasPasswordType) { 4182 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4183 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4184 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4185 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4186 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4187 } 4188 } 4189 } 4190 } 4191 4192 /** 4193 * Set the default {@link Locale} of the text in this TextView to a one-member 4194 * {@link LocaleList} containing just the given Locale. 4195 * 4196 * @param locale the {@link Locale} for drawing text, must not be null. 4197 * 4198 * @see #setTextLocales 4199 */ setTextLocale(@onNull Locale locale)4200 public void setTextLocale(@NonNull Locale locale) { 4201 mLocalesChanged = true; 4202 mTextPaint.setTextLocale(locale); 4203 if (mLayout != null) { 4204 nullLayouts(); 4205 requestLayout(); 4206 invalidate(); 4207 } 4208 } 4209 4210 /** 4211 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4212 * 4213 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4214 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4215 * other aspects of text display, including line breaking. 4216 * 4217 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4218 * 4219 * @see Paint#setTextLocales 4220 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4221 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4222 mLocalesChanged = true; 4223 mTextPaint.setTextLocales(locales); 4224 if (mLayout != null) { 4225 nullLayouts(); 4226 requestLayout(); 4227 invalidate(); 4228 } 4229 } 4230 4231 @Override onConfigurationChanged(Configuration newConfig)4232 protected void onConfigurationChanged(Configuration newConfig) { 4233 super.onConfigurationChanged(newConfig); 4234 if (!mLocalesChanged) { 4235 mTextPaint.setTextLocales(LocaleList.getDefault()); 4236 if (mLayout != null) { 4237 nullLayouts(); 4238 requestLayout(); 4239 invalidate(); 4240 } 4241 } 4242 } 4243 4244 /** 4245 * @return the size (in pixels) of the default text size in this TextView. 4246 */ 4247 @InspectableProperty 4248 @ViewDebug.ExportedProperty(category = "text") getTextSize()4249 public float getTextSize() { 4250 return mTextPaint.getTextSize(); 4251 } 4252 4253 /** 4254 * @return the size (in scaled pixels) of the default text size in this TextView. 4255 * @hide 4256 */ 4257 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4258 public float getScaledTextSize() { 4259 return mTextPaint.getTextSize() / mTextPaint.density; 4260 } 4261 4262 /** @hide */ 4263 @ViewDebug.ExportedProperty(category = "text", mapping = { 4264 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4265 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4266 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4267 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4268 }) getTypefaceStyle()4269 public int getTypefaceStyle() { 4270 Typeface typeface = mTextPaint.getTypeface(); 4271 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4272 } 4273 4274 /** 4275 * Set the default text size to the given value, interpreted as "scaled 4276 * pixel" units. This size is adjusted based on the current density and 4277 * user font size preference. 4278 * 4279 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4280 * 4281 * @param size The scaled pixel size. 4282 * 4283 * @attr ref android.R.styleable#TextView_textSize 4284 */ 4285 @android.view.RemotableViewMethod setTextSize(float size)4286 public void setTextSize(float size) { 4287 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4288 } 4289 4290 /** 4291 * Set the default text size to a given unit and value. See {@link 4292 * TypedValue} for the possible dimension units. 4293 * 4294 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4295 * 4296 * @param unit The desired dimension unit. 4297 * @param size The desired size in the given units. 4298 * 4299 * @attr ref android.R.styleable#TextView_textSize 4300 */ setTextSize(int unit, float size)4301 public void setTextSize(int unit, float size) { 4302 if (!isAutoSizeEnabled()) { 4303 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4304 } 4305 } 4306 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4307 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4308 Context c = getContext(); 4309 Resources r; 4310 4311 if (c == null) { 4312 r = Resources.getSystem(); 4313 } else { 4314 r = c.getResources(); 4315 } 4316 4317 mTextSizeUnit = unit; 4318 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4319 shouldRequestLayout); 4320 } 4321 4322 @UnsupportedAppUsage setRawTextSize(float size, boolean shouldRequestLayout)4323 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4324 if (size != mTextPaint.getTextSize()) { 4325 mTextPaint.setTextSize(size); 4326 4327 if (shouldRequestLayout && mLayout != null) { 4328 // Do not auto-size right after setting the text size. 4329 mNeedsAutoSizeText = false; 4330 nullLayouts(); 4331 requestLayout(); 4332 invalidate(); 4333 } 4334 } 4335 } 4336 4337 /** 4338 * Gets the text size unit defined by the developer. It may be specified in resources or be 4339 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4340 * 4341 * @return the dimension type of the text size unit originally defined. 4342 * @see TypedValue#TYPE_DIMENSION 4343 */ getTextSizeUnit()4344 public int getTextSizeUnit() { 4345 return mTextSizeUnit; 4346 } 4347 4348 /** 4349 * Gets the extent by which text should be stretched horizontally. 4350 * This will usually be 1.0. 4351 * @return The horizontal scale factor. 4352 */ 4353 @InspectableProperty getTextScaleX()4354 public float getTextScaleX() { 4355 return mTextPaint.getTextScaleX(); 4356 } 4357 4358 /** 4359 * Sets the horizontal scale factor for text. The default value 4360 * is 1.0. Values greater than 1.0 stretch the text wider. 4361 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4362 * @param size The horizontal scale factor. 4363 * @attr ref android.R.styleable#TextView_textScaleX 4364 */ 4365 @android.view.RemotableViewMethod setTextScaleX(float size)4366 public void setTextScaleX(float size) { 4367 if (size != mTextPaint.getTextScaleX()) { 4368 mUserSetTextScaleX = true; 4369 mTextPaint.setTextScaleX(size); 4370 4371 if (mLayout != null) { 4372 nullLayouts(); 4373 requestLayout(); 4374 invalidate(); 4375 } 4376 } 4377 } 4378 4379 /** 4380 * Sets the typeface and style in which the text should be displayed. 4381 * Note that not all Typeface families actually have bold and italic 4382 * variants, so you may need to use 4383 * {@link #setTypeface(Typeface, int)} to get the appearance 4384 * that you actually want. 4385 * 4386 * @see #getTypeface() 4387 * 4388 * @attr ref android.R.styleable#TextView_fontFamily 4389 * @attr ref android.R.styleable#TextView_typeface 4390 * @attr ref android.R.styleable#TextView_textStyle 4391 */ setTypeface(@ullable Typeface tf)4392 public void setTypeface(@Nullable Typeface tf) { 4393 if (mTextPaint.getTypeface() != tf) { 4394 mTextPaint.setTypeface(tf); 4395 4396 if (mLayout != null) { 4397 nullLayouts(); 4398 requestLayout(); 4399 invalidate(); 4400 } 4401 } 4402 } 4403 4404 /** 4405 * Gets the current {@link Typeface} that is used to style the text. 4406 * @return The current Typeface. 4407 * 4408 * @see #setTypeface(Typeface) 4409 * 4410 * @attr ref android.R.styleable#TextView_fontFamily 4411 * @attr ref android.R.styleable#TextView_typeface 4412 * @attr ref android.R.styleable#TextView_textStyle 4413 */ 4414 @InspectableProperty getTypeface()4415 public Typeface getTypeface() { 4416 return mTextPaint.getTypeface(); 4417 } 4418 4419 /** 4420 * Set the TextView's elegant height metrics flag. This setting selects font 4421 * variants that have not been compacted to fit Latin-based vertical 4422 * metrics, and also increases top and bottom bounds to provide more space. 4423 * 4424 * @param elegant set the paint's elegant metrics flag. 4425 * 4426 * @see #isElegantTextHeight() 4427 * @see Paint#isElegantTextHeight() 4428 * 4429 * @attr ref android.R.styleable#TextView_elegantTextHeight 4430 */ setElegantTextHeight(boolean elegant)4431 public void setElegantTextHeight(boolean elegant) { 4432 if (elegant != mTextPaint.isElegantTextHeight()) { 4433 mTextPaint.setElegantTextHeight(elegant); 4434 if (mLayout != null) { 4435 nullLayouts(); 4436 requestLayout(); 4437 invalidate(); 4438 } 4439 } 4440 } 4441 4442 /** 4443 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4444 * displaying the text (which is needed to avoid text from consecutive lines running into 4445 * each other). If set, fallback fonts that end up getting used can increase the ascent 4446 * and descent of the lines that they are used on. 4447 * <p/> 4448 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4449 * is typically much taller or deeper than Latin text. 4450 * 4451 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4452 * 4453 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4454 * 4455 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4456 */ setFallbackLineSpacing(boolean enabled)4457 public void setFallbackLineSpacing(boolean enabled) { 4458 if (mUseFallbackLineSpacing != enabled) { 4459 mUseFallbackLineSpacing = enabled; 4460 if (mLayout != null) { 4461 nullLayouts(); 4462 requestLayout(); 4463 invalidate(); 4464 } 4465 } 4466 } 4467 4468 /** 4469 * @return whether fallback line spacing is enabled, {@code true} by default 4470 * 4471 * @see #setFallbackLineSpacing(boolean) 4472 * 4473 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4474 */ 4475 @InspectableProperty isFallbackLineSpacing()4476 public boolean isFallbackLineSpacing() { 4477 return mUseFallbackLineSpacing; 4478 } 4479 4480 /** 4481 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4482 * variants that have not been compacted to fit Latin-based vertical 4483 * metrics, and also increases top and bottom bounds to provide more space. 4484 * @return {@code true} if the elegant height metrics flag is set. 4485 * 4486 * @see #setElegantTextHeight(boolean) 4487 * @see Paint#setElegantTextHeight(boolean) 4488 */ 4489 @InspectableProperty isElegantTextHeight()4490 public boolean isElegantTextHeight() { 4491 return mTextPaint.isElegantTextHeight(); 4492 } 4493 4494 /** 4495 * Gets the text letter-space value, which determines the spacing between characters. 4496 * The value returned is in ems. Normally, this value is 0.0. 4497 * @return The text letter-space value in ems. 4498 * 4499 * @see #setLetterSpacing(float) 4500 * @see Paint#setLetterSpacing 4501 */ 4502 @InspectableProperty getLetterSpacing()4503 public float getLetterSpacing() { 4504 return mTextPaint.getLetterSpacing(); 4505 } 4506 4507 /** 4508 * Sets text letter-spacing in em units. Typical values 4509 * for slight expansion will be around 0.05. Negative values tighten text. 4510 * 4511 * @see #getLetterSpacing() 4512 * @see Paint#getLetterSpacing 4513 * 4514 * @param letterSpacing A text letter-space value in ems. 4515 * @attr ref android.R.styleable#TextView_letterSpacing 4516 */ 4517 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4518 public void setLetterSpacing(float letterSpacing) { 4519 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4520 mTextPaint.setLetterSpacing(letterSpacing); 4521 4522 if (mLayout != null) { 4523 nullLayouts(); 4524 requestLayout(); 4525 invalidate(); 4526 } 4527 } 4528 } 4529 4530 /** 4531 * Returns the font feature settings. The format is the same as the CSS 4532 * font-feature-settings attribute: 4533 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4534 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4535 * 4536 * @return the currently set font feature settings. Default is null. 4537 * 4538 * @see #setFontFeatureSettings(String) 4539 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4540 */ 4541 @InspectableProperty 4542 @Nullable getFontFeatureSettings()4543 public String getFontFeatureSettings() { 4544 return mTextPaint.getFontFeatureSettings(); 4545 } 4546 4547 /** 4548 * Returns the font variation settings. 4549 * 4550 * @return the currently set font variation settings. Returns null if no variation is 4551 * specified. 4552 * 4553 * @see #setFontVariationSettings(String) 4554 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4555 */ 4556 @Nullable getFontVariationSettings()4557 public String getFontVariationSettings() { 4558 return mTextPaint.getFontVariationSettings(); 4559 } 4560 4561 /** 4562 * Sets the break strategy for breaking paragraphs into lines. The default value for 4563 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4564 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4565 * text "dancing" when being edited. 4566 * <p/> 4567 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4568 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4569 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4570 * improves the structure of text layout however has performance impact and requires more time 4571 * to do the text layout. 4572 * 4573 * @attr ref android.R.styleable#TextView_breakStrategy 4574 * @see #getBreakStrategy() 4575 * @see #setHyphenationFrequency(int) 4576 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4577 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4578 mBreakStrategy = breakStrategy; 4579 if (mLayout != null) { 4580 nullLayouts(); 4581 requestLayout(); 4582 invalidate(); 4583 } 4584 } 4585 4586 /** 4587 * Gets the current strategy for breaking paragraphs into lines. 4588 * @return the current strategy for breaking paragraphs into lines. 4589 * 4590 * @attr ref android.R.styleable#TextView_breakStrategy 4591 * @see #setBreakStrategy(int) 4592 */ 4593 @InspectableProperty(enumMapping = { 4594 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4595 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4596 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4597 }) 4598 @Layout.BreakStrategy getBreakStrategy()4599 public int getBreakStrategy() { 4600 return mBreakStrategy; 4601 } 4602 4603 /** 4604 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4605 * The default value for both TextView and {@link EditText} is 4606 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4607 * is set from the theme. 4608 * <p/> 4609 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4610 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4611 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4612 * improves the structure of text layout however has performance impact and requires more time 4613 * to do the text layout. 4614 * <p/> 4615 * Note: Before Android Q, in the theme hyphenation frequency is set to 4616 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4617 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4618 * 4619 * @param hyphenationFrequency the hyphenation frequency to use, one of 4620 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4621 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4622 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4623 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4624 * @see #getHyphenationFrequency() 4625 * @see #getBreakStrategy() 4626 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4627 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4628 mHyphenationFrequency = hyphenationFrequency; 4629 if (mLayout != null) { 4630 nullLayouts(); 4631 requestLayout(); 4632 invalidate(); 4633 } 4634 } 4635 4636 /** 4637 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4638 * @return the current frequency of automatic hyphenation to be used when determining word 4639 * breaks. 4640 * 4641 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4642 * @see #setHyphenationFrequency(int) 4643 */ 4644 @InspectableProperty(enumMapping = { 4645 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4646 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4647 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4648 }) 4649 @Layout.HyphenationFrequency getHyphenationFrequency()4650 public int getHyphenationFrequency() { 4651 return mHyphenationFrequency; 4652 } 4653 4654 /** 4655 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4656 * 4657 * @return a current {@link PrecomputedText.Params} 4658 * @see PrecomputedText 4659 */ getTextMetricsParams()4660 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4661 return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), 4662 mBreakStrategy, mHyphenationFrequency); 4663 } 4664 4665 /** 4666 * Apply the text layout parameter. 4667 * 4668 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4669 * @see PrecomputedText 4670 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4671 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4672 mTextPaint.set(params.getTextPaint()); 4673 mUserSetTextScaleX = true; 4674 mTextDir = params.getTextDirection(); 4675 mBreakStrategy = params.getBreakStrategy(); 4676 mHyphenationFrequency = params.getHyphenationFrequency(); 4677 if (mLayout != null) { 4678 nullLayouts(); 4679 requestLayout(); 4680 invalidate(); 4681 } 4682 } 4683 4684 /** 4685 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4686 * last line is too short for justification, the last line will be displayed with the 4687 * alignment set by {@link android.view.View#setTextAlignment}. 4688 * 4689 * @see #getJustificationMode() 4690 */ 4691 @Layout.JustificationMode setJustificationMode(@ayout.JustificationMode int justificationMode)4692 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 4693 mJustificationMode = justificationMode; 4694 if (mLayout != null) { 4695 nullLayouts(); 4696 requestLayout(); 4697 invalidate(); 4698 } 4699 } 4700 4701 /** 4702 * @return true if currently paragraph justification mode. 4703 * 4704 * @see #setJustificationMode(int) 4705 */ 4706 @InspectableProperty(enumMapping = { 4707 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 4708 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 4709 }) getJustificationMode()4710 public @Layout.JustificationMode int getJustificationMode() { 4711 return mJustificationMode; 4712 } 4713 4714 /** 4715 * Sets font feature settings. The format is the same as the CSS 4716 * font-feature-settings attribute: 4717 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4718 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4719 * 4720 * @param fontFeatureSettings font feature settings represented as CSS compatible string 4721 * 4722 * @see #getFontFeatureSettings() 4723 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 4724 * 4725 * @attr ref android.R.styleable#TextView_fontFeatureSettings 4726 */ 4727 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)4728 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 4729 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 4730 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 4731 4732 if (mLayout != null) { 4733 nullLayouts(); 4734 requestLayout(); 4735 invalidate(); 4736 } 4737 } 4738 } 4739 4740 4741 /** 4742 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 4743 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 4744 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 4745 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 4746 * are invalid. If a specified axis name is not defined in the font, the settings will be 4747 * ignored. 4748 * 4749 * <p> 4750 * Examples, 4751 * <ul> 4752 * <li>Set font width to 150. 4753 * <pre> 4754 * <code> 4755 * TextView textView = (TextView) findViewById(R.id.textView); 4756 * textView.setFontVariationSettings("'wdth' 150"); 4757 * </code> 4758 * </pre> 4759 * </li> 4760 * 4761 * <li>Set the font slant to 20 degrees and ask for italic style. 4762 * <pre> 4763 * <code> 4764 * TextView textView = (TextView) findViewById(R.id.textView); 4765 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 4766 * </code> 4767 * </pre> 4768 * </p> 4769 * </li> 4770 * </ul> 4771 * 4772 * @param fontVariationSettings font variation settings. You can pass null or empty string as 4773 * no variation settings. 4774 * @return true if the given settings is effective to at least one font file underlying this 4775 * TextView. This function also returns true for empty settings string. Otherwise 4776 * returns false. 4777 * 4778 * @throws IllegalArgumentException If given string is not a valid font variation settings 4779 * format. 4780 * 4781 * @see #getFontVariationSettings() 4782 * @see FontVariationAxis 4783 * 4784 * @attr ref android.R.styleable#TextView_fontVariationSettings 4785 */ setFontVariationSettings(@ullable String fontVariationSettings)4786 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 4787 final String existingSettings = mTextPaint.getFontVariationSettings(); 4788 if (fontVariationSettings == existingSettings 4789 || (fontVariationSettings != null 4790 && fontVariationSettings.equals(existingSettings))) { 4791 return true; 4792 } 4793 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 4794 4795 if (effective && mLayout != null) { 4796 nullLayouts(); 4797 requestLayout(); 4798 invalidate(); 4799 } 4800 return effective; 4801 } 4802 4803 /** 4804 * Sets the text color for all the states (normal, selected, 4805 * focused) to be this color. 4806 * 4807 * @param color A color value in the form 0xAARRGGBB. 4808 * Do not pass a resource ID. To get a color value from a resource ID, call 4809 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 4810 * 4811 * @see #setTextColor(ColorStateList) 4812 * @see #getTextColors() 4813 * 4814 * @attr ref android.R.styleable#TextView_textColor 4815 */ 4816 @android.view.RemotableViewMethod setTextColor(@olorInt int color)4817 public void setTextColor(@ColorInt int color) { 4818 mTextColor = ColorStateList.valueOf(color); 4819 updateTextColors(); 4820 } 4821 4822 /** 4823 * Sets the text color. 4824 * 4825 * @see #setTextColor(int) 4826 * @see #getTextColors() 4827 * @see #setHintTextColor(ColorStateList) 4828 * @see #setLinkTextColor(ColorStateList) 4829 * 4830 * @attr ref android.R.styleable#TextView_textColor 4831 */ 4832 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)4833 public void setTextColor(ColorStateList colors) { 4834 if (colors == null) { 4835 throw new NullPointerException(); 4836 } 4837 4838 mTextColor = colors; 4839 updateTextColors(); 4840 } 4841 4842 /** 4843 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4844 * 4845 * @see #setTextColor(ColorStateList) 4846 * @see #setTextColor(int) 4847 * 4848 * @attr ref android.R.styleable#TextView_textColor 4849 */ 4850 @InspectableProperty(name = "textColor") getTextColors()4851 public final ColorStateList getTextColors() { 4852 return mTextColor; 4853 } 4854 4855 /** 4856 * Return the current color selected for normal text. 4857 * 4858 * @return Returns the current text color. 4859 */ 4860 @ColorInt getCurrentTextColor()4861 public final int getCurrentTextColor() { 4862 return mCurTextColor; 4863 } 4864 4865 /** 4866 * Sets the color used to display the selection highlight. 4867 * 4868 * @attr ref android.R.styleable#TextView_textColorHighlight 4869 */ 4870 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)4871 public void setHighlightColor(@ColorInt int color) { 4872 if (mHighlightColor != color) { 4873 mHighlightColor = color; 4874 invalidate(); 4875 } 4876 } 4877 4878 /** 4879 * @return the color used to display the selection highlight 4880 * 4881 * @see #setHighlightColor(int) 4882 * 4883 * @attr ref android.R.styleable#TextView_textColorHighlight 4884 */ 4885 @InspectableProperty(name = "textColorHighlight") 4886 @ColorInt getHighlightColor()4887 public int getHighlightColor() { 4888 return mHighlightColor; 4889 } 4890 4891 /** 4892 * Sets whether the soft input method will be made visible when this 4893 * TextView gets focused. The default is true. 4894 */ 4895 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)4896 public final void setShowSoftInputOnFocus(boolean show) { 4897 createEditorIfNeeded(); 4898 mEditor.mShowSoftInputOnFocus = show; 4899 } 4900 4901 /** 4902 * Returns whether the soft input method will be made visible when this 4903 * TextView gets focused. The default is true. 4904 */ getShowSoftInputOnFocus()4905 public final boolean getShowSoftInputOnFocus() { 4906 // When there is no Editor, return default true value 4907 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4908 } 4909 4910 /** 4911 * Gives the text a shadow of the specified blur radius and color, the specified 4912 * distance from its drawn position. 4913 * <p> 4914 * The text shadow produced does not interact with the properties on view 4915 * that are responsible for real time shadows, 4916 * {@link View#getElevation() elevation} and 4917 * {@link View#getTranslationZ() translationZ}. 4918 * 4919 * @see Paint#setShadowLayer(float, float, float, int) 4920 * 4921 * @attr ref android.R.styleable#TextView_shadowColor 4922 * @attr ref android.R.styleable#TextView_shadowDx 4923 * @attr ref android.R.styleable#TextView_shadowDy 4924 * @attr ref android.R.styleable#TextView_shadowRadius 4925 */ setShadowLayer(float radius, float dx, float dy, int color)4926 public void setShadowLayer(float radius, float dx, float dy, int color) { 4927 mTextPaint.setShadowLayer(radius, dx, dy, color); 4928 4929 mShadowRadius = radius; 4930 mShadowDx = dx; 4931 mShadowDy = dy; 4932 mShadowColor = color; 4933 4934 // Will change text clip region 4935 if (mEditor != null) { 4936 mEditor.invalidateTextDisplayList(); 4937 mEditor.invalidateHandlesAndActionMode(); 4938 } 4939 invalidate(); 4940 } 4941 4942 /** 4943 * Gets the radius of the shadow layer. 4944 * 4945 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 4946 * 4947 * @see #setShadowLayer(float, float, float, int) 4948 * 4949 * @attr ref android.R.styleable#TextView_shadowRadius 4950 */ 4951 @InspectableProperty getShadowRadius()4952 public float getShadowRadius() { 4953 return mShadowRadius; 4954 } 4955 4956 /** 4957 * @return the horizontal offset of the shadow layer 4958 * 4959 * @see #setShadowLayer(float, float, float, int) 4960 * 4961 * @attr ref android.R.styleable#TextView_shadowDx 4962 */ 4963 @InspectableProperty getShadowDx()4964 public float getShadowDx() { 4965 return mShadowDx; 4966 } 4967 4968 /** 4969 * Gets the vertical offset of the shadow layer. 4970 * @return The vertical offset of the shadow layer. 4971 * 4972 * @see #setShadowLayer(float, float, float, int) 4973 * 4974 * @attr ref android.R.styleable#TextView_shadowDy 4975 */ 4976 @InspectableProperty getShadowDy()4977 public float getShadowDy() { 4978 return mShadowDy; 4979 } 4980 4981 /** 4982 * Gets the color of the shadow layer. 4983 * @return the color of the shadow layer 4984 * 4985 * @see #setShadowLayer(float, float, float, int) 4986 * 4987 * @attr ref android.R.styleable#TextView_shadowColor 4988 */ 4989 @InspectableProperty 4990 @ColorInt getShadowColor()4991 public int getShadowColor() { 4992 return mShadowColor; 4993 } 4994 4995 /** 4996 * Gets the {@link TextPaint} used for the text. 4997 * Use this only to consult the Paint's properties and not to change them. 4998 * @return The base paint used for the text. 4999 */ getPaint()5000 public TextPaint getPaint() { 5001 return mTextPaint; 5002 } 5003 5004 /** 5005 * Sets the autolink mask of the text. See {@link 5006 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5007 * possible values. 5008 * 5009 * <p class="note"><b>Note:</b> 5010 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5011 * is deprecated and should be avoided; see its documentation. 5012 * 5013 * @attr ref android.R.styleable#TextView_autoLink 5014 */ 5015 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5016 public final void setAutoLinkMask(int mask) { 5017 mAutoLinkMask = mask; 5018 } 5019 5020 /** 5021 * Sets whether the movement method will automatically be set to 5022 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5023 * set to nonzero and links are detected in {@link #setText}. 5024 * The default is true. 5025 * 5026 * @attr ref android.R.styleable#TextView_linksClickable 5027 */ 5028 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5029 public final void setLinksClickable(boolean whether) { 5030 mLinksClickable = whether; 5031 } 5032 5033 /** 5034 * Returns whether the movement method will automatically be set to 5035 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5036 * set to nonzero and links are detected in {@link #setText}. 5037 * The default is true. 5038 * 5039 * @attr ref android.R.styleable#TextView_linksClickable 5040 */ 5041 @InspectableProperty getLinksClickable()5042 public final boolean getLinksClickable() { 5043 return mLinksClickable; 5044 } 5045 5046 /** 5047 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5048 * (by {@link Linkify} or otherwise) if any. You can call 5049 * {@link URLSpan#getURL} on them to find where they link to 5050 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5051 * to find the region of the text they are attached to. 5052 */ getUrls()5053 public URLSpan[] getUrls() { 5054 if (mText instanceof Spanned) { 5055 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5056 } else { 5057 return new URLSpan[0]; 5058 } 5059 } 5060 5061 /** 5062 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5063 * TextView. 5064 * 5065 * @see #setHintTextColor(ColorStateList) 5066 * @see #getHintTextColors() 5067 * @see #setTextColor(int) 5068 * 5069 * @attr ref android.R.styleable#TextView_textColorHint 5070 */ 5071 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5072 public final void setHintTextColor(@ColorInt int color) { 5073 mHintTextColor = ColorStateList.valueOf(color); 5074 updateTextColors(); 5075 } 5076 5077 /** 5078 * Sets the color of the hint text. 5079 * 5080 * @see #getHintTextColors() 5081 * @see #setHintTextColor(int) 5082 * @see #setTextColor(ColorStateList) 5083 * @see #setLinkTextColor(ColorStateList) 5084 * 5085 * @attr ref android.R.styleable#TextView_textColorHint 5086 */ setHintTextColor(ColorStateList colors)5087 public final void setHintTextColor(ColorStateList colors) { 5088 mHintTextColor = colors; 5089 updateTextColors(); 5090 } 5091 5092 /** 5093 * @return the color of the hint text, for the different states of this TextView. 5094 * 5095 * @see #setHintTextColor(ColorStateList) 5096 * @see #setHintTextColor(int) 5097 * @see #setTextColor(ColorStateList) 5098 * @see #setLinkTextColor(ColorStateList) 5099 * 5100 * @attr ref android.R.styleable#TextView_textColorHint 5101 */ 5102 @InspectableProperty(name = "textColorHint") getHintTextColors()5103 public final ColorStateList getHintTextColors() { 5104 return mHintTextColor; 5105 } 5106 5107 /** 5108 * <p>Return the current color selected to paint the hint text.</p> 5109 * 5110 * @return Returns the current hint text color. 5111 */ 5112 @ColorInt getCurrentHintTextColor()5113 public final int getCurrentHintTextColor() { 5114 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5115 } 5116 5117 /** 5118 * Sets the color of links in the text. 5119 * 5120 * @see #setLinkTextColor(ColorStateList) 5121 * @see #getLinkTextColors() 5122 * 5123 * @attr ref android.R.styleable#TextView_textColorLink 5124 */ 5125 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5126 public final void setLinkTextColor(@ColorInt int color) { 5127 mLinkTextColor = ColorStateList.valueOf(color); 5128 updateTextColors(); 5129 } 5130 5131 /** 5132 * Sets the color of links in the text. 5133 * 5134 * @see #setLinkTextColor(int) 5135 * @see #getLinkTextColors() 5136 * @see #setTextColor(ColorStateList) 5137 * @see #setHintTextColor(ColorStateList) 5138 * 5139 * @attr ref android.R.styleable#TextView_textColorLink 5140 */ setLinkTextColor(ColorStateList colors)5141 public final void setLinkTextColor(ColorStateList colors) { 5142 mLinkTextColor = colors; 5143 updateTextColors(); 5144 } 5145 5146 /** 5147 * @return the list of colors used to paint the links in the text, for the different states of 5148 * this TextView 5149 * 5150 * @see #setLinkTextColor(ColorStateList) 5151 * @see #setLinkTextColor(int) 5152 * 5153 * @attr ref android.R.styleable#TextView_textColorLink 5154 */ 5155 @InspectableProperty(name = "textColorLink") getLinkTextColors()5156 public final ColorStateList getLinkTextColors() { 5157 return mLinkTextColor; 5158 } 5159 5160 /** 5161 * Sets the horizontal alignment of the text and the 5162 * vertical gravity that will be used when there is extra space 5163 * in the TextView beyond what is required for the text itself. 5164 * 5165 * @see android.view.Gravity 5166 * @attr ref android.R.styleable#TextView_gravity 5167 */ setGravity(int gravity)5168 public void setGravity(int gravity) { 5169 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5170 gravity |= Gravity.START; 5171 } 5172 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5173 gravity |= Gravity.TOP; 5174 } 5175 5176 boolean newLayout = false; 5177 5178 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5179 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5180 newLayout = true; 5181 } 5182 5183 if (gravity != mGravity) { 5184 invalidate(); 5185 } 5186 5187 mGravity = gravity; 5188 5189 if (mLayout != null && newLayout) { 5190 // XXX this is heavy-handed because no actual content changes. 5191 int want = mLayout.getWidth(); 5192 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5193 5194 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5195 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5196 } 5197 } 5198 5199 /** 5200 * Returns the horizontal and vertical alignment of this TextView. 5201 * 5202 * @see android.view.Gravity 5203 * @attr ref android.R.styleable#TextView_gravity 5204 */ 5205 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5206 public int getGravity() { 5207 return mGravity; 5208 } 5209 5210 /** 5211 * Gets the flags on the Paint being used to display the text. 5212 * @return The flags on the Paint being used to display the text. 5213 * @see Paint#getFlags 5214 */ getPaintFlags()5215 public int getPaintFlags() { 5216 return mTextPaint.getFlags(); 5217 } 5218 5219 /** 5220 * Sets flags on the Paint being used to display the text and 5221 * reflows the text if they are different from the old flags. 5222 * @see Paint#setFlags 5223 */ 5224 @android.view.RemotableViewMethod setPaintFlags(int flags)5225 public void setPaintFlags(int flags) { 5226 if (mTextPaint.getFlags() != flags) { 5227 mTextPaint.setFlags(flags); 5228 5229 if (mLayout != null) { 5230 nullLayouts(); 5231 requestLayout(); 5232 invalidate(); 5233 } 5234 } 5235 } 5236 5237 /** 5238 * Sets whether the text should be allowed to be wider than the 5239 * View is. If false, it will be wrapped to the width of the View. 5240 * 5241 * @attr ref android.R.styleable#TextView_scrollHorizontally 5242 */ setHorizontallyScrolling(boolean whether)5243 public void setHorizontallyScrolling(boolean whether) { 5244 if (mHorizontallyScrolling != whether) { 5245 mHorizontallyScrolling = whether; 5246 5247 if (mLayout != null) { 5248 nullLayouts(); 5249 requestLayout(); 5250 invalidate(); 5251 } 5252 } 5253 } 5254 5255 /** 5256 * Returns whether the text is allowed to be wider than the View. 5257 * If false, the text will be wrapped to the width of the View. 5258 * 5259 * @attr ref android.R.styleable#TextView_scrollHorizontally 5260 * @see #setHorizontallyScrolling(boolean) 5261 */ 5262 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5263 public final boolean isHorizontallyScrollable() { 5264 return mHorizontallyScrolling; 5265 } 5266 5267 /** 5268 * Returns whether the text is allowed to be wider than the View. 5269 * If false, the text will be wrapped to the width of the View. 5270 * 5271 * @attr ref android.R.styleable#TextView_scrollHorizontally 5272 * @hide 5273 */ 5274 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5275 public boolean getHorizontallyScrolling() { 5276 return mHorizontallyScrolling; 5277 } 5278 5279 /** 5280 * Sets the height of the TextView to be at least {@code minLines} tall. 5281 * <p> 5282 * This value is used for height calculation if LayoutParams does not force TextView to have an 5283 * exact height. Setting this value overrides other previous minimum height configurations such 5284 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5285 * this value to 1. 5286 * 5287 * @param minLines the minimum height of TextView in terms of number of lines 5288 * 5289 * @see #getMinLines() 5290 * @see #setLines(int) 5291 * 5292 * @attr ref android.R.styleable#TextView_minLines 5293 */ 5294 @android.view.RemotableViewMethod setMinLines(int minLines)5295 public void setMinLines(int minLines) { 5296 mMinimum = minLines; 5297 mMinMode = LINES; 5298 5299 requestLayout(); 5300 invalidate(); 5301 } 5302 5303 /** 5304 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5305 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5306 * 5307 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5308 * height is not defined in lines 5309 * 5310 * @see #setMinLines(int) 5311 * @see #setLines(int) 5312 * 5313 * @attr ref android.R.styleable#TextView_minLines 5314 */ 5315 @InspectableProperty getMinLines()5316 public int getMinLines() { 5317 return mMinMode == LINES ? mMinimum : -1; 5318 } 5319 5320 /** 5321 * Sets the height of the TextView to be at least {@code minPixels} tall. 5322 * <p> 5323 * This value is used for height calculation if LayoutParams does not force TextView to have an 5324 * exact height. Setting this value overrides previous minimum height configurations such as 5325 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5326 * <p> 5327 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5328 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5329 * used to decide the final height. 5330 * 5331 * @param minPixels the minimum height of TextView in terms of pixels 5332 * 5333 * @see #getMinHeight() 5334 * @see #setHeight(int) 5335 * 5336 * @attr ref android.R.styleable#TextView_minHeight 5337 */ 5338 @android.view.RemotableViewMethod setMinHeight(int minPixels)5339 public void setMinHeight(int minPixels) { 5340 mMinimum = minPixels; 5341 mMinMode = PIXELS; 5342 5343 requestLayout(); 5344 invalidate(); 5345 } 5346 5347 /** 5348 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5349 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5350 * 5351 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5352 * defined in pixels 5353 * 5354 * @see #setMinHeight(int) 5355 * @see #setHeight(int) 5356 * 5357 * @attr ref android.R.styleable#TextView_minHeight 5358 */ getMinHeight()5359 public int getMinHeight() { 5360 return mMinMode == PIXELS ? mMinimum : -1; 5361 } 5362 5363 /** 5364 * Sets the height of the TextView to be at most {@code maxLines} tall. 5365 * <p> 5366 * This value is used for height calculation if LayoutParams does not force TextView to have an 5367 * exact height. Setting this value overrides previous maximum height configurations such as 5368 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5369 * 5370 * @param maxLines the maximum height of TextView in terms of number of lines 5371 * 5372 * @see #getMaxLines() 5373 * @see #setLines(int) 5374 * 5375 * @attr ref android.R.styleable#TextView_maxLines 5376 */ 5377 @android.view.RemotableViewMethod setMaxLines(int maxLines)5378 public void setMaxLines(int maxLines) { 5379 mMaximum = maxLines; 5380 mMaxMode = LINES; 5381 5382 requestLayout(); 5383 invalidate(); 5384 } 5385 5386 /** 5387 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5388 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5389 * 5390 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5391 * is not defined in lines. 5392 * 5393 * @see #setMaxLines(int) 5394 * @see #setLines(int) 5395 * 5396 * @attr ref android.R.styleable#TextView_maxLines 5397 */ 5398 @InspectableProperty getMaxLines()5399 public int getMaxLines() { 5400 return mMaxMode == LINES ? mMaximum : -1; 5401 } 5402 5403 /** 5404 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5405 * <p> 5406 * This value is used for height calculation if LayoutParams does not force TextView to have an 5407 * exact height. Setting this value overrides previous maximum height configurations such as 5408 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5409 * 5410 * @param maxPixels the maximum height of TextView in terms of pixels 5411 * 5412 * @see #getMaxHeight() 5413 * @see #setHeight(int) 5414 * 5415 * @attr ref android.R.styleable#TextView_maxHeight 5416 */ 5417 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5418 public void setMaxHeight(int maxPixels) { 5419 mMaximum = maxPixels; 5420 mMaxMode = PIXELS; 5421 5422 requestLayout(); 5423 invalidate(); 5424 } 5425 5426 /** 5427 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5428 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5429 * 5430 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5431 * is not defined in pixels 5432 * 5433 * @see #setMaxHeight(int) 5434 * @see #setHeight(int) 5435 * 5436 * @attr ref android.R.styleable#TextView_maxHeight 5437 */ 5438 @InspectableProperty getMaxHeight()5439 public int getMaxHeight() { 5440 return mMaxMode == PIXELS ? mMaximum : -1; 5441 } 5442 5443 /** 5444 * Sets the height of the TextView to be exactly {@code lines} tall. 5445 * <p> 5446 * This value is used for height calculation if LayoutParams does not force TextView to have an 5447 * exact height. Setting this value overrides previous minimum/maximum height configurations 5448 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5449 * set this value to 1. 5450 * 5451 * @param lines the exact height of the TextView in terms of lines 5452 * 5453 * @see #setHeight(int) 5454 * 5455 * @attr ref android.R.styleable#TextView_lines 5456 */ 5457 @android.view.RemotableViewMethod setLines(int lines)5458 public void setLines(int lines) { 5459 mMaximum = mMinimum = lines; 5460 mMaxMode = mMinMode = LINES; 5461 5462 requestLayout(); 5463 invalidate(); 5464 } 5465 5466 /** 5467 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5468 * <p> 5469 * This value is used for height calculation if LayoutParams does not force TextView to have an 5470 * exact height. Setting this value overrides previous minimum/maximum height configurations 5471 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5472 * 5473 * @param pixels the exact height of the TextView in terms of pixels 5474 * 5475 * @see #setLines(int) 5476 * 5477 * @attr ref android.R.styleable#TextView_height 5478 */ 5479 @android.view.RemotableViewMethod setHeight(int pixels)5480 public void setHeight(int pixels) { 5481 mMaximum = mMinimum = pixels; 5482 mMaxMode = mMinMode = PIXELS; 5483 5484 requestLayout(); 5485 invalidate(); 5486 } 5487 5488 /** 5489 * Sets the width of the TextView to be at least {@code minEms} wide. 5490 * <p> 5491 * This value is used for width calculation if LayoutParams does not force TextView to have an 5492 * exact width. Setting this value overrides previous minimum width configurations such as 5493 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5494 * 5495 * @param minEms the minimum width of TextView in terms of ems 5496 * 5497 * @see #getMinEms() 5498 * @see #setEms(int) 5499 * 5500 * @attr ref android.R.styleable#TextView_minEms 5501 */ 5502 @android.view.RemotableViewMethod setMinEms(int minEms)5503 public void setMinEms(int minEms) { 5504 mMinWidth = minEms; 5505 mMinWidthMode = EMS; 5506 5507 requestLayout(); 5508 invalidate(); 5509 } 5510 5511 /** 5512 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5513 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5514 * 5515 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5516 * defined in ems 5517 * 5518 * @see #setMinEms(int) 5519 * @see #setEms(int) 5520 * 5521 * @attr ref android.R.styleable#TextView_minEms 5522 */ 5523 @InspectableProperty getMinEms()5524 public int getMinEms() { 5525 return mMinWidthMode == EMS ? mMinWidth : -1; 5526 } 5527 5528 /** 5529 * Sets the width of the TextView to be at least {@code minPixels} wide. 5530 * <p> 5531 * This value is used for width calculation if LayoutParams does not force TextView to have an 5532 * exact width. Setting this value overrides previous minimum width configurations such as 5533 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5534 * <p> 5535 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5536 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5537 * to decide the final width. 5538 * 5539 * @param minPixels the minimum width of TextView in terms of pixels 5540 * 5541 * @see #getMinWidth() 5542 * @see #setWidth(int) 5543 * 5544 * @attr ref android.R.styleable#TextView_minWidth 5545 */ 5546 @android.view.RemotableViewMethod setMinWidth(int minPixels)5547 public void setMinWidth(int minPixels) { 5548 mMinWidth = minPixels; 5549 mMinWidthMode = PIXELS; 5550 5551 requestLayout(); 5552 invalidate(); 5553 } 5554 5555 /** 5556 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5557 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5558 * 5559 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5560 * defined in pixels 5561 * 5562 * @see #setMinWidth(int) 5563 * @see #setWidth(int) 5564 * 5565 * @attr ref android.R.styleable#TextView_minWidth 5566 */ 5567 @InspectableProperty getMinWidth()5568 public int getMinWidth() { 5569 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5570 } 5571 5572 /** 5573 * Sets the width of the TextView to be at most {@code maxEms} wide. 5574 * <p> 5575 * This value is used for width calculation if LayoutParams does not force TextView to have an 5576 * exact width. Setting this value overrides previous maximum width configurations such as 5577 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5578 * 5579 * @param maxEms the maximum width of TextView in terms of ems 5580 * 5581 * @see #getMaxEms() 5582 * @see #setEms(int) 5583 * 5584 * @attr ref android.R.styleable#TextView_maxEms 5585 */ 5586 @android.view.RemotableViewMethod setMaxEms(int maxEms)5587 public void setMaxEms(int maxEms) { 5588 mMaxWidth = maxEms; 5589 mMaxWidthMode = EMS; 5590 5591 requestLayout(); 5592 invalidate(); 5593 } 5594 5595 /** 5596 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5597 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5598 * 5599 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5600 * defined in ems 5601 * 5602 * @see #setMaxEms(int) 5603 * @see #setEms(int) 5604 * 5605 * @attr ref android.R.styleable#TextView_maxEms 5606 */ 5607 @InspectableProperty getMaxEms()5608 public int getMaxEms() { 5609 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5610 } 5611 5612 /** 5613 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5614 * <p> 5615 * This value is used for width calculation if LayoutParams does not force TextView to have an 5616 * exact width. Setting this value overrides previous maximum width configurations such as 5617 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5618 * 5619 * @param maxPixels the maximum width of TextView in terms of pixels 5620 * 5621 * @see #getMaxWidth() 5622 * @see #setWidth(int) 5623 * 5624 * @attr ref android.R.styleable#TextView_maxWidth 5625 */ 5626 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5627 public void setMaxWidth(int maxPixels) { 5628 mMaxWidth = maxPixels; 5629 mMaxWidthMode = PIXELS; 5630 5631 requestLayout(); 5632 invalidate(); 5633 } 5634 5635 /** 5636 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5637 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5638 * 5639 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5640 * defined in pixels 5641 * 5642 * @see #setMaxWidth(int) 5643 * @see #setWidth(int) 5644 * 5645 * @attr ref android.R.styleable#TextView_maxWidth 5646 */ 5647 @InspectableProperty getMaxWidth()5648 public int getMaxWidth() { 5649 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5650 } 5651 5652 /** 5653 * Sets the width of the TextView to be exactly {@code ems} wide. 5654 * 5655 * This value is used for width calculation if LayoutParams does not force TextView to have an 5656 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5657 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5658 * 5659 * @param ems the exact width of the TextView in terms of ems 5660 * 5661 * @see #setWidth(int) 5662 * 5663 * @attr ref android.R.styleable#TextView_ems 5664 */ 5665 @android.view.RemotableViewMethod setEms(int ems)5666 public void setEms(int ems) { 5667 mMaxWidth = mMinWidth = ems; 5668 mMaxWidthMode = mMinWidthMode = EMS; 5669 5670 requestLayout(); 5671 invalidate(); 5672 } 5673 5674 /** 5675 * Sets the width of the TextView to be exactly {@code pixels} wide. 5676 * <p> 5677 * This value is used for width calculation if LayoutParams does not force TextView to have an 5678 * exact width. Setting this value overrides previous minimum/maximum width configurations 5679 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5680 * 5681 * @param pixels the exact width of the TextView in terms of pixels 5682 * 5683 * @see #setEms(int) 5684 * 5685 * @attr ref android.R.styleable#TextView_width 5686 */ 5687 @android.view.RemotableViewMethod setWidth(int pixels)5688 public void setWidth(int pixels) { 5689 mMaxWidth = mMinWidth = pixels; 5690 mMaxWidthMode = mMinWidthMode = PIXELS; 5691 5692 requestLayout(); 5693 invalidate(); 5694 } 5695 5696 /** 5697 * Sets line spacing for this TextView. Each line other than the last line will have its height 5698 * multiplied by {@code mult} and have {@code add} added to it. 5699 * 5700 * @param add The value in pixels that should be added to each line other than the last line. 5701 * This will be applied after the multiplier 5702 * @param mult The value by which each line height other than the last line will be multiplied 5703 * by 5704 * 5705 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5706 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5707 */ setLineSpacing(float add, float mult)5708 public void setLineSpacing(float add, float mult) { 5709 if (mSpacingAdd != add || mSpacingMult != mult) { 5710 mSpacingAdd = add; 5711 mSpacingMult = mult; 5712 5713 if (mLayout != null) { 5714 nullLayouts(); 5715 requestLayout(); 5716 invalidate(); 5717 } 5718 } 5719 } 5720 5721 /** 5722 * Gets the line spacing multiplier 5723 * 5724 * @return the value by which each line's height is multiplied to get its actual height. 5725 * 5726 * @see #setLineSpacing(float, float) 5727 * @see #getLineSpacingExtra() 5728 * 5729 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5730 */ 5731 @InspectableProperty getLineSpacingMultiplier()5732 public float getLineSpacingMultiplier() { 5733 return mSpacingMult; 5734 } 5735 5736 /** 5737 * Gets the line spacing extra space 5738 * 5739 * @return the extra space that is added to the height of each lines of this TextView. 5740 * 5741 * @see #setLineSpacing(float, float) 5742 * @see #getLineSpacingMultiplier() 5743 * 5744 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5745 */ 5746 @InspectableProperty getLineSpacingExtra()5747 public float getLineSpacingExtra() { 5748 return mSpacingAdd; 5749 } 5750 5751 /** 5752 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 5753 * between subsequent baselines in the TextView. 5754 * 5755 * @param lineHeight the line height in pixels 5756 * 5757 * @see #setLineSpacing(float, float) 5758 * @see #getLineSpacingExtra() 5759 * 5760 * @attr ref android.R.styleable#TextView_lineHeight 5761 */ setLineHeight(@x @ntRangefrom = 0) int lineHeight)5762 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 5763 Preconditions.checkArgumentNonnegative(lineHeight); 5764 5765 final int fontHeight = getPaint().getFontMetricsInt(null); 5766 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 5767 if (lineHeight != fontHeight) { 5768 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 5769 setLineSpacing(lineHeight - fontHeight, 1f); 5770 } 5771 } 5772 5773 /** 5774 * Convenience method to append the specified text to the TextView's 5775 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5776 * if it was not already editable. 5777 * 5778 * @param text text to be appended to the already displayed text 5779 */ append(CharSequence text)5780 public final void append(CharSequence text) { 5781 append(text, 0, text.length()); 5782 } 5783 5784 /** 5785 * Convenience method to append the specified text slice to the TextView's 5786 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5787 * if it was not already editable. 5788 * 5789 * @param text text to be appended to the already displayed text 5790 * @param start the index of the first character in the {@code text} 5791 * @param end the index of the character following the last character in the {@code text} 5792 * 5793 * @see Appendable#append(CharSequence, int, int) 5794 */ append(CharSequence text, int start, int end)5795 public void append(CharSequence text, int start, int end) { 5796 if (!(mText instanceof Editable)) { 5797 setText(mText, BufferType.EDITABLE); 5798 } 5799 5800 ((Editable) mText).append(text, start, end); 5801 5802 if (mAutoLinkMask != 0) { 5803 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 5804 // Do not change the movement method for text that support text selection as it 5805 // would prevent an arbitrary cursor displacement. 5806 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 5807 setMovementMethod(LinkMovementMethod.getInstance()); 5808 } 5809 } 5810 } 5811 updateTextColors()5812 private void updateTextColors() { 5813 boolean inval = false; 5814 final int[] drawableState = getDrawableState(); 5815 int color = mTextColor.getColorForState(drawableState, 0); 5816 if (color != mCurTextColor) { 5817 mCurTextColor = color; 5818 inval = true; 5819 } 5820 if (mLinkTextColor != null) { 5821 color = mLinkTextColor.getColorForState(drawableState, 0); 5822 if (color != mTextPaint.linkColor) { 5823 mTextPaint.linkColor = color; 5824 inval = true; 5825 } 5826 } 5827 if (mHintTextColor != null) { 5828 color = mHintTextColor.getColorForState(drawableState, 0); 5829 if (color != mCurHintTextColor) { 5830 mCurHintTextColor = color; 5831 if (mText.length() == 0) { 5832 inval = true; 5833 } 5834 } 5835 } 5836 if (inval) { 5837 // Text needs to be redrawn with the new color 5838 if (mEditor != null) mEditor.invalidateTextDisplayList(); 5839 invalidate(); 5840 } 5841 } 5842 5843 @Override drawableStateChanged()5844 protected void drawableStateChanged() { 5845 super.drawableStateChanged(); 5846 5847 if (mTextColor != null && mTextColor.isStateful() 5848 || (mHintTextColor != null && mHintTextColor.isStateful()) 5849 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 5850 updateTextColors(); 5851 } 5852 5853 if (mDrawables != null) { 5854 final int[] state = getDrawableState(); 5855 for (Drawable dr : mDrawables.mShowing) { 5856 if (dr != null && dr.isStateful() && dr.setState(state)) { 5857 invalidateDrawable(dr); 5858 } 5859 } 5860 } 5861 } 5862 5863 @Override drawableHotspotChanged(float x, float y)5864 public void drawableHotspotChanged(float x, float y) { 5865 super.drawableHotspotChanged(x, y); 5866 5867 if (mDrawables != null) { 5868 for (Drawable dr : mDrawables.mShowing) { 5869 if (dr != null) { 5870 dr.setHotspot(x, y); 5871 } 5872 } 5873 } 5874 } 5875 5876 @Override onSaveInstanceState()5877 public Parcelable onSaveInstanceState() { 5878 Parcelable superState = super.onSaveInstanceState(); 5879 5880 // Save state if we are forced to 5881 final boolean freezesText = getFreezesText(); 5882 boolean hasSelection = false; 5883 int start = -1; 5884 int end = -1; 5885 5886 if (mText != null) { 5887 start = getSelectionStart(); 5888 end = getSelectionEnd(); 5889 if (start >= 0 || end >= 0) { 5890 // Or save state if there is a selection 5891 hasSelection = true; 5892 } 5893 } 5894 5895 if (freezesText || hasSelection) { 5896 SavedState ss = new SavedState(superState); 5897 5898 if (freezesText) { 5899 if (mText instanceof Spanned) { 5900 final Spannable sp = new SpannableStringBuilder(mText); 5901 5902 if (mEditor != null) { 5903 removeMisspelledSpans(sp); 5904 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5905 } 5906 5907 ss.text = sp; 5908 } else { 5909 ss.text = mText.toString(); 5910 } 5911 } 5912 5913 if (hasSelection) { 5914 // XXX Should also save the current scroll position! 5915 ss.selStart = start; 5916 ss.selEnd = end; 5917 } 5918 5919 if (isFocused() && start >= 0 && end >= 0) { 5920 ss.frozenWithFocus = true; 5921 } 5922 5923 ss.error = getError(); 5924 5925 if (mEditor != null) { 5926 ss.editorState = mEditor.saveInstanceState(); 5927 } 5928 return ss; 5929 } 5930 5931 return superState; 5932 } 5933 removeMisspelledSpans(Spannable spannable)5934 void removeMisspelledSpans(Spannable spannable) { 5935 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 5936 SuggestionSpan.class); 5937 for (int i = 0; i < suggestionSpans.length; i++) { 5938 int flags = suggestionSpans[i].getFlags(); 5939 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 5940 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 5941 spannable.removeSpan(suggestionSpans[i]); 5942 } 5943 } 5944 } 5945 5946 @Override onRestoreInstanceState(Parcelable state)5947 public void onRestoreInstanceState(Parcelable state) { 5948 if (!(state instanceof SavedState)) { 5949 super.onRestoreInstanceState(state); 5950 return; 5951 } 5952 5953 SavedState ss = (SavedState) state; 5954 super.onRestoreInstanceState(ss.getSuperState()); 5955 5956 // XXX restore buffer type too, as well as lots of other stuff 5957 if (ss.text != null) { 5958 setText(ss.text); 5959 } 5960 5961 if (ss.selStart >= 0 && ss.selEnd >= 0) { 5962 if (mSpannable != null) { 5963 int len = mText.length(); 5964 5965 if (ss.selStart > len || ss.selEnd > len) { 5966 String restored = ""; 5967 5968 if (ss.text != null) { 5969 restored = "(restored) "; 5970 } 5971 5972 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 5973 + " out of range for " + restored + "text " + mText); 5974 } else { 5975 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 5976 5977 if (ss.frozenWithFocus) { 5978 createEditorIfNeeded(); 5979 mEditor.mFrozenWithFocus = true; 5980 } 5981 } 5982 } 5983 } 5984 5985 if (ss.error != null) { 5986 final CharSequence error = ss.error; 5987 // Display the error later, after the first layout pass 5988 post(new Runnable() { 5989 public void run() { 5990 if (mEditor == null || !mEditor.mErrorWasChanged) { 5991 setError(error); 5992 } 5993 } 5994 }); 5995 } 5996 5997 if (ss.editorState != null) { 5998 createEditorIfNeeded(); 5999 mEditor.restoreInstanceState(ss.editorState); 6000 } 6001 } 6002 6003 /** 6004 * Control whether this text view saves its entire text contents when 6005 * freezing to an icicle, in addition to dynamic state such as cursor 6006 * position. By default this is false, not saving the text. Set to true 6007 * if the text in the text view is not being saved somewhere else in 6008 * persistent storage (such as in a content provider) so that if the 6009 * view is later thawed the user will not lose their data. For 6010 * {@link android.widget.EditText} it is always enabled, regardless of 6011 * the value of the attribute. 6012 * 6013 * @param freezesText Controls whether a frozen icicle should include the 6014 * entire text data: true to include it, false to not. 6015 * 6016 * @attr ref android.R.styleable#TextView_freezesText 6017 */ 6018 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)6019 public void setFreezesText(boolean freezesText) { 6020 mFreezesText = freezesText; 6021 } 6022 6023 /** 6024 * Return whether this text view is including its entire text contents 6025 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 6026 * 6027 * @return Returns true if text is included, false if it isn't. 6028 * 6029 * @see #setFreezesText 6030 */ 6031 @InspectableProperty getFreezesText()6032 public boolean getFreezesText() { 6033 return mFreezesText; 6034 } 6035 6036 /////////////////////////////////////////////////////////////////////////// 6037 6038 /** 6039 * Sets the Factory used to create new {@link Editable Editables}. 6040 * 6041 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6042 * 6043 * @see android.text.Editable.Factory 6044 * @see android.widget.TextView.BufferType#EDITABLE 6045 */ setEditableFactory(Editable.Factory factory)6046 public final void setEditableFactory(Editable.Factory factory) { 6047 mEditableFactory = factory; 6048 setText(mText); 6049 } 6050 6051 /** 6052 * Sets the Factory used to create new {@link Spannable Spannables}. 6053 * 6054 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6055 * 6056 * @see android.text.Spannable.Factory 6057 * @see android.widget.TextView.BufferType#SPANNABLE 6058 */ setSpannableFactory(Spannable.Factory factory)6059 public final void setSpannableFactory(Spannable.Factory factory) { 6060 mSpannableFactory = factory; 6061 setText(mText); 6062 } 6063 6064 /** 6065 * Sets the text to be displayed. TextView <em>does not</em> accept 6066 * HTML-like formatting, which you can do with text strings in XML resource files. 6067 * To style your strings, attach android.text.style.* objects to a 6068 * {@link android.text.SpannableString}, or see the 6069 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6070 * Available Resource Types</a> documentation for an example of setting 6071 * formatted text in the XML resource file. 6072 * <p/> 6073 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6074 * intermediate {@link Spannable Spannables}. Likewise it will use 6075 * {@link android.text.Editable.Factory} to create final or intermediate 6076 * {@link Editable Editables}. 6077 * 6078 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6079 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6080 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6081 * 6082 * @param text text to be displayed 6083 * 6084 * @attr ref android.R.styleable#TextView_text 6085 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6086 * parameters used to create the PrecomputedText mismatches 6087 * with this TextView. 6088 */ 6089 @android.view.RemotableViewMethod setText(CharSequence text)6090 public final void setText(CharSequence text) { 6091 setText(text, mBufferType); 6092 } 6093 6094 /** 6095 * Sets the text to be displayed but retains the cursor position. Same as 6096 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6097 * new text. 6098 * <p/> 6099 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6100 * intermediate {@link Spannable Spannables}. Likewise it will use 6101 * {@link android.text.Editable.Factory} to create final or intermediate 6102 * {@link Editable Editables}. 6103 * 6104 * @param text text to be displayed 6105 * 6106 * @see #setText(CharSequence) 6107 */ 6108 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6109 public final void setTextKeepState(CharSequence text) { 6110 setTextKeepState(text, mBufferType); 6111 } 6112 6113 /** 6114 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6115 * <p/> 6116 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6117 * intermediate {@link Spannable Spannables}. Likewise it will use 6118 * {@link android.text.Editable.Factory} to create final or intermediate 6119 * {@link Editable Editables}. 6120 * 6121 * Subclasses overriding this method should ensure that the following post condition holds, 6122 * in order to guarantee the safety of the view's measurement and layout operations: 6123 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6124 * will be different from {@code null}. 6125 * 6126 * @param text text to be displayed 6127 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6128 * stored as a static text, styleable/spannable text, or editable text 6129 * 6130 * @see #setText(CharSequence) 6131 * @see android.widget.TextView.BufferType 6132 * @see #setSpannableFactory(Spannable.Factory) 6133 * @see #setEditableFactory(Editable.Factory) 6134 * 6135 * @attr ref android.R.styleable#TextView_text 6136 * @attr ref android.R.styleable#TextView_bufferType 6137 */ setText(CharSequence text, BufferType type)6138 public void setText(CharSequence text, BufferType type) { 6139 setText(text, type, true, 0); 6140 6141 if (mCharWrapper != null) { 6142 mCharWrapper.mChars = null; 6143 } 6144 } 6145 6146 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6147 private void setText(CharSequence text, BufferType type, 6148 boolean notifyBefore, int oldlen) { 6149 mTextSetFromXmlOrResourceId = false; 6150 if (text == null) { 6151 text = ""; 6152 } 6153 6154 // If suggestions are not enabled, remove the suggestion spans from the text 6155 if (!isSuggestionsEnabled()) { 6156 text = removeSuggestionSpans(text); 6157 } 6158 6159 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6160 6161 if (text instanceof Spanned 6162 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6163 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6164 setHorizontalFadingEdgeEnabled(true); 6165 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6166 } else { 6167 setHorizontalFadingEdgeEnabled(false); 6168 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6169 } 6170 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6171 } 6172 6173 int n = mFilters.length; 6174 for (int i = 0; i < n; i++) { 6175 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6176 if (out != null) { 6177 text = out; 6178 } 6179 } 6180 6181 if (notifyBefore) { 6182 if (mText != null) { 6183 oldlen = mText.length(); 6184 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6185 } else { 6186 sendBeforeTextChanged("", 0, 0, text.length()); 6187 } 6188 } 6189 6190 boolean needEditableForNotification = false; 6191 6192 if (mListeners != null && mListeners.size() != 0) { 6193 needEditableForNotification = true; 6194 } 6195 6196 PrecomputedText precomputed = 6197 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6198 if (type == BufferType.EDITABLE || getKeyListener() != null 6199 || needEditableForNotification) { 6200 createEditorIfNeeded(); 6201 mEditor.forgetUndoRedo(); 6202 Editable t = mEditableFactory.newEditable(text); 6203 text = t; 6204 setFilters(t, mFilters); 6205 InputMethodManager imm = getInputMethodManager(); 6206 if (imm != null) imm.restartInput(this); 6207 } else if (precomputed != null) { 6208 if (mTextDir == null) { 6209 mTextDir = getTextDirectionHeuristic(); 6210 } 6211 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6212 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6213 mHyphenationFrequency); 6214 switch (checkResult) { 6215 case PrecomputedText.Params.UNUSABLE: 6216 throw new IllegalArgumentException( 6217 "PrecomputedText's Parameters don't match the parameters of this TextView." 6218 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6219 + "to override the settings of this TextView: " 6220 + "PrecomputedText: " + precomputed.getParams() 6221 + "TextView: " + getTextMetricsParams()); 6222 case PrecomputedText.Params.NEED_RECOMPUTE: 6223 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6224 break; 6225 case PrecomputedText.Params.USABLE: 6226 // pass through 6227 } 6228 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6229 text = mSpannableFactory.newSpannable(text); 6230 } else if (!(text instanceof CharWrapper)) { 6231 text = TextUtils.stringOrSpannedString(text); 6232 } 6233 6234 if (mAutoLinkMask != 0) { 6235 Spannable s2; 6236 6237 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6238 s2 = (Spannable) text; 6239 } else { 6240 s2 = mSpannableFactory.newSpannable(text); 6241 } 6242 6243 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6244 text = s2; 6245 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6246 6247 /* 6248 * We must go ahead and set the text before changing the 6249 * movement method, because setMovementMethod() may call 6250 * setText() again to try to upgrade the buffer type. 6251 */ 6252 setTextInternal(text); 6253 6254 // Do not change the movement method for text that support text selection as it 6255 // would prevent an arbitrary cursor displacement. 6256 if (mLinksClickable && !textCanBeSelected()) { 6257 setMovementMethod(LinkMovementMethod.getInstance()); 6258 } 6259 } 6260 } 6261 6262 mBufferType = type; 6263 setTextInternal(text); 6264 6265 if (mTransformation == null) { 6266 mTransformed = text; 6267 } else { 6268 mTransformed = mTransformation.getTransformation(text, this); 6269 } 6270 if (mTransformed == null) { 6271 // Should not happen if the transformation method follows the non-null postcondition. 6272 mTransformed = ""; 6273 } 6274 6275 final int textLength = text.length(); 6276 6277 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6278 Spannable sp = (Spannable) text; 6279 6280 // Remove any ChangeWatchers that might have come from other TextViews. 6281 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6282 final int count = watchers.length; 6283 for (int i = 0; i < count; i++) { 6284 sp.removeSpan(watchers[i]); 6285 } 6286 6287 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6288 6289 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6290 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6291 6292 if (mEditor != null) mEditor.addSpanWatchers(sp); 6293 6294 if (mTransformation != null) { 6295 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6296 } 6297 6298 if (mMovement != null) { 6299 mMovement.initialize(this, (Spannable) text); 6300 6301 /* 6302 * Initializing the movement method will have set the 6303 * selection, so reset mSelectionMoved to keep that from 6304 * interfering with the normal on-focus selection-setting. 6305 */ 6306 if (mEditor != null) mEditor.mSelectionMoved = false; 6307 } 6308 } 6309 6310 if (mLayout != null) { 6311 checkForRelayout(); 6312 } 6313 6314 sendOnTextChanged(text, 0, oldlen, textLength); 6315 onTextChanged(text, 0, oldlen, textLength); 6316 6317 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6318 6319 if (needEditableForNotification) { 6320 sendAfterTextChanged((Editable) text); 6321 } else { 6322 notifyListeningManagersAfterTextChanged(); 6323 } 6324 6325 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6326 if (mEditor != null) mEditor.prepareCursorControllers(); 6327 } 6328 6329 /** 6330 * Sets the TextView to display the specified slice of the specified 6331 * char array. You must promise that you will not change the contents 6332 * of the array except for right before another call to setText(), 6333 * since the TextView has no way to know that the text 6334 * has changed and that it needs to invalidate and re-layout. 6335 * 6336 * @param text char array to be displayed 6337 * @param start start index in the char array 6338 * @param len length of char count after {@code start} 6339 */ setText(char[] text, int start, int len)6340 public final void setText(char[] text, int start, int len) { 6341 int oldlen = 0; 6342 6343 if (start < 0 || len < 0 || start + len > text.length) { 6344 throw new IndexOutOfBoundsException(start + ", " + len); 6345 } 6346 6347 /* 6348 * We must do the before-notification here ourselves because if 6349 * the old text is a CharWrapper we destroy it before calling 6350 * into the normal path. 6351 */ 6352 if (mText != null) { 6353 oldlen = mText.length(); 6354 sendBeforeTextChanged(mText, 0, oldlen, len); 6355 } else { 6356 sendBeforeTextChanged("", 0, 0, len); 6357 } 6358 6359 if (mCharWrapper == null) { 6360 mCharWrapper = new CharWrapper(text, start, len); 6361 } else { 6362 mCharWrapper.set(text, start, len); 6363 } 6364 6365 setText(mCharWrapper, mBufferType, false, oldlen); 6366 } 6367 6368 /** 6369 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6370 * the cursor position. Same as 6371 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6372 * position (if any) is retained in the new text. 6373 * <p/> 6374 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6375 * intermediate {@link Spannable Spannables}. Likewise it will use 6376 * {@link android.text.Editable.Factory} to create final or intermediate 6377 * {@link Editable Editables}. 6378 * 6379 * @param text text to be displayed 6380 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6381 * stored as a static text, styleable/spannable text, or editable text 6382 * 6383 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6384 */ setTextKeepState(CharSequence text, BufferType type)6385 public final void setTextKeepState(CharSequence text, BufferType type) { 6386 int start = getSelectionStart(); 6387 int end = getSelectionEnd(); 6388 int len = text.length(); 6389 6390 setText(text, type); 6391 6392 if (start >= 0 || end >= 0) { 6393 if (mSpannable != null) { 6394 Selection.setSelection(mSpannable, 6395 Math.max(0, Math.min(start, len)), 6396 Math.max(0, Math.min(end, len))); 6397 } 6398 } 6399 } 6400 6401 /** 6402 * Sets the text to be displayed using a string resource identifier. 6403 * 6404 * @param resid the resource identifier of the string resource to be displayed 6405 * 6406 * @see #setText(CharSequence) 6407 * 6408 * @attr ref android.R.styleable#TextView_text 6409 */ 6410 @android.view.RemotableViewMethod setText(@tringRes int resid)6411 public final void setText(@StringRes int resid) { 6412 setText(getContext().getResources().getText(resid)); 6413 mTextSetFromXmlOrResourceId = true; 6414 mTextId = resid; 6415 } 6416 6417 /** 6418 * Sets the text to be displayed using a string resource identifier and the 6419 * {@link android.widget.TextView.BufferType}. 6420 * <p/> 6421 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6422 * intermediate {@link Spannable Spannables}. Likewise it will use 6423 * {@link android.text.Editable.Factory} to create final or intermediate 6424 * {@link Editable Editables}. 6425 * 6426 * @param resid the resource identifier of the string resource to be displayed 6427 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6428 * stored as a static text, styleable/spannable text, or editable text 6429 * 6430 * @see #setText(int) 6431 * @see #setText(CharSequence) 6432 * @see android.widget.TextView.BufferType 6433 * @see #setSpannableFactory(Spannable.Factory) 6434 * @see #setEditableFactory(Editable.Factory) 6435 * 6436 * @attr ref android.R.styleable#TextView_text 6437 * @attr ref android.R.styleable#TextView_bufferType 6438 */ setText(@tringRes int resid, BufferType type)6439 public final void setText(@StringRes int resid, BufferType type) { 6440 setText(getContext().getResources().getText(resid), type); 6441 mTextSetFromXmlOrResourceId = true; 6442 mTextId = resid; 6443 } 6444 6445 /** 6446 * Sets the text to be displayed when the text of the TextView is empty. 6447 * Null means to use the normal empty text. The hint does not currently 6448 * participate in determining the size of the view. 6449 * 6450 * @attr ref android.R.styleable#TextView_hint 6451 */ 6452 @android.view.RemotableViewMethod setHint(CharSequence hint)6453 public final void setHint(CharSequence hint) { 6454 setHintInternal(hint); 6455 6456 if (mEditor != null && isInputMethodTarget()) { 6457 mEditor.reportExtractedText(); 6458 } 6459 } 6460 setHintInternal(CharSequence hint)6461 private void setHintInternal(CharSequence hint) { 6462 mHint = TextUtils.stringOrSpannedString(hint); 6463 6464 if (mLayout != null) { 6465 checkForRelayout(); 6466 } 6467 6468 if (mText.length() == 0) { 6469 invalidate(); 6470 } 6471 6472 // Invalidate display list if hint is currently used 6473 if (mEditor != null && mText.length() == 0 && mHint != null) { 6474 mEditor.invalidateTextDisplayList(); 6475 } 6476 } 6477 6478 /** 6479 * Sets the text to be displayed when the text of the TextView is empty, 6480 * from a resource. 6481 * 6482 * @attr ref android.R.styleable#TextView_hint 6483 */ 6484 @android.view.RemotableViewMethod setHint(@tringRes int resid)6485 public final void setHint(@StringRes int resid) { 6486 mHintId = resid; 6487 setHint(getContext().getResources().getText(resid)); 6488 } 6489 6490 /** 6491 * Returns the hint that is displayed when the text of the TextView 6492 * is empty. 6493 * 6494 * @attr ref android.R.styleable#TextView_hint 6495 */ 6496 @InspectableProperty 6497 @ViewDebug.CapturedViewProperty getHint()6498 public CharSequence getHint() { 6499 return mHint; 6500 } 6501 6502 /** 6503 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6504 * line characters instead of letting it wrap onto multiple lines. 6505 * 6506 * @attr ref android.R.styleable#TextView_singleLine 6507 */ 6508 @InspectableProperty isSingleLine()6509 public boolean isSingleLine() { 6510 return mSingleLine; 6511 } 6512 isMultilineInputType(int type)6513 private static boolean isMultilineInputType(int type) { 6514 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6515 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6516 } 6517 6518 /** 6519 * Removes the suggestion spans. 6520 */ removeSuggestionSpans(CharSequence text)6521 CharSequence removeSuggestionSpans(CharSequence text) { 6522 if (text instanceof Spanned) { 6523 Spannable spannable; 6524 if (text instanceof Spannable) { 6525 spannable = (Spannable) text; 6526 } else { 6527 spannable = mSpannableFactory.newSpannable(text); 6528 } 6529 6530 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6531 if (spans.length == 0) { 6532 return text; 6533 } else { 6534 text = spannable; 6535 } 6536 6537 for (int i = 0; i < spans.length; i++) { 6538 spannable.removeSpan(spans[i]); 6539 } 6540 } 6541 return text; 6542 } 6543 6544 /** 6545 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6546 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6547 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6548 * then a soft keyboard will not be displayed for this text view. 6549 * 6550 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6551 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6552 * type. 6553 * 6554 * @see #getInputType() 6555 * @see #setRawInputType(int) 6556 * @see android.text.InputType 6557 * @attr ref android.R.styleable#TextView_inputType 6558 */ setInputType(int type)6559 public void setInputType(int type) { 6560 final boolean wasPassword = isPasswordInputType(getInputType()); 6561 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6562 setInputType(type, false); 6563 final boolean isPassword = isPasswordInputType(type); 6564 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6565 boolean forceUpdate = false; 6566 if (isPassword) { 6567 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6568 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6569 Typeface.NORMAL, -1 /* weight, not specifeid */); 6570 } else if (isVisiblePassword) { 6571 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6572 forceUpdate = true; 6573 } 6574 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6575 Typeface.NORMAL, -1 /* weight, not specified */); 6576 } else if (wasPassword || wasVisiblePassword) { 6577 // not in password mode, clean up typeface and transformation 6578 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6579 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6580 -1 /* weight, not specified */); 6581 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6582 forceUpdate = true; 6583 } 6584 } 6585 6586 boolean singleLine = !isMultilineInputType(type); 6587 6588 // We need to update the single line mode if it has changed or we 6589 // were previously in password mode. 6590 if (mSingleLine != singleLine || forceUpdate) { 6591 // Change single line mode, but only change the transformation if 6592 // we are not in password mode. 6593 applySingleLine(singleLine, !isPassword, true); 6594 } 6595 6596 if (!isSuggestionsEnabled()) { 6597 setTextInternal(removeSuggestionSpans(mText)); 6598 } 6599 6600 InputMethodManager imm = getInputMethodManager(); 6601 if (imm != null) imm.restartInput(this); 6602 } 6603 6604 /** 6605 * It would be better to rely on the input type for everything. A password inputType should have 6606 * a password transformation. We should hence use isPasswordInputType instead of this method. 6607 * 6608 * We should: 6609 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6610 * would install the correct transformation). 6611 * - Refuse the installation of a non-password transformation in setTransformation if the input 6612 * type is password. 6613 * 6614 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6615 * is useful since it matches what the user can see (obfuscated text or not). 6616 * 6617 * @return true if the current transformation method is of the password type. 6618 */ hasPasswordTransformationMethod()6619 boolean hasPasswordTransformationMethod() { 6620 return mTransformation instanceof PasswordTransformationMethod; 6621 } 6622 6623 /** 6624 * Returns true if the current inputType is any type of password. 6625 * 6626 * @hide 6627 */ isAnyPasswordInputType()6628 public boolean isAnyPasswordInputType() { 6629 final int inputType = getInputType(); 6630 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6631 } 6632 isPasswordInputType(int inputType)6633 static boolean isPasswordInputType(int inputType) { 6634 final int variation = 6635 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6636 return variation 6637 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6638 || variation 6639 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6640 || variation 6641 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6642 } 6643 isVisiblePasswordInputType(int inputType)6644 private static boolean isVisiblePasswordInputType(int inputType) { 6645 final int variation = 6646 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6647 return variation 6648 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6649 } 6650 6651 /** 6652 * Directly change the content type integer of the text view, without 6653 * modifying any other state. 6654 * @see #setInputType(int) 6655 * @see android.text.InputType 6656 * @attr ref android.R.styleable#TextView_inputType 6657 */ setRawInputType(int type)6658 public void setRawInputType(int type) { 6659 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6660 createEditorIfNeeded(); 6661 mEditor.mInputType = type; 6662 } 6663 6664 /** 6665 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6666 * a {@code Locale} object that can be used to customize key various listeners. 6667 * @see DateKeyListener#getInstance(Locale) 6668 * @see DateTimeKeyListener#getInstance(Locale) 6669 * @see DigitsKeyListener#getInstance(Locale) 6670 * @see TimeKeyListener#getInstance(Locale) 6671 */ 6672 @Nullable getCustomLocaleForKeyListenerOrNull()6673 private Locale getCustomLocaleForKeyListenerOrNull() { 6674 if (!mUseInternationalizedInput) { 6675 // If the application does not target O, stick to the previous behavior. 6676 return null; 6677 } 6678 final LocaleList locales = getImeHintLocales(); 6679 if (locales == null) { 6680 // If the application does not explicitly specify IME hint locale, also stick to the 6681 // previous behavior. 6682 return null; 6683 } 6684 return locales.get(0); 6685 } 6686 6687 @UnsupportedAppUsage setInputType(int type, boolean direct)6688 private void setInputType(int type, boolean direct) { 6689 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6690 KeyListener input; 6691 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6692 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6693 TextKeyListener.Capitalize cap; 6694 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6695 cap = TextKeyListener.Capitalize.CHARACTERS; 6696 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6697 cap = TextKeyListener.Capitalize.WORDS; 6698 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6699 cap = TextKeyListener.Capitalize.SENTENCES; 6700 } else { 6701 cap = TextKeyListener.Capitalize.NONE; 6702 } 6703 input = TextKeyListener.getInstance(autotext, cap); 6704 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6705 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6706 input = DigitsKeyListener.getInstance( 6707 locale, 6708 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6709 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6710 if (locale != null) { 6711 // Override type, if necessary for i18n. 6712 int newType = input.getInputType(); 6713 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6714 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6715 // The class is different from the original class. So we need to override 6716 // 'type'. But we want to keep the password flag if it's there. 6717 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6718 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6719 } 6720 type = newType; 6721 } 6722 } 6723 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6724 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6725 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6726 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6727 input = DateKeyListener.getInstance(locale); 6728 break; 6729 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6730 input = TimeKeyListener.getInstance(locale); 6731 break; 6732 default: 6733 input = DateTimeKeyListener.getInstance(locale); 6734 break; 6735 } 6736 if (mUseInternationalizedInput) { 6737 type = input.getInputType(); // Override type, if necessary for i18n. 6738 } 6739 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6740 input = DialerKeyListener.getInstance(); 6741 } else { 6742 input = TextKeyListener.getInstance(); 6743 } 6744 setRawInputType(type); 6745 mListenerChanged = false; 6746 if (direct) { 6747 createEditorIfNeeded(); 6748 mEditor.mKeyListener = input; 6749 } else { 6750 setKeyListenerOnly(input); 6751 } 6752 } 6753 6754 /** 6755 * Get the type of the editable content. 6756 * 6757 * @see #setInputType(int) 6758 * @see android.text.InputType 6759 */ 6760 @InspectableProperty(flagMapping = { 6761 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6762 @FlagEntry( 6763 name = "text", 6764 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6765 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6766 @FlagEntry( 6767 name = "textUri", 6768 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6769 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6770 @FlagEntry( 6771 name = "textEmailAddress", 6772 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6773 target = InputType.TYPE_CLASS_TEXT 6774 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6775 @FlagEntry( 6776 name = "textEmailSubject", 6777 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6778 target = InputType.TYPE_CLASS_TEXT 6779 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6780 @FlagEntry( 6781 name = "textShortMessage", 6782 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6783 target = InputType.TYPE_CLASS_TEXT 6784 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6785 @FlagEntry( 6786 name = "textLongMessage", 6787 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6788 target = InputType.TYPE_CLASS_TEXT 6789 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6790 @FlagEntry( 6791 name = "textPersonName", 6792 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6793 target = InputType.TYPE_CLASS_TEXT 6794 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6795 @FlagEntry( 6796 name = "textPostalAddress", 6797 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6798 target = InputType.TYPE_CLASS_TEXT 6799 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6800 @FlagEntry( 6801 name = "textPassword", 6802 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6803 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6804 @FlagEntry( 6805 name = "textVisiblePassword", 6806 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6807 target = InputType.TYPE_CLASS_TEXT 6808 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6809 @FlagEntry( 6810 name = "textWebEditText", 6811 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6812 target = InputType.TYPE_CLASS_TEXT 6813 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6814 @FlagEntry( 6815 name = "textFilter", 6816 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6817 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6818 @FlagEntry( 6819 name = "textPhonetic", 6820 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6821 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6822 @FlagEntry( 6823 name = "textWebEmailAddress", 6824 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6825 target = InputType.TYPE_CLASS_TEXT 6826 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6827 @FlagEntry( 6828 name = "textWebPassword", 6829 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6830 target = InputType.TYPE_CLASS_TEXT 6831 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6832 @FlagEntry( 6833 name = "number", 6834 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6835 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6836 @FlagEntry( 6837 name = "numberPassword", 6838 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6839 target = InputType.TYPE_CLASS_NUMBER 6840 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6841 @FlagEntry( 6842 name = "phone", 6843 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6844 target = InputType.TYPE_CLASS_PHONE), 6845 @FlagEntry( 6846 name = "datetime", 6847 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6848 target = InputType.TYPE_CLASS_DATETIME 6849 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6850 @FlagEntry( 6851 name = "date", 6852 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6853 target = InputType.TYPE_CLASS_DATETIME 6854 | InputType.TYPE_DATETIME_VARIATION_DATE), 6855 @FlagEntry( 6856 name = "time", 6857 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6858 target = InputType.TYPE_CLASS_DATETIME 6859 | InputType.TYPE_DATETIME_VARIATION_TIME), 6860 @FlagEntry( 6861 name = "textCapCharacters", 6862 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6863 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6864 @FlagEntry( 6865 name = "textCapWords", 6866 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6867 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6868 @FlagEntry( 6869 name = "textCapSentences", 6870 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6871 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6872 @FlagEntry( 6873 name = "textAutoCorrect", 6874 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6875 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6876 @FlagEntry( 6877 name = "textAutoComplete", 6878 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6879 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6880 @FlagEntry( 6881 name = "textMultiLine", 6882 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6883 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6884 @FlagEntry( 6885 name = "textImeMultiLine", 6886 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6887 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6888 @FlagEntry( 6889 name = "textNoSuggestions", 6890 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6891 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6892 @FlagEntry( 6893 name = "numberSigned", 6894 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6895 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6896 @FlagEntry( 6897 name = "numberDecimal", 6898 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6899 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6900 }) getInputType()6901 public int getInputType() { 6902 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6903 } 6904 6905 /** 6906 * Change the editor type integer associated with the text view, which 6907 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6908 * when it has focus. 6909 * @see #getImeOptions 6910 * @see android.view.inputmethod.EditorInfo 6911 * @attr ref android.R.styleable#TextView_imeOptions 6912 */ setImeOptions(int imeOptions)6913 public void setImeOptions(int imeOptions) { 6914 createEditorIfNeeded(); 6915 mEditor.createInputContentTypeIfNeeded(); 6916 mEditor.mInputContentType.imeOptions = imeOptions; 6917 } 6918 6919 /** 6920 * Get the type of the Input Method Editor (IME). 6921 * @return the type of the IME 6922 * @see #setImeOptions(int) 6923 * @see EditorInfo 6924 */ 6925 @InspectableProperty(flagMapping = { 6926 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 6927 @FlagEntry( 6928 name = "actionUnspecified", 6929 mask = EditorInfo.IME_MASK_ACTION, 6930 target = EditorInfo.IME_ACTION_UNSPECIFIED), 6931 @FlagEntry( 6932 name = "actionNone", 6933 mask = EditorInfo.IME_MASK_ACTION, 6934 target = EditorInfo.IME_ACTION_NONE), 6935 @FlagEntry( 6936 name = "actionGo", 6937 mask = EditorInfo.IME_MASK_ACTION, 6938 target = EditorInfo.IME_ACTION_GO), 6939 @FlagEntry( 6940 name = "actionSearch", 6941 mask = EditorInfo.IME_MASK_ACTION, 6942 target = EditorInfo.IME_ACTION_SEARCH), 6943 @FlagEntry( 6944 name = "actionSend", 6945 mask = EditorInfo.IME_MASK_ACTION, 6946 target = EditorInfo.IME_ACTION_SEND), 6947 @FlagEntry( 6948 name = "actionNext", 6949 mask = EditorInfo.IME_MASK_ACTION, 6950 target = EditorInfo.IME_ACTION_NEXT), 6951 @FlagEntry( 6952 name = "actionDone", 6953 mask = EditorInfo.IME_MASK_ACTION, 6954 target = EditorInfo.IME_ACTION_DONE), 6955 @FlagEntry( 6956 name = "actionPrevious", 6957 mask = EditorInfo.IME_MASK_ACTION, 6958 target = EditorInfo.IME_ACTION_PREVIOUS), 6959 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 6960 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 6961 @FlagEntry( 6962 name = "flagNavigatePrevious", 6963 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 6964 @FlagEntry( 6965 name = "flagNoAccessoryAction", 6966 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 6967 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 6968 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 6969 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 6970 @FlagEntry( 6971 name = "flagNoPersonalizedLearning", 6972 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 6973 }) getImeOptions()6974 public int getImeOptions() { 6975 return mEditor != null && mEditor.mInputContentType != null 6976 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 6977 } 6978 6979 /** 6980 * Change the custom IME action associated with the text view, which 6981 * will be reported to an IME with {@link EditorInfo#actionLabel} 6982 * and {@link EditorInfo#actionId} when it has focus. 6983 * @see #getImeActionLabel 6984 * @see #getImeActionId 6985 * @see android.view.inputmethod.EditorInfo 6986 * @attr ref android.R.styleable#TextView_imeActionLabel 6987 * @attr ref android.R.styleable#TextView_imeActionId 6988 */ setImeActionLabel(CharSequence label, int actionId)6989 public void setImeActionLabel(CharSequence label, int actionId) { 6990 createEditorIfNeeded(); 6991 mEditor.createInputContentTypeIfNeeded(); 6992 mEditor.mInputContentType.imeActionLabel = label; 6993 mEditor.mInputContentType.imeActionId = actionId; 6994 } 6995 6996 /** 6997 * Get the IME action label previous set with {@link #setImeActionLabel}. 6998 * 6999 * @see #setImeActionLabel 7000 * @see android.view.inputmethod.EditorInfo 7001 */ 7002 @InspectableProperty getImeActionLabel()7003 public CharSequence getImeActionLabel() { 7004 return mEditor != null && mEditor.mInputContentType != null 7005 ? mEditor.mInputContentType.imeActionLabel : null; 7006 } 7007 7008 /** 7009 * Get the IME action ID previous set with {@link #setImeActionLabel}. 7010 * 7011 * @see #setImeActionLabel 7012 * @see android.view.inputmethod.EditorInfo 7013 */ 7014 @InspectableProperty getImeActionId()7015 public int getImeActionId() { 7016 return mEditor != null && mEditor.mInputContentType != null 7017 ? mEditor.mInputContentType.imeActionId : 0; 7018 } 7019 7020 /** 7021 * Set a special listener to be called when an action is performed 7022 * on the text view. This will be called when the enter key is pressed, 7023 * or when an action supplied to the IME is selected by the user. Setting 7024 * this means that the normal hard key event will not insert a newline 7025 * into the text view, even if it is multi-line; holding down the ALT 7026 * modifier will, however, allow the user to insert a newline character. 7027 */ setOnEditorActionListener(OnEditorActionListener l)7028 public void setOnEditorActionListener(OnEditorActionListener l) { 7029 createEditorIfNeeded(); 7030 mEditor.createInputContentTypeIfNeeded(); 7031 mEditor.mInputContentType.onEditorActionListener = l; 7032 } 7033 7034 /** 7035 * Called when an attached input method calls 7036 * {@link InputConnection#performEditorAction(int) 7037 * InputConnection.performEditorAction()} 7038 * for this text view. The default implementation will call your action 7039 * listener supplied to {@link #setOnEditorActionListener}, or perform 7040 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7041 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7042 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7043 * EditorInfo.IME_ACTION_DONE}. 7044 * 7045 * <p>For backwards compatibility, if no IME options have been set and the 7046 * text view would not normally advance focus on enter, then 7047 * the NEXT and DONE actions received here will be turned into an enter 7048 * key down/up pair to go through the normal key handling. 7049 * 7050 * @param actionCode The code of the action being performed. 7051 * 7052 * @see #setOnEditorActionListener 7053 */ onEditorAction(int actionCode)7054 public void onEditorAction(int actionCode) { 7055 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7056 if (ict != null) { 7057 if (ict.onEditorActionListener != null) { 7058 if (ict.onEditorActionListener.onEditorAction(this, 7059 actionCode, null)) { 7060 return; 7061 } 7062 } 7063 7064 // This is the handling for some default action. 7065 // Note that for backwards compatibility we don't do this 7066 // default handling if explicit ime options have not been given, 7067 // instead turning this into the normal enter key codes that an 7068 // app may be expecting. 7069 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7070 View v = focusSearch(FOCUS_FORWARD); 7071 if (v != null) { 7072 if (!v.requestFocus(FOCUS_FORWARD)) { 7073 throw new IllegalStateException("focus search returned a view " 7074 + "that wasn't able to take focus!"); 7075 } 7076 } 7077 return; 7078 7079 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7080 View v = focusSearch(FOCUS_BACKWARD); 7081 if (v != null) { 7082 if (!v.requestFocus(FOCUS_BACKWARD)) { 7083 throw new IllegalStateException("focus search returned a view " 7084 + "that wasn't able to take focus!"); 7085 } 7086 } 7087 return; 7088 7089 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7090 InputMethodManager imm = getInputMethodManager(); 7091 if (imm != null && imm.isActive(this)) { 7092 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7093 } 7094 return; 7095 } 7096 } 7097 7098 ViewRootImpl viewRootImpl = getViewRootImpl(); 7099 if (viewRootImpl != null) { 7100 long eventTime = SystemClock.uptimeMillis(); 7101 viewRootImpl.dispatchKeyFromIme( 7102 new KeyEvent(eventTime, eventTime, 7103 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7104 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7105 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7106 | KeyEvent.FLAG_EDITOR_ACTION)); 7107 viewRootImpl.dispatchKeyFromIme( 7108 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7109 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7110 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7111 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7112 | KeyEvent.FLAG_EDITOR_ACTION)); 7113 } 7114 } 7115 7116 /** 7117 * Set the private content type of the text, which is the 7118 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7119 * field that will be filled in when creating an input connection. 7120 * 7121 * @see #getPrivateImeOptions() 7122 * @see EditorInfo#privateImeOptions 7123 * @attr ref android.R.styleable#TextView_privateImeOptions 7124 */ setPrivateImeOptions(String type)7125 public void setPrivateImeOptions(String type) { 7126 createEditorIfNeeded(); 7127 mEditor.createInputContentTypeIfNeeded(); 7128 mEditor.mInputContentType.privateImeOptions = type; 7129 } 7130 7131 /** 7132 * Get the private type of the content. 7133 * 7134 * @see #setPrivateImeOptions(String) 7135 * @see EditorInfo#privateImeOptions 7136 */ 7137 @InspectableProperty getPrivateImeOptions()7138 public String getPrivateImeOptions() { 7139 return mEditor != null && mEditor.mInputContentType != null 7140 ? mEditor.mInputContentType.privateImeOptions : null; 7141 } 7142 7143 /** 7144 * Set the extra input data of the text, which is the 7145 * {@link EditorInfo#extras TextBoxAttribute.extras} 7146 * Bundle that will be filled in when creating an input connection. The 7147 * given integer is the resource identifier of an XML resource holding an 7148 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7149 * 7150 * @see #getInputExtras(boolean) 7151 * @see EditorInfo#extras 7152 * @attr ref android.R.styleable#TextView_editorExtras 7153 */ setInputExtras(@mlRes int xmlResId)7154 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7155 createEditorIfNeeded(); 7156 XmlResourceParser parser = getResources().getXml(xmlResId); 7157 mEditor.createInputContentTypeIfNeeded(); 7158 mEditor.mInputContentType.extras = new Bundle(); 7159 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7160 } 7161 7162 /** 7163 * Retrieve the input extras currently associated with the text view, which 7164 * can be viewed as well as modified. 7165 * 7166 * @param create If true, the extras will be created if they don't already 7167 * exist. Otherwise, null will be returned if none have been created. 7168 * @see #setInputExtras(int) 7169 * @see EditorInfo#extras 7170 * @attr ref android.R.styleable#TextView_editorExtras 7171 */ getInputExtras(boolean create)7172 public Bundle getInputExtras(boolean create) { 7173 if (mEditor == null && !create) return null; 7174 createEditorIfNeeded(); 7175 if (mEditor.mInputContentType == null) { 7176 if (!create) return null; 7177 mEditor.createInputContentTypeIfNeeded(); 7178 } 7179 if (mEditor.mInputContentType.extras == null) { 7180 if (!create) return null; 7181 mEditor.mInputContentType.extras = new Bundle(); 7182 } 7183 return mEditor.mInputContentType.extras; 7184 } 7185 7186 /** 7187 * Change "hint" locales associated with the text view, which will be reported to an IME with 7188 * {@link EditorInfo#hintLocales} when it has focus. 7189 * 7190 * Starting with Android O, this also causes internationalized listeners to be created (or 7191 * change locale) based on the first locale in the input locale list. 7192 * 7193 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7194 * call {@link InputMethodManager#restartInput(View)}.</p> 7195 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7196 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7197 * @see #getImeHintLocales() 7198 * @see android.view.inputmethod.EditorInfo#hintLocales 7199 */ setImeHintLocales(@ullable LocaleList hintLocales)7200 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7201 createEditorIfNeeded(); 7202 mEditor.createInputContentTypeIfNeeded(); 7203 mEditor.mInputContentType.imeHintLocales = hintLocales; 7204 if (mUseInternationalizedInput) { 7205 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7206 } 7207 } 7208 7209 /** 7210 * @return The current languages list "hint". {@code null} when no "hint" is available. 7211 * @see #setImeHintLocales(LocaleList) 7212 * @see android.view.inputmethod.EditorInfo#hintLocales 7213 */ 7214 @Nullable getImeHintLocales()7215 public LocaleList getImeHintLocales() { 7216 if (mEditor == null) { 7217 return null; 7218 } 7219 if (mEditor.mInputContentType == null) { 7220 return null; 7221 } 7222 return mEditor.mInputContentType.imeHintLocales; 7223 } 7224 7225 /** 7226 * Returns the error message that was set to be displayed with 7227 * {@link #setError}, or <code>null</code> if no error was set 7228 * or if it the error was cleared by the widget after user input. 7229 */ getError()7230 public CharSequence getError() { 7231 return mEditor == null ? null : mEditor.mError; 7232 } 7233 7234 /** 7235 * Sets the right-hand compound drawable of the TextView to the "error" 7236 * icon and sets an error message that will be displayed in a popup when 7237 * the TextView has focus. The icon and error message will be reset to 7238 * null when any key events cause changes to the TextView's text. If the 7239 * <code>error</code> is <code>null</code>, the error message and icon 7240 * will be cleared. 7241 */ 7242 @android.view.RemotableViewMethod setError(CharSequence error)7243 public void setError(CharSequence error) { 7244 if (error == null) { 7245 setError(null, null); 7246 } else { 7247 Drawable dr = getContext().getDrawable( 7248 com.android.internal.R.drawable.indicator_input_error); 7249 7250 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7251 setError(error, dr); 7252 } 7253 } 7254 7255 /** 7256 * Sets the right-hand compound drawable of the TextView to the specified 7257 * icon and sets an error message that will be displayed in a popup when 7258 * the TextView has focus. The icon and error message will be reset to 7259 * null when any key events cause changes to the TextView's text. The 7260 * drawable must already have had {@link Drawable#setBounds} set on it. 7261 * If the <code>error</code> is <code>null</code>, the error message will 7262 * be cleared (and you should provide a <code>null</code> icon as well). 7263 */ setError(CharSequence error, Drawable icon)7264 public void setError(CharSequence error, Drawable icon) { 7265 createEditorIfNeeded(); 7266 mEditor.setError(error, icon); 7267 notifyViewAccessibilityStateChangedIfNeeded( 7268 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7269 } 7270 7271 @Override setFrame(int l, int t, int r, int b)7272 protected boolean setFrame(int l, int t, int r, int b) { 7273 boolean result = super.setFrame(l, t, r, b); 7274 7275 if (mEditor != null) mEditor.setFrame(); 7276 7277 restartMarqueeIfNeeded(); 7278 7279 return result; 7280 } 7281 restartMarqueeIfNeeded()7282 private void restartMarqueeIfNeeded() { 7283 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7284 mRestartMarquee = false; 7285 startMarquee(); 7286 } 7287 } 7288 7289 /** 7290 * Sets the list of input filters that will be used if the buffer is 7291 * Editable. Has no effect otherwise. 7292 * 7293 * @attr ref android.R.styleable#TextView_maxLength 7294 */ setFilters(InputFilter[] filters)7295 public void setFilters(InputFilter[] filters) { 7296 if (filters == null) { 7297 throw new IllegalArgumentException(); 7298 } 7299 7300 mFilters = filters; 7301 7302 if (mText instanceof Editable) { 7303 setFilters((Editable) mText, filters); 7304 } 7305 } 7306 7307 /** 7308 * Sets the list of input filters on the specified Editable, 7309 * and includes mInput in the list if it is an InputFilter. 7310 */ setFilters(Editable e, InputFilter[] filters)7311 private void setFilters(Editable e, InputFilter[] filters) { 7312 if (mEditor != null) { 7313 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7314 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7315 int num = 0; 7316 if (undoFilter) num++; 7317 if (keyFilter) num++; 7318 if (num > 0) { 7319 InputFilter[] nf = new InputFilter[filters.length + num]; 7320 7321 System.arraycopy(filters, 0, nf, 0, filters.length); 7322 num = 0; 7323 if (undoFilter) { 7324 nf[filters.length] = mEditor.mUndoInputFilter; 7325 num++; 7326 } 7327 if (keyFilter) { 7328 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7329 } 7330 7331 e.setFilters(nf); 7332 return; 7333 } 7334 } 7335 e.setFilters(filters); 7336 } 7337 7338 /** 7339 * Returns the current list of input filters. 7340 * 7341 * @attr ref android.R.styleable#TextView_maxLength 7342 */ getFilters()7343 public InputFilter[] getFilters() { 7344 return mFilters; 7345 } 7346 7347 ///////////////////////////////////////////////////////////////////////// 7348 getBoxHeight(Layout l)7349 private int getBoxHeight(Layout l) { 7350 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7351 int padding = (l == mHintLayout) 7352 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7353 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7354 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7355 } 7356 7357 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7358 int getVerticalOffset(boolean forceNormal) { 7359 int voffset = 0; 7360 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7361 7362 Layout l = mLayout; 7363 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7364 l = mHintLayout; 7365 } 7366 7367 if (gravity != Gravity.TOP) { 7368 int boxht = getBoxHeight(l); 7369 int textht = l.getHeight(); 7370 7371 if (textht < boxht) { 7372 if (gravity == Gravity.BOTTOM) { 7373 voffset = boxht - textht; 7374 } else { // (gravity == Gravity.CENTER_VERTICAL) 7375 voffset = (boxht - textht) >> 1; 7376 } 7377 } 7378 } 7379 return voffset; 7380 } 7381 getBottomVerticalOffset(boolean forceNormal)7382 private int getBottomVerticalOffset(boolean forceNormal) { 7383 int voffset = 0; 7384 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7385 7386 Layout l = mLayout; 7387 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7388 l = mHintLayout; 7389 } 7390 7391 if (gravity != Gravity.BOTTOM) { 7392 int boxht = getBoxHeight(l); 7393 int textht = l.getHeight(); 7394 7395 if (textht < boxht) { 7396 if (gravity == Gravity.TOP) { 7397 voffset = boxht - textht; 7398 } else { // (gravity == Gravity.CENTER_VERTICAL) 7399 voffset = (boxht - textht) >> 1; 7400 } 7401 } 7402 } 7403 return voffset; 7404 } 7405 invalidateCursorPath()7406 void invalidateCursorPath() { 7407 if (mHighlightPathBogus) { 7408 invalidateCursor(); 7409 } else { 7410 final int horizontalPadding = getCompoundPaddingLeft(); 7411 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7412 7413 if (mEditor.mDrawableForCursor == null) { 7414 synchronized (TEMP_RECTF) { 7415 /* 7416 * The reason for this concern about the thickness of the 7417 * cursor and doing the floor/ceil on the coordinates is that 7418 * some EditTexts (notably textfields in the Browser) have 7419 * anti-aliased text where not all the characters are 7420 * necessarily at integer-multiple locations. This should 7421 * make sure the entire cursor gets invalidated instead of 7422 * sometimes missing half a pixel. 7423 */ 7424 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7425 if (thick < 1.0f) { 7426 thick = 1.0f; 7427 } 7428 7429 thick /= 2.0f; 7430 7431 // mHighlightPath is guaranteed to be non null at that point. 7432 mHighlightPath.computeBounds(TEMP_RECTF, false); 7433 7434 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7435 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7436 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7437 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7438 } 7439 } else { 7440 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7441 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7442 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7443 } 7444 } 7445 } 7446 invalidateCursor()7447 void invalidateCursor() { 7448 int where = getSelectionEnd(); 7449 7450 invalidateCursor(where, where, where); 7451 } 7452 invalidateCursor(int a, int b, int c)7453 private void invalidateCursor(int a, int b, int c) { 7454 if (a >= 0 || b >= 0 || c >= 0) { 7455 int start = Math.min(Math.min(a, b), c); 7456 int end = Math.max(Math.max(a, b), c); 7457 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7458 } 7459 } 7460 7461 /** 7462 * Invalidates the region of text enclosed between the start and end text offsets. 7463 */ invalidateRegion(int start, int end, boolean invalidateCursor)7464 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7465 if (mLayout == null) { 7466 invalidate(); 7467 } else { 7468 int lineStart = mLayout.getLineForOffset(start); 7469 int top = mLayout.getLineTop(lineStart); 7470 7471 // This is ridiculous, but the descent from the line above 7472 // can hang down into the line we really want to redraw, 7473 // so we have to invalidate part of the line above to make 7474 // sure everything that needs to be redrawn really is. 7475 // (But not the whole line above, because that would cause 7476 // the same problem with the descenders on the line above it!) 7477 if (lineStart > 0) { 7478 top -= mLayout.getLineDescent(lineStart - 1); 7479 } 7480 7481 int lineEnd; 7482 7483 if (start == end) { 7484 lineEnd = lineStart; 7485 } else { 7486 lineEnd = mLayout.getLineForOffset(end); 7487 } 7488 7489 int bottom = mLayout.getLineBottom(lineEnd); 7490 7491 // mEditor can be null in case selection is set programmatically. 7492 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7493 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7494 top = Math.min(top, bounds.top); 7495 bottom = Math.max(bottom, bounds.bottom); 7496 } 7497 7498 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7499 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7500 7501 int left, right; 7502 if (lineStart == lineEnd && !invalidateCursor) { 7503 left = (int) mLayout.getPrimaryHorizontal(start); 7504 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7505 left += compoundPaddingLeft; 7506 right += compoundPaddingLeft; 7507 } else { 7508 // Rectangle bounding box when the region spans several lines 7509 left = compoundPaddingLeft; 7510 right = getWidth() - getCompoundPaddingRight(); 7511 } 7512 7513 invalidate(mScrollX + left, verticalPadding + top, 7514 mScrollX + right, verticalPadding + bottom); 7515 } 7516 } 7517 registerForPreDraw()7518 private void registerForPreDraw() { 7519 if (!mPreDrawRegistered) { 7520 getViewTreeObserver().addOnPreDrawListener(this); 7521 mPreDrawRegistered = true; 7522 } 7523 } 7524 unregisterForPreDraw()7525 private void unregisterForPreDraw() { 7526 getViewTreeObserver().removeOnPreDrawListener(this); 7527 mPreDrawRegistered = false; 7528 mPreDrawListenerDetached = false; 7529 } 7530 7531 /** 7532 * {@inheritDoc} 7533 */ 7534 @Override onPreDraw()7535 public boolean onPreDraw() { 7536 if (mLayout == null) { 7537 assumeLayout(); 7538 } 7539 7540 if (mMovement != null) { 7541 /* This code also provides auto-scrolling when a cursor is moved using a 7542 * CursorController (insertion point or selection limits). 7543 * For selection, ensure start or end is visible depending on controller's state. 7544 */ 7545 int curs = getSelectionEnd(); 7546 // Do not create the controller if it is not already created. 7547 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7548 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7549 curs = getSelectionStart(); 7550 } 7551 7552 /* 7553 * TODO: This should really only keep the end in view if 7554 * it already was before the text changed. I'm not sure 7555 * of a good way to tell from here if it was. 7556 */ 7557 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7558 curs = mText.length(); 7559 } 7560 7561 if (curs >= 0) { 7562 bringPointIntoView(curs); 7563 } 7564 } else { 7565 bringTextIntoView(); 7566 } 7567 7568 // This has to be checked here since: 7569 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7570 // a screen rotation) since layout is not yet initialized at that point. 7571 if (mEditor != null && mEditor.mCreatedWithASelection) { 7572 mEditor.refreshTextActionMode(); 7573 mEditor.mCreatedWithASelection = false; 7574 } 7575 7576 unregisterForPreDraw(); 7577 7578 return true; 7579 } 7580 7581 @Override onAttachedToWindow()7582 protected void onAttachedToWindow() { 7583 super.onAttachedToWindow(); 7584 7585 if (mEditor != null) mEditor.onAttachedToWindow(); 7586 7587 if (mPreDrawListenerDetached) { 7588 getViewTreeObserver().addOnPreDrawListener(this); 7589 mPreDrawListenerDetached = false; 7590 } 7591 } 7592 7593 /** @hide */ 7594 @Override onDetachedFromWindowInternal()7595 protected void onDetachedFromWindowInternal() { 7596 if (mPreDrawRegistered) { 7597 getViewTreeObserver().removeOnPreDrawListener(this); 7598 mPreDrawListenerDetached = true; 7599 } 7600 7601 resetResolvedDrawables(); 7602 7603 if (mEditor != null) mEditor.onDetachedFromWindow(); 7604 7605 super.onDetachedFromWindowInternal(); 7606 } 7607 7608 @Override onScreenStateChanged(int screenState)7609 public void onScreenStateChanged(int screenState) { 7610 super.onScreenStateChanged(screenState); 7611 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7612 } 7613 7614 @Override isPaddingOffsetRequired()7615 protected boolean isPaddingOffsetRequired() { 7616 return mShadowRadius != 0 || mDrawables != null; 7617 } 7618 7619 @Override getLeftPaddingOffset()7620 protected int getLeftPaddingOffset() { 7621 return getCompoundPaddingLeft() - mPaddingLeft 7622 + (int) Math.min(0, mShadowDx - mShadowRadius); 7623 } 7624 7625 @Override getTopPaddingOffset()7626 protected int getTopPaddingOffset() { 7627 return (int) Math.min(0, mShadowDy - mShadowRadius); 7628 } 7629 7630 @Override getBottomPaddingOffset()7631 protected int getBottomPaddingOffset() { 7632 return (int) Math.max(0, mShadowDy + mShadowRadius); 7633 } 7634 7635 @Override getRightPaddingOffset()7636 protected int getRightPaddingOffset() { 7637 return -(getCompoundPaddingRight() - mPaddingRight) 7638 + (int) Math.max(0, mShadowDx + mShadowRadius); 7639 } 7640 7641 @Override verifyDrawable(@onNull Drawable who)7642 protected boolean verifyDrawable(@NonNull Drawable who) { 7643 final boolean verified = super.verifyDrawable(who); 7644 if (!verified && mDrawables != null) { 7645 for (Drawable dr : mDrawables.mShowing) { 7646 if (who == dr) { 7647 return true; 7648 } 7649 } 7650 } 7651 return verified; 7652 } 7653 7654 @Override jumpDrawablesToCurrentState()7655 public void jumpDrawablesToCurrentState() { 7656 super.jumpDrawablesToCurrentState(); 7657 if (mDrawables != null) { 7658 for (Drawable dr : mDrawables.mShowing) { 7659 if (dr != null) { 7660 dr.jumpToCurrentState(); 7661 } 7662 } 7663 } 7664 } 7665 7666 @Override invalidateDrawable(@onNull Drawable drawable)7667 public void invalidateDrawable(@NonNull Drawable drawable) { 7668 boolean handled = false; 7669 7670 if (verifyDrawable(drawable)) { 7671 final Rect dirty = drawable.getBounds(); 7672 int scrollX = mScrollX; 7673 int scrollY = mScrollY; 7674 7675 // IMPORTANT: The coordinates below are based on the coordinates computed 7676 // for each compound drawable in onDraw(). Make sure to update each section 7677 // accordingly. 7678 final TextView.Drawables drawables = mDrawables; 7679 if (drawables != null) { 7680 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7681 final int compoundPaddingTop = getCompoundPaddingTop(); 7682 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7683 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7684 7685 scrollX += mPaddingLeft; 7686 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7687 handled = true; 7688 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7689 final int compoundPaddingTop = getCompoundPaddingTop(); 7690 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7691 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7692 7693 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7694 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7695 handled = true; 7696 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7697 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7698 final int compoundPaddingRight = getCompoundPaddingRight(); 7699 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7700 7701 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7702 scrollY += mPaddingTop; 7703 handled = true; 7704 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7705 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7706 final int compoundPaddingRight = getCompoundPaddingRight(); 7707 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7708 7709 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7710 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7711 handled = true; 7712 } 7713 } 7714 7715 if (handled) { 7716 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7717 dirty.right + scrollX, dirty.bottom + scrollY); 7718 } 7719 } 7720 7721 if (!handled) { 7722 super.invalidateDrawable(drawable); 7723 } 7724 } 7725 7726 @Override hasOverlappingRendering()7727 public boolean hasOverlappingRendering() { 7728 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7729 return ((getBackground() != null && getBackground().getCurrent() != null) 7730 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7731 || mShadowColor != 0); 7732 } 7733 7734 /** 7735 * 7736 * Returns the state of the {@code textIsSelectable} flag (See 7737 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7738 * to allow users to select and copy text in a non-editable TextView, the content of an 7739 * {@link EditText} can always be selected, independently of the value of this flag. 7740 * <p> 7741 * 7742 * @return True if the text displayed in this TextView can be selected by the user. 7743 * 7744 * @attr ref android.R.styleable#TextView_textIsSelectable 7745 */ 7746 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7747 public boolean isTextSelectable() { 7748 return mEditor == null ? false : mEditor.mTextIsSelectable; 7749 } 7750 7751 /** 7752 * Sets whether the content of this view is selectable by the user. The default is 7753 * {@code false}, meaning that the content is not selectable. 7754 * <p> 7755 * When you use a TextView to display a useful piece of information to the user (such as a 7756 * contact's address), make it selectable, so that the user can select and copy its 7757 * content. You can also use set the XML attribute 7758 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7759 * <p> 7760 * When you call this method to set the value of {@code textIsSelectable}, it sets 7761 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7762 * and {@code longClickable} to the same value. These flags correspond to the attributes 7763 * {@link android.R.styleable#View_focusable android:focusable}, 7764 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7765 * {@link android.R.styleable#View_clickable android:clickable}, and 7766 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7767 * flags to a state you had set previously, call one or more of the following methods: 7768 * {@link #setFocusable(boolean) setFocusable()}, 7769 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7770 * {@link #setClickable(boolean) setClickable()} or 7771 * {@link #setLongClickable(boolean) setLongClickable()}. 7772 * 7773 * @param selectable Whether the content of this TextView should be selectable. 7774 */ setTextIsSelectable(boolean selectable)7775 public void setTextIsSelectable(boolean selectable) { 7776 if (!selectable && mEditor == null) return; // false is default value with no edit data 7777 7778 createEditorIfNeeded(); 7779 if (mEditor.mTextIsSelectable == selectable) return; 7780 7781 mEditor.mTextIsSelectable = selectable; 7782 setFocusableInTouchMode(selectable); 7783 setFocusable(FOCUSABLE_AUTO); 7784 setClickable(selectable); 7785 setLongClickable(selectable); 7786 7787 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7788 7789 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7790 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7791 7792 // Called by setText above, but safer in case of future code changes 7793 mEditor.prepareCursorControllers(); 7794 } 7795 7796 @Override onCreateDrawableState(int extraSpace)7797 protected int[] onCreateDrawableState(int extraSpace) { 7798 final int[] drawableState; 7799 7800 if (mSingleLine) { 7801 drawableState = super.onCreateDrawableState(extraSpace); 7802 } else { 7803 drawableState = super.onCreateDrawableState(extraSpace + 1); 7804 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7805 } 7806 7807 if (isTextSelectable()) { 7808 // Disable pressed state, which was introduced when TextView was made clickable. 7809 // Prevents text color change. 7810 // setClickable(false) would have a similar effect, but it also disables focus changes 7811 // and long press actions, which are both needed by text selection. 7812 final int length = drawableState.length; 7813 for (int i = 0; i < length; i++) { 7814 if (drawableState[i] == R.attr.state_pressed) { 7815 final int[] nonPressedState = new int[length - 1]; 7816 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7817 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7818 return nonPressedState; 7819 } 7820 } 7821 } 7822 7823 return drawableState; 7824 } 7825 7826 @UnsupportedAppUsage getUpdatedHighlightPath()7827 private Path getUpdatedHighlightPath() { 7828 Path highlight = null; 7829 Paint highlightPaint = mHighlightPaint; 7830 7831 final int selStart = getSelectionStart(); 7832 final int selEnd = getSelectionEnd(); 7833 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7834 if (selStart == selEnd) { 7835 if (mEditor != null && mEditor.shouldRenderCursor()) { 7836 if (mHighlightPathBogus) { 7837 if (mHighlightPath == null) mHighlightPath = new Path(); 7838 mHighlightPath.reset(); 7839 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7840 mEditor.updateCursorPosition(); 7841 mHighlightPathBogus = false; 7842 } 7843 7844 // XXX should pass to skin instead of drawing directly 7845 highlightPaint.setColor(mCurTextColor); 7846 highlightPaint.setStyle(Paint.Style.STROKE); 7847 highlight = mHighlightPath; 7848 } 7849 } else { 7850 if (mHighlightPathBogus) { 7851 if (mHighlightPath == null) mHighlightPath = new Path(); 7852 mHighlightPath.reset(); 7853 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7854 mHighlightPathBogus = false; 7855 } 7856 7857 // XXX should pass to skin instead of drawing directly 7858 highlightPaint.setColor(mHighlightColor); 7859 highlightPaint.setStyle(Paint.Style.FILL); 7860 7861 highlight = mHighlightPath; 7862 } 7863 } 7864 return highlight; 7865 } 7866 7867 /** 7868 * @hide 7869 */ getHorizontalOffsetForDrawables()7870 public int getHorizontalOffsetForDrawables() { 7871 return 0; 7872 } 7873 7874 @Override onDraw(Canvas canvas)7875 protected void onDraw(Canvas canvas) { 7876 restartMarqueeIfNeeded(); 7877 7878 // Draw the background for this view 7879 super.onDraw(canvas); 7880 7881 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7882 final int compoundPaddingTop = getCompoundPaddingTop(); 7883 final int compoundPaddingRight = getCompoundPaddingRight(); 7884 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7885 final int scrollX = mScrollX; 7886 final int scrollY = mScrollY; 7887 final int right = mRight; 7888 final int left = mLeft; 7889 final int bottom = mBottom; 7890 final int top = mTop; 7891 final boolean isLayoutRtl = isLayoutRtl(); 7892 final int offset = getHorizontalOffsetForDrawables(); 7893 final int leftOffset = isLayoutRtl ? 0 : offset; 7894 final int rightOffset = isLayoutRtl ? offset : 0; 7895 7896 final Drawables dr = mDrawables; 7897 if (dr != null) { 7898 /* 7899 * Compound, not extended, because the icon is not clipped 7900 * if the text height is smaller. 7901 */ 7902 7903 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7904 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7905 7906 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7907 // Make sure to update invalidateDrawable() when changing this code. 7908 if (dr.mShowing[Drawables.LEFT] != null) { 7909 canvas.save(); 7910 canvas.translate(scrollX + mPaddingLeft + leftOffset, 7911 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 7912 dr.mShowing[Drawables.LEFT].draw(canvas); 7913 canvas.restore(); 7914 } 7915 7916 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7917 // Make sure to update invalidateDrawable() when changing this code. 7918 if (dr.mShowing[Drawables.RIGHT] != null) { 7919 canvas.save(); 7920 canvas.translate(scrollX + right - left - mPaddingRight 7921 - dr.mDrawableSizeRight - rightOffset, 7922 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 7923 dr.mShowing[Drawables.RIGHT].draw(canvas); 7924 canvas.restore(); 7925 } 7926 7927 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7928 // Make sure to update invalidateDrawable() when changing this code. 7929 if (dr.mShowing[Drawables.TOP] != null) { 7930 canvas.save(); 7931 canvas.translate(scrollX + compoundPaddingLeft 7932 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 7933 dr.mShowing[Drawables.TOP].draw(canvas); 7934 canvas.restore(); 7935 } 7936 7937 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7938 // Make sure to update invalidateDrawable() when changing this code. 7939 if (dr.mShowing[Drawables.BOTTOM] != null) { 7940 canvas.save(); 7941 canvas.translate(scrollX + compoundPaddingLeft 7942 + (hspace - dr.mDrawableWidthBottom) / 2, 7943 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 7944 dr.mShowing[Drawables.BOTTOM].draw(canvas); 7945 canvas.restore(); 7946 } 7947 } 7948 7949 int color = mCurTextColor; 7950 7951 if (mLayout == null) { 7952 assumeLayout(); 7953 } 7954 7955 Layout layout = mLayout; 7956 7957 if (mHint != null && mText.length() == 0) { 7958 if (mHintTextColor != null) { 7959 color = mCurHintTextColor; 7960 } 7961 7962 layout = mHintLayout; 7963 } 7964 7965 mTextPaint.setColor(color); 7966 mTextPaint.drawableState = getDrawableState(); 7967 7968 canvas.save(); 7969 /* Would be faster if we didn't have to do this. Can we chop the 7970 (displayable) text so that we don't need to do this ever? 7971 */ 7972 7973 int extendedPaddingTop = getExtendedPaddingTop(); 7974 int extendedPaddingBottom = getExtendedPaddingBottom(); 7975 7976 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7977 final int maxScrollY = mLayout.getHeight() - vspace; 7978 7979 float clipLeft = compoundPaddingLeft + scrollX; 7980 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 7981 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 7982 float clipBottom = bottom - top + scrollY 7983 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 7984 7985 if (mShadowRadius != 0) { 7986 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 7987 clipRight += Math.max(0, mShadowDx + mShadowRadius); 7988 7989 clipTop += Math.min(0, mShadowDy - mShadowRadius); 7990 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 7991 } 7992 7993 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 7994 7995 int voffsetText = 0; 7996 int voffsetCursor = 0; 7997 7998 // translate in by our padding 7999 /* shortcircuit calling getVerticaOffset() */ 8000 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8001 voffsetText = getVerticalOffset(false); 8002 voffsetCursor = getVerticalOffset(true); 8003 } 8004 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 8005 8006 final int layoutDirection = getLayoutDirection(); 8007 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8008 if (isMarqueeFadeEnabled()) { 8009 if (!mSingleLine && getLineCount() == 1 && canMarquee() 8010 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 8011 final int width = mRight - mLeft; 8012 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 8013 final float dx = mLayout.getLineRight(0) - (width - padding); 8014 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8015 } 8016 8017 if (mMarquee != null && mMarquee.isRunning()) { 8018 final float dx = -mMarquee.getScroll(); 8019 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8020 } 8021 } 8022 8023 final int cursorOffsetVertical = voffsetCursor - voffsetText; 8024 8025 Path highlight = getUpdatedHighlightPath(); 8026 if (mEditor != null) { 8027 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 8028 } else { 8029 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8030 } 8031 8032 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 8033 final float dx = mMarquee.getGhostOffset(); 8034 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8035 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8036 } 8037 8038 canvas.restore(); 8039 } 8040 8041 @Override getFocusedRect(Rect r)8042 public void getFocusedRect(Rect r) { 8043 if (mLayout == null) { 8044 super.getFocusedRect(r); 8045 return; 8046 } 8047 8048 int selEnd = getSelectionEnd(); 8049 if (selEnd < 0) { 8050 super.getFocusedRect(r); 8051 return; 8052 } 8053 8054 int selStart = getSelectionStart(); 8055 if (selStart < 0 || selStart >= selEnd) { 8056 int line = mLayout.getLineForOffset(selEnd); 8057 r.top = mLayout.getLineTop(line); 8058 r.bottom = mLayout.getLineBottom(line); 8059 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8060 r.right = r.left + 4; 8061 } else { 8062 int lineStart = mLayout.getLineForOffset(selStart); 8063 int lineEnd = mLayout.getLineForOffset(selEnd); 8064 r.top = mLayout.getLineTop(lineStart); 8065 r.bottom = mLayout.getLineBottom(lineEnd); 8066 if (lineStart == lineEnd) { 8067 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8068 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8069 } else { 8070 // Selection extends across multiple lines -- make the focused 8071 // rect cover the entire width. 8072 if (mHighlightPathBogus) { 8073 if (mHighlightPath == null) mHighlightPath = new Path(); 8074 mHighlightPath.reset(); 8075 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8076 mHighlightPathBogus = false; 8077 } 8078 synchronized (TEMP_RECTF) { 8079 mHighlightPath.computeBounds(TEMP_RECTF, true); 8080 r.left = (int) TEMP_RECTF.left - 1; 8081 r.right = (int) TEMP_RECTF.right + 1; 8082 } 8083 } 8084 } 8085 8086 // Adjust for padding and gravity. 8087 int paddingLeft = getCompoundPaddingLeft(); 8088 int paddingTop = getExtendedPaddingTop(); 8089 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8090 paddingTop += getVerticalOffset(false); 8091 } 8092 r.offset(paddingLeft, paddingTop); 8093 int paddingBottom = getExtendedPaddingBottom(); 8094 r.bottom += paddingBottom; 8095 } 8096 8097 /** 8098 * Return the number of lines of text, or 0 if the internal Layout has not 8099 * been built. 8100 */ getLineCount()8101 public int getLineCount() { 8102 return mLayout != null ? mLayout.getLineCount() : 0; 8103 } 8104 8105 /** 8106 * Return the baseline for the specified line (0...getLineCount() - 1) 8107 * If bounds is not null, return the top, left, right, bottom extents 8108 * of the specified line in it. If the internal Layout has not been built, 8109 * return 0 and set bounds to (0, 0, 0, 0) 8110 * @param line which line to examine (0..getLineCount() - 1) 8111 * @param bounds Optional. If not null, it returns the extent of the line 8112 * @return the Y-coordinate of the baseline 8113 */ getLineBounds(int line, Rect bounds)8114 public int getLineBounds(int line, Rect bounds) { 8115 if (mLayout == null) { 8116 if (bounds != null) { 8117 bounds.set(0, 0, 0, 0); 8118 } 8119 return 0; 8120 } else { 8121 int baseline = mLayout.getLineBounds(line, bounds); 8122 8123 int voffset = getExtendedPaddingTop(); 8124 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8125 voffset += getVerticalOffset(true); 8126 } 8127 if (bounds != null) { 8128 bounds.offset(getCompoundPaddingLeft(), voffset); 8129 } 8130 return baseline + voffset; 8131 } 8132 } 8133 8134 @Override getBaseline()8135 public int getBaseline() { 8136 if (mLayout == null) { 8137 return super.getBaseline(); 8138 } 8139 8140 return getBaselineOffset() + mLayout.getLineBaseline(0); 8141 } 8142 getBaselineOffset()8143 int getBaselineOffset() { 8144 int voffset = 0; 8145 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8146 voffset = getVerticalOffset(true); 8147 } 8148 8149 if (isLayoutModeOptical(mParent)) { 8150 voffset -= getOpticalInsets().top; 8151 } 8152 8153 return getExtendedPaddingTop() + voffset; 8154 } 8155 8156 /** 8157 * @hide 8158 */ 8159 @Override getFadeTop(boolean offsetRequired)8160 protected int getFadeTop(boolean offsetRequired) { 8161 if (mLayout == null) return 0; 8162 8163 int voffset = 0; 8164 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8165 voffset = getVerticalOffset(true); 8166 } 8167 8168 if (offsetRequired) voffset += getTopPaddingOffset(); 8169 8170 return getExtendedPaddingTop() + voffset; 8171 } 8172 8173 /** 8174 * @hide 8175 */ 8176 @Override getFadeHeight(boolean offsetRequired)8177 protected int getFadeHeight(boolean offsetRequired) { 8178 return mLayout != null ? mLayout.getHeight() : 0; 8179 } 8180 8181 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8182 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8183 if (mSpannable != null && mLinksClickable) { 8184 final float x = event.getX(pointerIndex); 8185 final float y = event.getY(pointerIndex); 8186 final int offset = getOffsetForPosition(x, y); 8187 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8188 ClickableSpan.class); 8189 if (clickables.length > 0) { 8190 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8191 } 8192 } 8193 if (isTextSelectable() || isTextEditable()) { 8194 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8195 } 8196 return super.onResolvePointerIcon(event, pointerIndex); 8197 } 8198 8199 @Override onKeyPreIme(int keyCode, KeyEvent event)8200 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8201 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8202 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8203 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8204 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8205 return true; 8206 } 8207 return super.onKeyPreIme(keyCode, event); 8208 } 8209 8210 /** 8211 * @hide 8212 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8213 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8214 // Do nothing unless mEditor is in text action mode. 8215 if (mEditor == null || mEditor.getTextActionMode() == null) { 8216 return false; 8217 } 8218 8219 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8220 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8221 if (state != null) { 8222 state.startTracking(event, this); 8223 } 8224 return true; 8225 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8226 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8227 if (state != null) { 8228 state.handleUpEvent(event); 8229 } 8230 if (event.isTracking() && !event.isCanceled()) { 8231 stopTextActionMode(); 8232 return true; 8233 } 8234 } 8235 return false; 8236 } 8237 8238 @Override onKeyDown(int keyCode, KeyEvent event)8239 public boolean onKeyDown(int keyCode, KeyEvent event) { 8240 final int which = doKeyDown(keyCode, event, null); 8241 if (which == KEY_EVENT_NOT_HANDLED) { 8242 return super.onKeyDown(keyCode, event); 8243 } 8244 8245 return true; 8246 } 8247 8248 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8249 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8250 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8251 final int which = doKeyDown(keyCode, down, event); 8252 if (which == KEY_EVENT_NOT_HANDLED) { 8253 // Go through default dispatching. 8254 return super.onKeyMultiple(keyCode, repeatCount, event); 8255 } 8256 if (which == KEY_EVENT_HANDLED) { 8257 // Consumed the whole thing. 8258 return true; 8259 } 8260 8261 repeatCount--; 8262 8263 // We are going to dispatch the remaining events to either the input 8264 // or movement method. To do this, we will just send a repeated stream 8265 // of down and up events until we have done the complete repeatCount. 8266 // It would be nice if those interfaces had an onKeyMultiple() method, 8267 // but adding that is a more complicated change. 8268 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8269 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8270 // mEditor and mEditor.mInput are not null from doKeyDown 8271 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8272 while (--repeatCount > 0) { 8273 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8274 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8275 } 8276 hideErrorIfUnchanged(); 8277 8278 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8279 // mMovement is not null from doKeyDown 8280 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8281 while (--repeatCount > 0) { 8282 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8283 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8284 } 8285 } 8286 8287 return true; 8288 } 8289 8290 /** 8291 * Returns true if pressing ENTER in this field advances focus instead 8292 * of inserting the character. This is true mostly in single-line fields, 8293 * but also in mail addresses and subjects which will display on multiple 8294 * lines but where it doesn't make sense to insert newlines. 8295 */ shouldAdvanceFocusOnEnter()8296 private boolean shouldAdvanceFocusOnEnter() { 8297 if (getKeyListener() == null) { 8298 return false; 8299 } 8300 8301 if (mSingleLine) { 8302 return true; 8303 } 8304 8305 if (mEditor != null 8306 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8307 == EditorInfo.TYPE_CLASS_TEXT) { 8308 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8309 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8310 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8311 return true; 8312 } 8313 } 8314 8315 return false; 8316 } 8317 isDirectionalNavigationKey(int keyCode)8318 private boolean isDirectionalNavigationKey(int keyCode) { 8319 switch(keyCode) { 8320 case KeyEvent.KEYCODE_DPAD_UP: 8321 case KeyEvent.KEYCODE_DPAD_DOWN: 8322 case KeyEvent.KEYCODE_DPAD_LEFT: 8323 case KeyEvent.KEYCODE_DPAD_RIGHT: 8324 return true; 8325 } 8326 return false; 8327 } 8328 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8329 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8330 if (!isEnabled()) { 8331 return KEY_EVENT_NOT_HANDLED; 8332 } 8333 8334 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8335 // While this shouldn't be necessary because any time we're preventing default movement we 8336 // should be restricting the focus to remain within this view, thus we'll also receive 8337 // the key up event, occasionally key up events will get dropped and we don't want to 8338 // prevent the user from traversing out of this on the next key down. 8339 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8340 mPreventDefaultMovement = false; 8341 } 8342 8343 switch (keyCode) { 8344 case KeyEvent.KEYCODE_ENTER: 8345 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8346 if (event.hasNoModifiers()) { 8347 // When mInputContentType is set, we know that we are 8348 // running in a "modern" cupcake environment, so don't need 8349 // to worry about the application trying to capture 8350 // enter key events. 8351 if (mEditor != null && mEditor.mInputContentType != null) { 8352 // If there is an action listener, given them a 8353 // chance to consume the event. 8354 if (mEditor.mInputContentType.onEditorActionListener != null 8355 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8356 this, EditorInfo.IME_NULL, event)) { 8357 mEditor.mInputContentType.enterDown = true; 8358 // We are consuming the enter key for them. 8359 return KEY_EVENT_HANDLED; 8360 } 8361 } 8362 8363 // If our editor should move focus when enter is pressed, or 8364 // this is a generated event from an IME action button, then 8365 // don't let it be inserted into the text. 8366 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8367 || shouldAdvanceFocusOnEnter()) { 8368 if (hasOnClickListeners()) { 8369 return KEY_EVENT_NOT_HANDLED; 8370 } 8371 return KEY_EVENT_HANDLED; 8372 } 8373 } 8374 break; 8375 8376 case KeyEvent.KEYCODE_DPAD_CENTER: 8377 if (event.hasNoModifiers()) { 8378 if (shouldAdvanceFocusOnEnter()) { 8379 return KEY_EVENT_NOT_HANDLED; 8380 } 8381 } 8382 break; 8383 8384 case KeyEvent.KEYCODE_TAB: 8385 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8386 // Tab is used to move focus. 8387 return KEY_EVENT_NOT_HANDLED; 8388 } 8389 break; 8390 8391 // Has to be done on key down (and not on key up) to correctly be intercepted. 8392 case KeyEvent.KEYCODE_BACK: 8393 if (mEditor != null && mEditor.getTextActionMode() != null) { 8394 stopTextActionMode(); 8395 return KEY_EVENT_HANDLED; 8396 } 8397 break; 8398 8399 case KeyEvent.KEYCODE_CUT: 8400 if (event.hasNoModifiers() && canCut()) { 8401 if (onTextContextMenuItem(ID_CUT)) { 8402 return KEY_EVENT_HANDLED; 8403 } 8404 } 8405 break; 8406 8407 case KeyEvent.KEYCODE_COPY: 8408 if (event.hasNoModifiers() && canCopy()) { 8409 if (onTextContextMenuItem(ID_COPY)) { 8410 return KEY_EVENT_HANDLED; 8411 } 8412 } 8413 break; 8414 8415 case KeyEvent.KEYCODE_PASTE: 8416 if (event.hasNoModifiers() && canPaste()) { 8417 if (onTextContextMenuItem(ID_PASTE)) { 8418 return KEY_EVENT_HANDLED; 8419 } 8420 } 8421 break; 8422 8423 case KeyEvent.KEYCODE_FORWARD_DEL: 8424 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8425 if (onTextContextMenuItem(ID_CUT)) { 8426 return KEY_EVENT_HANDLED; 8427 } 8428 } 8429 break; 8430 8431 case KeyEvent.KEYCODE_INSERT: 8432 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8433 if (onTextContextMenuItem(ID_COPY)) { 8434 return KEY_EVENT_HANDLED; 8435 } 8436 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8437 if (onTextContextMenuItem(ID_PASTE)) { 8438 return KEY_EVENT_HANDLED; 8439 } 8440 } 8441 break; 8442 } 8443 8444 if (mEditor != null && mEditor.mKeyListener != null) { 8445 boolean doDown = true; 8446 if (otherEvent != null) { 8447 try { 8448 beginBatchEdit(); 8449 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8450 otherEvent); 8451 hideErrorIfUnchanged(); 8452 doDown = false; 8453 if (handled) { 8454 return KEY_EVENT_HANDLED; 8455 } 8456 } catch (AbstractMethodError e) { 8457 // onKeyOther was added after 1.0, so if it isn't 8458 // implemented we need to try to dispatch as a regular down. 8459 } finally { 8460 endBatchEdit(); 8461 } 8462 } 8463 8464 if (doDown) { 8465 beginBatchEdit(); 8466 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8467 keyCode, event); 8468 endBatchEdit(); 8469 hideErrorIfUnchanged(); 8470 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8471 } 8472 } 8473 8474 // bug 650865: sometimes we get a key event before a layout. 8475 // don't try to move around if we don't know the layout. 8476 8477 if (mMovement != null && mLayout != null) { 8478 boolean doDown = true; 8479 if (otherEvent != null) { 8480 try { 8481 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8482 doDown = false; 8483 if (handled) { 8484 return KEY_EVENT_HANDLED; 8485 } 8486 } catch (AbstractMethodError e) { 8487 // onKeyOther was added after 1.0, so if it isn't 8488 // implemented we need to try to dispatch as a regular down. 8489 } 8490 } 8491 if (doDown) { 8492 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8493 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8494 mPreventDefaultMovement = true; 8495 } 8496 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8497 } 8498 } 8499 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8500 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8501 // to move focus with arrows. 8502 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8503 && isDirectionalNavigationKey(keyCode)) { 8504 return KEY_EVENT_HANDLED; 8505 } 8506 } 8507 8508 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8509 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8510 } 8511 8512 /** 8513 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8514 * can be recorded. 8515 * @hide 8516 */ resetErrorChangedFlag()8517 public void resetErrorChangedFlag() { 8518 /* 8519 * Keep track of what the error was before doing the input 8520 * so that if an input filter changed the error, we leave 8521 * that error showing. Otherwise, we take down whatever 8522 * error was showing when the user types something. 8523 */ 8524 if (mEditor != null) mEditor.mErrorWasChanged = false; 8525 } 8526 8527 /** 8528 * @hide 8529 */ hideErrorIfUnchanged()8530 public void hideErrorIfUnchanged() { 8531 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8532 setError(null, null); 8533 } 8534 } 8535 8536 @Override onKeyUp(int keyCode, KeyEvent event)8537 public boolean onKeyUp(int keyCode, KeyEvent event) { 8538 if (!isEnabled()) { 8539 return super.onKeyUp(keyCode, event); 8540 } 8541 8542 if (!KeyEvent.isModifierKey(keyCode)) { 8543 mPreventDefaultMovement = false; 8544 } 8545 8546 switch (keyCode) { 8547 case KeyEvent.KEYCODE_DPAD_CENTER: 8548 if (event.hasNoModifiers()) { 8549 /* 8550 * If there is a click listener, just call through to 8551 * super, which will invoke it. 8552 * 8553 * If there isn't a click listener, try to show the soft 8554 * input method. (It will also 8555 * call performClick(), but that won't do anything in 8556 * this case.) 8557 */ 8558 if (!hasOnClickListeners()) { 8559 if (mMovement != null && mText instanceof Editable 8560 && mLayout != null && onCheckIsTextEditor()) { 8561 InputMethodManager imm = getInputMethodManager(); 8562 viewClicked(imm); 8563 if (imm != null && getShowSoftInputOnFocus()) { 8564 imm.showSoftInput(this, 0); 8565 } 8566 } 8567 } 8568 } 8569 return super.onKeyUp(keyCode, event); 8570 8571 case KeyEvent.KEYCODE_ENTER: 8572 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8573 if (event.hasNoModifiers()) { 8574 if (mEditor != null && mEditor.mInputContentType != null 8575 && mEditor.mInputContentType.onEditorActionListener != null 8576 && mEditor.mInputContentType.enterDown) { 8577 mEditor.mInputContentType.enterDown = false; 8578 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8579 this, EditorInfo.IME_NULL, event)) { 8580 return true; 8581 } 8582 } 8583 8584 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8585 || shouldAdvanceFocusOnEnter()) { 8586 /* 8587 * If there is a click listener, just call through to 8588 * super, which will invoke it. 8589 * 8590 * If there isn't a click listener, try to advance focus, 8591 * but still call through to super, which will reset the 8592 * pressed state and longpress state. (It will also 8593 * call performClick(), but that won't do anything in 8594 * this case.) 8595 */ 8596 if (!hasOnClickListeners()) { 8597 View v = focusSearch(FOCUS_DOWN); 8598 8599 if (v != null) { 8600 if (!v.requestFocus(FOCUS_DOWN)) { 8601 throw new IllegalStateException("focus search returned a view " 8602 + "that wasn't able to take focus!"); 8603 } 8604 8605 /* 8606 * Return true because we handled the key; super 8607 * will return false because there was no click 8608 * listener. 8609 */ 8610 super.onKeyUp(keyCode, event); 8611 return true; 8612 } else if ((event.getFlags() 8613 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8614 // No target for next focus, but make sure the IME 8615 // if this came from it. 8616 InputMethodManager imm = getInputMethodManager(); 8617 if (imm != null && imm.isActive(this)) { 8618 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8619 } 8620 } 8621 } 8622 } 8623 return super.onKeyUp(keyCode, event); 8624 } 8625 break; 8626 } 8627 8628 if (mEditor != null && mEditor.mKeyListener != null) { 8629 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8630 return true; 8631 } 8632 } 8633 8634 if (mMovement != null && mLayout != null) { 8635 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8636 return true; 8637 } 8638 } 8639 8640 return super.onKeyUp(keyCode, event); 8641 } 8642 8643 @Override onCheckIsTextEditor()8644 public boolean onCheckIsTextEditor() { 8645 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8646 } 8647 8648 @Override onCreateInputConnection(EditorInfo outAttrs)8649 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8650 if (onCheckIsTextEditor() && isEnabled()) { 8651 mEditor.createInputMethodStateIfNeeded(); 8652 outAttrs.inputType = getInputType(); 8653 if (mEditor.mInputContentType != null) { 8654 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8655 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8656 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8657 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8658 outAttrs.extras = mEditor.mInputContentType.extras; 8659 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8660 } else { 8661 outAttrs.imeOptions = EditorInfo.IME_NULL; 8662 outAttrs.hintLocales = null; 8663 } 8664 if (focusSearch(FOCUS_DOWN) != null) { 8665 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8666 } 8667 if (focusSearch(FOCUS_UP) != null) { 8668 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8669 } 8670 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8671 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8672 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8673 // An action has not been set, but the enter key will move to 8674 // the next focus, so set the action to that. 8675 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8676 } else { 8677 // An action has not been set, and there is no focus to move 8678 // to, so let's just supply a "done" action. 8679 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8680 } 8681 if (!shouldAdvanceFocusOnEnter()) { 8682 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8683 } 8684 } 8685 if (isMultilineInputType(outAttrs.inputType)) { 8686 // Multi-line text editors should always show an enter key. 8687 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8688 } 8689 outAttrs.hintText = mHint; 8690 outAttrs.targetInputMethodUser = mTextOperationUser; 8691 if (mText instanceof Editable) { 8692 InputConnection ic = new EditableInputConnection(this); 8693 outAttrs.initialSelStart = getSelectionStart(); 8694 outAttrs.initialSelEnd = getSelectionEnd(); 8695 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8696 outAttrs.setInitialSurroundingText(mText); 8697 return ic; 8698 } 8699 } 8700 return null; 8701 } 8702 8703 /** 8704 * If this TextView contains editable content, extract a portion of it 8705 * based on the information in <var>request</var> in to <var>outText</var>. 8706 * @return Returns true if the text was successfully extracted, else false. 8707 */ extractText(ExtractedTextRequest request, ExtractedText outText)8708 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8709 createEditorIfNeeded(); 8710 return mEditor.extractText(request, outText); 8711 } 8712 8713 /** 8714 * This is used to remove all style-impacting spans from text before new 8715 * extracted text is being replaced into it, so that we don't have any 8716 * lingering spans applied during the replace. 8717 */ removeParcelableSpans(Spannable spannable, int start, int end)8718 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8719 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8720 int i = spans.length; 8721 while (i > 0) { 8722 i--; 8723 spannable.removeSpan(spans[i]); 8724 } 8725 } 8726 8727 /** 8728 * Apply to this text view the given extracted text, as previously 8729 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8730 */ setExtractedText(ExtractedText text)8731 public void setExtractedText(ExtractedText text) { 8732 Editable content = getEditableText(); 8733 if (text.text != null) { 8734 if (content == null) { 8735 setText(text.text, TextView.BufferType.EDITABLE); 8736 } else { 8737 int start = 0; 8738 int end = content.length(); 8739 8740 if (text.partialStartOffset >= 0) { 8741 final int N = content.length(); 8742 start = text.partialStartOffset; 8743 if (start > N) start = N; 8744 end = text.partialEndOffset; 8745 if (end > N) end = N; 8746 } 8747 8748 removeParcelableSpans(content, start, end); 8749 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8750 if (text.text instanceof Spanned) { 8751 // OK to copy spans only. 8752 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8753 Object.class, content, start); 8754 } 8755 } else { 8756 content.replace(start, end, text.text); 8757 } 8758 } 8759 } 8760 8761 // Now set the selection position... make sure it is in range, to 8762 // avoid crashes. If this is a partial update, it is possible that 8763 // the underlying text may have changed, causing us problems here. 8764 // Also we just don't want to trust clients to do the right thing. 8765 Spannable sp = (Spannable) getText(); 8766 final int N = sp.length(); 8767 int start = text.selectionStart; 8768 if (start < 0) { 8769 start = 0; 8770 } else if (start > N) { 8771 start = N; 8772 } 8773 int end = text.selectionEnd; 8774 if (end < 0) { 8775 end = 0; 8776 } else if (end > N) { 8777 end = N; 8778 } 8779 Selection.setSelection(sp, start, end); 8780 8781 // Finally, update the selection mode. 8782 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8783 MetaKeyKeyListener.startSelecting(this, sp); 8784 } else { 8785 MetaKeyKeyListener.stopSelecting(this, sp); 8786 } 8787 8788 setHintInternal(text.hint); 8789 } 8790 8791 /** 8792 * @hide 8793 */ setExtracting(ExtractedTextRequest req)8794 public void setExtracting(ExtractedTextRequest req) { 8795 if (mEditor.mInputMethodState != null) { 8796 mEditor.mInputMethodState.mExtractedTextRequest = req; 8797 } 8798 // This would stop a possible selection mode, but no such mode is started in case 8799 // extracted mode will start. Some text is selected though, and will trigger an action mode 8800 // in the extracted view. 8801 mEditor.hideCursorAndSpanControllers(); 8802 stopTextActionMode(); 8803 if (mEditor.mSelectionModifierCursorController != null) { 8804 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8805 } 8806 } 8807 8808 /** 8809 * Called by the framework in response to a text completion from 8810 * the current input method, provided by it calling 8811 * {@link InputConnection#commitCompletion 8812 * InputConnection.commitCompletion()}. The default implementation does 8813 * nothing; text views that are supporting auto-completion should override 8814 * this to do their desired behavior. 8815 * 8816 * @param text The auto complete text the user has selected. 8817 */ onCommitCompletion(CompletionInfo text)8818 public void onCommitCompletion(CompletionInfo text) { 8819 // intentionally empty 8820 } 8821 8822 /** 8823 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8824 * dictionary) from the current input method, provided by it calling 8825 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8826 * The default implementation flashes the background of the corrected word to provide 8827 * feedback to the user. 8828 * 8829 * @param info The auto correct info about the text that was corrected. 8830 */ onCommitCorrection(CorrectionInfo info)8831 public void onCommitCorrection(CorrectionInfo info) { 8832 if (mEditor != null) mEditor.onCommitCorrection(info); 8833 } 8834 beginBatchEdit()8835 public void beginBatchEdit() { 8836 if (mEditor != null) mEditor.beginBatchEdit(); 8837 } 8838 endBatchEdit()8839 public void endBatchEdit() { 8840 if (mEditor != null) mEditor.endBatchEdit(); 8841 } 8842 8843 /** 8844 * Called by the framework in response to a request to begin a batch 8845 * of edit operations through a call to link {@link #beginBatchEdit()}. 8846 */ onBeginBatchEdit()8847 public void onBeginBatchEdit() { 8848 // intentionally empty 8849 } 8850 8851 /** 8852 * Called by the framework in response to a request to end a batch 8853 * of edit operations through a call to link {@link #endBatchEdit}. 8854 */ onEndBatchEdit()8855 public void onEndBatchEdit() { 8856 // intentionally empty 8857 } 8858 8859 /** 8860 * Called by the framework in response to a private command from the 8861 * current method, provided by it calling 8862 * {@link InputConnection#performPrivateCommand 8863 * InputConnection.performPrivateCommand()}. 8864 * 8865 * @param action The action name of the command. 8866 * @param data Any additional data for the command. This may be null. 8867 * @return Return true if you handled the command, else false. 8868 */ onPrivateIMECommand(String action, Bundle data)8869 public boolean onPrivateIMECommand(String action, Bundle data) { 8870 return false; 8871 } 8872 8873 /** @hide */ 8874 @VisibleForTesting 8875 @UnsupportedAppUsage nullLayouts()8876 public void nullLayouts() { 8877 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8878 mSavedLayout = (BoringLayout) mLayout; 8879 } 8880 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8881 mSavedHintLayout = (BoringLayout) mHintLayout; 8882 } 8883 8884 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8885 8886 mBoring = mHintBoring = null; 8887 8888 // Since it depends on the value of mLayout 8889 if (mEditor != null) mEditor.prepareCursorControllers(); 8890 } 8891 8892 /** 8893 * Make a new Layout based on the already-measured size of the view, 8894 * on the assumption that it was measured correctly at some point. 8895 */ 8896 @UnsupportedAppUsage assumeLayout()8897 private void assumeLayout() { 8898 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8899 8900 if (width < 1) { 8901 width = 0; 8902 } 8903 8904 int physicalWidth = width; 8905 8906 if (mHorizontallyScrolling) { 8907 width = VERY_WIDE; 8908 } 8909 8910 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 8911 physicalWidth, false); 8912 } 8913 8914 @UnsupportedAppUsage getLayoutAlignment()8915 private Layout.Alignment getLayoutAlignment() { 8916 Layout.Alignment alignment; 8917 switch (getTextAlignment()) { 8918 case TEXT_ALIGNMENT_GRAVITY: 8919 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 8920 case Gravity.START: 8921 alignment = Layout.Alignment.ALIGN_NORMAL; 8922 break; 8923 case Gravity.END: 8924 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8925 break; 8926 case Gravity.LEFT: 8927 alignment = Layout.Alignment.ALIGN_LEFT; 8928 break; 8929 case Gravity.RIGHT: 8930 alignment = Layout.Alignment.ALIGN_RIGHT; 8931 break; 8932 case Gravity.CENTER_HORIZONTAL: 8933 alignment = Layout.Alignment.ALIGN_CENTER; 8934 break; 8935 default: 8936 alignment = Layout.Alignment.ALIGN_NORMAL; 8937 break; 8938 } 8939 break; 8940 case TEXT_ALIGNMENT_TEXT_START: 8941 alignment = Layout.Alignment.ALIGN_NORMAL; 8942 break; 8943 case TEXT_ALIGNMENT_TEXT_END: 8944 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8945 break; 8946 case TEXT_ALIGNMENT_CENTER: 8947 alignment = Layout.Alignment.ALIGN_CENTER; 8948 break; 8949 case TEXT_ALIGNMENT_VIEW_START: 8950 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8951 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8952 break; 8953 case TEXT_ALIGNMENT_VIEW_END: 8954 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8955 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8956 break; 8957 case TEXT_ALIGNMENT_INHERIT: 8958 // This should never happen as we have already resolved the text alignment 8959 // but better safe than sorry so we just fall through 8960 default: 8961 alignment = Layout.Alignment.ALIGN_NORMAL; 8962 break; 8963 } 8964 return alignment; 8965 } 8966 8967 /** 8968 * The width passed in is now the desired layout width, 8969 * not the full view width with padding. 8970 * {@hide} 8971 */ 8972 @VisibleForTesting 8973 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8974 public void makeNewLayout(int wantWidth, int hintWidth, 8975 BoringLayout.Metrics boring, 8976 BoringLayout.Metrics hintBoring, 8977 int ellipsisWidth, boolean bringIntoView) { 8978 stopMarquee(); 8979 8980 // Update "old" cached values 8981 mOldMaximum = mMaximum; 8982 mOldMaxMode = mMaxMode; 8983 8984 mHighlightPathBogus = true; 8985 8986 if (wantWidth < 0) { 8987 wantWidth = 0; 8988 } 8989 if (hintWidth < 0) { 8990 hintWidth = 0; 8991 } 8992 8993 Layout.Alignment alignment = getLayoutAlignment(); 8994 final boolean testDirChange = mSingleLine && mLayout != null 8995 && (alignment == Layout.Alignment.ALIGN_NORMAL 8996 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 8997 int oldDir = 0; 8998 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 8999 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 9000 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 9001 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 9002 TruncateAt effectiveEllipsize = mEllipsize; 9003 if (mEllipsize == TruncateAt.MARQUEE 9004 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 9005 effectiveEllipsize = TruncateAt.END_SMALL; 9006 } 9007 9008 if (mTextDir == null) { 9009 mTextDir = getTextDirectionHeuristic(); 9010 } 9011 9012 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 9013 effectiveEllipsize, effectiveEllipsize == mEllipsize); 9014 if (switchEllipsize) { 9015 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 9016 ? TruncateAt.END : TruncateAt.MARQUEE; 9017 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 9018 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 9019 } 9020 9021 shouldEllipsize = mEllipsize != null; 9022 mHintLayout = null; 9023 9024 if (mHint != null) { 9025 if (shouldEllipsize) hintWidth = wantWidth; 9026 9027 if (hintBoring == UNKNOWN_BORING) { 9028 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9029 mHintBoring); 9030 if (hintBoring != null) { 9031 mHintBoring = hintBoring; 9032 } 9033 } 9034 9035 if (hintBoring != null) { 9036 if (hintBoring.width <= hintWidth 9037 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9038 if (mSavedHintLayout != null) { 9039 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9040 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9041 hintBoring, mIncludePad); 9042 } else { 9043 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9044 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9045 hintBoring, mIncludePad); 9046 } 9047 9048 mSavedHintLayout = (BoringLayout) mHintLayout; 9049 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9050 if (mSavedHintLayout != null) { 9051 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9052 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9053 hintBoring, mIncludePad, mEllipsize, 9054 ellipsisWidth); 9055 } else { 9056 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9057 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9058 hintBoring, mIncludePad, mEllipsize, 9059 ellipsisWidth); 9060 } 9061 } 9062 } 9063 // TODO: code duplication with makeSingleLayout() 9064 if (mHintLayout == null) { 9065 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9066 mHint.length(), mTextPaint, hintWidth) 9067 .setAlignment(alignment) 9068 .setTextDirection(mTextDir) 9069 .setLineSpacing(mSpacingAdd, mSpacingMult) 9070 .setIncludePad(mIncludePad) 9071 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9072 .setBreakStrategy(mBreakStrategy) 9073 .setHyphenationFrequency(mHyphenationFrequency) 9074 .setJustificationMode(mJustificationMode) 9075 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9076 if (shouldEllipsize) { 9077 builder.setEllipsize(mEllipsize) 9078 .setEllipsizedWidth(ellipsisWidth); 9079 } 9080 mHintLayout = builder.build(); 9081 } 9082 } 9083 9084 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9085 registerForPreDraw(); 9086 } 9087 9088 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9089 if (!compressText(ellipsisWidth)) { 9090 final int height = mLayoutParams.height; 9091 // If the size of the view does not depend on the size of the text, try to 9092 // start the marquee immediately 9093 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9094 startMarquee(); 9095 } else { 9096 // Defer the start of the marquee until we know our width (see setFrame()) 9097 mRestartMarquee = true; 9098 } 9099 } 9100 } 9101 9102 // CursorControllers need a non-null mLayout 9103 if (mEditor != null) mEditor.prepareCursorControllers(); 9104 } 9105 9106 /** 9107 * Returns true if DynamicLayout is required 9108 * 9109 * @hide 9110 */ 9111 @VisibleForTesting useDynamicLayout()9112 public boolean useDynamicLayout() { 9113 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9114 } 9115 9116 /** 9117 * @hide 9118 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9119 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9120 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9121 boolean useSaved) { 9122 Layout result = null; 9123 if (useDynamicLayout()) { 9124 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9125 wantWidth) 9126 .setDisplayText(mTransformed) 9127 .setAlignment(alignment) 9128 .setTextDirection(mTextDir) 9129 .setLineSpacing(mSpacingAdd, mSpacingMult) 9130 .setIncludePad(mIncludePad) 9131 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9132 .setBreakStrategy(mBreakStrategy) 9133 .setHyphenationFrequency(mHyphenationFrequency) 9134 .setJustificationMode(mJustificationMode) 9135 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9136 .setEllipsizedWidth(ellipsisWidth); 9137 result = builder.build(); 9138 } else { 9139 if (boring == UNKNOWN_BORING) { 9140 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9141 if (boring != null) { 9142 mBoring = boring; 9143 } 9144 } 9145 9146 if (boring != null) { 9147 if (boring.width <= wantWidth 9148 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9149 if (useSaved && mSavedLayout != null) { 9150 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9151 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9152 boring, mIncludePad); 9153 } else { 9154 result = BoringLayout.make(mTransformed, mTextPaint, 9155 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9156 boring, mIncludePad); 9157 } 9158 9159 if (useSaved) { 9160 mSavedLayout = (BoringLayout) result; 9161 } 9162 } else if (shouldEllipsize && boring.width <= wantWidth) { 9163 if (useSaved && mSavedLayout != null) { 9164 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9165 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9166 boring, mIncludePad, effectiveEllipsize, 9167 ellipsisWidth); 9168 } else { 9169 result = BoringLayout.make(mTransformed, mTextPaint, 9170 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9171 boring, mIncludePad, effectiveEllipsize, 9172 ellipsisWidth); 9173 } 9174 } 9175 } 9176 } 9177 if (result == null) { 9178 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9179 0, mTransformed.length(), mTextPaint, wantWidth) 9180 .setAlignment(alignment) 9181 .setTextDirection(mTextDir) 9182 .setLineSpacing(mSpacingAdd, mSpacingMult) 9183 .setIncludePad(mIncludePad) 9184 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9185 .setBreakStrategy(mBreakStrategy) 9186 .setHyphenationFrequency(mHyphenationFrequency) 9187 .setJustificationMode(mJustificationMode) 9188 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9189 if (shouldEllipsize) { 9190 builder.setEllipsize(effectiveEllipsize) 9191 .setEllipsizedWidth(ellipsisWidth); 9192 } 9193 result = builder.build(); 9194 } 9195 return result; 9196 } 9197 9198 @UnsupportedAppUsage compressText(float width)9199 private boolean compressText(float width) { 9200 if (isHardwareAccelerated()) return false; 9201 9202 // Only compress the text if it hasn't been compressed by the previous pass 9203 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9204 && mTextPaint.getTextScaleX() == 1.0f) { 9205 final float textWidth = mLayout.getLineWidth(0); 9206 final float overflow = (textWidth + 1.0f - width) / width; 9207 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9208 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9209 post(new Runnable() { 9210 public void run() { 9211 requestLayout(); 9212 } 9213 }); 9214 return true; 9215 } 9216 } 9217 9218 return false; 9219 } 9220 desired(Layout layout)9221 private static int desired(Layout layout) { 9222 int n = layout.getLineCount(); 9223 CharSequence text = layout.getText(); 9224 float max = 0; 9225 9226 // if any line was wrapped, we can't use it. 9227 // but it's ok for the last line not to have a newline 9228 9229 for (int i = 0; i < n - 1; i++) { 9230 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9231 return -1; 9232 } 9233 } 9234 9235 for (int i = 0; i < n; i++) { 9236 max = Math.max(max, layout.getLineWidth(i)); 9237 } 9238 9239 return (int) Math.ceil(max); 9240 } 9241 9242 /** 9243 * Set whether the TextView includes extra top and bottom padding to make 9244 * room for accents that go above the normal ascent and descent. 9245 * The default is true. 9246 * 9247 * @see #getIncludeFontPadding() 9248 * 9249 * @attr ref android.R.styleable#TextView_includeFontPadding 9250 */ setIncludeFontPadding(boolean includepad)9251 public void setIncludeFontPadding(boolean includepad) { 9252 if (mIncludePad != includepad) { 9253 mIncludePad = includepad; 9254 9255 if (mLayout != null) { 9256 nullLayouts(); 9257 requestLayout(); 9258 invalidate(); 9259 } 9260 } 9261 } 9262 9263 /** 9264 * Gets whether the TextView includes extra top and bottom padding to make 9265 * room for accents that go above the normal ascent and descent. 9266 * 9267 * @see #setIncludeFontPadding(boolean) 9268 * 9269 * @attr ref android.R.styleable#TextView_includeFontPadding 9270 */ 9271 @InspectableProperty getIncludeFontPadding()9272 public boolean getIncludeFontPadding() { 9273 return mIncludePad; 9274 } 9275 9276 /** @hide */ 9277 @VisibleForTesting 9278 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9279 9280 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9281 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9282 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9283 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9284 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9285 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9286 9287 int width; 9288 int height; 9289 9290 BoringLayout.Metrics boring = UNKNOWN_BORING; 9291 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9292 9293 if (mTextDir == null) { 9294 mTextDir = getTextDirectionHeuristic(); 9295 } 9296 9297 int des = -1; 9298 boolean fromexisting = false; 9299 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9300 ? (float) widthSize : Float.MAX_VALUE; 9301 9302 if (widthMode == MeasureSpec.EXACTLY) { 9303 // Parent has told us how big to be. So be it. 9304 width = widthSize; 9305 } else { 9306 if (mLayout != null && mEllipsize == null) { 9307 des = desired(mLayout); 9308 } 9309 9310 if (des < 0) { 9311 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9312 if (boring != null) { 9313 mBoring = boring; 9314 } 9315 } else { 9316 fromexisting = true; 9317 } 9318 9319 if (boring == null || boring == UNKNOWN_BORING) { 9320 if (des < 0) { 9321 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9322 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9323 } 9324 width = des; 9325 } else { 9326 width = boring.width; 9327 } 9328 9329 final Drawables dr = mDrawables; 9330 if (dr != null) { 9331 width = Math.max(width, dr.mDrawableWidthTop); 9332 width = Math.max(width, dr.mDrawableWidthBottom); 9333 } 9334 9335 if (mHint != null) { 9336 int hintDes = -1; 9337 int hintWidth; 9338 9339 if (mHintLayout != null && mEllipsize == null) { 9340 hintDes = desired(mHintLayout); 9341 } 9342 9343 if (hintDes < 0) { 9344 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9345 if (hintBoring != null) { 9346 mHintBoring = hintBoring; 9347 } 9348 } 9349 9350 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9351 if (hintDes < 0) { 9352 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9353 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9354 } 9355 hintWidth = hintDes; 9356 } else { 9357 hintWidth = hintBoring.width; 9358 } 9359 9360 if (hintWidth > width) { 9361 width = hintWidth; 9362 } 9363 } 9364 9365 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9366 9367 if (mMaxWidthMode == EMS) { 9368 width = Math.min(width, mMaxWidth * getLineHeight()); 9369 } else { 9370 width = Math.min(width, mMaxWidth); 9371 } 9372 9373 if (mMinWidthMode == EMS) { 9374 width = Math.max(width, mMinWidth * getLineHeight()); 9375 } else { 9376 width = Math.max(width, mMinWidth); 9377 } 9378 9379 // Check against our minimum width 9380 width = Math.max(width, getSuggestedMinimumWidth()); 9381 9382 if (widthMode == MeasureSpec.AT_MOST) { 9383 width = Math.min(widthSize, width); 9384 } 9385 } 9386 9387 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9388 int unpaddedWidth = want; 9389 9390 if (mHorizontallyScrolling) want = VERY_WIDE; 9391 9392 int hintWant = want; 9393 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9394 9395 if (mLayout == null) { 9396 makeNewLayout(want, hintWant, boring, hintBoring, 9397 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9398 } else { 9399 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9400 || (mLayout.getEllipsizedWidth() 9401 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9402 9403 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9404 && (want > mLayout.getWidth()) 9405 && (mLayout instanceof BoringLayout 9406 || (fromexisting && des >= 0 && des <= want)); 9407 9408 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9409 9410 if (layoutChanged || maximumChanged) { 9411 if (!maximumChanged && widthChanged) { 9412 mLayout.increaseWidthTo(want); 9413 } else { 9414 makeNewLayout(want, hintWant, boring, hintBoring, 9415 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9416 } 9417 } else { 9418 // Nothing has changed 9419 } 9420 } 9421 9422 if (heightMode == MeasureSpec.EXACTLY) { 9423 // Parent has told us how big to be. So be it. 9424 height = heightSize; 9425 mDesiredHeightAtMeasure = -1; 9426 } else { 9427 int desired = getDesiredHeight(); 9428 9429 height = desired; 9430 mDesiredHeightAtMeasure = desired; 9431 9432 if (heightMode == MeasureSpec.AT_MOST) { 9433 height = Math.min(desired, heightSize); 9434 } 9435 } 9436 9437 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9438 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9439 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9440 } 9441 9442 /* 9443 * We didn't let makeNewLayout() register to bring the cursor into view, 9444 * so do it here if there is any possibility that it is needed. 9445 */ 9446 if (mMovement != null 9447 || mLayout.getWidth() > unpaddedWidth 9448 || mLayout.getHeight() > unpaddedHeight) { 9449 registerForPreDraw(); 9450 } else { 9451 scrollTo(0, 0); 9452 } 9453 9454 setMeasuredDimension(width, height); 9455 } 9456 9457 /** 9458 * Automatically computes and sets the text size. 9459 */ autoSizeText()9460 private void autoSizeText() { 9461 if (!isAutoSizeEnabled()) { 9462 return; 9463 } 9464 9465 if (mNeedsAutoSizeText) { 9466 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9467 return; 9468 } 9469 9470 final int availableWidth = mHorizontallyScrolling 9471 ? VERY_WIDE 9472 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9473 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9474 - getExtendedPaddingTop(); 9475 9476 if (availableWidth <= 0 || availableHeight <= 0) { 9477 return; 9478 } 9479 9480 synchronized (TEMP_RECTF) { 9481 TEMP_RECTF.setEmpty(); 9482 TEMP_RECTF.right = availableWidth; 9483 TEMP_RECTF.bottom = availableHeight; 9484 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9485 9486 if (optimalTextSize != getTextSize()) { 9487 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9488 false /* shouldRequestLayout */); 9489 9490 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9491 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9492 false /* bringIntoView */); 9493 } 9494 } 9495 } 9496 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9497 // after the next layout pass should set this to false. 9498 mNeedsAutoSizeText = true; 9499 } 9500 9501 /** 9502 * Performs a binary search to find the largest text size that will still fit within the size 9503 * available to this view. 9504 */ findLargestTextSizeWhichFits(RectF availableSpace)9505 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9506 final int sizesCount = mAutoSizeTextSizesInPx.length; 9507 if (sizesCount == 0) { 9508 throw new IllegalStateException("No available text sizes to choose from."); 9509 } 9510 9511 int bestSizeIndex = 0; 9512 int lowIndex = bestSizeIndex + 1; 9513 int highIndex = sizesCount - 1; 9514 int sizeToTryIndex; 9515 while (lowIndex <= highIndex) { 9516 sizeToTryIndex = (lowIndex + highIndex) / 2; 9517 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9518 bestSizeIndex = lowIndex; 9519 lowIndex = sizeToTryIndex + 1; 9520 } else { 9521 highIndex = sizeToTryIndex - 1; 9522 bestSizeIndex = highIndex; 9523 } 9524 } 9525 9526 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9527 } 9528 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9529 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9530 final CharSequence text = mTransformed != null 9531 ? mTransformed 9532 : getText(); 9533 final int maxLines = getMaxLines(); 9534 if (mTempTextPaint == null) { 9535 mTempTextPaint = new TextPaint(); 9536 } else { 9537 mTempTextPaint.reset(); 9538 } 9539 mTempTextPaint.set(getPaint()); 9540 mTempTextPaint.setTextSize(suggestedSizeInPx); 9541 9542 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9543 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9544 9545 layoutBuilder.setAlignment(getLayoutAlignment()) 9546 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9547 .setIncludePad(getIncludeFontPadding()) 9548 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9549 .setBreakStrategy(getBreakStrategy()) 9550 .setHyphenationFrequency(getHyphenationFrequency()) 9551 .setJustificationMode(getJustificationMode()) 9552 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9553 .setTextDirection(getTextDirectionHeuristic()); 9554 9555 final StaticLayout layout = layoutBuilder.build(); 9556 9557 // Lines overflow. 9558 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9559 return false; 9560 } 9561 9562 // Height overflow. 9563 if (layout.getHeight() > availableSpace.bottom) { 9564 return false; 9565 } 9566 9567 return true; 9568 } 9569 getDesiredHeight()9570 private int getDesiredHeight() { 9571 return Math.max( 9572 getDesiredHeight(mLayout, true), 9573 getDesiredHeight(mHintLayout, mEllipsize != null)); 9574 } 9575 getDesiredHeight(Layout layout, boolean cap)9576 private int getDesiredHeight(Layout layout, boolean cap) { 9577 if (layout == null) { 9578 return 0; 9579 } 9580 9581 /* 9582 * Don't cap the hint to a certain number of lines. 9583 * (Do cap it, though, if we have a maximum pixel height.) 9584 */ 9585 int desired = layout.getHeight(cap); 9586 9587 final Drawables dr = mDrawables; 9588 if (dr != null) { 9589 desired = Math.max(desired, dr.mDrawableHeightLeft); 9590 desired = Math.max(desired, dr.mDrawableHeightRight); 9591 } 9592 9593 int linecount = layout.getLineCount(); 9594 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9595 desired += padding; 9596 9597 if (mMaxMode != LINES) { 9598 desired = Math.min(desired, mMaximum); 9599 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9600 || layout instanceof BoringLayout)) { 9601 desired = layout.getLineTop(mMaximum); 9602 9603 if (dr != null) { 9604 desired = Math.max(desired, dr.mDrawableHeightLeft); 9605 desired = Math.max(desired, dr.mDrawableHeightRight); 9606 } 9607 9608 desired += padding; 9609 linecount = mMaximum; 9610 } 9611 9612 if (mMinMode == LINES) { 9613 if (linecount < mMinimum) { 9614 desired += getLineHeight() * (mMinimum - linecount); 9615 } 9616 } else { 9617 desired = Math.max(desired, mMinimum); 9618 } 9619 9620 // Check against our minimum height 9621 desired = Math.max(desired, getSuggestedMinimumHeight()); 9622 9623 return desired; 9624 } 9625 9626 /** 9627 * Check whether a change to the existing text layout requires a 9628 * new view layout. 9629 */ checkForResize()9630 private void checkForResize() { 9631 boolean sizeChanged = false; 9632 9633 if (mLayout != null) { 9634 // Check if our width changed 9635 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9636 sizeChanged = true; 9637 invalidate(); 9638 } 9639 9640 // Check if our height changed 9641 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9642 int desiredHeight = getDesiredHeight(); 9643 9644 if (desiredHeight != this.getHeight()) { 9645 sizeChanged = true; 9646 } 9647 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9648 if (mDesiredHeightAtMeasure >= 0) { 9649 int desiredHeight = getDesiredHeight(); 9650 9651 if (desiredHeight != mDesiredHeightAtMeasure) { 9652 sizeChanged = true; 9653 } 9654 } 9655 } 9656 } 9657 9658 if (sizeChanged) { 9659 requestLayout(); 9660 // caller will have already invalidated 9661 } 9662 } 9663 9664 /** 9665 * Check whether entirely new text requires a new view layout 9666 * or merely a new text layout. 9667 */ 9668 @UnsupportedAppUsage checkForRelayout()9669 private void checkForRelayout() { 9670 // If we have a fixed width, we can just swap in a new text layout 9671 // if the text height stays the same or if the view height is fixed. 9672 9673 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9674 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9675 && (mHint == null || mHintLayout != null) 9676 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9677 // Static width, so try making a new text layout. 9678 9679 int oldht = mLayout.getHeight(); 9680 int want = mLayout.getWidth(); 9681 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9682 9683 /* 9684 * No need to bring the text into view, since the size is not 9685 * changing (unless we do the requestLayout(), in which case it 9686 * will happen at measure). 9687 */ 9688 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9689 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9690 false); 9691 9692 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9693 // In a fixed-height view, so use our new text layout. 9694 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9695 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9696 autoSizeText(); 9697 invalidate(); 9698 return; 9699 } 9700 9701 // Dynamic height, but height has stayed the same, 9702 // so use our new text layout. 9703 if (mLayout.getHeight() == oldht 9704 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9705 autoSizeText(); 9706 invalidate(); 9707 return; 9708 } 9709 } 9710 9711 // We lose: the height has changed and we have a dynamic height. 9712 // Request a new view layout using our new text layout. 9713 requestLayout(); 9714 invalidate(); 9715 } else { 9716 // Dynamic width, so we have no choice but to request a new 9717 // view layout with a new text layout. 9718 nullLayouts(); 9719 requestLayout(); 9720 invalidate(); 9721 } 9722 } 9723 9724 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9725 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9726 super.onLayout(changed, left, top, right, bottom); 9727 if (mDeferScroll >= 0) { 9728 int curs = mDeferScroll; 9729 mDeferScroll = -1; 9730 bringPointIntoView(Math.min(curs, mText.length())); 9731 } 9732 // Call auto-size after the width and height have been calculated. 9733 autoSizeText(); 9734 } 9735 isShowingHint()9736 private boolean isShowingHint() { 9737 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9738 } 9739 9740 /** 9741 * Returns true if anything changed. 9742 */ 9743 @UnsupportedAppUsage bringTextIntoView()9744 private boolean bringTextIntoView() { 9745 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9746 int line = 0; 9747 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9748 line = layout.getLineCount() - 1; 9749 } 9750 9751 Layout.Alignment a = layout.getParagraphAlignment(line); 9752 int dir = layout.getParagraphDirection(line); 9753 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9754 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9755 int ht = layout.getHeight(); 9756 9757 int scrollx, scrolly; 9758 9759 // Convert to left, center, or right alignment. 9760 if (a == Layout.Alignment.ALIGN_NORMAL) { 9761 a = dir == Layout.DIR_LEFT_TO_RIGHT 9762 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9763 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9764 a = dir == Layout.DIR_LEFT_TO_RIGHT 9765 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9766 } 9767 9768 if (a == Layout.Alignment.ALIGN_CENTER) { 9769 /* 9770 * Keep centered if possible, or, if it is too wide to fit, 9771 * keep leading edge in view. 9772 */ 9773 9774 int left = (int) Math.floor(layout.getLineLeft(line)); 9775 int right = (int) Math.ceil(layout.getLineRight(line)); 9776 9777 if (right - left < hspace) { 9778 scrollx = (right + left) / 2 - hspace / 2; 9779 } else { 9780 if (dir < 0) { 9781 scrollx = right - hspace; 9782 } else { 9783 scrollx = left; 9784 } 9785 } 9786 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9787 int right = (int) Math.ceil(layout.getLineRight(line)); 9788 scrollx = right - hspace; 9789 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9790 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9791 } 9792 9793 if (ht < vspace) { 9794 scrolly = 0; 9795 } else { 9796 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9797 scrolly = ht - vspace; 9798 } else { 9799 scrolly = 0; 9800 } 9801 } 9802 9803 if (scrollx != mScrollX || scrolly != mScrollY) { 9804 scrollTo(scrollx, scrolly); 9805 return true; 9806 } else { 9807 return false; 9808 } 9809 } 9810 9811 /** 9812 * Move the point, specified by the offset, into the view if it is needed. 9813 * This has to be called after layout. Returns true if anything changed. 9814 */ bringPointIntoView(int offset)9815 public boolean bringPointIntoView(int offset) { 9816 if (isLayoutRequested()) { 9817 mDeferScroll = offset; 9818 return false; 9819 } 9820 boolean changed = false; 9821 9822 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9823 9824 if (layout == null) return changed; 9825 9826 int line = layout.getLineForOffset(offset); 9827 9828 int grav; 9829 9830 switch (layout.getParagraphAlignment(line)) { 9831 case ALIGN_LEFT: 9832 grav = 1; 9833 break; 9834 case ALIGN_RIGHT: 9835 grav = -1; 9836 break; 9837 case ALIGN_NORMAL: 9838 grav = layout.getParagraphDirection(line); 9839 break; 9840 case ALIGN_OPPOSITE: 9841 grav = -layout.getParagraphDirection(line); 9842 break; 9843 case ALIGN_CENTER: 9844 default: 9845 grav = 0; 9846 break; 9847 } 9848 9849 // We only want to clamp the cursor to fit within the layout width 9850 // in left-to-right modes, because in a right to left alignment, 9851 // we want to scroll to keep the line-right on the screen, as other 9852 // lines are likely to have text flush with the right margin, which 9853 // we want to keep visible. 9854 // A better long-term solution would probably be to measure both 9855 // the full line and a blank-trimmed version, and, for example, use 9856 // the latter measurement for centering and right alignment, but for 9857 // the time being we only implement the cursor clamping in left to 9858 // right where it is most likely to be annoying. 9859 final boolean clamped = grav > 0; 9860 // FIXME: Is it okay to truncate this, or should we round? 9861 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9862 final int top = layout.getLineTop(line); 9863 final int bottom = layout.getLineTop(line + 1); 9864 9865 int left = (int) Math.floor(layout.getLineLeft(line)); 9866 int right = (int) Math.ceil(layout.getLineRight(line)); 9867 int ht = layout.getHeight(); 9868 9869 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9870 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9871 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9872 // If cursor has been clamped, make sure we don't scroll. 9873 right = Math.max(x, left + hspace); 9874 } 9875 9876 int hslack = (bottom - top) / 2; 9877 int vslack = hslack; 9878 9879 if (vslack > vspace / 4) { 9880 vslack = vspace / 4; 9881 } 9882 if (hslack > hspace / 4) { 9883 hslack = hspace / 4; 9884 } 9885 9886 int hs = mScrollX; 9887 int vs = mScrollY; 9888 9889 if (top - vs < vslack) { 9890 vs = top - vslack; 9891 } 9892 if (bottom - vs > vspace - vslack) { 9893 vs = bottom - (vspace - vslack); 9894 } 9895 if (ht - vs < vspace) { 9896 vs = ht - vspace; 9897 } 9898 if (0 - vs > 0) { 9899 vs = 0; 9900 } 9901 9902 if (grav != 0) { 9903 if (x - hs < hslack) { 9904 hs = x - hslack; 9905 } 9906 if (x - hs > hspace - hslack) { 9907 hs = x - (hspace - hslack); 9908 } 9909 } 9910 9911 if (grav < 0) { 9912 if (left - hs > 0) { 9913 hs = left; 9914 } 9915 if (right - hs < hspace) { 9916 hs = right - hspace; 9917 } 9918 } else if (grav > 0) { 9919 if (right - hs < hspace) { 9920 hs = right - hspace; 9921 } 9922 if (left - hs > 0) { 9923 hs = left; 9924 } 9925 } else /* grav == 0 */ { 9926 if (right - left <= hspace) { 9927 /* 9928 * If the entire text fits, center it exactly. 9929 */ 9930 hs = left - (hspace - (right - left)) / 2; 9931 } else if (x > right - hslack) { 9932 /* 9933 * If we are near the right edge, keep the right edge 9934 * at the edge of the view. 9935 */ 9936 hs = right - hspace; 9937 } else if (x < left + hslack) { 9938 /* 9939 * If we are near the left edge, keep the left edge 9940 * at the edge of the view. 9941 */ 9942 hs = left; 9943 } else if (left > hs) { 9944 /* 9945 * Is there whitespace visible at the left? Fix it if so. 9946 */ 9947 hs = left; 9948 } else if (right < hs + hspace) { 9949 /* 9950 * Is there whitespace visible at the right? Fix it if so. 9951 */ 9952 hs = right - hspace; 9953 } else { 9954 /* 9955 * Otherwise, float as needed. 9956 */ 9957 if (x - hs < hslack) { 9958 hs = x - hslack; 9959 } 9960 if (x - hs > hspace - hslack) { 9961 hs = x - (hspace - hslack); 9962 } 9963 } 9964 } 9965 9966 if (hs != mScrollX || vs != mScrollY) { 9967 if (mScroller == null) { 9968 scrollTo(hs, vs); 9969 } else { 9970 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 9971 int dx = hs - mScrollX; 9972 int dy = vs - mScrollY; 9973 9974 if (duration > ANIMATED_SCROLL_GAP) { 9975 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 9976 awakenScrollBars(mScroller.getDuration()); 9977 invalidate(); 9978 } else { 9979 if (!mScroller.isFinished()) { 9980 mScroller.abortAnimation(); 9981 } 9982 9983 scrollBy(dx, dy); 9984 } 9985 9986 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 9987 } 9988 9989 changed = true; 9990 } 9991 9992 if (isFocused()) { 9993 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 9994 // requestRectangleOnScreen() is in terms of content coordinates. 9995 9996 // The offsets here are to ensure the rectangle we are using is 9997 // within our view bounds, in case the cursor is on the far left 9998 // or right. If it isn't withing the bounds, then this request 9999 // will be ignored. 10000 if (mTempRect == null) mTempRect = new Rect(); 10001 mTempRect.set(x - 2, top, x + 2, bottom); 10002 getInterestingRect(mTempRect, line); 10003 mTempRect.offset(mScrollX, mScrollY); 10004 10005 if (requestRectangleOnScreen(mTempRect)) { 10006 changed = true; 10007 } 10008 } 10009 10010 return changed; 10011 } 10012 10013 /** 10014 * Move the cursor, if needed, so that it is at an offset that is visible 10015 * to the user. This will not move the cursor if it represents more than 10016 * one character (a selection range). This will only work if the 10017 * TextView contains spannable text; otherwise it will do nothing. 10018 * 10019 * @return True if the cursor was actually moved, false otherwise. 10020 */ moveCursorToVisibleOffset()10021 public boolean moveCursorToVisibleOffset() { 10022 if (!(mText instanceof Spannable)) { 10023 return false; 10024 } 10025 int start = getSelectionStart(); 10026 int end = getSelectionEnd(); 10027 if (start != end) { 10028 return false; 10029 } 10030 10031 // First: make sure the line is visible on screen: 10032 10033 int line = mLayout.getLineForOffset(start); 10034 10035 final int top = mLayout.getLineTop(line); 10036 final int bottom = mLayout.getLineTop(line + 1); 10037 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10038 int vslack = (bottom - top) / 2; 10039 if (vslack > vspace / 4) { 10040 vslack = vspace / 4; 10041 } 10042 final int vs = mScrollY; 10043 10044 if (top < (vs + vslack)) { 10045 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10046 } else if (bottom > (vspace + vs - vslack)) { 10047 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10048 } 10049 10050 // Next: make sure the character is visible on screen: 10051 10052 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10053 final int hs = mScrollX; 10054 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10055 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10056 10057 // line might contain bidirectional text 10058 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10059 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10060 10061 int newStart = start; 10062 if (newStart < lowChar) { 10063 newStart = lowChar; 10064 } else if (newStart > highChar) { 10065 newStart = highChar; 10066 } 10067 10068 if (newStart != start) { 10069 Selection.setSelection(mSpannable, newStart); 10070 return true; 10071 } 10072 10073 return false; 10074 } 10075 10076 @Override computeScroll()10077 public void computeScroll() { 10078 if (mScroller != null) { 10079 if (mScroller.computeScrollOffset()) { 10080 mScrollX = mScroller.getCurrX(); 10081 mScrollY = mScroller.getCurrY(); 10082 invalidateParentCaches(); 10083 postInvalidate(); // So we draw again 10084 } 10085 } 10086 } 10087 getInterestingRect(Rect r, int line)10088 private void getInterestingRect(Rect r, int line) { 10089 convertFromViewportToContentCoordinates(r); 10090 10091 // Rectangle can can be expanded on first and last line to take 10092 // padding into account. 10093 // TODO Take left/right padding into account too? 10094 if (line == 0) r.top -= getExtendedPaddingTop(); 10095 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10096 } 10097 convertFromViewportToContentCoordinates(Rect r)10098 private void convertFromViewportToContentCoordinates(Rect r) { 10099 final int horizontalOffset = viewportToContentHorizontalOffset(); 10100 r.left += horizontalOffset; 10101 r.right += horizontalOffset; 10102 10103 final int verticalOffset = viewportToContentVerticalOffset(); 10104 r.top += verticalOffset; 10105 r.bottom += verticalOffset; 10106 } 10107 viewportToContentHorizontalOffset()10108 int viewportToContentHorizontalOffset() { 10109 return getCompoundPaddingLeft() - mScrollX; 10110 } 10111 10112 @UnsupportedAppUsage viewportToContentVerticalOffset()10113 int viewportToContentVerticalOffset() { 10114 int offset = getExtendedPaddingTop() - mScrollY; 10115 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10116 offset += getVerticalOffset(false); 10117 } 10118 return offset; 10119 } 10120 10121 @Override debug(int depth)10122 public void debug(int depth) { 10123 super.debug(depth); 10124 10125 String output = debugIndent(depth); 10126 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10127 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10128 + "} "; 10129 10130 if (mText != null) { 10131 10132 output += "mText=\"" + mText + "\" "; 10133 if (mLayout != null) { 10134 output += "mLayout width=" + mLayout.getWidth() 10135 + " height=" + mLayout.getHeight(); 10136 } 10137 } else { 10138 output += "mText=NULL"; 10139 } 10140 Log.d(VIEW_LOG_TAG, output); 10141 } 10142 10143 /** 10144 * Convenience for {@link Selection#getSelectionStart}. 10145 */ 10146 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10147 public int getSelectionStart() { 10148 return Selection.getSelectionStart(getText()); 10149 } 10150 10151 /** 10152 * Convenience for {@link Selection#getSelectionEnd}. 10153 */ 10154 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10155 public int getSelectionEnd() { 10156 return Selection.getSelectionEnd(getText()); 10157 } 10158 10159 /** 10160 * Return true iff there is a selection of nonzero length inside this text view. 10161 */ hasSelection()10162 public boolean hasSelection() { 10163 final int selectionStart = getSelectionStart(); 10164 final int selectionEnd = getSelectionEnd(); 10165 10166 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10167 } 10168 getSelectedText()10169 String getSelectedText() { 10170 if (!hasSelection()) { 10171 return null; 10172 } 10173 10174 final int start = getSelectionStart(); 10175 final int end = getSelectionEnd(); 10176 return String.valueOf( 10177 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10178 } 10179 10180 /** 10181 * Sets the properties of this field (lines, horizontally scrolling, 10182 * transformation method) to be for a single-line input. 10183 * 10184 * @attr ref android.R.styleable#TextView_singleLine 10185 */ setSingleLine()10186 public void setSingleLine() { 10187 setSingleLine(true); 10188 } 10189 10190 /** 10191 * Sets the properties of this field to transform input to ALL CAPS 10192 * display. This may use a "small caps" formatting if available. 10193 * This setting will be ignored if this field is editable or selectable. 10194 * 10195 * This call replaces the current transformation method. Disabling this 10196 * will not necessarily restore the previous behavior from before this 10197 * was enabled. 10198 * 10199 * @see #setTransformationMethod(TransformationMethod) 10200 * @attr ref android.R.styleable#TextView_textAllCaps 10201 */ setAllCaps(boolean allCaps)10202 public void setAllCaps(boolean allCaps) { 10203 if (allCaps) { 10204 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10205 } else { 10206 setTransformationMethod(null); 10207 } 10208 } 10209 10210 /** 10211 * 10212 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10213 * @return Whether the current transformation method is for ALL CAPS. 10214 * 10215 * @see #setAllCaps(boolean) 10216 * @see #setTransformationMethod(TransformationMethod) 10217 */ 10218 @InspectableProperty(name = "textAllCaps") isAllCaps()10219 public boolean isAllCaps() { 10220 final TransformationMethod method = getTransformationMethod(); 10221 return method != null && method instanceof AllCapsTransformationMethod; 10222 } 10223 10224 /** 10225 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10226 * transformation method) to be for a single-line input; if false, restores these to the default 10227 * conditions. 10228 * 10229 * Note that the default conditions are not necessarily those that were in effect prior this 10230 * method, and you may want to reset these properties to your custom values. 10231 * 10232 * @attr ref android.R.styleable#TextView_singleLine 10233 */ 10234 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10235 public void setSingleLine(boolean singleLine) { 10236 // Could be used, but may break backward compatibility. 10237 // if (mSingleLine == singleLine) return; 10238 setInputTypeSingleLine(singleLine); 10239 applySingleLine(singleLine, true, true); 10240 } 10241 10242 /** 10243 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10244 * @param singleLine 10245 */ setInputTypeSingleLine(boolean singleLine)10246 private void setInputTypeSingleLine(boolean singleLine) { 10247 if (mEditor != null 10248 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10249 == EditorInfo.TYPE_CLASS_TEXT) { 10250 if (singleLine) { 10251 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10252 } else { 10253 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10254 } 10255 } 10256 } 10257 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)10258 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10259 boolean changeMaxLines) { 10260 mSingleLine = singleLine; 10261 if (singleLine) { 10262 setLines(1); 10263 setHorizontallyScrolling(true); 10264 if (applyTransformation) { 10265 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10266 } 10267 } else { 10268 if (changeMaxLines) { 10269 setMaxLines(Integer.MAX_VALUE); 10270 } 10271 setHorizontallyScrolling(false); 10272 if (applyTransformation) { 10273 setTransformationMethod(null); 10274 } 10275 } 10276 } 10277 10278 /** 10279 * Causes words in the text that are longer than the view's width 10280 * to be ellipsized instead of broken in the middle. You may also 10281 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10282 * to constrain the text to a single line. Use <code>null</code> 10283 * to turn off ellipsizing. 10284 * 10285 * If {@link #setMaxLines} has been used to set two or more lines, 10286 * only {@link android.text.TextUtils.TruncateAt#END} and 10287 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10288 * (other ellipsizing types will not do anything). 10289 * 10290 * @attr ref android.R.styleable#TextView_ellipsize 10291 */ setEllipsize(TextUtils.TruncateAt where)10292 public void setEllipsize(TextUtils.TruncateAt where) { 10293 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10294 if (mEllipsize != where) { 10295 mEllipsize = where; 10296 10297 if (mLayout != null) { 10298 nullLayouts(); 10299 requestLayout(); 10300 invalidate(); 10301 } 10302 } 10303 } 10304 10305 /** 10306 * Sets how many times to repeat the marquee animation. Only applied if the 10307 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10308 * 10309 * @see #getMarqueeRepeatLimit() 10310 * 10311 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10312 */ setMarqueeRepeatLimit(int marqueeLimit)10313 public void setMarqueeRepeatLimit(int marqueeLimit) { 10314 mMarqueeRepeatLimit = marqueeLimit; 10315 } 10316 10317 /** 10318 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10319 * TextView has marquee enabled. 10320 * 10321 * @return the number of times the marquee animation is repeated. -1 if the animation 10322 * repeats indefinitely 10323 * 10324 * @see #setMarqueeRepeatLimit(int) 10325 * 10326 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10327 */ 10328 @InspectableProperty getMarqueeRepeatLimit()10329 public int getMarqueeRepeatLimit() { 10330 return mMarqueeRepeatLimit; 10331 } 10332 10333 /** 10334 * Returns where, if anywhere, words that are longer than the view 10335 * is wide should be ellipsized. 10336 */ 10337 @InspectableProperty 10338 @ViewDebug.ExportedProperty getEllipsize()10339 public TextUtils.TruncateAt getEllipsize() { 10340 return mEllipsize; 10341 } 10342 10343 /** 10344 * Set the TextView so that when it takes focus, all the text is 10345 * selected. 10346 * 10347 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10348 */ 10349 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10350 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10351 createEditorIfNeeded(); 10352 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10353 10354 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10355 setText(mText, BufferType.SPANNABLE); 10356 } 10357 } 10358 10359 /** 10360 * Set whether the cursor is visible. The default is true. Note that this property only 10361 * makes sense for editable TextView. 10362 * 10363 * @see #isCursorVisible() 10364 * 10365 * @attr ref android.R.styleable#TextView_cursorVisible 10366 */ 10367 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10368 public void setCursorVisible(boolean visible) { 10369 if (visible && mEditor == null) return; // visible is the default value with no edit data 10370 createEditorIfNeeded(); 10371 if (mEditor.mCursorVisible != visible) { 10372 mEditor.mCursorVisible = visible; 10373 invalidate(); 10374 10375 mEditor.makeBlink(); 10376 10377 // InsertionPointCursorController depends on mCursorVisible 10378 mEditor.prepareCursorControllers(); 10379 } 10380 } 10381 10382 /** 10383 * @return whether or not the cursor is visible (assuming this TextView is editable) 10384 * 10385 * @see #setCursorVisible(boolean) 10386 * 10387 * @attr ref android.R.styleable#TextView_cursorVisible 10388 */ 10389 @InspectableProperty isCursorVisible()10390 public boolean isCursorVisible() { 10391 // true is the default value 10392 return mEditor == null ? true : mEditor.mCursorVisible; 10393 } 10394 canMarquee()10395 private boolean canMarquee() { 10396 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10397 return width > 0 && (mLayout.getLineWidth(0) > width 10398 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10399 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10400 } 10401 10402 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10403 private void startMarquee() { 10404 // Do not ellipsize EditText 10405 if (getKeyListener() != null) return; 10406 10407 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10408 return; 10409 } 10410 10411 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10412 && getLineCount() == 1 && canMarquee()) { 10413 10414 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10415 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10416 final Layout tmp = mLayout; 10417 mLayout = mSavedMarqueeModeLayout; 10418 mSavedMarqueeModeLayout = tmp; 10419 setHorizontalFadingEdgeEnabled(true); 10420 requestLayout(); 10421 invalidate(); 10422 } 10423 10424 if (mMarquee == null) mMarquee = new Marquee(this); 10425 mMarquee.start(mMarqueeRepeatLimit); 10426 } 10427 } 10428 stopMarquee()10429 private void stopMarquee() { 10430 if (mMarquee != null && !mMarquee.isStopped()) { 10431 mMarquee.stop(); 10432 } 10433 10434 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10435 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10436 final Layout tmp = mSavedMarqueeModeLayout; 10437 mSavedMarqueeModeLayout = mLayout; 10438 mLayout = tmp; 10439 setHorizontalFadingEdgeEnabled(false); 10440 requestLayout(); 10441 invalidate(); 10442 } 10443 } 10444 10445 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10446 private void startStopMarquee(boolean start) { 10447 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10448 if (start) { 10449 startMarquee(); 10450 } else { 10451 stopMarquee(); 10452 } 10453 } 10454 } 10455 10456 /** 10457 * This method is called when the text is changed, in case any subclasses 10458 * would like to know. 10459 * 10460 * Within <code>text</code>, the <code>lengthAfter</code> characters 10461 * beginning at <code>start</code> have just replaced old text that had 10462 * length <code>lengthBefore</code>. It is an error to attempt to make 10463 * changes to <code>text</code> from this callback. 10464 * 10465 * @param text The text the TextView is displaying 10466 * @param start The offset of the start of the range of the text that was 10467 * modified 10468 * @param lengthBefore The length of the former text that has been replaced 10469 * @param lengthAfter The length of the replacement modified text 10470 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10471 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10472 // intentionally empty, template pattern method can be overridden by subclasses 10473 } 10474 10475 /** 10476 * This method is called when the selection has changed, in case any 10477 * subclasses would like to know. 10478 * </p> 10479 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 10480 * the accessibility subsystem about the selection change. 10481 * </p> 10482 * 10483 * @param selStart The new selection start location. 10484 * @param selEnd The new selection end location. 10485 */ 10486 @CallSuper onSelectionChanged(int selStart, int selEnd)10487 protected void onSelectionChanged(int selStart, int selEnd) { 10488 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10489 } 10490 10491 /** 10492 * Adds a TextWatcher to the list of those whose methods are called 10493 * whenever this TextView's text changes. 10494 * <p> 10495 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10496 * not called after {@link #setText} calls. Now, doing {@link #setText} 10497 * if there are any text changed listeners forces the buffer type to 10498 * Editable if it would not otherwise be and does call this method. 10499 */ addTextChangedListener(TextWatcher watcher)10500 public void addTextChangedListener(TextWatcher watcher) { 10501 if (mListeners == null) { 10502 mListeners = new ArrayList<TextWatcher>(); 10503 } 10504 10505 mListeners.add(watcher); 10506 } 10507 10508 /** 10509 * Removes the specified TextWatcher from the list of those whose 10510 * methods are called 10511 * whenever this TextView's text changes. 10512 */ removeTextChangedListener(TextWatcher watcher)10513 public void removeTextChangedListener(TextWatcher watcher) { 10514 if (mListeners != null) { 10515 int i = mListeners.indexOf(watcher); 10516 10517 if (i >= 0) { 10518 mListeners.remove(i); 10519 } 10520 } 10521 } 10522 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10523 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10524 if (mListeners != null) { 10525 final ArrayList<TextWatcher> list = mListeners; 10526 final int count = list.size(); 10527 for (int i = 0; i < count; i++) { 10528 list.get(i).beforeTextChanged(text, start, before, after); 10529 } 10530 } 10531 10532 // The spans that are inside or intersect the modified region no longer make sense 10533 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10534 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10535 } 10536 10537 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10538 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10539 if (!(mText instanceof Editable)) return; 10540 Editable text = (Editable) mText; 10541 10542 T[] spans = text.getSpans(start, end, type); 10543 final int length = spans.length; 10544 for (int i = 0; i < length; i++) { 10545 final int spanStart = text.getSpanStart(spans[i]); 10546 final int spanEnd = text.getSpanEnd(spans[i]); 10547 if (spanEnd == start || spanStart == end) break; 10548 text.removeSpan(spans[i]); 10549 } 10550 } 10551 removeAdjacentSuggestionSpans(final int pos)10552 void removeAdjacentSuggestionSpans(final int pos) { 10553 if (!(mText instanceof Editable)) return; 10554 final Editable text = (Editable) mText; 10555 10556 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10557 final int length = spans.length; 10558 for (int i = 0; i < length; i++) { 10559 final int spanStart = text.getSpanStart(spans[i]); 10560 final int spanEnd = text.getSpanEnd(spans[i]); 10561 if (spanEnd == pos || spanStart == pos) { 10562 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10563 text.removeSpan(spans[i]); 10564 } 10565 } 10566 } 10567 } 10568 10569 /** 10570 * Not private so it can be called from an inner class without going 10571 * through a thunk. 10572 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10573 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10574 if (mListeners != null) { 10575 final ArrayList<TextWatcher> list = mListeners; 10576 final int count = list.size(); 10577 for (int i = 0; i < count; i++) { 10578 list.get(i).onTextChanged(text, start, before, after); 10579 } 10580 } 10581 10582 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10583 } 10584 10585 /** 10586 * Not private so it can be called from an inner class without going 10587 * through a thunk. 10588 */ sendAfterTextChanged(Editable text)10589 void sendAfterTextChanged(Editable text) { 10590 if (mListeners != null) { 10591 final ArrayList<TextWatcher> list = mListeners; 10592 final int count = list.size(); 10593 for (int i = 0; i < count; i++) { 10594 list.get(i).afterTextChanged(text); 10595 } 10596 } 10597 10598 notifyListeningManagersAfterTextChanged(); 10599 10600 hideErrorIfUnchanged(); 10601 } 10602 10603 /** 10604 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 10605 * interested on text changes. 10606 */ notifyListeningManagersAfterTextChanged()10607 private void notifyListeningManagersAfterTextChanged() { 10608 10609 // Autofill 10610 if (isAutofillable()) { 10611 // It is important to not check whether the view is important for autofill 10612 // since the user can trigger autofill manually on not important views. 10613 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10614 if (afm != null) { 10615 if (android.view.autofill.Helper.sVerbose) { 10616 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10617 } 10618 afm.notifyValueChanged(TextView.this); 10619 } 10620 } 10621 10622 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 10623 // of using isLaidout(), so it's not called in cases where it's laid out but a 10624 // notifyAppeared was not sent. 10625 10626 // ContentCapture 10627 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 10628 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 10629 if (cm != null && cm.isContentCaptureEnabled()) { 10630 final ContentCaptureSession session = getContentCaptureSession(); 10631 if (session != null) { 10632 // TODO(b/111276913): pass flags when edited by user / add CTS test 10633 session.notifyViewTextChanged(getAutofillId(), getText()); 10634 } 10635 } 10636 } 10637 } 10638 isAutofillable()10639 private boolean isAutofillable() { 10640 // It is important to not check whether the view is important for autofill 10641 // since the user can trigger autofill manually on not important views. 10642 return getAutofillType() != AUTOFILL_TYPE_NONE; 10643 } 10644 updateAfterEdit()10645 void updateAfterEdit() { 10646 invalidate(); 10647 int curs = getSelectionStart(); 10648 10649 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10650 registerForPreDraw(); 10651 } 10652 10653 checkForResize(); 10654 10655 if (curs >= 0) { 10656 mHighlightPathBogus = true; 10657 if (mEditor != null) mEditor.makeBlink(); 10658 bringPointIntoView(curs); 10659 } 10660 } 10661 10662 /** 10663 * Not private so it can be called from an inner class without going 10664 * through a thunk. 10665 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10666 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10667 sLastCutCopyOrTextChangedTime = 0; 10668 10669 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10670 if (ims == null || ims.mBatchEditNesting == 0) { 10671 updateAfterEdit(); 10672 } 10673 if (ims != null) { 10674 ims.mContentChanged = true; 10675 if (ims.mChangedStart < 0) { 10676 ims.mChangedStart = start; 10677 ims.mChangedEnd = start + before; 10678 } else { 10679 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10680 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10681 } 10682 ims.mChangedDelta += after - before; 10683 } 10684 resetErrorChangedFlag(); 10685 sendOnTextChanged(buffer, start, before, after); 10686 onTextChanged(buffer, start, before, after); 10687 } 10688 10689 /** 10690 * Not private so it can be called from an inner class without going 10691 * through a thunk. 10692 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10693 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10694 // XXX Make the start and end move together if this ends up 10695 // spending too much time invalidating. 10696 10697 boolean selChanged = false; 10698 int newSelStart = -1, newSelEnd = -1; 10699 10700 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10701 10702 if (what == Selection.SELECTION_END) { 10703 selChanged = true; 10704 newSelEnd = newStart; 10705 10706 if (oldStart >= 0 || newStart >= 0) { 10707 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10708 checkForResize(); 10709 registerForPreDraw(); 10710 if (mEditor != null) mEditor.makeBlink(); 10711 } 10712 } 10713 10714 if (what == Selection.SELECTION_START) { 10715 selChanged = true; 10716 newSelStart = newStart; 10717 10718 if (oldStart >= 0 || newStart >= 0) { 10719 int end = Selection.getSelectionEnd(buf); 10720 invalidateCursor(end, oldStart, newStart); 10721 } 10722 } 10723 10724 if (selChanged) { 10725 mHighlightPathBogus = true; 10726 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10727 10728 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10729 if (newSelStart < 0) { 10730 newSelStart = Selection.getSelectionStart(buf); 10731 } 10732 if (newSelEnd < 0) { 10733 newSelEnd = Selection.getSelectionEnd(buf); 10734 } 10735 10736 if (mEditor != null) { 10737 mEditor.refreshTextActionMode(); 10738 if (!hasSelection() 10739 && mEditor.getTextActionMode() == null && hasTransientState()) { 10740 // User generated selection has been removed. 10741 setHasTransientState(false); 10742 } 10743 } 10744 onSelectionChanged(newSelStart, newSelEnd); 10745 } 10746 } 10747 10748 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10749 || what instanceof CharacterStyle) { 10750 if (ims == null || ims.mBatchEditNesting == 0) { 10751 invalidate(); 10752 mHighlightPathBogus = true; 10753 checkForResize(); 10754 } else { 10755 ims.mContentChanged = true; 10756 } 10757 if (mEditor != null) { 10758 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10759 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10760 mEditor.invalidateHandlesAndActionMode(); 10761 } 10762 } 10763 10764 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10765 mHighlightPathBogus = true; 10766 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10767 ims.mSelectionModeChanged = true; 10768 } 10769 10770 if (Selection.getSelectionStart(buf) >= 0) { 10771 if (ims == null || ims.mBatchEditNesting == 0) { 10772 invalidateCursor(); 10773 } else { 10774 ims.mCursorChanged = true; 10775 } 10776 } 10777 } 10778 10779 if (what instanceof ParcelableSpan) { 10780 // If this is a span that can be sent to a remote process, 10781 // the current extract editor would be interested in it. 10782 if (ims != null && ims.mExtractedTextRequest != null) { 10783 if (ims.mBatchEditNesting != 0) { 10784 if (oldStart >= 0) { 10785 if (ims.mChangedStart > oldStart) { 10786 ims.mChangedStart = oldStart; 10787 } 10788 if (ims.mChangedStart > oldEnd) { 10789 ims.mChangedStart = oldEnd; 10790 } 10791 } 10792 if (newStart >= 0) { 10793 if (ims.mChangedStart > newStart) { 10794 ims.mChangedStart = newStart; 10795 } 10796 if (ims.mChangedStart > newEnd) { 10797 ims.mChangedStart = newEnd; 10798 } 10799 } 10800 } else { 10801 if (DEBUG_EXTRACT) { 10802 Log.v(LOG_TAG, "Span change outside of batch: " 10803 + oldStart + "-" + oldEnd + "," 10804 + newStart + "-" + newEnd + " " + what); 10805 } 10806 ims.mContentChanged = true; 10807 } 10808 } 10809 } 10810 10811 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 10812 && what instanceof SpellCheckSpan) { 10813 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 10814 } 10815 } 10816 10817 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10818 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 10819 if (isTemporarilyDetached()) { 10820 // If we are temporarily in the detach state, then do nothing. 10821 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10822 return; 10823 } 10824 10825 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 10826 10827 if (focused) { 10828 if (mSpannable != null) { 10829 MetaKeyKeyListener.resetMetaState(mSpannable); 10830 } 10831 } 10832 10833 startStopMarquee(focused); 10834 10835 if (mTransformation != null) { 10836 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 10837 } 10838 10839 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10840 } 10841 10842 @Override onWindowFocusChanged(boolean hasWindowFocus)10843 public void onWindowFocusChanged(boolean hasWindowFocus) { 10844 super.onWindowFocusChanged(hasWindowFocus); 10845 10846 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 10847 10848 startStopMarquee(hasWindowFocus); 10849 } 10850 10851 @Override onVisibilityChanged(View changedView, int visibility)10852 protected void onVisibilityChanged(View changedView, int visibility) { 10853 super.onVisibilityChanged(changedView, visibility); 10854 if (mEditor != null && visibility != VISIBLE) { 10855 mEditor.hideCursorAndSpanControllers(); 10856 stopTextActionMode(); 10857 } 10858 } 10859 10860 /** 10861 * Use {@link BaseInputConnection#removeComposingSpans 10862 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 10863 * state from this text view. 10864 */ clearComposingText()10865 public void clearComposingText() { 10866 if (mText instanceof Spannable) { 10867 BaseInputConnection.removeComposingSpans(mSpannable); 10868 } 10869 } 10870 10871 @Override setSelected(boolean selected)10872 public void setSelected(boolean selected) { 10873 boolean wasSelected = isSelected(); 10874 10875 super.setSelected(selected); 10876 10877 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10878 if (selected) { 10879 startMarquee(); 10880 } else { 10881 stopMarquee(); 10882 } 10883 } 10884 } 10885 10886 /** 10887 * Called from onTouchEvent() to prevent the touches by secondary fingers. 10888 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 10889 * This method is a lock to avoid processing multiple fingers on both text view and handles. 10890 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 10891 * 10892 * @param event The motion event that is being handled and carries the pointer info. 10893 * @param fromHandleView true if the event is delivered to selection handle or insertion 10894 * handle; false if this event is delivered to TextView. 10895 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 10896 * event, otherwise false. 10897 * - Always returns true for the first finger. 10898 * - For secondary fingers, if the first or current finger is from TextView, returns false. 10899 * This is to make touch mutually exclusive between the TextView and the handles, but 10900 * not among the handles. 10901 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)10902 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 10903 boolean res = true; 10904 if (mPrimePointerId == NO_POINTER_ID) { 10905 mPrimePointerId = event.getPointerId(0); 10906 mIsPrimePointerFromHandleView = fromHandleView; 10907 } else if (mPrimePointerId != event.getPointerId(0)) { 10908 res = mIsPrimePointerFromHandleView && fromHandleView; 10909 } 10910 if (event.getActionMasked() == MotionEvent.ACTION_UP 10911 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 10912 mPrimePointerId = -1; 10913 } 10914 return res; 10915 } 10916 10917 @Override onTouchEvent(MotionEvent event)10918 public boolean onTouchEvent(MotionEvent event) { 10919 if (DEBUG_CURSOR) { 10920 logCursor("onTouchEvent", "%d: %s (%f,%f)", 10921 event.getSequenceNumber(), 10922 MotionEvent.actionToString(event.getActionMasked()), 10923 event.getX(), event.getY()); 10924 } 10925 if (!isFromPrimePointer(event, false)) { 10926 return true; 10927 } 10928 10929 final int action = event.getActionMasked(); 10930 if (mEditor != null) { 10931 mEditor.onTouchEvent(event); 10932 10933 if (mEditor.mInsertionPointCursorController != null 10934 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 10935 return true; 10936 } 10937 if (mEditor.mSelectionModifierCursorController != null 10938 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 10939 return true; 10940 } 10941 } 10942 10943 final boolean superResult = super.onTouchEvent(event); 10944 if (DEBUG_CURSOR) { 10945 logCursor("onTouchEvent", "superResult=%s", superResult); 10946 } 10947 10948 /* 10949 * Don't handle the release after a long press, because it will move the selection away from 10950 * whatever the menu action was trying to affect. If the long press should have triggered an 10951 * insertion action mode, we can now actually show it. 10952 */ 10953 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 10954 mEditor.mDiscardNextActionUp = false; 10955 if (DEBUG_CURSOR) { 10956 logCursor("onTouchEvent", "release after long press detected"); 10957 } 10958 if (mEditor.mIsInsertionActionModeStartPending) { 10959 mEditor.startInsertionActionMode(); 10960 mEditor.mIsInsertionActionModeStartPending = false; 10961 } 10962 return superResult; 10963 } 10964 10965 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 10966 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 10967 10968 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 10969 && mText instanceof Spannable && mLayout != null) { 10970 boolean handled = false; 10971 10972 if (mMovement != null) { 10973 handled |= mMovement.onTouchEvent(this, mSpannable, event); 10974 } 10975 10976 final boolean textIsSelectable = isTextSelectable(); 10977 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 10978 // The LinkMovementMethod which should handle taps on links has not been installed 10979 // on non editable text that support text selection. 10980 // We reproduce its behavior here to open links for these. 10981 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 10982 getSelectionEnd(), ClickableSpan.class); 10983 10984 if (links.length > 0) { 10985 links[0].onClick(this); 10986 handled = true; 10987 } 10988 } 10989 10990 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 10991 // Show the IME, except when selecting in read-only text. 10992 final InputMethodManager imm = getInputMethodManager(); 10993 viewClicked(imm); 10994 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10995 imm.showSoftInput(this, 0); 10996 } 10997 10998 // The above condition ensures that the mEditor is not null 10999 mEditor.onTouchUpEvent(event); 11000 11001 handled = true; 11002 } 11003 11004 if (handled) { 11005 return true; 11006 } 11007 } 11008 11009 return superResult; 11010 } 11011 11012 @Override onGenericMotionEvent(MotionEvent event)11013 public boolean onGenericMotionEvent(MotionEvent event) { 11014 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 11015 try { 11016 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 11017 return true; 11018 } 11019 } catch (AbstractMethodError ex) { 11020 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 11021 // Ignore its absence in case third party applications implemented the 11022 // interface directly. 11023 } 11024 } 11025 return super.onGenericMotionEvent(event); 11026 } 11027 11028 @Override onCreateContextMenu(ContextMenu menu)11029 protected void onCreateContextMenu(ContextMenu menu) { 11030 if (mEditor != null) { 11031 mEditor.onCreateContextMenu(menu); 11032 } 11033 } 11034 11035 @Override showContextMenu()11036 public boolean showContextMenu() { 11037 if (mEditor != null) { 11038 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 11039 } 11040 return super.showContextMenu(); 11041 } 11042 11043 @Override showContextMenu(float x, float y)11044 public boolean showContextMenu(float x, float y) { 11045 if (mEditor != null) { 11046 mEditor.setContextMenuAnchor(x, y); 11047 } 11048 return super.showContextMenu(x, y); 11049 } 11050 11051 /** 11052 * @return True iff this TextView contains a text that can be edited, or if this is 11053 * a selectable TextView. 11054 */ 11055 @UnsupportedAppUsage isTextEditable()11056 boolean isTextEditable() { 11057 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 11058 } 11059 11060 /** 11061 * Returns true, only while processing a touch gesture, if the initial 11062 * touch down event caused focus to move to the text view and as a result 11063 * its selection changed. Only valid while processing the touch gesture 11064 * of interest, in an editable text view. 11065 */ didTouchFocusSelect()11066 public boolean didTouchFocusSelect() { 11067 return mEditor != null && mEditor.mTouchFocusSelected; 11068 } 11069 11070 @Override cancelLongPress()11071 public void cancelLongPress() { 11072 super.cancelLongPress(); 11073 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 11074 } 11075 11076 @Override onTrackballEvent(MotionEvent event)11077 public boolean onTrackballEvent(MotionEvent event) { 11078 if (mMovement != null && mSpannable != null && mLayout != null) { 11079 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 11080 return true; 11081 } 11082 } 11083 11084 return super.onTrackballEvent(event); 11085 } 11086 11087 /** 11088 * Sets the Scroller used for producing a scrolling animation 11089 * 11090 * @param s A Scroller instance 11091 */ setScroller(Scroller s)11092 public void setScroller(Scroller s) { 11093 mScroller = s; 11094 } 11095 11096 @Override getLeftFadingEdgeStrength()11097 protected float getLeftFadingEdgeStrength() { 11098 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11099 final Marquee marquee = mMarquee; 11100 if (marquee.shouldDrawLeftFade()) { 11101 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11102 } else { 11103 return 0.0f; 11104 } 11105 } else if (getLineCount() == 1) { 11106 final float lineLeft = getLayout().getLineLeft(0); 11107 if (lineLeft > mScrollX) return 0.0f; 11108 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11109 } 11110 return super.getLeftFadingEdgeStrength(); 11111 } 11112 11113 @Override getRightFadingEdgeStrength()11114 protected float getRightFadingEdgeStrength() { 11115 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11116 final Marquee marquee = mMarquee; 11117 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11118 } else if (getLineCount() == 1) { 11119 final float rightEdge = mScrollX + 11120 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11121 final float lineRight = getLayout().getLineRight(0); 11122 if (lineRight < rightEdge) return 0.0f; 11123 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11124 } 11125 return super.getRightFadingEdgeStrength(); 11126 } 11127 11128 /** 11129 * Calculates the fading edge strength as the ratio of the distance between two 11130 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11131 * value for the distance calculation. 11132 * 11133 * @param position1 A horizontal position. 11134 * @param position2 A horizontal position. 11135 * @return Fading edge strength between [0.0f, 1.0f]. 11136 */ 11137 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11138 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11139 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11140 if (horizontalFadingEdgeLength == 0) return 0.0f; 11141 final float diff = Math.abs(position1 - position2); 11142 if (diff > horizontalFadingEdgeLength) return 1.0f; 11143 return diff / horizontalFadingEdgeLength; 11144 } 11145 isMarqueeFadeEnabled()11146 private boolean isMarqueeFadeEnabled() { 11147 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11148 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11149 } 11150 11151 @Override computeHorizontalScrollRange()11152 protected int computeHorizontalScrollRange() { 11153 if (mLayout != null) { 11154 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11155 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11156 } 11157 11158 return super.computeHorizontalScrollRange(); 11159 } 11160 11161 @Override computeVerticalScrollRange()11162 protected int computeVerticalScrollRange() { 11163 if (mLayout != null) { 11164 return mLayout.getHeight(); 11165 } 11166 return super.computeVerticalScrollRange(); 11167 } 11168 11169 @Override computeVerticalScrollExtent()11170 protected int computeVerticalScrollExtent() { 11171 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11172 } 11173 11174 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11175 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11176 super.findViewsWithText(outViews, searched, flags); 11177 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11178 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11179 String searchedLowerCase = searched.toString().toLowerCase(); 11180 String textLowerCase = mText.toString().toLowerCase(); 11181 if (textLowerCase.contains(searchedLowerCase)) { 11182 outViews.add(this); 11183 } 11184 } 11185 } 11186 11187 /** 11188 * Type of the text buffer that defines the characteristics of the text such as static, 11189 * styleable, or editable. 11190 */ 11191 public enum BufferType { 11192 NORMAL, SPANNABLE, EDITABLE 11193 } 11194 11195 /** 11196 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11197 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11198 * if TextView_textColor was not set directly. 11199 * 11200 * @removed 11201 */ getTextColors(Context context, TypedArray attrs)11202 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11203 if (attrs == null) { 11204 // Preserve behavior prior to removal of this API. 11205 throw new NullPointerException(); 11206 } 11207 11208 // It's not safe to use this method from apps. The parameter 'attrs' 11209 // must have been obtained using the TextView filter array which is not 11210 // available to the SDK. As such, we grab a default TypedArray with the 11211 // right filter instead here. 11212 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11213 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11214 if (colors == null) { 11215 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11216 if (ap != 0) { 11217 final TypedArray appearance = context.obtainStyledAttributes( 11218 ap, R.styleable.TextAppearance); 11219 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11220 appearance.recycle(); 11221 } 11222 } 11223 a.recycle(); 11224 11225 return colors; 11226 } 11227 11228 /** 11229 * Returns the default color from the TextView_textColor attribute from the 11230 * AttributeSet, if set, or the default color from the 11231 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11232 * TextView_textColor was not set directly. 11233 * 11234 * @removed 11235 */ getTextColor(Context context, TypedArray attrs, int def)11236 public static int getTextColor(Context context, TypedArray attrs, int def) { 11237 final ColorStateList colors = getTextColors(context, attrs); 11238 if (colors == null) { 11239 return def; 11240 } else { 11241 return colors.getDefaultColor(); 11242 } 11243 } 11244 11245 @Override onKeyShortcut(int keyCode, KeyEvent event)11246 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11247 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11248 // Handle Ctrl-only shortcuts. 11249 switch (keyCode) { 11250 case KeyEvent.KEYCODE_A: 11251 if (canSelectText()) { 11252 return onTextContextMenuItem(ID_SELECT_ALL); 11253 } 11254 break; 11255 case KeyEvent.KEYCODE_Z: 11256 if (canUndo()) { 11257 return onTextContextMenuItem(ID_UNDO); 11258 } 11259 break; 11260 case KeyEvent.KEYCODE_X: 11261 if (canCut()) { 11262 return onTextContextMenuItem(ID_CUT); 11263 } 11264 break; 11265 case KeyEvent.KEYCODE_C: 11266 if (canCopy()) { 11267 return onTextContextMenuItem(ID_COPY); 11268 } 11269 break; 11270 case KeyEvent.KEYCODE_V: 11271 if (canPaste()) { 11272 return onTextContextMenuItem(ID_PASTE); 11273 } 11274 break; 11275 } 11276 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11277 // Handle Ctrl-Shift shortcuts. 11278 switch (keyCode) { 11279 case KeyEvent.KEYCODE_Z: 11280 if (canRedo()) { 11281 return onTextContextMenuItem(ID_REDO); 11282 } 11283 break; 11284 case KeyEvent.KEYCODE_V: 11285 if (canPaste()) { 11286 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11287 } 11288 } 11289 } 11290 return super.onKeyShortcut(keyCode, event); 11291 } 11292 11293 /** 11294 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11295 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11296 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11297 * sufficient. 11298 */ canSelectText()11299 boolean canSelectText() { 11300 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11301 } 11302 11303 /** 11304 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11305 * The text must be spannable and the movement method must allow for arbitary selection. 11306 * 11307 * See also {@link #canSelectText()}. 11308 */ textCanBeSelected()11309 boolean textCanBeSelected() { 11310 // prepareCursorController() relies on this method. 11311 // If you change this condition, make sure prepareCursorController is called anywhere 11312 // the value of this condition might be changed. 11313 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11314 return isTextEditable() 11315 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11316 } 11317 11318 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11319 private Locale getTextServicesLocale(boolean allowNullLocale) { 11320 // Start fetching the text services locale asynchronously. 11321 updateTextServicesLocaleAsync(); 11322 // If !allowNullLocale and there is no cached text services locale, just return the default 11323 // locale. 11324 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11325 : mCurrentSpellCheckerLocaleCache; 11326 } 11327 11328 /** 11329 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11330 * this {@link TextView}. 11331 * 11332 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11333 * other apps may need to set this so that the system can user right user's resources and 11334 * services such as input methods and spell checkers.</p> 11335 * 11336 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11337 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11338 * @hide 11339 */ 11340 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11341 public final void setTextOperationUser(@Nullable UserHandle user) { 11342 if (Objects.equals(mTextOperationUser, user)) { 11343 return; 11344 } 11345 if (user != null && !Process.myUserHandle().equals(user)) { 11346 // Just for preventing people from accidentally using this hidden API without 11347 // the required permission. The same permission is also checked in the system server. 11348 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11349 != PackageManager.PERMISSION_GRANTED) { 11350 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11351 + " userId=" + user.getIdentifier() 11352 + " callingUserId" + UserHandle.myUserId()); 11353 } 11354 } 11355 mTextOperationUser = user; 11356 // Invalidate some resources 11357 mCurrentSpellCheckerLocaleCache = null; 11358 if (mEditor != null) { 11359 mEditor.onTextOperationUserChanged(); 11360 } 11361 } 11362 11363 @Nullable getTextServicesManagerForUser()11364 final TextServicesManager getTextServicesManagerForUser() { 11365 return getServiceManagerForUser("android", TextServicesManager.class); 11366 } 11367 11368 @Nullable getClipboardManagerForUser()11369 final ClipboardManager getClipboardManagerForUser() { 11370 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11371 } 11372 11373 @Nullable getTextClassificationManagerForUser()11374 final TextClassificationManager getTextClassificationManagerForUser() { 11375 return getServiceManagerForUser( 11376 getContext().getPackageName(), TextClassificationManager.class); 11377 } 11378 11379 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11380 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11381 if (mTextOperationUser == null) { 11382 return getContext().getSystemService(managerClazz); 11383 } 11384 try { 11385 Context context = getContext().createPackageContextAsUser( 11386 packageName, 0 /* flags */, mTextOperationUser); 11387 return context.getSystemService(managerClazz); 11388 } catch (PackageManager.NameNotFoundException e) { 11389 return null; 11390 } 11391 } 11392 11393 /** 11394 * Starts {@link Activity} as a text-operation user if it is specified with 11395 * {@link #setTextOperationUser(UserHandle)}. 11396 * 11397 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11398 * 11399 * @param intent The description of the activity to start. 11400 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11401 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11402 if (mTextOperationUser != null) { 11403 getContext().startActivityAsUser(intent, mTextOperationUser); 11404 } else { 11405 getContext().startActivity(intent); 11406 } 11407 } 11408 11409 /** 11410 * This is a temporary method. Future versions may support multi-locale text. 11411 * Caveat: This method may not return the latest text services locale, but this should be 11412 * acceptable and it's more important to make this method asynchronous. 11413 * 11414 * @return The locale that should be used for a word iterator 11415 * in this TextView, based on the current spell checker settings, 11416 * the current IME's locale, or the system default locale. 11417 * Please note that a word iterator in this TextView is different from another word iterator 11418 * used by SpellChecker.java of TextView. This method should be used for the former. 11419 * @hide 11420 */ 11421 // TODO: Support multi-locale 11422 // TODO: Update the text services locale immediately after the keyboard locale is switched 11423 // by catching intent of keyboard switch event getTextServicesLocale()11424 public Locale getTextServicesLocale() { 11425 return getTextServicesLocale(false /* allowNullLocale */); 11426 } 11427 11428 /** 11429 * @return {@code true} if this TextView is specialized for showing and interacting with the 11430 * extracted text in a full-screen input method. 11431 * @hide 11432 */ isInExtractedMode()11433 public boolean isInExtractedMode() { 11434 return false; 11435 } 11436 11437 /** 11438 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11439 * auto-size. 11440 */ isAutoSizeEnabled()11441 private boolean isAutoSizeEnabled() { 11442 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11443 } 11444 11445 /** 11446 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11447 * @hide 11448 */ supportsAutoSizeText()11449 protected boolean supportsAutoSizeText() { 11450 return true; 11451 } 11452 11453 /** 11454 * This is a temporary method. Future versions may support multi-locale text. 11455 * Caveat: This method may not return the latest spell checker locale, but this should be 11456 * acceptable and it's more important to make this method asynchronous. 11457 * 11458 * @return The locale that should be used for a spell checker in this TextView, 11459 * based on the current spell checker settings, the current IME's locale, or the system default 11460 * locale. 11461 * @hide 11462 */ getSpellCheckerLocale()11463 public Locale getSpellCheckerLocale() { 11464 return getTextServicesLocale(true /* allowNullLocale */); 11465 } 11466 updateTextServicesLocaleAsync()11467 private void updateTextServicesLocaleAsync() { 11468 // AsyncTask.execute() uses a serial executor which means we don't have 11469 // to lock around updateTextServicesLocaleLocked() to prevent it from 11470 // being executed n times in parallel. 11471 AsyncTask.execute(new Runnable() { 11472 @Override 11473 public void run() { 11474 updateTextServicesLocaleLocked(); 11475 } 11476 }); 11477 } 11478 11479 @UnsupportedAppUsage updateTextServicesLocaleLocked()11480 private void updateTextServicesLocaleLocked() { 11481 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11482 if (textServicesManager == null) { 11483 return; 11484 } 11485 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11486 final Locale locale; 11487 if (subtype != null) { 11488 locale = subtype.getLocaleObject(); 11489 } else { 11490 locale = null; 11491 } 11492 mCurrentSpellCheckerLocaleCache = locale; 11493 } 11494 onLocaleChanged()11495 void onLocaleChanged() { 11496 mEditor.onLocaleChanged(); 11497 } 11498 11499 /** 11500 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11501 * Made available to achieve a consistent behavior. 11502 * @hide 11503 */ getWordIterator()11504 public WordIterator getWordIterator() { 11505 if (mEditor != null) { 11506 return mEditor.getWordIterator(); 11507 } else { 11508 return null; 11509 } 11510 } 11511 11512 /** @hide */ 11513 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11514 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11515 super.onPopulateAccessibilityEventInternal(event); 11516 11517 final CharSequence text = getTextForAccessibility(); 11518 if (!TextUtils.isEmpty(text)) { 11519 event.getText().add(text); 11520 } 11521 } 11522 11523 @Override getAccessibilityClassName()11524 public CharSequence getAccessibilityClassName() { 11525 return TextView.class.getName(); 11526 } 11527 11528 /** @hide */ 11529 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11530 protected void onProvideStructure(@NonNull ViewStructure structure, 11531 @ViewStructureType int viewFor, int flags) { 11532 super.onProvideStructure(structure, viewFor, flags); 11533 11534 final boolean isPassword = hasPasswordTransformationMethod() 11535 || isPasswordInputType(getInputType()); 11536 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11537 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11538 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11539 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11540 } 11541 if (mTextId != Resources.ID_NULL) { 11542 try { 11543 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11544 } catch (Resources.NotFoundException e) { 11545 if (android.view.autofill.Helper.sVerbose) { 11546 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11547 + mTextId + ": " + e.getMessage()); 11548 } 11549 } 11550 } 11551 } 11552 11553 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11554 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11555 if (mLayout == null) { 11556 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11557 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 11558 } 11559 assumeLayout(); 11560 } 11561 Layout layout = mLayout; 11562 final int lineCount = layout.getLineCount(); 11563 if (lineCount <= 1) { 11564 // Simple case: this is a single line. 11565 final CharSequence text = getText(); 11566 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11567 structure.setText(text); 11568 } else { 11569 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11570 } 11571 } else { 11572 // Complex case: multi-line, could be scrolled or within a scroll container 11573 // so some lines are not visible. 11574 final int[] tmpCords = new int[2]; 11575 getLocationInWindow(tmpCords); 11576 final int topWindowLocation = tmpCords[1]; 11577 View root = this; 11578 ViewParent viewParent = getParent(); 11579 while (viewParent instanceof View) { 11580 root = (View) viewParent; 11581 viewParent = root.getParent(); 11582 } 11583 final int windowHeight = root.getHeight(); 11584 final int topLine; 11585 final int bottomLine; 11586 if (topWindowLocation >= 0) { 11587 // The top of the view is fully within its window; start text at line 0. 11588 topLine = getLineAtCoordinateUnclamped(0); 11589 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11590 } else { 11591 // The top of hte window has scrolled off the top of the window; figure out 11592 // the starting line for this. 11593 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11594 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11595 } 11596 // We want to return some contextual lines above/below the lines that are 11597 // actually visible. 11598 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11599 if (expandedTopLine < 0) { 11600 expandedTopLine = 0; 11601 } 11602 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11603 if (expandedBottomLine >= lineCount) { 11604 expandedBottomLine = lineCount - 1; 11605 } 11606 11607 // Convert lines into character offsets. 11608 int expandedTopChar = layout.getLineStart(expandedTopLine); 11609 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11610 11611 // Take into account selection -- if there is a selection, we need to expand 11612 // the text we are returning to include that selection. 11613 final int selStart = getSelectionStart(); 11614 final int selEnd = getSelectionEnd(); 11615 if (selStart < selEnd) { 11616 if (selStart < expandedTopChar) { 11617 expandedTopChar = selStart; 11618 } 11619 if (selEnd > expandedBottomChar) { 11620 expandedBottomChar = selEnd; 11621 } 11622 } 11623 11624 // Get the text and trim it to the range we are reporting. 11625 CharSequence text = getText(); 11626 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11627 text = text.subSequence(expandedTopChar, expandedBottomChar); 11628 } 11629 11630 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11631 structure.setText(text); 11632 } else { 11633 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 11634 11635 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11636 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11637 final int baselineOffset = getBaselineOffset(); 11638 for (int i = topLine; i <= bottomLine; i++) { 11639 lineOffsets[i - topLine] = layout.getLineStart(i); 11640 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11641 } 11642 structure.setTextLines(lineOffsets, lineBaselines); 11643 } 11644 } 11645 11646 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 11647 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11648 // Extract style information that applies to the TextView as a whole. 11649 int style = 0; 11650 int typefaceStyle = getTypefaceStyle(); 11651 if ((typefaceStyle & Typeface.BOLD) != 0) { 11652 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11653 } 11654 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11655 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11656 } 11657 11658 // Global styles can also be set via TextView.setPaintFlags(). 11659 int paintFlags = mTextPaint.getFlags(); 11660 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11661 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11662 } 11663 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11664 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11665 } 11666 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11667 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11668 } 11669 11670 // TextView does not have its own text background color. A background is either part 11671 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11672 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11673 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11674 } 11675 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11676 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11677 structure.setMinTextEms(getMinEms()); 11678 structure.setMaxTextEms(getMaxEms()); 11679 int maxLength = -1; 11680 for (InputFilter filter: getFilters()) { 11681 if (filter instanceof InputFilter.LengthFilter) { 11682 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11683 break; 11684 } 11685 } 11686 structure.setMaxTextLength(maxLength); 11687 } 11688 } 11689 if (mHintId != Resources.ID_NULL) { 11690 try { 11691 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 11692 } catch (Resources.NotFoundException e) { 11693 if (android.view.autofill.Helper.sVerbose) { 11694 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 11695 + mHintId + ": " + e.getMessage()); 11696 } 11697 } 11698 } 11699 structure.setHint(getHint()); 11700 structure.setInputType(getInputType()); 11701 } 11702 canRequestAutofill()11703 boolean canRequestAutofill() { 11704 if (!isAutofillable()) { 11705 return false; 11706 } 11707 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11708 if (afm != null) { 11709 return afm.isEnabled(); 11710 } 11711 return false; 11712 } 11713 requestAutofill()11714 private void requestAutofill() { 11715 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11716 if (afm != null) { 11717 afm.requestAutofill(this); 11718 } 11719 } 11720 11721 @Override autofill(AutofillValue value)11722 public void autofill(AutofillValue value) { 11723 if (!value.isText() || !isTextEditable()) { 11724 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 11725 return; 11726 } 11727 11728 final CharSequence autofilledValue = value.getTextValue(); 11729 11730 // First autofill it... 11731 setText(autofilledValue, mBufferType, true, 0); 11732 11733 // ...then move cursor to the end. 11734 final CharSequence text = getText(); 11735 if ((text instanceof Spannable)) { 11736 Selection.setSelection((Spannable) text, text.length()); 11737 } 11738 } 11739 11740 @Override getAutofillType()11741 public @AutofillType int getAutofillType() { 11742 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11743 } 11744 11745 /** 11746 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11747 * {@code char}s if longer. 11748 * 11749 * @return current text, {@code null} if the text is not editable 11750 * 11751 * @see View#getAutofillValue() 11752 */ 11753 @Override 11754 @Nullable getAutofillValue()11755 public AutofillValue getAutofillValue() { 11756 if (isTextEditable()) { 11757 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 11758 return AutofillValue.forText(text); 11759 } 11760 return null; 11761 } 11762 11763 /** @hide */ 11764 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)11765 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 11766 super.onInitializeAccessibilityEventInternal(event); 11767 11768 final boolean isPassword = hasPasswordTransformationMethod(); 11769 event.setPassword(isPassword); 11770 11771 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 11772 event.setFromIndex(Selection.getSelectionStart(mText)); 11773 event.setToIndex(Selection.getSelectionEnd(mText)); 11774 event.setItemCount(mText.length()); 11775 } 11776 } 11777 11778 /** @hide */ 11779 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11780 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 11781 super.onInitializeAccessibilityNodeInfoInternal(info); 11782 11783 final boolean isPassword = hasPasswordTransformationMethod(); 11784 info.setPassword(isPassword); 11785 info.setText(getTextForAccessibility()); 11786 info.setHintText(mHint); 11787 info.setShowingHintText(isShowingHint()); 11788 11789 if (mBufferType == BufferType.EDITABLE) { 11790 info.setEditable(true); 11791 if (isEnabled()) { 11792 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 11793 } 11794 } 11795 11796 if (mEditor != null) { 11797 info.setInputType(mEditor.mInputType); 11798 11799 if (mEditor.mError != null) { 11800 info.setContentInvalid(true); 11801 info.setError(mEditor.mError); 11802 } 11803 // TextView will expose this action if it is editable and has focus. 11804 if (isTextEditable() && isFocused()) { 11805 CharSequence imeActionLabel = mContext.getResources().getString( 11806 com.android.internal.R.string.keyboardview_keycode_enter); 11807 if (getImeActionLabel() != null) { 11808 imeActionLabel = getImeActionLabel(); 11809 } 11810 AccessibilityNodeInfo.AccessibilityAction action = 11811 new AccessibilityNodeInfo.AccessibilityAction( 11812 R.id.accessibilityActionImeEnter, imeActionLabel); 11813 info.addAction(action); 11814 } 11815 } 11816 11817 if (!TextUtils.isEmpty(mText)) { 11818 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 11819 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 11820 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 11821 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 11822 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 11823 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 11824 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 11825 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 11826 info.setAvailableExtraData(Arrays.asList( 11827 EXTRA_DATA_RENDERING_INFO_KEY, 11828 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 11829 )); 11830 } else { 11831 info.setAvailableExtraData(Arrays.asList( 11832 EXTRA_DATA_RENDERING_INFO_KEY 11833 )); 11834 } 11835 11836 if (isFocused()) { 11837 if (canCopy()) { 11838 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 11839 } 11840 if (canPaste()) { 11841 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 11842 } 11843 if (canCut()) { 11844 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 11845 } 11846 if (canShare()) { 11847 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 11848 ACCESSIBILITY_ACTION_SHARE, 11849 getResources().getString(com.android.internal.R.string.share))); 11850 } 11851 if (canProcessText()) { // also implies mEditor is not null. 11852 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 11853 } 11854 } 11855 11856 // Check for known input filter types. 11857 final int numFilters = mFilters.length; 11858 for (int i = 0; i < numFilters; i++) { 11859 final InputFilter filter = mFilters[i]; 11860 if (filter instanceof InputFilter.LengthFilter) { 11861 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 11862 } 11863 } 11864 11865 if (!isSingleLine()) { 11866 info.setMultiLine(true); 11867 } 11868 11869 // A view should not be exposed as clickable/long-clickable to a service because of a 11870 // LinkMovementMethod. 11871 if ((info.isClickable() || info.isLongClickable()) 11872 && mMovement instanceof LinkMovementMethod) { 11873 if (!hasOnClickListeners()) { 11874 info.setClickable(false); 11875 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 11876 } 11877 if (!hasOnLongClickListeners()) { 11878 info.setLongClickable(false); 11879 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 11880 } 11881 } 11882 } 11883 11884 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11885 public void addExtraDataToAccessibilityNodeInfo( 11886 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 11887 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 11888 int positionInfoStartIndex = arguments.getInt( 11889 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 11890 int positionInfoLength = arguments.getInt( 11891 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 11892 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 11893 || (positionInfoStartIndex >= mText.length())) { 11894 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 11895 return; 11896 } 11897 RectF[] boundingRects = new RectF[positionInfoLength]; 11898 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 11899 populateCharacterBounds(builder, positionInfoStartIndex, 11900 positionInfoStartIndex + positionInfoLength, 11901 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 11902 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 11903 for (int i = 0; i < positionInfoLength; i++) { 11904 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 11905 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 11906 RectF bounds = cursorAnchorInfo 11907 .getCharacterBounds(positionInfoStartIndex + i); 11908 if (bounds != null) { 11909 mapRectFromViewToScreenCoords(bounds, true); 11910 boundingRects[i] = bounds; 11911 } 11912 } 11913 } 11914 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 11915 return; 11916 } 11917 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 11918 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 11919 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 11920 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 11921 extraRenderingInfo.setTextSizeInPx(getTextSize()); 11922 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 11923 info.setExtraRenderingInfo(extraRenderingInfo); 11924 } 11925 } 11926 11927 /** 11928 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 11929 * 11930 * @param builder The builder to populate 11931 * @param startIndex The starting character index to populate 11932 * @param endIndex The ending character index to populate 11933 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 11934 * content 11935 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 11936 * @hide 11937 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)11938 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 11939 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 11940 float viewportToContentVerticalOffset) { 11941 final int minLine = mLayout.getLineForOffset(startIndex); 11942 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 11943 for (int line = minLine; line <= maxLine; ++line) { 11944 final int lineStart = mLayout.getLineStart(line); 11945 final int lineEnd = mLayout.getLineEnd(line); 11946 final int offsetStart = Math.max(lineStart, startIndex); 11947 final int offsetEnd = Math.min(lineEnd, endIndex); 11948 final boolean ltrLine = 11949 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 11950 final float[] widths = new float[offsetEnd - offsetStart]; 11951 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 11952 final float top = mLayout.getLineTop(line); 11953 final float bottom = mLayout.getLineBottom(line); 11954 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 11955 final float charWidth = widths[offset - offsetStart]; 11956 final boolean isRtl = mLayout.isRtlCharAt(offset); 11957 final float primary = mLayout.getPrimaryHorizontal(offset); 11958 final float secondary = mLayout.getSecondaryHorizontal(offset); 11959 // TODO: This doesn't work perfectly for text with custom styles and 11960 // TAB chars. 11961 final float left; 11962 final float right; 11963 if (ltrLine) { 11964 if (isRtl) { 11965 left = secondary - charWidth; 11966 right = secondary; 11967 } else { 11968 left = primary; 11969 right = primary + charWidth; 11970 } 11971 } else { 11972 if (!isRtl) { 11973 left = secondary; 11974 right = secondary + charWidth; 11975 } else { 11976 left = primary - charWidth; 11977 right = primary; 11978 } 11979 } 11980 // TODO: Check top-right and bottom-left as well. 11981 final float localLeft = left + viewportToContentHorizontalOffset; 11982 final float localRight = right + viewportToContentHorizontalOffset; 11983 final float localTop = top + viewportToContentVerticalOffset; 11984 final float localBottom = bottom + viewportToContentVerticalOffset; 11985 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 11986 final boolean isBottomRightVisible = 11987 isPositionVisible(localRight, localBottom); 11988 int characterBoundsFlags = 0; 11989 if (isTopLeftVisible || isBottomRightVisible) { 11990 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 11991 } 11992 if (!isTopLeftVisible || !isBottomRightVisible) { 11993 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 11994 } 11995 if (isRtl) { 11996 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 11997 } 11998 // Here offset is the index in Java chars. 11999 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 12000 localBottom, characterBoundsFlags); 12001 } 12002 } 12003 } 12004 12005 /** 12006 * @hide 12007 */ isPositionVisible(final float positionX, final float positionY)12008 public boolean isPositionVisible(final float positionX, final float positionY) { 12009 synchronized (TEMP_POSITION) { 12010 final float[] position = TEMP_POSITION; 12011 position[0] = positionX; 12012 position[1] = positionY; 12013 View view = this; 12014 12015 while (view != null) { 12016 if (view != this) { 12017 // Local scroll is already taken into account in positionX/Y 12018 position[0] -= view.getScrollX(); 12019 position[1] -= view.getScrollY(); 12020 } 12021 12022 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 12023 || position[1] > view.getHeight()) { 12024 return false; 12025 } 12026 12027 if (!view.getMatrix().isIdentity()) { 12028 view.getMatrix().mapPoints(position); 12029 } 12030 12031 position[0] += view.getLeft(); 12032 position[1] += view.getTop(); 12033 12034 final ViewParent parent = view.getParent(); 12035 if (parent instanceof View) { 12036 view = (View) parent; 12037 } else { 12038 // We've reached the ViewRoot, stop iterating 12039 view = null; 12040 } 12041 } 12042 } 12043 12044 // We've been able to walk up the view hierarchy and the position was never clipped 12045 return true; 12046 } 12047 12048 /** 12049 * Performs an accessibility action after it has been offered to the 12050 * delegate. 12051 * 12052 * @hide 12053 */ 12054 @Override performAccessibilityActionInternal(int action, Bundle arguments)12055 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 12056 if (mEditor != null 12057 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 12058 return true; 12059 } 12060 switch (action) { 12061 case AccessibilityNodeInfo.ACTION_CLICK: { 12062 return performAccessibilityActionClick(arguments); 12063 } 12064 case AccessibilityNodeInfo.ACTION_COPY: { 12065 if (isFocused() && canCopy()) { 12066 if (onTextContextMenuItem(ID_COPY)) { 12067 return true; 12068 } 12069 } 12070 } return false; 12071 case AccessibilityNodeInfo.ACTION_PASTE: { 12072 if (isFocused() && canPaste()) { 12073 if (onTextContextMenuItem(ID_PASTE)) { 12074 return true; 12075 } 12076 } 12077 } return false; 12078 case AccessibilityNodeInfo.ACTION_CUT: { 12079 if (isFocused() && canCut()) { 12080 if (onTextContextMenuItem(ID_CUT)) { 12081 return true; 12082 } 12083 } 12084 } return false; 12085 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 12086 ensureIterableTextForAccessibilitySelectable(); 12087 CharSequence text = getIterableTextForAccessibility(); 12088 if (text == null) { 12089 return false; 12090 } 12091 final int start = (arguments != null) ? arguments.getInt( 12092 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 12093 final int end = (arguments != null) ? arguments.getInt( 12094 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 12095 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 12096 // No arguments clears the selection. 12097 if (start == end && end == -1) { 12098 Selection.removeSelection((Spannable) text); 12099 return true; 12100 } 12101 if (start >= 0 && start <= end && end <= text.length()) { 12102 Selection.setSelection((Spannable) text, start, end); 12103 // Make sure selection mode is engaged. 12104 if (mEditor != null) { 12105 mEditor.startSelectionActionModeAsync(false); 12106 } 12107 return true; 12108 } 12109 } 12110 } return false; 12111 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 12112 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 12113 ensureIterableTextForAccessibilitySelectable(); 12114 return super.performAccessibilityActionInternal(action, arguments); 12115 } 12116 case ACCESSIBILITY_ACTION_SHARE: { 12117 if (isFocused() && canShare()) { 12118 if (onTextContextMenuItem(ID_SHARE)) { 12119 return true; 12120 } 12121 } 12122 } return false; 12123 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 12124 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 12125 return false; 12126 } 12127 CharSequence text = (arguments != null) ? arguments.getCharSequence( 12128 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 12129 setText(text); 12130 if (mText != null) { 12131 int updatedTextLength = mText.length(); 12132 if (updatedTextLength > 0) { 12133 Selection.setSelection(mSpannable, updatedTextLength); 12134 } 12135 } 12136 } return true; 12137 case R.id.accessibilityActionImeEnter: { 12138 if (isFocused() && isTextEditable()) { 12139 onEditorAction(getImeActionId()); 12140 } 12141 } return true; 12142 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 12143 if (isLongClickable()) { 12144 boolean handled; 12145 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 12146 mEditor.mIsBeingLongClickedByAccessibility = true; 12147 try { 12148 handled = performLongClick(); 12149 } finally { 12150 mEditor.mIsBeingLongClickedByAccessibility = false; 12151 } 12152 } else { 12153 handled = performLongClick(); 12154 } 12155 return handled; 12156 } 12157 } 12158 return false; 12159 default: { 12160 return super.performAccessibilityActionInternal(action, arguments); 12161 } 12162 } 12163 } 12164 performAccessibilityActionClick(Bundle arguments)12165 private boolean performAccessibilityActionClick(Bundle arguments) { 12166 boolean handled = false; 12167 12168 if (!isEnabled()) { 12169 return false; 12170 } 12171 12172 if (isClickable() || isLongClickable()) { 12173 // Simulate View.onTouchEvent for an ACTION_UP event 12174 if (isFocusable() && !isFocused()) { 12175 requestFocus(); 12176 } 12177 12178 performClick(); 12179 handled = true; 12180 } 12181 12182 // Show the IME, except when selecting in read-only text. 12183 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12184 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12185 final InputMethodManager imm = getInputMethodManager(); 12186 viewClicked(imm); 12187 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12188 handled |= imm.showSoftInput(this, 0); 12189 } 12190 } 12191 12192 return handled; 12193 } 12194 hasSpannableText()12195 private boolean hasSpannableText() { 12196 return mText != null && mText instanceof Spannable; 12197 } 12198 12199 /** @hide */ 12200 @Override sendAccessibilityEventInternal(int eventType)12201 public void sendAccessibilityEventInternal(int eventType) { 12202 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12203 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12204 } 12205 12206 super.sendAccessibilityEventInternal(eventType); 12207 } 12208 12209 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12210 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12211 // Do not send scroll events since first they are not interesting for 12212 // accessibility and second such events a generated too frequently. 12213 // For details see the implementation of bringTextIntoView(). 12214 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12215 return; 12216 } 12217 super.sendAccessibilityEventUnchecked(event); 12218 } 12219 12220 /** 12221 * Returns the text that should be exposed to accessibility services. 12222 * <p> 12223 * This approximates what is displayed visually. If the user has specified 12224 * that accessibility services should speak passwords, this method will 12225 * bypass any password transformation method and return unobscured text. 12226 * 12227 * @return the text that should be exposed to accessibility services, may 12228 * be {@code null} if no text is set 12229 */ 12230 @Nullable 12231 @UnsupportedAppUsage getTextForAccessibility()12232 private CharSequence getTextForAccessibility() { 12233 // If the text is empty, we must be showing the hint text. 12234 if (TextUtils.isEmpty(mText)) { 12235 return mHint; 12236 } 12237 12238 // Otherwise, return whatever text is being displayed. 12239 return TextUtils.trimToParcelableSize(mTransformed); 12240 } 12241 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12242 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12243 int fromIndex, int removedCount, int addedCount) { 12244 AccessibilityEvent event = 12245 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12246 event.setFromIndex(fromIndex); 12247 event.setRemovedCount(removedCount); 12248 event.setAddedCount(addedCount); 12249 event.setBeforeText(beforeText); 12250 sendAccessibilityEventUnchecked(event); 12251 } 12252 getInputMethodManager()12253 private InputMethodManager getInputMethodManager() { 12254 return getContext().getSystemService(InputMethodManager.class); 12255 } 12256 12257 /** 12258 * Returns whether this text view is a current input method target. The 12259 * default implementation just checks with {@link InputMethodManager}. 12260 * @return True if the TextView is a current input method target; false otherwise. 12261 */ isInputMethodTarget()12262 public boolean isInputMethodTarget() { 12263 InputMethodManager imm = getInputMethodManager(); 12264 return imm != null && imm.isActive(this); 12265 } 12266 12267 static final int ID_SELECT_ALL = android.R.id.selectAll; 12268 static final int ID_UNDO = android.R.id.undo; 12269 static final int ID_REDO = android.R.id.redo; 12270 static final int ID_CUT = android.R.id.cut; 12271 static final int ID_COPY = android.R.id.copy; 12272 static final int ID_PASTE = android.R.id.paste; 12273 static final int ID_SHARE = android.R.id.shareText; 12274 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12275 static final int ID_REPLACE = android.R.id.replaceText; 12276 static final int ID_ASSIST = android.R.id.textAssist; 12277 static final int ID_AUTOFILL = android.R.id.autofill; 12278 12279 /** 12280 * Called when a context menu option for the text view is selected. Currently 12281 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12282 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12283 * 12284 * @return true if the context menu item action was performed. 12285 */ onTextContextMenuItem(int id)12286 public boolean onTextContextMenuItem(int id) { 12287 int min = 0; 12288 int max = mText.length(); 12289 12290 if (isFocused()) { 12291 final int selStart = getSelectionStart(); 12292 final int selEnd = getSelectionEnd(); 12293 12294 min = Math.max(0, Math.min(selStart, selEnd)); 12295 max = Math.max(0, Math.max(selStart, selEnd)); 12296 } 12297 12298 switch (id) { 12299 case ID_SELECT_ALL: 12300 final boolean hadSelection = hasSelection(); 12301 selectAllText(); 12302 if (mEditor != null && hadSelection) { 12303 mEditor.invalidateActionModeAsync(); 12304 } 12305 return true; 12306 12307 case ID_UNDO: 12308 if (mEditor != null) { 12309 mEditor.undo(); 12310 } 12311 return true; // Returns true even if nothing was undone. 12312 12313 case ID_REDO: 12314 if (mEditor != null) { 12315 mEditor.redo(); 12316 } 12317 return true; // Returns true even if nothing was undone. 12318 12319 case ID_PASTE: 12320 paste(min, max, true /* withFormatting */); 12321 return true; 12322 12323 case ID_PASTE_AS_PLAIN_TEXT: 12324 paste(min, max, false /* withFormatting */); 12325 return true; 12326 12327 case ID_CUT: 12328 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12329 if (setPrimaryClip(cutData)) { 12330 deleteText_internal(min, max); 12331 } else { 12332 Toast.makeText(getContext(), 12333 com.android.internal.R.string.failed_to_copy_to_clipboard, 12334 Toast.LENGTH_SHORT).show(); 12335 } 12336 return true; 12337 12338 case ID_COPY: 12339 // For link action mode in a non-selectable/non-focusable TextView, 12340 // make sure that we set the appropriate min/max. 12341 final int selStart = getSelectionStart(); 12342 final int selEnd = getSelectionEnd(); 12343 min = Math.max(0, Math.min(selStart, selEnd)); 12344 max = Math.max(0, Math.max(selStart, selEnd)); 12345 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12346 if (setPrimaryClip(copyData)) { 12347 stopTextActionMode(); 12348 } else { 12349 Toast.makeText(getContext(), 12350 com.android.internal.R.string.failed_to_copy_to_clipboard, 12351 Toast.LENGTH_SHORT).show(); 12352 } 12353 return true; 12354 12355 case ID_REPLACE: 12356 if (mEditor != null) { 12357 mEditor.replace(); 12358 } 12359 return true; 12360 12361 case ID_SHARE: 12362 shareSelectedText(); 12363 return true; 12364 12365 case ID_AUTOFILL: 12366 requestAutofill(); 12367 stopTextActionMode(); 12368 return true; 12369 } 12370 return false; 12371 } 12372 12373 @UnsupportedAppUsage getTransformedText(int start, int end)12374 CharSequence getTransformedText(int start, int end) { 12375 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12376 } 12377 12378 @Override performLongClick()12379 public boolean performLongClick() { 12380 if (DEBUG_CURSOR) { 12381 logCursor("performLongClick", null); 12382 } 12383 12384 boolean handled = false; 12385 boolean performedHapticFeedback = false; 12386 12387 if (mEditor != null) { 12388 mEditor.mIsBeingLongClicked = true; 12389 } 12390 12391 if (super.performLongClick()) { 12392 handled = true; 12393 performedHapticFeedback = true; 12394 } 12395 12396 if (mEditor != null) { 12397 handled |= mEditor.performLongClick(handled); 12398 mEditor.mIsBeingLongClicked = false; 12399 } 12400 12401 if (handled) { 12402 if (!performedHapticFeedback) { 12403 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12404 } 12405 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12406 } else { 12407 MetricsLogger.action( 12408 mContext, 12409 MetricsEvent.TEXT_LONGPRESS, 12410 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12411 } 12412 12413 return handled; 12414 } 12415 12416 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12417 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12418 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12419 if (mEditor != null) { 12420 mEditor.onScrollChanged(); 12421 } 12422 } 12423 12424 /** 12425 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12426 * by the IME or by the spell checker as the user types. This is done by adding 12427 * {@link SuggestionSpan}s to the text. 12428 * 12429 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12430 * user asks for them on these parts of the text. This value depends on the inputType of this 12431 * TextView. 12432 * 12433 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12434 * 12435 * In addition, the type variation must be one of 12436 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12437 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12438 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12439 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12440 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12441 * 12442 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12443 * 12444 * @return true if the suggestions popup window is enabled, based on the inputType. 12445 */ isSuggestionsEnabled()12446 public boolean isSuggestionsEnabled() { 12447 if (mEditor == null) return false; 12448 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12449 return false; 12450 } 12451 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12452 12453 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12454 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12455 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12456 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12457 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12458 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12459 } 12460 12461 /** 12462 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12463 * selection is initiated in this View. 12464 * 12465 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12466 * Paste, Replace and Share actions, depending on what this View supports. 12467 * 12468 * <p>A custom implementation can add new entries in the default menu in its 12469 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12470 * method. The default actions can also be removed from the menu using 12471 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12472 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12473 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12474 * 12475 * <p>Returning false from 12476 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12477 * will prevent the action mode from being started. 12478 * 12479 * <p>Action click events should be handled by the custom implementation of 12480 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12481 * android.view.MenuItem)}. 12482 * 12483 * <p>Note that text selection mode is not started when a TextView receives focus and the 12484 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12485 * that case, to allow for quick replacement. 12486 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12487 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12488 createEditorIfNeeded(); 12489 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12490 } 12491 12492 /** 12493 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12494 * 12495 * @return The current custom selection callback. 12496 */ getCustomSelectionActionModeCallback()12497 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12498 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12499 } 12500 12501 /** 12502 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12503 * insertion is initiated in this View. 12504 * The standard implementation populates the menu with a subset of Select All, 12505 * Paste and Replace actions, depending on what this View supports. 12506 * 12507 * <p>A custom implementation can add new entries in the default menu in its 12508 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12509 * android.view.Menu)} method. The default actions can also be removed from the menu using 12510 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12511 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12512 * 12513 * <p>Returning false from 12514 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12515 * android.view.Menu)} will prevent the action mode from being started.</p> 12516 * 12517 * <p>Action click events should be handled by the custom implementation of 12518 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12519 * android.view.MenuItem)}.</p> 12520 * 12521 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12522 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12523 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12524 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12525 createEditorIfNeeded(); 12526 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12527 } 12528 12529 /** 12530 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12531 * 12532 * @return The current custom insertion callback. 12533 */ getCustomInsertionActionModeCallback()12534 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12535 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12536 } 12537 12538 /** 12539 * Sets the {@link TextClassifier} for this TextView. 12540 */ setTextClassifier(@ullable TextClassifier textClassifier)12541 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12542 mTextClassifier = textClassifier; 12543 } 12544 12545 /** 12546 * Returns the {@link TextClassifier} used by this TextView. 12547 * If no TextClassifier has been set, this TextView uses the default set by the 12548 * {@link TextClassificationManager}. 12549 */ 12550 @NonNull getTextClassifier()12551 public TextClassifier getTextClassifier() { 12552 if (mTextClassifier == null) { 12553 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12554 if (tcm != null) { 12555 return tcm.getTextClassifier(); 12556 } 12557 return TextClassifier.NO_OP; 12558 } 12559 return mTextClassifier; 12560 } 12561 12562 /** 12563 * Returns a session-aware text classifier. 12564 * This method creates one if none already exists or the current one is destroyed. 12565 */ 12566 @NonNull getTextClassificationSession()12567 TextClassifier getTextClassificationSession() { 12568 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12569 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12570 if (tcm != null) { 12571 final String widgetType; 12572 if (isTextEditable()) { 12573 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12574 } else if (isTextSelectable()) { 12575 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12576 } else { 12577 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12578 } 12579 mTextClassificationContext = new TextClassificationContext.Builder( 12580 mContext.getPackageName(), widgetType) 12581 .build(); 12582 if (mTextClassifier != null) { 12583 mTextClassificationSession = tcm.createTextClassificationSession( 12584 mTextClassificationContext, mTextClassifier); 12585 } else { 12586 mTextClassificationSession = tcm.createTextClassificationSession( 12587 mTextClassificationContext); 12588 } 12589 } else { 12590 mTextClassificationSession = TextClassifier.NO_OP; 12591 } 12592 } 12593 return mTextClassificationSession; 12594 } 12595 12596 /** 12597 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12598 * @see #getTextClassificationSession() 12599 */ 12600 @Nullable getTextClassificationContext()12601 TextClassificationContext getTextClassificationContext() { 12602 return mTextClassificationContext; 12603 } 12604 12605 /** 12606 * Returns true if this TextView uses a no-op TextClassifier. 12607 */ usesNoOpTextClassifier()12608 boolean usesNoOpTextClassifier() { 12609 return getTextClassifier() == TextClassifier.NO_OP; 12610 } 12611 12612 /** 12613 * Starts an ActionMode for the specified TextLinkSpan. 12614 * 12615 * @return Whether or not we're attempting to start the action mode. 12616 * @hide 12617 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12618 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12619 Preconditions.checkNotNull(clickedSpan); 12620 12621 if (!(mText instanceof Spanned)) { 12622 return false; 12623 } 12624 12625 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12626 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12627 12628 if (start < 0 || end > mText.length() || start >= end) { 12629 return false; 12630 } 12631 12632 createEditorIfNeeded(); 12633 mEditor.startLinkActionModeAsync(start, end); 12634 return true; 12635 } 12636 12637 /** 12638 * Handles a click on the specified TextLinkSpan. 12639 * 12640 * @return Whether or not the click is being handled. 12641 * @hide 12642 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12643 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12644 Preconditions.checkNotNull(clickedSpan); 12645 if (mText instanceof Spanned) { 12646 final Spanned spanned = (Spanned) mText; 12647 final int start = spanned.getSpanStart(clickedSpan); 12648 final int end = spanned.getSpanEnd(clickedSpan); 12649 if (start >= 0 && end <= mText.length() && start < end) { 12650 final TextClassification.Request request = new TextClassification.Request.Builder( 12651 mText, start, end) 12652 .setDefaultLocales(getTextLocales()) 12653 .build(); 12654 final Supplier<TextClassification> supplier = () -> 12655 getTextClassificationSession().classifyText(request); 12656 final Consumer<TextClassification> consumer = classification -> { 12657 if (classification != null) { 12658 if (!classification.getActions().isEmpty()) { 12659 try { 12660 classification.getActions().get(0).getActionIntent().send(); 12661 } catch (PendingIntent.CanceledException e) { 12662 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12663 } 12664 } else { 12665 Log.d(LOG_TAG, "No link action to perform"); 12666 } 12667 } else { 12668 // classification == null 12669 Log.d(LOG_TAG, "Timeout while classifying text"); 12670 } 12671 }; 12672 CompletableFuture.supplyAsync(supplier) 12673 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12674 .thenAccept(consumer); 12675 return true; 12676 } 12677 } 12678 return false; 12679 } 12680 12681 /** 12682 * @hide 12683 */ 12684 @UnsupportedAppUsage stopTextActionMode()12685 protected void stopTextActionMode() { 12686 if (mEditor != null) { 12687 mEditor.stopTextActionMode(); 12688 } 12689 } 12690 12691 /** @hide */ hideFloatingToolbar(int durationMs)12692 public void hideFloatingToolbar(int durationMs) { 12693 if (mEditor != null) { 12694 mEditor.hideFloatingToolbar(durationMs); 12695 } 12696 } 12697 canUndo()12698 boolean canUndo() { 12699 return mEditor != null && mEditor.canUndo(); 12700 } 12701 canRedo()12702 boolean canRedo() { 12703 return mEditor != null && mEditor.canRedo(); 12704 } 12705 canCut()12706 boolean canCut() { 12707 if (hasPasswordTransformationMethod()) { 12708 return false; 12709 } 12710 12711 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12712 && mEditor.mKeyListener != null) { 12713 return true; 12714 } 12715 12716 return false; 12717 } 12718 canCopy()12719 boolean canCopy() { 12720 if (hasPasswordTransformationMethod()) { 12721 return false; 12722 } 12723 12724 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12725 return true; 12726 } 12727 12728 return false; 12729 } 12730 canShare()12731 boolean canShare() { 12732 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12733 return false; 12734 } 12735 return canCopy(); 12736 } 12737 isDeviceProvisioned()12738 boolean isDeviceProvisioned() { 12739 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12740 mDeviceProvisionedState = Settings.Global.getInt( 12741 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12742 ? DEVICE_PROVISIONED_YES 12743 : DEVICE_PROVISIONED_NO; 12744 } 12745 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12746 } 12747 12748 @UnsupportedAppUsage canPaste()12749 boolean canPaste() { 12750 return (mText instanceof Editable 12751 && mEditor != null && mEditor.mKeyListener != null 12752 && getSelectionStart() >= 0 12753 && getSelectionEnd() >= 0 12754 && getClipboardManagerForUser().hasPrimaryClip()); 12755 } 12756 canPasteAsPlainText()12757 boolean canPasteAsPlainText() { 12758 if (!canPaste()) { 12759 return false; 12760 } 12761 12762 final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); 12763 final ClipDescription description = clipData.getDescription(); 12764 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 12765 final CharSequence text = clipData.getItemAt(0).getText(); 12766 if (isPlainType && (text instanceof Spanned)) { 12767 Spanned spanned = (Spanned) text; 12768 if (TextUtils.hasStyleSpan(spanned)) { 12769 return true; 12770 } 12771 } 12772 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 12773 } 12774 canProcessText()12775 boolean canProcessText() { 12776 if (getId() == View.NO_ID) { 12777 return false; 12778 } 12779 return canShare(); 12780 } 12781 canSelectAllText()12782 boolean canSelectAllText() { 12783 return canSelectText() && !hasPasswordTransformationMethod() 12784 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 12785 } 12786 selectAllText()12787 boolean selectAllText() { 12788 if (mEditor != null) { 12789 // Hide the toolbar before changing the selection to avoid flickering. 12790 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 12791 } 12792 final int length = mText.length(); 12793 Selection.setSelection(mSpannable, 0, length); 12794 return length > 0; 12795 } 12796 replaceSelectionWithText(CharSequence text)12797 void replaceSelectionWithText(CharSequence text) { 12798 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 12799 } 12800 12801 /** 12802 * Paste clipboard content between min and max positions. 12803 */ paste(int min, int max, boolean withFormatting)12804 private void paste(int min, int max, boolean withFormatting) { 12805 ClipboardManager clipboard = getClipboardManagerForUser(); 12806 ClipData clip = clipboard.getPrimaryClip(); 12807 if (clip != null) { 12808 boolean didFirst = false; 12809 for (int i = 0; i < clip.getItemCount(); i++) { 12810 final CharSequence paste; 12811 if (withFormatting) { 12812 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 12813 } else { 12814 // Get an item as text and remove all spans by toString(). 12815 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 12816 paste = (text instanceof Spanned) ? text.toString() : text; 12817 } 12818 if (paste != null) { 12819 if (!didFirst) { 12820 Selection.setSelection(mSpannable, max); 12821 ((Editable) mText).replace(min, max, paste); 12822 didFirst = true; 12823 } else { 12824 ((Editable) mText).insert(getSelectionEnd(), "\n"); 12825 ((Editable) mText).insert(getSelectionEnd(), paste); 12826 } 12827 } 12828 } 12829 sLastCutCopyOrTextChangedTime = 0; 12830 } 12831 } 12832 shareSelectedText()12833 private void shareSelectedText() { 12834 String selectedText = getSelectedText(); 12835 if (selectedText != null && !selectedText.isEmpty()) { 12836 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 12837 sharingIntent.setType("text/plain"); 12838 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 12839 selectedText = TextUtils.trimToParcelableSize(selectedText); 12840 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 12841 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 12842 Selection.setSelection(mSpannable, getSelectionEnd()); 12843 } 12844 } 12845 12846 @CheckResult setPrimaryClip(ClipData clip)12847 private boolean setPrimaryClip(ClipData clip) { 12848 ClipboardManager clipboard = getClipboardManagerForUser(); 12849 try { 12850 clipboard.setPrimaryClip(clip); 12851 } catch (Throwable t) { 12852 return false; 12853 } 12854 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 12855 return true; 12856 } 12857 12858 /** 12859 * Get the character offset closest to the specified absolute position. A typical use case is to 12860 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 12861 * 12862 * @param x The horizontal absolute position of a point on screen 12863 * @param y The vertical absolute position of a point on screen 12864 * @return the character offset for the character whose position is closest to the specified 12865 * position. Returns -1 if there is no layout. 12866 */ getOffsetForPosition(float x, float y)12867 public int getOffsetForPosition(float x, float y) { 12868 if (getLayout() == null) return -1; 12869 final int line = getLineAtCoordinate(y); 12870 final int offset = getOffsetAtCoordinate(line, x); 12871 return offset; 12872 } 12873 convertToLocalHorizontalCoordinate(float x)12874 float convertToLocalHorizontalCoordinate(float x) { 12875 x -= getTotalPaddingLeft(); 12876 // Clamp the position to inside of the view. 12877 x = Math.max(0.0f, x); 12878 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 12879 x += getScrollX(); 12880 return x; 12881 } 12882 12883 @UnsupportedAppUsage getLineAtCoordinate(float y)12884 int getLineAtCoordinate(float y) { 12885 y -= getTotalPaddingTop(); 12886 // Clamp the position to inside of the view. 12887 y = Math.max(0.0f, y); 12888 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 12889 y += getScrollY(); 12890 return getLayout().getLineForVertical((int) y); 12891 } 12892 getLineAtCoordinateUnclamped(float y)12893 int getLineAtCoordinateUnclamped(float y) { 12894 y -= getTotalPaddingTop(); 12895 y += getScrollY(); 12896 return getLayout().getLineForVertical((int) y); 12897 } 12898 getOffsetAtCoordinate(int line, float x)12899 int getOffsetAtCoordinate(int line, float x) { 12900 x = convertToLocalHorizontalCoordinate(x); 12901 return getLayout().getOffsetForHorizontal(line, x); 12902 } 12903 12904 @Override onDragEvent(DragEvent event)12905 public boolean onDragEvent(DragEvent event) { 12906 switch (event.getAction()) { 12907 case DragEvent.ACTION_DRAG_STARTED: 12908 return mEditor != null && mEditor.hasInsertionController(); 12909 12910 case DragEvent.ACTION_DRAG_ENTERED: 12911 TextView.this.requestFocus(); 12912 return true; 12913 12914 case DragEvent.ACTION_DRAG_LOCATION: 12915 if (mText instanceof Spannable) { 12916 final int offset = getOffsetForPosition(event.getX(), event.getY()); 12917 Selection.setSelection(mSpannable, offset); 12918 } 12919 return true; 12920 12921 case DragEvent.ACTION_DROP: 12922 if (mEditor != null) mEditor.onDrop(event); 12923 return true; 12924 12925 case DragEvent.ACTION_DRAG_ENDED: 12926 case DragEvent.ACTION_DRAG_EXITED: 12927 default: 12928 return true; 12929 } 12930 } 12931 isInBatchEditMode()12932 boolean isInBatchEditMode() { 12933 if (mEditor == null) return false; 12934 final Editor.InputMethodState ims = mEditor.mInputMethodState; 12935 if (ims != null) { 12936 return ims.mBatchEditNesting > 0; 12937 } 12938 return mEditor.mInBatchEditControllers; 12939 } 12940 12941 @Override onRtlPropertiesChanged(int layoutDirection)12942 public void onRtlPropertiesChanged(int layoutDirection) { 12943 super.onRtlPropertiesChanged(layoutDirection); 12944 12945 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 12946 if (mTextDir != newTextDir) { 12947 mTextDir = newTextDir; 12948 if (mLayout != null) { 12949 checkForRelayout(); 12950 } 12951 } 12952 } 12953 12954 /** 12955 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 12956 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 12957 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 12958 * return value may not be the same as the one TextView uses if the View's layout direction is 12959 * not resolved or detached from parent root view. 12960 */ getTextDirectionHeuristic()12961 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 12962 if (hasPasswordTransformationMethod()) { 12963 // passwords fields should be LTR 12964 return TextDirectionHeuristics.LTR; 12965 } 12966 12967 if (mEditor != null 12968 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 12969 == EditorInfo.TYPE_CLASS_PHONE) { 12970 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 12971 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 12972 // RTL digits. 12973 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 12974 final String zero = symbols.getDigitStrings()[0]; 12975 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 12976 // direction. 12977 final int firstCodepoint = zero.codePointAt(0); 12978 final byte digitDirection = Character.getDirectionality(firstCodepoint); 12979 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 12980 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 12981 return TextDirectionHeuristics.RTL; 12982 } else { 12983 return TextDirectionHeuristics.LTR; 12984 } 12985 } 12986 12987 // Always need to resolve layout direction first 12988 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 12989 12990 // Now, we can select the heuristic 12991 switch (getTextDirection()) { 12992 default: 12993 case TEXT_DIRECTION_FIRST_STRONG: 12994 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 12995 TextDirectionHeuristics.FIRSTSTRONG_LTR); 12996 case TEXT_DIRECTION_ANY_RTL: 12997 return TextDirectionHeuristics.ANYRTL_LTR; 12998 case TEXT_DIRECTION_LTR: 12999 return TextDirectionHeuristics.LTR; 13000 case TEXT_DIRECTION_RTL: 13001 return TextDirectionHeuristics.RTL; 13002 case TEXT_DIRECTION_LOCALE: 13003 return TextDirectionHeuristics.LOCALE; 13004 case TEXT_DIRECTION_FIRST_STRONG_LTR: 13005 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 13006 case TEXT_DIRECTION_FIRST_STRONG_RTL: 13007 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 13008 } 13009 } 13010 13011 /** 13012 * @hide 13013 */ 13014 @Override onResolveDrawables(int layoutDirection)13015 public void onResolveDrawables(int layoutDirection) { 13016 // No need to resolve twice 13017 if (mLastLayoutDirection == layoutDirection) { 13018 return; 13019 } 13020 mLastLayoutDirection = layoutDirection; 13021 13022 // Resolve drawables 13023 if (mDrawables != null) { 13024 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 13025 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 13026 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 13027 applyCompoundDrawableTint(); 13028 } 13029 } 13030 } 13031 13032 /** 13033 * Prepares a drawable for display by propagating layout direction and 13034 * drawable state. 13035 * 13036 * @param dr the drawable to prepare 13037 */ prepareDrawableForDisplay(@ullable Drawable dr)13038 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 13039 if (dr == null) { 13040 return; 13041 } 13042 13043 dr.setLayoutDirection(getLayoutDirection()); 13044 13045 if (dr.isStateful()) { 13046 dr.setState(getDrawableState()); 13047 dr.jumpToCurrentState(); 13048 } 13049 } 13050 13051 /** 13052 * @hide 13053 */ resetResolvedDrawables()13054 protected void resetResolvedDrawables() { 13055 super.resetResolvedDrawables(); 13056 mLastLayoutDirection = -1; 13057 } 13058 13059 /** 13060 * @hide 13061 */ viewClicked(InputMethodManager imm)13062 protected void viewClicked(InputMethodManager imm) { 13063 if (imm != null) { 13064 imm.viewClicked(this); 13065 } 13066 } 13067 13068 /** 13069 * Deletes the range of text [start, end[. 13070 * @hide 13071 */ 13072 @UnsupportedAppUsage deleteText_internal(int start, int end)13073 protected void deleteText_internal(int start, int end) { 13074 ((Editable) mText).delete(start, end); 13075 } 13076 13077 /** 13078 * Replaces the range of text [start, end[ by replacement text 13079 * @hide 13080 */ replaceText_internal(int start, int end, CharSequence text)13081 protected void replaceText_internal(int start, int end, CharSequence text) { 13082 ((Editable) mText).replace(start, end, text); 13083 } 13084 13085 /** 13086 * Sets a span on the specified range of text 13087 * @hide 13088 */ setSpan_internal(Object span, int start, int end, int flags)13089 protected void setSpan_internal(Object span, int start, int end, int flags) { 13090 ((Editable) mText).setSpan(span, start, end, flags); 13091 } 13092 13093 /** 13094 * Moves the cursor to the specified offset position in text 13095 * @hide 13096 */ setCursorPosition_internal(int start, int end)13097 protected void setCursorPosition_internal(int start, int end) { 13098 Selection.setSelection(((Editable) mText), start, end); 13099 } 13100 13101 /** 13102 * An Editor should be created as soon as any of the editable-specific fields (grouped 13103 * inside the Editor object) is assigned to a non-default value. 13104 * This method will create the Editor if needed. 13105 * 13106 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 13107 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 13108 * Editor for backward compatibility, as soon as one of these fields is assigned. 13109 * 13110 * Also note that for performance reasons, the mEditor is created when needed, but not 13111 * reset when no more edit-specific fields are needed. 13112 */ 13113 @UnsupportedAppUsage createEditorIfNeeded()13114 private void createEditorIfNeeded() { 13115 if (mEditor == null) { 13116 mEditor = new Editor(this); 13117 } 13118 } 13119 13120 /** 13121 * @hide 13122 */ 13123 @Override 13124 @UnsupportedAppUsage getIterableTextForAccessibility()13125 public CharSequence getIterableTextForAccessibility() { 13126 return mText; 13127 } 13128 ensureIterableTextForAccessibilitySelectable()13129 private void ensureIterableTextForAccessibilitySelectable() { 13130 if (!(mText instanceof Spannable)) { 13131 setText(mText, BufferType.SPANNABLE); 13132 } 13133 } 13134 13135 /** 13136 * @hide 13137 */ 13138 @Override getIteratorForGranularity(int granularity)13139 public TextSegmentIterator getIteratorForGranularity(int granularity) { 13140 switch (granularity) { 13141 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 13142 Spannable text = (Spannable) getIterableTextForAccessibility(); 13143 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13144 AccessibilityIterators.LineTextSegmentIterator iterator = 13145 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 13146 iterator.initialize(text, getLayout()); 13147 return iterator; 13148 } 13149 } break; 13150 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 13151 Spannable text = (Spannable) getIterableTextForAccessibility(); 13152 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13153 AccessibilityIterators.PageTextSegmentIterator iterator = 13154 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 13155 iterator.initialize(this); 13156 return iterator; 13157 } 13158 } break; 13159 } 13160 return super.getIteratorForGranularity(granularity); 13161 } 13162 13163 /** 13164 * @hide 13165 */ 13166 @Override getAccessibilitySelectionStart()13167 public int getAccessibilitySelectionStart() { 13168 return getSelectionStart(); 13169 } 13170 13171 /** 13172 * @hide 13173 */ isAccessibilitySelectionExtendable()13174 public boolean isAccessibilitySelectionExtendable() { 13175 return true; 13176 } 13177 13178 /** 13179 * @hide 13180 */ 13181 @Override getAccessibilitySelectionEnd()13182 public int getAccessibilitySelectionEnd() { 13183 return getSelectionEnd(); 13184 } 13185 13186 /** 13187 * @hide 13188 */ 13189 @Override setAccessibilitySelection(int start, int end)13190 public void setAccessibilitySelection(int start, int end) { 13191 if (getAccessibilitySelectionStart() == start 13192 && getAccessibilitySelectionEnd() == end) { 13193 return; 13194 } 13195 CharSequence text = getIterableTextForAccessibility(); 13196 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13197 Selection.setSelection((Spannable) text, start, end); 13198 } else { 13199 Selection.removeSelection((Spannable) text); 13200 } 13201 // Hide all selection controllers used for adjusting selection 13202 // since we are doing so explicitlty by other means and these 13203 // controllers interact with how selection behaves. 13204 if (mEditor != null) { 13205 mEditor.hideCursorAndSpanControllers(); 13206 mEditor.stopTextActionMode(); 13207 } 13208 } 13209 13210 /** @hide */ 13211 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13212 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13213 super.encodeProperties(stream); 13214 13215 TruncateAt ellipsize = getEllipsize(); 13216 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13217 stream.addProperty("text:textSize", getTextSize()); 13218 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13219 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13220 stream.addProperty("text:selectionStart", getSelectionStart()); 13221 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13222 stream.addProperty("text:curTextColor", mCurTextColor); 13223 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 13224 stream.addProperty("text:gravity", mGravity); 13225 } 13226 13227 /** 13228 * User interface state that is stored by TextView for implementing 13229 * {@link View#onSaveInstanceState}. 13230 */ 13231 public static class SavedState extends BaseSavedState { 13232 int selStart = -1; 13233 int selEnd = -1; 13234 @UnsupportedAppUsage 13235 CharSequence text; 13236 boolean frozenWithFocus; 13237 CharSequence error; 13238 ParcelableParcel editorState; // Optional state from Editor. 13239 SavedState(Parcelable superState)13240 SavedState(Parcelable superState) { 13241 super(superState); 13242 } 13243 13244 @Override writeToParcel(Parcel out, int flags)13245 public void writeToParcel(Parcel out, int flags) { 13246 super.writeToParcel(out, flags); 13247 out.writeInt(selStart); 13248 out.writeInt(selEnd); 13249 out.writeInt(frozenWithFocus ? 1 : 0); 13250 TextUtils.writeToParcel(text, out, flags); 13251 13252 if (error == null) { 13253 out.writeInt(0); 13254 } else { 13255 out.writeInt(1); 13256 TextUtils.writeToParcel(error, out, flags); 13257 } 13258 13259 if (editorState == null) { 13260 out.writeInt(0); 13261 } else { 13262 out.writeInt(1); 13263 editorState.writeToParcel(out, flags); 13264 } 13265 } 13266 13267 @Override toString()13268 public String toString() { 13269 String str = "TextView.SavedState{" 13270 + Integer.toHexString(System.identityHashCode(this)) 13271 + " start=" + selStart + " end=" + selEnd; 13272 if (text != null) { 13273 str += " text=" + text; 13274 } 13275 return str + "}"; 13276 } 13277 13278 @SuppressWarnings("hiding") 13279 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13280 new Parcelable.Creator<SavedState>() { 13281 public SavedState createFromParcel(Parcel in) { 13282 return new SavedState(in); 13283 } 13284 13285 public SavedState[] newArray(int size) { 13286 return new SavedState[size]; 13287 } 13288 }; 13289 SavedState(Parcel in)13290 private SavedState(Parcel in) { 13291 super(in); 13292 selStart = in.readInt(); 13293 selEnd = in.readInt(); 13294 frozenWithFocus = (in.readInt() != 0); 13295 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13296 13297 if (in.readInt() != 0) { 13298 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13299 } 13300 13301 if (in.readInt() != 0) { 13302 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13303 } 13304 } 13305 } 13306 13307 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13308 private char[] mChars; 13309 private int mStart, mLength; 13310 CharWrapper(char[] chars, int start, int len)13311 public CharWrapper(char[] chars, int start, int len) { 13312 mChars = chars; 13313 mStart = start; 13314 mLength = len; 13315 } 13316 set(char[] chars, int start, int len)13317 /* package */ void set(char[] chars, int start, int len) { 13318 mChars = chars; 13319 mStart = start; 13320 mLength = len; 13321 } 13322 length()13323 public int length() { 13324 return mLength; 13325 } 13326 charAt(int off)13327 public char charAt(int off) { 13328 return mChars[off + mStart]; 13329 } 13330 13331 @Override toString()13332 public String toString() { 13333 return new String(mChars, mStart, mLength); 13334 } 13335 subSequence(int start, int end)13336 public CharSequence subSequence(int start, int end) { 13337 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13338 throw new IndexOutOfBoundsException(start + ", " + end); 13339 } 13340 13341 return new String(mChars, start + mStart, end - start); 13342 } 13343 getChars(int start, int end, char[] buf, int off)13344 public void getChars(int start, int end, char[] buf, int off) { 13345 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13346 throw new IndexOutOfBoundsException(start + ", " + end); 13347 } 13348 13349 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13350 } 13351 13352 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13353 public void drawText(BaseCanvas c, int start, int end, 13354 float x, float y, Paint p) { 13355 c.drawText(mChars, start + mStart, end - start, x, y, p); 13356 } 13357 13358 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13359 public void drawTextRun(BaseCanvas c, int start, int end, 13360 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13361 int count = end - start; 13362 int contextCount = contextEnd - contextStart; 13363 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13364 contextCount, x, y, isRtl, p); 13365 } 13366 measureText(int start, int end, Paint p)13367 public float measureText(int start, int end, Paint p) { 13368 return p.measureText(mChars, start + mStart, end - start); 13369 } 13370 getTextWidths(int start, int end, float[] widths, Paint p)13371 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13372 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13373 } 13374 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13375 public float getTextRunAdvances(int start, int end, int contextStart, 13376 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13377 Paint p) { 13378 int count = end - start; 13379 int contextCount = contextEnd - contextStart; 13380 return p.getTextRunAdvances(mChars, start + mStart, count, 13381 contextStart + mStart, contextCount, isRtl, advances, 13382 advancesIndex); 13383 } 13384 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13385 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13386 int offset, int cursorOpt, Paint p) { 13387 int contextCount = contextEnd - contextStart; 13388 return p.getTextRunCursor(mChars, contextStart + mStart, 13389 contextCount, isRtl, offset + mStart, cursorOpt); 13390 } 13391 } 13392 13393 private static final class Marquee { 13394 // TODO: Add an option to configure this 13395 private static final float MARQUEE_DELTA_MAX = 0.07f; 13396 private static final int MARQUEE_DELAY = 1200; 13397 private static final int MARQUEE_DP_PER_SECOND = 30; 13398 13399 private static final byte MARQUEE_STOPPED = 0x0; 13400 private static final byte MARQUEE_STARTING = 0x1; 13401 private static final byte MARQUEE_RUNNING = 0x2; 13402 13403 private final WeakReference<TextView> mView; 13404 private final Choreographer mChoreographer; 13405 13406 private byte mStatus = MARQUEE_STOPPED; 13407 private final float mPixelsPerMs; 13408 private float mMaxScroll; 13409 private float mMaxFadeScroll; 13410 private float mGhostStart; 13411 private float mGhostOffset; 13412 private float mFadeStop; 13413 private int mRepeatLimit; 13414 13415 private float mScroll; 13416 private long mLastAnimationMs; 13417 Marquee(TextView v)13418 Marquee(TextView v) { 13419 final float density = v.getContext().getResources().getDisplayMetrics().density; 13420 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13421 mView = new WeakReference<TextView>(v); 13422 mChoreographer = Choreographer.getInstance(); 13423 } 13424 13425 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13426 @Override 13427 public void doFrame(long frameTimeNanos) { 13428 tick(); 13429 } 13430 }; 13431 13432 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13433 @Override 13434 public void doFrame(long frameTimeNanos) { 13435 mStatus = MARQUEE_RUNNING; 13436 mLastAnimationMs = mChoreographer.getFrameTime(); 13437 tick(); 13438 } 13439 }; 13440 13441 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13442 @Override 13443 public void doFrame(long frameTimeNanos) { 13444 if (mStatus == MARQUEE_RUNNING) { 13445 if (mRepeatLimit >= 0) { 13446 mRepeatLimit--; 13447 } 13448 start(mRepeatLimit); 13449 } 13450 } 13451 }; 13452 tick()13453 void tick() { 13454 if (mStatus != MARQUEE_RUNNING) { 13455 return; 13456 } 13457 13458 mChoreographer.removeFrameCallback(mTickCallback); 13459 13460 final TextView textView = mView.get(); 13461 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13462 long currentMs = mChoreographer.getFrameTime(); 13463 long deltaMs = currentMs - mLastAnimationMs; 13464 mLastAnimationMs = currentMs; 13465 float deltaPx = deltaMs * mPixelsPerMs; 13466 mScroll += deltaPx; 13467 if (mScroll > mMaxScroll) { 13468 mScroll = mMaxScroll; 13469 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13470 } else { 13471 mChoreographer.postFrameCallback(mTickCallback); 13472 } 13473 textView.invalidate(); 13474 } 13475 } 13476 stop()13477 void stop() { 13478 mStatus = MARQUEE_STOPPED; 13479 mChoreographer.removeFrameCallback(mStartCallback); 13480 mChoreographer.removeFrameCallback(mRestartCallback); 13481 mChoreographer.removeFrameCallback(mTickCallback); 13482 resetScroll(); 13483 } 13484 resetScroll()13485 private void resetScroll() { 13486 mScroll = 0.0f; 13487 final TextView textView = mView.get(); 13488 if (textView != null) textView.invalidate(); 13489 } 13490 start(int repeatLimit)13491 void start(int repeatLimit) { 13492 if (repeatLimit == 0) { 13493 stop(); 13494 return; 13495 } 13496 mRepeatLimit = repeatLimit; 13497 final TextView textView = mView.get(); 13498 if (textView != null && textView.mLayout != null) { 13499 mStatus = MARQUEE_STARTING; 13500 mScroll = 0.0f; 13501 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13502 - textView.getCompoundPaddingRight(); 13503 final float lineWidth = textView.mLayout.getLineWidth(0); 13504 final float gap = textWidth / 3.0f; 13505 mGhostStart = lineWidth - textWidth + gap; 13506 mMaxScroll = mGhostStart + textWidth; 13507 mGhostOffset = lineWidth + gap; 13508 mFadeStop = lineWidth + textWidth / 6.0f; 13509 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13510 13511 textView.invalidate(); 13512 mChoreographer.postFrameCallback(mStartCallback); 13513 } 13514 } 13515 getGhostOffset()13516 float getGhostOffset() { 13517 return mGhostOffset; 13518 } 13519 getScroll()13520 float getScroll() { 13521 return mScroll; 13522 } 13523 getMaxFadeScroll()13524 float getMaxFadeScroll() { 13525 return mMaxFadeScroll; 13526 } 13527 shouldDrawLeftFade()13528 boolean shouldDrawLeftFade() { 13529 return mScroll <= mFadeStop; 13530 } 13531 shouldDrawGhost()13532 boolean shouldDrawGhost() { 13533 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13534 } 13535 isRunning()13536 boolean isRunning() { 13537 return mStatus == MARQUEE_RUNNING; 13538 } 13539 isStopped()13540 boolean isStopped() { 13541 return mStatus == MARQUEE_STOPPED; 13542 } 13543 } 13544 13545 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13546 13547 private CharSequence mBeforeText; 13548 beforeTextChanged(CharSequence buffer, int start, int before, int after)13549 public void beforeTextChanged(CharSequence buffer, int start, 13550 int before, int after) { 13551 if (DEBUG_EXTRACT) { 13552 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13553 + " before=" + before + " after=" + after + ": " + buffer); 13554 } 13555 13556 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13557 mBeforeText = mTransformed.toString(); 13558 } 13559 13560 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13561 } 13562 onTextChanged(CharSequence buffer, int start, int before, int after)13563 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13564 if (DEBUG_EXTRACT) { 13565 Log.v(LOG_TAG, "onTextChanged start=" + start 13566 + " before=" + before + " after=" + after + ": " + buffer); 13567 } 13568 TextView.this.handleTextChanged(buffer, start, before, after); 13569 13570 if (AccessibilityManager.getInstance(mContext).isEnabled() 13571 && (isFocused() || isSelected() && isShown())) { 13572 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13573 mBeforeText = null; 13574 } 13575 } 13576 afterTextChanged(Editable buffer)13577 public void afterTextChanged(Editable buffer) { 13578 if (DEBUG_EXTRACT) { 13579 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13580 } 13581 TextView.this.sendAfterTextChanged(buffer); 13582 13583 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13584 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13585 } 13586 } 13587 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13588 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13589 if (DEBUG_EXTRACT) { 13590 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13591 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13592 } 13593 TextView.this.spanChange(buf, what, s, st, e, en); 13594 } 13595 onSpanAdded(Spannable buf, Object what, int s, int e)13596 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13597 if (DEBUG_EXTRACT) { 13598 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13599 } 13600 TextView.this.spanChange(buf, what, -1, s, -1, e); 13601 } 13602 onSpanRemoved(Spannable buf, Object what, int s, int e)13603 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13604 if (DEBUG_EXTRACT) { 13605 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13606 } 13607 TextView.this.spanChange(buf, what, s, -1, e, -1); 13608 } 13609 } 13610 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13611 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 13612 if (msgFormat == null) { 13613 Log.d(LOG_TAG, location); 13614 } else { 13615 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 13616 } 13617 } 13618 } 13619