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.content.res.Configuration.ORIENTATION_PORTRAIT; 21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT; 22 import static android.view.ContentInfo.SOURCE_AUTOFILL; 23 import static android.view.ContentInfo.SOURCE_CLIPBOARD; 24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT; 25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; 26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 29 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 30 31 import android.R; 32 import android.annotation.CallSuper; 33 import android.annotation.CheckResult; 34 import android.annotation.ColorInt; 35 import android.annotation.DrawableRes; 36 import android.annotation.FloatRange; 37 import android.annotation.IntDef; 38 import android.annotation.IntRange; 39 import android.annotation.NonNull; 40 import android.annotation.Nullable; 41 import android.annotation.Px; 42 import android.annotation.RequiresPermission; 43 import android.annotation.Size; 44 import android.annotation.StringRes; 45 import android.annotation.StyleRes; 46 import android.annotation.TestApi; 47 import android.annotation.XmlRes; 48 import android.app.Activity; 49 import android.app.PendingIntent; 50 import android.app.assist.AssistStructure; 51 import android.app.compat.CompatChanges; 52 import android.compat.annotation.ChangeId; 53 import android.compat.annotation.EnabledSince; 54 import android.compat.annotation.UnsupportedAppUsage; 55 import android.content.ClipData; 56 import android.content.ClipDescription; 57 import android.content.ClipboardManager; 58 import android.content.Context; 59 import android.content.Intent; 60 import android.content.UndoManager; 61 import android.content.pm.PackageManager; 62 import android.content.res.ColorStateList; 63 import android.content.res.CompatibilityInfo; 64 import android.content.res.Configuration; 65 import android.content.res.Resources; 66 import android.content.res.TypedArray; 67 import android.content.res.XmlResourceParser; 68 import android.graphics.BaseCanvas; 69 import android.graphics.BlendMode; 70 import android.graphics.Canvas; 71 import android.graphics.Insets; 72 import android.graphics.Paint; 73 import android.graphics.Paint.FontMetricsInt; 74 import android.graphics.Path; 75 import android.graphics.PorterDuff; 76 import android.graphics.Rect; 77 import android.graphics.RectF; 78 import android.graphics.Typeface; 79 import android.graphics.drawable.Drawable; 80 import android.graphics.fonts.FontStyle; 81 import android.graphics.fonts.FontVariationAxis; 82 import android.graphics.text.LineBreakConfig; 83 import android.icu.text.DecimalFormatSymbols; 84 import android.os.AsyncTask; 85 import android.os.Build; 86 import android.os.Build.VERSION_CODES; 87 import android.os.Bundle; 88 import android.os.Handler; 89 import android.os.LocaleList; 90 import android.os.Parcel; 91 import android.os.Parcelable; 92 import android.os.ParcelableParcel; 93 import android.os.Process; 94 import android.os.SystemClock; 95 import android.os.UserHandle; 96 import android.provider.Settings; 97 import android.text.BoringLayout; 98 import android.text.DynamicLayout; 99 import android.text.Editable; 100 import android.text.GetChars; 101 import android.text.GraphicsOperations; 102 import android.text.InputFilter; 103 import android.text.InputType; 104 import android.text.Layout; 105 import android.text.ParcelableSpan; 106 import android.text.PrecomputedText; 107 import android.text.Selection; 108 import android.text.SpanWatcher; 109 import android.text.Spannable; 110 import android.text.SpannableStringBuilder; 111 import android.text.Spanned; 112 import android.text.SpannedString; 113 import android.text.StaticLayout; 114 import android.text.TextDirectionHeuristic; 115 import android.text.TextDirectionHeuristics; 116 import android.text.TextPaint; 117 import android.text.TextUtils; 118 import android.text.TextUtils.TruncateAt; 119 import android.text.TextWatcher; 120 import android.text.method.AllCapsTransformationMethod; 121 import android.text.method.ArrowKeyMovementMethod; 122 import android.text.method.DateKeyListener; 123 import android.text.method.DateTimeKeyListener; 124 import android.text.method.DialerKeyListener; 125 import android.text.method.DigitsKeyListener; 126 import android.text.method.KeyListener; 127 import android.text.method.LinkMovementMethod; 128 import android.text.method.MetaKeyKeyListener; 129 import android.text.method.MovementMethod; 130 import android.text.method.PasswordTransformationMethod; 131 import android.text.method.SingleLineTransformationMethod; 132 import android.text.method.TextKeyListener; 133 import android.text.method.TimeKeyListener; 134 import android.text.method.TransformationMethod; 135 import android.text.method.TransformationMethod2; 136 import android.text.method.WordIterator; 137 import android.text.style.CharacterStyle; 138 import android.text.style.ClickableSpan; 139 import android.text.style.ParagraphStyle; 140 import android.text.style.SpellCheckSpan; 141 import android.text.style.SuggestionSpan; 142 import android.text.style.URLSpan; 143 import android.text.style.UpdateAppearance; 144 import android.text.util.Linkify; 145 import android.util.AttributeSet; 146 import android.util.DisplayMetrics; 147 import android.util.IntArray; 148 import android.util.Log; 149 import android.util.SparseIntArray; 150 import android.util.TypedValue; 151 import android.view.AccessibilityIterators.TextSegmentIterator; 152 import android.view.ActionMode; 153 import android.view.Choreographer; 154 import android.view.ContentInfo; 155 import android.view.ContextMenu; 156 import android.view.DragEvent; 157 import android.view.Gravity; 158 import android.view.HapticFeedbackConstants; 159 import android.view.InputDevice; 160 import android.view.KeyCharacterMap; 161 import android.view.KeyEvent; 162 import android.view.MotionEvent; 163 import android.view.PointerIcon; 164 import android.view.View; 165 import android.view.ViewConfiguration; 166 import android.view.ViewDebug; 167 import android.view.ViewGroup.LayoutParams; 168 import android.view.ViewHierarchyEncoder; 169 import android.view.ViewParent; 170 import android.view.ViewRootImpl; 171 import android.view.ViewStructure; 172 import android.view.ViewTreeObserver; 173 import android.view.accessibility.AccessibilityEvent; 174 import android.view.accessibility.AccessibilityManager; 175 import android.view.accessibility.AccessibilityNodeInfo; 176 import android.view.animation.AnimationUtils; 177 import android.view.autofill.AutofillManager; 178 import android.view.autofill.AutofillValue; 179 import android.view.contentcapture.ContentCaptureManager; 180 import android.view.contentcapture.ContentCaptureSession; 181 import android.view.inputmethod.BaseInputConnection; 182 import android.view.inputmethod.CompletionInfo; 183 import android.view.inputmethod.CorrectionInfo; 184 import android.view.inputmethod.CursorAnchorInfo; 185 import android.view.inputmethod.EditorInfo; 186 import android.view.inputmethod.ExtractedText; 187 import android.view.inputmethod.ExtractedTextRequest; 188 import android.view.inputmethod.InputConnection; 189 import android.view.inputmethod.InputMethodManager; 190 import android.view.inspector.InspectableProperty; 191 import android.view.inspector.InspectableProperty.EnumEntry; 192 import android.view.inspector.InspectableProperty.FlagEntry; 193 import android.view.textclassifier.TextClassification; 194 import android.view.textclassifier.TextClassificationContext; 195 import android.view.textclassifier.TextClassificationManager; 196 import android.view.textclassifier.TextClassifier; 197 import android.view.textclassifier.TextLinks; 198 import android.view.textservice.SpellCheckerSubtype; 199 import android.view.textservice.TextServicesManager; 200 import android.view.translation.TranslationRequestValue; 201 import android.view.translation.TranslationSpec; 202 import android.view.translation.UiTranslationController; 203 import android.view.translation.ViewTranslationCallback; 204 import android.view.translation.ViewTranslationRequest; 205 import android.widget.RemoteViews.RemoteView; 206 207 import com.android.internal.accessibility.util.AccessibilityUtils; 208 import com.android.internal.annotations.VisibleForTesting; 209 import com.android.internal.inputmethod.EditableInputConnection; 210 import com.android.internal.logging.MetricsLogger; 211 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 212 import com.android.internal.util.ArrayUtils; 213 import com.android.internal.util.FastMath; 214 import com.android.internal.util.Preconditions; 215 216 import libcore.util.EmptyArray; 217 218 import org.xmlpull.v1.XmlPullParserException; 219 220 import java.io.IOException; 221 import java.lang.annotation.Retention; 222 import java.lang.annotation.RetentionPolicy; 223 import java.lang.ref.WeakReference; 224 import java.util.ArrayList; 225 import java.util.Arrays; 226 import java.util.Locale; 227 import java.util.Objects; 228 import java.util.concurrent.CompletableFuture; 229 import java.util.concurrent.TimeUnit; 230 import java.util.function.Consumer; 231 import java.util.function.Supplier; 232 233 /** 234 * A user interface element that displays text to the user. 235 * To provide user-editable text, see {@link EditText}. 236 * <p> 237 * The following code sample shows a typical use, with an XML layout 238 * and code to modify the contents of the text view: 239 * </p> 240 241 * <pre> 242 * <LinearLayout 243 xmlns:android="http://schemas.android.com/apk/res/android" 244 android:layout_width="match_parent" 245 android:layout_height="match_parent"> 246 * <TextView 247 * android:id="@+id/text_view_id" 248 * android:layout_height="wrap_content" 249 * android:layout_width="wrap_content" 250 * android:text="@string/hello" /> 251 * </LinearLayout> 252 * </pre> 253 * <p> 254 * This code sample demonstrates how to modify the contents of the text view 255 * defined in the previous XML layout: 256 * </p> 257 * <pre> 258 * public class MainActivity extends Activity { 259 * 260 * protected void onCreate(Bundle savedInstanceState) { 261 * super.onCreate(savedInstanceState); 262 * setContentView(R.layout.activity_main); 263 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 264 * helloTextView.setText(R.string.user_greeting); 265 * } 266 * } 267 * </pre> 268 * <p> 269 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 270 * </p> 271 * <p> 272 * <b>XML attributes</b> 273 * <p> 274 * See {@link android.R.styleable#TextView TextView Attributes}, 275 * {@link android.R.styleable#View View Attributes} 276 * 277 * @attr ref android.R.styleable#TextView_text 278 * @attr ref android.R.styleable#TextView_bufferType 279 * @attr ref android.R.styleable#TextView_hint 280 * @attr ref android.R.styleable#TextView_textColor 281 * @attr ref android.R.styleable#TextView_textColorHighlight 282 * @attr ref android.R.styleable#TextView_textColorHint 283 * @attr ref android.R.styleable#TextView_textAppearance 284 * @attr ref android.R.styleable#TextView_textColorLink 285 * @attr ref android.R.styleable#TextView_textFontWeight 286 * @attr ref android.R.styleable#TextView_textSize 287 * @attr ref android.R.styleable#TextView_textScaleX 288 * @attr ref android.R.styleable#TextView_fontFamily 289 * @attr ref android.R.styleable#TextView_typeface 290 * @attr ref android.R.styleable#TextView_textStyle 291 * @attr ref android.R.styleable#TextView_cursorVisible 292 * @attr ref android.R.styleable#TextView_maxLines 293 * @attr ref android.R.styleable#TextView_maxHeight 294 * @attr ref android.R.styleable#TextView_lines 295 * @attr ref android.R.styleable#TextView_height 296 * @attr ref android.R.styleable#TextView_minLines 297 * @attr ref android.R.styleable#TextView_minHeight 298 * @attr ref android.R.styleable#TextView_maxEms 299 * @attr ref android.R.styleable#TextView_maxWidth 300 * @attr ref android.R.styleable#TextView_ems 301 * @attr ref android.R.styleable#TextView_width 302 * @attr ref android.R.styleable#TextView_minEms 303 * @attr ref android.R.styleable#TextView_minWidth 304 * @attr ref android.R.styleable#TextView_gravity 305 * @attr ref android.R.styleable#TextView_scrollHorizontally 306 * @attr ref android.R.styleable#TextView_password 307 * @attr ref android.R.styleable#TextView_singleLine 308 * @attr ref android.R.styleable#TextView_selectAllOnFocus 309 * @attr ref android.R.styleable#TextView_includeFontPadding 310 * @attr ref android.R.styleable#TextView_maxLength 311 * @attr ref android.R.styleable#TextView_shadowColor 312 * @attr ref android.R.styleable#TextView_shadowDx 313 * @attr ref android.R.styleable#TextView_shadowDy 314 * @attr ref android.R.styleable#TextView_shadowRadius 315 * @attr ref android.R.styleable#TextView_autoLink 316 * @attr ref android.R.styleable#TextView_linksClickable 317 * @attr ref android.R.styleable#TextView_numeric 318 * @attr ref android.R.styleable#TextView_digits 319 * @attr ref android.R.styleable#TextView_phoneNumber 320 * @attr ref android.R.styleable#TextView_inputMethod 321 * @attr ref android.R.styleable#TextView_capitalize 322 * @attr ref android.R.styleable#TextView_autoText 323 * @attr ref android.R.styleable#TextView_editable 324 * @attr ref android.R.styleable#TextView_freezesText 325 * @attr ref android.R.styleable#TextView_ellipsize 326 * @attr ref android.R.styleable#TextView_drawableTop 327 * @attr ref android.R.styleable#TextView_drawableBottom 328 * @attr ref android.R.styleable#TextView_drawableRight 329 * @attr ref android.R.styleable#TextView_drawableLeft 330 * @attr ref android.R.styleable#TextView_drawableStart 331 * @attr ref android.R.styleable#TextView_drawableEnd 332 * @attr ref android.R.styleable#TextView_drawablePadding 333 * @attr ref android.R.styleable#TextView_drawableTint 334 * @attr ref android.R.styleable#TextView_drawableTintMode 335 * @attr ref android.R.styleable#TextView_lineSpacingExtra 336 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 337 * @attr ref android.R.styleable#TextView_justificationMode 338 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 339 * @attr ref android.R.styleable#TextView_inputType 340 * @attr ref android.R.styleable#TextView_imeOptions 341 * @attr ref android.R.styleable#TextView_privateImeOptions 342 * @attr ref android.R.styleable#TextView_imeActionLabel 343 * @attr ref android.R.styleable#TextView_imeActionId 344 * @attr ref android.R.styleable#TextView_editorExtras 345 * @attr ref android.R.styleable#TextView_elegantTextHeight 346 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 347 * @attr ref android.R.styleable#TextView_letterSpacing 348 * @attr ref android.R.styleable#TextView_fontFeatureSettings 349 * @attr ref android.R.styleable#TextView_fontVariationSettings 350 * @attr ref android.R.styleable#TextView_breakStrategy 351 * @attr ref android.R.styleable#TextView_hyphenationFrequency 352 * @attr ref android.R.styleable#TextView_lineBreakStyle 353 * @attr ref android.R.styleable#TextView_lineBreakWordStyle 354 * @attr ref android.R.styleable#TextView_autoSizeTextType 355 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 356 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 357 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 358 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 359 * @attr ref android.R.styleable#TextView_textCursorDrawable 360 * @attr ref android.R.styleable#TextView_textSelectHandle 361 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 362 * @attr ref android.R.styleable#TextView_textSelectHandleRight 363 * @attr ref android.R.styleable#TextView_allowUndo 364 * @attr ref android.R.styleable#TextView_enabled 365 */ 366 @RemoteView 367 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 368 static final String LOG_TAG = "TextView"; 369 static final boolean DEBUG_EXTRACT = false; 370 static final boolean DEBUG_CURSOR = false; 371 372 private static final float[] TEMP_POSITION = new float[2]; 373 374 // Enum for the "typeface" XML parameter. 375 // TODO: How can we get this from the XML instead of hardcoding it here? 376 /** @hide */ 377 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 378 @Retention(RetentionPolicy.SOURCE) 379 public @interface XMLTypefaceAttr{} 380 private static final int DEFAULT_TYPEFACE = -1; 381 private static final int SANS = 1; 382 private static final int SERIF = 2; 383 private static final int MONOSPACE = 3; 384 385 // Enum for the "ellipsize" XML parameter. 386 private static final int ELLIPSIZE_NOT_SET = -1; 387 private static final int ELLIPSIZE_NONE = 0; 388 private static final int ELLIPSIZE_START = 1; 389 private static final int ELLIPSIZE_MIDDLE = 2; 390 private static final int ELLIPSIZE_END = 3; 391 private static final int ELLIPSIZE_MARQUEE = 4; 392 393 // Bitfield for the "numeric" XML parameter. 394 // TODO: How can we get this from the XML instead of hardcoding it here? 395 private static final int SIGNED = 2; 396 private static final int DECIMAL = 4; 397 398 /** 399 * Draw marquee text with fading edges as usual 400 */ 401 private static final int MARQUEE_FADE_NORMAL = 0; 402 403 /** 404 * Draw marquee text as ellipsize end while inactive instead of with the fade. 405 * (Useful for devices where the fade can be expensive if overdone) 406 */ 407 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 408 409 /** 410 * Draw marquee text with fading edges because it is currently active/animating. 411 */ 412 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 413 414 @UnsupportedAppUsage 415 private static final int LINES = 1; 416 private static final int EMS = LINES; 417 private static final int PIXELS = 2; 418 419 // Maximum text length for single line input. 420 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 421 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 422 423 private static final RectF TEMP_RECTF = new RectF(); 424 425 /** @hide */ 426 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 427 private static final int ANIMATED_SCROLL_GAP = 250; 428 429 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 430 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 431 432 private static final int CHANGE_WATCHER_PRIORITY = 100; 433 434 // New state used to change background based on whether this TextView is multiline. 435 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 436 437 // Accessibility action to share selected text. 438 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 439 440 /** 441 * @hide 442 */ 443 // Accessibility action start id for "process text" actions. 444 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 445 446 /** Accessibility action start id for "smart" actions. @hide */ 447 static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; 448 449 /** 450 * @hide 451 */ 452 @TestApi 453 public static final int PROCESS_TEXT_REQUEST_CODE = 100; 454 455 /** 456 * Return code of {@link #doKeyDown}. 457 */ 458 private static final int KEY_EVENT_NOT_HANDLED = 0; 459 private static final int KEY_EVENT_HANDLED = -1; 460 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 461 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 462 463 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 464 465 // The default value of the line break style. 466 private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; 467 468 // The default value of the line break word style. 469 private static final int DEFAULT_LINE_BREAK_WORD_STYLE = 470 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; 471 472 /** 473 * This change ID enables the fallback text line spacing (line height) for BoringLayout. 474 * @hide 475 */ 476 @ChangeId 477 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) 478 public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id 479 480 /** 481 * This change ID enables the fallback text line spacing (line height) for StaticLayout. 482 * @hide 483 */ 484 @ChangeId 485 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) 486 public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id 487 488 // System wide time for last cut, copy or text changed action. 489 static long sLastCutCopyOrTextChangedTime; 490 491 private ColorStateList mTextColor; 492 private ColorStateList mHintTextColor; 493 private ColorStateList mLinkTextColor; 494 @ViewDebug.ExportedProperty(category = "text") 495 496 /** 497 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 498 */ 499 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 500 private int mCurTextColor; 501 502 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 503 private int mCurHintTextColor; 504 private boolean mFreezesText; 505 506 @UnsupportedAppUsage 507 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 508 @UnsupportedAppUsage 509 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 510 511 @UnsupportedAppUsage 512 private float mShadowRadius; 513 @UnsupportedAppUsage 514 private float mShadowDx; 515 @UnsupportedAppUsage 516 private float mShadowDy; 517 private int mShadowColor; 518 519 private boolean mPreDrawRegistered; 520 private boolean mPreDrawListenerDetached; 521 522 private TextClassifier mTextClassifier; 523 private TextClassifier mTextClassificationSession; 524 private TextClassificationContext mTextClassificationContext; 525 526 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 527 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 528 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 529 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 530 // into by the user holding the movement key down) then we shouldn't prevent the focus from 531 // changing. 532 private boolean mPreventDefaultMovement; 533 534 private TextUtils.TruncateAt mEllipsize; 535 536 // A flag to indicate the cursor was hidden by IME. 537 private boolean mImeIsConsumingInput; 538 539 // Whether cursor is visible without regard to {@link mImeConsumesInput}. 540 // {@code true} is the default value. 541 private boolean mCursorVisibleFromAttr = true; 542 543 static class Drawables { 544 static final int LEFT = 0; 545 static final int TOP = 1; 546 static final int RIGHT = 2; 547 static final int BOTTOM = 3; 548 549 static final int DRAWABLE_NONE = -1; 550 static final int DRAWABLE_RIGHT = 0; 551 static final int DRAWABLE_LEFT = 1; 552 553 final Rect mCompoundRect = new Rect(); 554 555 final Drawable[] mShowing = new Drawable[4]; 556 557 ColorStateList mTintList; 558 BlendMode mBlendMode; 559 boolean mHasTint; 560 boolean mHasTintMode; 561 562 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 563 Drawable mDrawableLeftInitial, mDrawableRightInitial; 564 565 boolean mIsRtlCompatibilityMode; 566 boolean mOverride; 567 568 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 569 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 570 571 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 572 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 573 574 int mDrawablePadding; 575 576 int mDrawableSaved = DRAWABLE_NONE; 577 Drawables(Context context)578 public Drawables(Context context) { 579 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 580 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 581 || !context.getApplicationInfo().hasRtlSupport(); 582 mOverride = false; 583 } 584 585 /** 586 * @return {@code true} if this object contains metadata that needs to 587 * be retained, {@code false} otherwise 588 */ 589 public boolean hasMetadata() { 590 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 591 } 592 593 /** 594 * Updates the list of displayed drawables to account for the current 595 * layout direction. 596 * 597 * @param layoutDirection the current layout direction 598 * @return {@code true} if the displayed drawables changed 599 */ 600 public boolean resolveWithLayoutDirection(int layoutDirection) { 601 final Drawable previousLeft = mShowing[Drawables.LEFT]; 602 final Drawable previousRight = mShowing[Drawables.RIGHT]; 603 604 // First reset "left" and "right" drawables to their initial values 605 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 606 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 607 608 if (mIsRtlCompatibilityMode) { 609 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 610 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 611 mShowing[Drawables.LEFT] = mDrawableStart; 612 mDrawableSizeLeft = mDrawableSizeStart; 613 mDrawableHeightLeft = mDrawableHeightStart; 614 } 615 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 616 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 617 mShowing[Drawables.RIGHT] = mDrawableEnd; 618 mDrawableSizeRight = mDrawableSizeEnd; 619 mDrawableHeightRight = mDrawableHeightEnd; 620 } 621 } else { 622 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 623 // drawable if and only if they have been defined 624 switch(layoutDirection) { 625 case LAYOUT_DIRECTION_RTL: 626 if (mOverride) { 627 mShowing[Drawables.RIGHT] = mDrawableStart; 628 mDrawableSizeRight = mDrawableSizeStart; 629 mDrawableHeightRight = mDrawableHeightStart; 630 631 mShowing[Drawables.LEFT] = mDrawableEnd; 632 mDrawableSizeLeft = mDrawableSizeEnd; 633 mDrawableHeightLeft = mDrawableHeightEnd; 634 } 635 break; 636 637 case LAYOUT_DIRECTION_LTR: 638 default: 639 if (mOverride) { 640 mShowing[Drawables.LEFT] = mDrawableStart; 641 mDrawableSizeLeft = mDrawableSizeStart; 642 mDrawableHeightLeft = mDrawableHeightStart; 643 644 mShowing[Drawables.RIGHT] = mDrawableEnd; 645 mDrawableSizeRight = mDrawableSizeEnd; 646 mDrawableHeightRight = mDrawableHeightEnd; 647 } 648 break; 649 } 650 } 651 652 applyErrorDrawableIfNeeded(layoutDirection); 653 654 return mShowing[Drawables.LEFT] != previousLeft 655 || mShowing[Drawables.RIGHT] != previousRight; 656 } 657 658 public void setErrorDrawable(Drawable dr, TextView tv) { 659 if (mDrawableError != dr && mDrawableError != null) { 660 mDrawableError.setCallback(null); 661 } 662 mDrawableError = dr; 663 664 if (mDrawableError != null) { 665 final Rect compoundRect = mCompoundRect; 666 final int[] state = tv.getDrawableState(); 667 668 mDrawableError.setState(state); 669 mDrawableError.copyBounds(compoundRect); 670 mDrawableError.setCallback(tv); 671 mDrawableSizeError = compoundRect.width(); 672 mDrawableHeightError = compoundRect.height(); 673 } else { 674 mDrawableSizeError = mDrawableHeightError = 0; 675 } 676 } 677 678 private void applyErrorDrawableIfNeeded(int layoutDirection) { 679 // first restore the initial state if needed 680 switch (mDrawableSaved) { 681 case DRAWABLE_LEFT: 682 mShowing[Drawables.LEFT] = mDrawableTemp; 683 mDrawableSizeLeft = mDrawableSizeTemp; 684 mDrawableHeightLeft = mDrawableHeightTemp; 685 break; 686 case DRAWABLE_RIGHT: 687 mShowing[Drawables.RIGHT] = mDrawableTemp; 688 mDrawableSizeRight = mDrawableSizeTemp; 689 mDrawableHeightRight = mDrawableHeightTemp; 690 break; 691 case DRAWABLE_NONE: 692 default: 693 } 694 // then, if needed, assign the Error drawable to the correct location 695 if (mDrawableError != null) { 696 switch(layoutDirection) { 697 case LAYOUT_DIRECTION_RTL: 698 mDrawableSaved = DRAWABLE_LEFT; 699 700 mDrawableTemp = mShowing[Drawables.LEFT]; 701 mDrawableSizeTemp = mDrawableSizeLeft; 702 mDrawableHeightTemp = mDrawableHeightLeft; 703 704 mShowing[Drawables.LEFT] = mDrawableError; 705 mDrawableSizeLeft = mDrawableSizeError; 706 mDrawableHeightLeft = mDrawableHeightError; 707 break; 708 case LAYOUT_DIRECTION_LTR: 709 default: 710 mDrawableSaved = DRAWABLE_RIGHT; 711 712 mDrawableTemp = mShowing[Drawables.RIGHT]; 713 mDrawableSizeTemp = mDrawableSizeRight; 714 mDrawableHeightTemp = mDrawableHeightRight; 715 716 mShowing[Drawables.RIGHT] = mDrawableError; 717 mDrawableSizeRight = mDrawableSizeError; 718 mDrawableHeightRight = mDrawableHeightError; 719 break; 720 } 721 } 722 } 723 } 724 725 @UnsupportedAppUsage 726 Drawables mDrawables; 727 728 @UnsupportedAppUsage 729 private CharWrapper mCharWrapper; 730 731 @UnsupportedAppUsage(trackingBug = 124050217) 732 private Marquee mMarquee; 733 @UnsupportedAppUsage 734 private boolean mRestartMarquee; 735 736 private int mMarqueeRepeatLimit = 3; 737 738 private int mLastLayoutDirection = -1; 739 740 /** 741 * On some devices the fading edges add a performance penalty if used 742 * extensively in the same layout. This mode indicates how the marquee 743 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 744 */ 745 @UnsupportedAppUsage 746 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 747 748 /** 749 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 750 * the layout that should be used when the mode switches. 751 */ 752 @UnsupportedAppUsage 753 private Layout mSavedMarqueeModeLayout; 754 755 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 756 @ViewDebug.ExportedProperty(category = "text") 757 @UnsupportedAppUsage 758 private @Nullable CharSequence mText; 759 private @Nullable Spannable mSpannable; 760 private @Nullable PrecomputedText mPrecomputed; 761 762 @UnsupportedAppUsage 763 private CharSequence mTransformed; 764 @UnsupportedAppUsage 765 private BufferType mBufferType = BufferType.NORMAL; 766 767 private CharSequence mHint; 768 @UnsupportedAppUsage 769 private Layout mHintLayout; 770 771 private MovementMethod mMovement; 772 773 private TransformationMethod mTransformation; 774 @UnsupportedAppUsage 775 private boolean mAllowTransformationLengthChange; 776 @UnsupportedAppUsage 777 private ChangeWatcher mChangeWatcher; 778 779 @UnsupportedAppUsage(trackingBug = 123769451) 780 private ArrayList<TextWatcher> mListeners; 781 782 // display attributes 783 @UnsupportedAppUsage 784 private final TextPaint mTextPaint; 785 @UnsupportedAppUsage 786 private boolean mUserSetTextScaleX; 787 @UnsupportedAppUsage 788 private Layout mLayout; 789 private boolean mLocalesChanged = false; 790 private int mTextSizeUnit = -1; 791 private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 792 private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 793 794 // This is used to reflect the current user preference for changing font weight and making text 795 // more bold. 796 private int mFontWeightAdjustment; 797 private Typeface mOriginalTypeface; 798 799 // True if setKeyListener() has been explicitly called 800 private boolean mListenerChanged = false; 801 // True if internationalized input should be used for numbers and date and time. 802 private final boolean mUseInternationalizedInput; 803 804 // Fallback fonts that end up getting used should be allowed to affect line spacing. 805 private static final int FALLBACK_LINE_SPACING_NONE = 0; 806 private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; 807 private static final int FALLBACK_LINE_SPACING_ALL = 2; 808 809 private int mUseFallbackLineSpacing; 810 // True if the view text can be padded for compat reasons, when the view is translated. 811 private final boolean mUseTextPaddingForUiTranslation; 812 813 @ViewDebug.ExportedProperty(category = "text") 814 @UnsupportedAppUsage 815 private int mGravity = Gravity.TOP | Gravity.START; 816 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 817 private boolean mHorizontallyScrolling; 818 819 private int mAutoLinkMask; 820 private boolean mLinksClickable = true; 821 822 @UnsupportedAppUsage 823 private float mSpacingMult = 1.0f; 824 @UnsupportedAppUsage 825 private float mSpacingAdd = 0.0f; 826 827 private int mBreakStrategy; 828 private int mHyphenationFrequency; 829 private int mJustificationMode; 830 831 @UnsupportedAppUsage 832 private int mMaximum = Integer.MAX_VALUE; 833 @UnsupportedAppUsage 834 private int mMaxMode = LINES; 835 @UnsupportedAppUsage 836 private int mMinimum = 0; 837 @UnsupportedAppUsage 838 private int mMinMode = LINES; 839 840 @UnsupportedAppUsage 841 private int mOldMaximum = mMaximum; 842 @UnsupportedAppUsage 843 private int mOldMaxMode = mMaxMode; 844 845 @UnsupportedAppUsage 846 private int mMaxWidth = Integer.MAX_VALUE; 847 @UnsupportedAppUsage 848 private int mMaxWidthMode = PIXELS; 849 @UnsupportedAppUsage 850 private int mMinWidth = 0; 851 @UnsupportedAppUsage 852 private int mMinWidthMode = PIXELS; 853 854 @UnsupportedAppUsage 855 private boolean mSingleLine; 856 @UnsupportedAppUsage 857 private int mDesiredHeightAtMeasure = -1; 858 @UnsupportedAppUsage 859 private boolean mIncludePad = true; 860 private int mDeferScroll = -1; 861 862 // tmp primitives, so we don't alloc them on each draw 863 private Rect mTempRect; 864 private long mLastScroll; 865 private Scroller mScroller; 866 private TextPaint mTempTextPaint; 867 868 @UnsupportedAppUsage 869 private BoringLayout.Metrics mBoring; 870 @UnsupportedAppUsage 871 private BoringLayout.Metrics mHintBoring; 872 @UnsupportedAppUsage 873 private BoringLayout mSavedLayout; 874 @UnsupportedAppUsage 875 private BoringLayout mSavedHintLayout; 876 877 @UnsupportedAppUsage 878 private TextDirectionHeuristic mTextDir; 879 880 private InputFilter[] mFilters = NO_FILTERS; 881 882 /** 883 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 884 * the same as {@link Process#myUserHandle()}. 885 * 886 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 887 * other apps may need to set this so that the system can use right user's resources and 888 * services such as input methods and spell checkers.</p> 889 * 890 * @see #setTextOperationUser(UserHandle) 891 */ 892 @Nullable 893 private UserHandle mTextOperationUser; 894 895 private volatile Locale mCurrentSpellCheckerLocaleCache; 896 897 // It is possible to have a selection even when mEditor is null (programmatically set, like when 898 // a link is pressed). These highlight-related fields do not go in mEditor. 899 @UnsupportedAppUsage 900 int mHighlightColor = 0x6633B5E5; 901 private Path mHighlightPath; 902 @UnsupportedAppUsage 903 private final Paint mHighlightPaint; 904 @UnsupportedAppUsage 905 private boolean mHighlightPathBogus = true; 906 907 // Although these fields are specific to editable text, they are not added to Editor because 908 // they are defined by the TextView's style and are theme-dependent. 909 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 910 int mCursorDrawableRes; 911 private Drawable mCursorDrawable; 912 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 913 // by removing it, but we would break apps targeting <= P that use it by reflection. 914 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 915 int mTextSelectHandleLeftRes; 916 private Drawable mTextSelectHandleLeft; 917 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 918 // by removing it, but we would break apps targeting <= P that use it by reflection. 919 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 920 int mTextSelectHandleRightRes; 921 private Drawable mTextSelectHandleRight; 922 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 923 // by removing it, but we would break apps targeting <= P that use it by reflection. 924 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 925 int mTextSelectHandleRes; 926 private Drawable mTextSelectHandle; 927 int mTextEditSuggestionItemLayout; 928 int mTextEditSuggestionContainerLayout; 929 int mTextEditSuggestionHighlightStyle; 930 931 private static final int NO_POINTER_ID = -1; 932 /** 933 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 934 * TextView and the handle views which are rendered on popup windows. 935 */ 936 private int mPrimePointerId = NO_POINTER_ID; 937 938 /** 939 * Whether the prime pointer is from the event delivered to selection handle or insertion 940 * handle. 941 */ 942 private boolean mIsPrimePointerFromHandleView; 943 944 /** 945 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 946 * See {@link #createEditorIfNeeded()}. 947 */ 948 @UnsupportedAppUsage 949 private Editor mEditor; 950 951 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 952 private static final int DEVICE_PROVISIONED_NO = 1; 953 private static final int DEVICE_PROVISIONED_YES = 2; 954 955 /** 956 * Some special options such as sharing selected text should only be shown if the device 957 * is provisioned. Only check the provisioned state once for a given view instance. 958 */ 959 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 960 961 /** 962 * The TextView does not auto-size text (default). 963 */ 964 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 965 966 /** 967 * The TextView scales text size both horizontally and vertically to fit within the 968 * container. 969 */ 970 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 971 972 /** @hide */ 973 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 974 AUTO_SIZE_TEXT_TYPE_NONE, 975 AUTO_SIZE_TEXT_TYPE_UNIFORM 976 }) 977 @Retention(RetentionPolicy.SOURCE) 978 public @interface AutoSizeTextType {} 979 // Default minimum size for auto-sizing text in scaled pixels. 980 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 981 // Default maximum size for auto-sizing text in scaled pixels. 982 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 983 // Default value for the step size in pixels. 984 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 985 // Use this to specify that any of the auto-size configuration int values have not been set. 986 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 987 // Auto-size text type. 988 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 989 // Specify if auto-size text is needed. 990 private boolean mNeedsAutoSizeText = false; 991 // Step size for auto-sizing in pixels. 992 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 993 // Minimum text size for auto-sizing in pixels. 994 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 995 // Maximum text size for auto-sizing in pixels. 996 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 997 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 998 // when auto-sizing text. 999 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 1000 // Specifies whether auto-size should use the provided auto size steps set or if it should 1001 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 1002 // mAutoSizeStepGranularityInPx. 1003 private boolean mHasPresetAutoSizeValues = false; 1004 1005 // Autofill-related attributes 1006 // 1007 // Indicates whether the text was set statically or dynamically, so it can be used to 1008 // sanitize autofill requests. 1009 private boolean mTextSetFromXmlOrResourceId = false; 1010 // Resource id used to set the text. 1011 private @StringRes int mTextId = Resources.ID_NULL; 1012 // Resource id used to set the hint. 1013 private @StringRes int mHintId = Resources.ID_NULL; 1014 // 1015 // End of autofill-related attributes 1016 1017 /** 1018 * Kick-start the font cache for the zygote process (to pay the cost of 1019 * initializing freetype for our default font only once). 1020 * @hide 1021 */ 1022 public static void preloadFontCache() { 1023 if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 1024 return; 1025 } 1026 Paint p = new Paint(); 1027 p.setAntiAlias(true); 1028 // Ensure that the Typeface is loaded here. 1029 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 1030 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 1031 // since Paint.measureText can not be called without Typeface static initializer. 1032 p.setTypeface(Typeface.DEFAULT); 1033 // We don't care about the result, just the side-effect of measuring. 1034 p.measureText("H"); 1035 } 1036 1037 /** 1038 * Interface definition for a callback to be invoked when an action is 1039 * performed on the editor. 1040 */ 1041 public interface OnEditorActionListener { 1042 /** 1043 * Called when an action is being performed. 1044 * 1045 * @param v The view that was clicked. 1046 * @param actionId Identifier of the action. This will be either the 1047 * identifier you supplied, or {@link EditorInfo#IME_NULL 1048 * EditorInfo.IME_NULL} if being called due to the enter key 1049 * being pressed. 1050 * @param event If triggered by an enter key, this is the event; 1051 * otherwise, this is null. 1052 * @return Return true if you have consumed the action, else false. 1053 */ 1054 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 1055 } 1056 1057 public TextView(Context context) { 1058 this(context, null); 1059 } 1060 1061 public TextView(Context context, @Nullable AttributeSet attrs) { 1062 this(context, attrs, com.android.internal.R.attr.textViewStyle); 1063 } 1064 1065 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 1066 this(context, attrs, defStyleAttr, 0); 1067 } 1068 1069 @SuppressWarnings("deprecation") 1070 public TextView( 1071 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1072 super(context, attrs, defStyleAttr, defStyleRes); 1073 1074 // TextView is important by default, unless app developer overrode attribute. 1075 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1076 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1077 } 1078 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1079 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1080 } 1081 1082 setTextInternal(""); 1083 1084 final Resources res = getResources(); 1085 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1086 1087 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1088 mTextPaint.density = res.getDisplayMetrics().density; 1089 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1090 1091 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1092 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1093 1094 mMovement = getDefaultMovementMethod(); 1095 1096 mTransformation = null; 1097 1098 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1099 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1100 attributes.mTextSize = 15; 1101 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1102 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1103 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1104 1105 final Resources.Theme theme = context.getTheme(); 1106 1107 /* 1108 * Look the appearance up without checking first if it exists because 1109 * almost every TextView has one and it greatly simplifies the logic 1110 * to be able to parse the appearance first and then let specific tags 1111 * for this View override it. 1112 */ 1113 TypedArray a = theme.obtainStyledAttributes(attrs, 1114 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1115 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1116 attrs, a, defStyleAttr, defStyleRes); 1117 TypedArray appearance = null; 1118 int ap = a.getResourceId( 1119 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1120 a.recycle(); 1121 if (ap != -1) { 1122 appearance = theme.obtainStyledAttributes( 1123 ap, com.android.internal.R.styleable.TextAppearance); 1124 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1125 null, appearance, 0, ap); 1126 } 1127 if (appearance != null) { 1128 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1129 attributes.mFontFamilyExplicit = false; 1130 appearance.recycle(); 1131 } 1132 1133 boolean editable = getDefaultEditable(); 1134 CharSequence inputMethod = null; 1135 int numeric = 0; 1136 CharSequence digits = null; 1137 boolean phone = false; 1138 boolean autotext = false; 1139 int autocap = -1; 1140 int buffertype = 0; 1141 boolean selectallonfocus = false; 1142 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1143 drawableBottom = null, drawableStart = null, drawableEnd = null; 1144 ColorStateList drawableTint = null; 1145 BlendMode drawableTintMode = null; 1146 int drawablePadding = 0; 1147 int ellipsize = ELLIPSIZE_NOT_SET; 1148 boolean singleLine = false; 1149 int maxlength = -1; 1150 CharSequence text = ""; 1151 CharSequence hint = null; 1152 boolean password = false; 1153 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1154 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1155 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1156 int inputType = EditorInfo.TYPE_NULL; 1157 a = theme.obtainStyledAttributes( 1158 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1159 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1160 defStyleAttr, defStyleRes); 1161 int firstBaselineToTopHeight = -1; 1162 int lastBaselineToBottomHeight = -1; 1163 int lineHeight = -1; 1164 1165 readTextAppearance(context, a, attributes, true /* styleArray */); 1166 1167 int n = a.getIndexCount(); 1168 1169 // Must set id in a temporary variable because it will be reset by setText() 1170 boolean textIsSetFromXml = false; 1171 for (int i = 0; i < n; i++) { 1172 int attr = a.getIndex(i); 1173 1174 switch (attr) { 1175 case com.android.internal.R.styleable.TextView_editable: 1176 editable = a.getBoolean(attr, editable); 1177 break; 1178 1179 case com.android.internal.R.styleable.TextView_inputMethod: 1180 inputMethod = a.getText(attr); 1181 break; 1182 1183 case com.android.internal.R.styleable.TextView_numeric: 1184 numeric = a.getInt(attr, numeric); 1185 break; 1186 1187 case com.android.internal.R.styleable.TextView_digits: 1188 digits = a.getText(attr); 1189 break; 1190 1191 case com.android.internal.R.styleable.TextView_phoneNumber: 1192 phone = a.getBoolean(attr, phone); 1193 break; 1194 1195 case com.android.internal.R.styleable.TextView_autoText: 1196 autotext = a.getBoolean(attr, autotext); 1197 break; 1198 1199 case com.android.internal.R.styleable.TextView_capitalize: 1200 autocap = a.getInt(attr, autocap); 1201 break; 1202 1203 case com.android.internal.R.styleable.TextView_bufferType: 1204 buffertype = a.getInt(attr, buffertype); 1205 break; 1206 1207 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1208 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1209 break; 1210 1211 case com.android.internal.R.styleable.TextView_autoLink: 1212 mAutoLinkMask = a.getInt(attr, 0); 1213 break; 1214 1215 case com.android.internal.R.styleable.TextView_linksClickable: 1216 mLinksClickable = a.getBoolean(attr, true); 1217 break; 1218 1219 case com.android.internal.R.styleable.TextView_drawableLeft: 1220 drawableLeft = a.getDrawable(attr); 1221 break; 1222 1223 case com.android.internal.R.styleable.TextView_drawableTop: 1224 drawableTop = a.getDrawable(attr); 1225 break; 1226 1227 case com.android.internal.R.styleable.TextView_drawableRight: 1228 drawableRight = a.getDrawable(attr); 1229 break; 1230 1231 case com.android.internal.R.styleable.TextView_drawableBottom: 1232 drawableBottom = a.getDrawable(attr); 1233 break; 1234 1235 case com.android.internal.R.styleable.TextView_drawableStart: 1236 drawableStart = a.getDrawable(attr); 1237 break; 1238 1239 case com.android.internal.R.styleable.TextView_drawableEnd: 1240 drawableEnd = a.getDrawable(attr); 1241 break; 1242 1243 case com.android.internal.R.styleable.TextView_drawableTint: 1244 drawableTint = a.getColorStateList(attr); 1245 break; 1246 1247 case com.android.internal.R.styleable.TextView_drawableTintMode: 1248 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1249 drawableTintMode); 1250 break; 1251 1252 case com.android.internal.R.styleable.TextView_drawablePadding: 1253 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1254 break; 1255 1256 case com.android.internal.R.styleable.TextView_maxLines: 1257 setMaxLines(a.getInt(attr, -1)); 1258 break; 1259 1260 case com.android.internal.R.styleable.TextView_maxHeight: 1261 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1262 break; 1263 1264 case com.android.internal.R.styleable.TextView_lines: 1265 setLines(a.getInt(attr, -1)); 1266 break; 1267 1268 case com.android.internal.R.styleable.TextView_height: 1269 setHeight(a.getDimensionPixelSize(attr, -1)); 1270 break; 1271 1272 case com.android.internal.R.styleable.TextView_minLines: 1273 setMinLines(a.getInt(attr, -1)); 1274 break; 1275 1276 case com.android.internal.R.styleable.TextView_minHeight: 1277 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1278 break; 1279 1280 case com.android.internal.R.styleable.TextView_maxEms: 1281 setMaxEms(a.getInt(attr, -1)); 1282 break; 1283 1284 case com.android.internal.R.styleable.TextView_maxWidth: 1285 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1286 break; 1287 1288 case com.android.internal.R.styleable.TextView_ems: 1289 setEms(a.getInt(attr, -1)); 1290 break; 1291 1292 case com.android.internal.R.styleable.TextView_width: 1293 setWidth(a.getDimensionPixelSize(attr, -1)); 1294 break; 1295 1296 case com.android.internal.R.styleable.TextView_minEms: 1297 setMinEms(a.getInt(attr, -1)); 1298 break; 1299 1300 case com.android.internal.R.styleable.TextView_minWidth: 1301 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1302 break; 1303 1304 case com.android.internal.R.styleable.TextView_gravity: 1305 setGravity(a.getInt(attr, -1)); 1306 break; 1307 1308 case com.android.internal.R.styleable.TextView_hint: 1309 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1310 hint = a.getText(attr); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_text: 1314 textIsSetFromXml = true; 1315 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1316 text = a.getText(attr); 1317 break; 1318 1319 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1320 if (a.getBoolean(attr, false)) { 1321 setHorizontallyScrolling(true); 1322 } 1323 break; 1324 1325 case com.android.internal.R.styleable.TextView_singleLine: 1326 singleLine = a.getBoolean(attr, singleLine); 1327 break; 1328 1329 case com.android.internal.R.styleable.TextView_ellipsize: 1330 ellipsize = a.getInt(attr, ellipsize); 1331 break; 1332 1333 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1334 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1335 break; 1336 1337 case com.android.internal.R.styleable.TextView_includeFontPadding: 1338 if (!a.getBoolean(attr, true)) { 1339 setIncludeFontPadding(false); 1340 } 1341 break; 1342 1343 case com.android.internal.R.styleable.TextView_cursorVisible: 1344 if (!a.getBoolean(attr, true)) { 1345 setCursorVisible(false); 1346 } 1347 break; 1348 1349 case com.android.internal.R.styleable.TextView_maxLength: 1350 maxlength = a.getInt(attr, -1); 1351 break; 1352 1353 case com.android.internal.R.styleable.TextView_textScaleX: 1354 setTextScaleX(a.getFloat(attr, 1.0f)); 1355 break; 1356 1357 case com.android.internal.R.styleable.TextView_freezesText: 1358 mFreezesText = a.getBoolean(attr, false); 1359 break; 1360 1361 case com.android.internal.R.styleable.TextView_enabled: 1362 setEnabled(a.getBoolean(attr, isEnabled())); 1363 break; 1364 1365 case com.android.internal.R.styleable.TextView_password: 1366 password = a.getBoolean(attr, password); 1367 break; 1368 1369 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1370 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1371 break; 1372 1373 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1374 mSpacingMult = a.getFloat(attr, mSpacingMult); 1375 break; 1376 1377 case com.android.internal.R.styleable.TextView_inputType: 1378 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1379 break; 1380 1381 case com.android.internal.R.styleable.TextView_allowUndo: 1382 createEditorIfNeeded(); 1383 mEditor.mAllowUndo = a.getBoolean(attr, true); 1384 break; 1385 1386 case com.android.internal.R.styleable.TextView_imeOptions: 1387 createEditorIfNeeded(); 1388 mEditor.createInputContentTypeIfNeeded(); 1389 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1390 mEditor.mInputContentType.imeOptions); 1391 break; 1392 1393 case com.android.internal.R.styleable.TextView_imeActionLabel: 1394 createEditorIfNeeded(); 1395 mEditor.createInputContentTypeIfNeeded(); 1396 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1397 break; 1398 1399 case com.android.internal.R.styleable.TextView_imeActionId: 1400 createEditorIfNeeded(); 1401 mEditor.createInputContentTypeIfNeeded(); 1402 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1403 mEditor.mInputContentType.imeActionId); 1404 break; 1405 1406 case com.android.internal.R.styleable.TextView_privateImeOptions: 1407 setPrivateImeOptions(a.getString(attr)); 1408 break; 1409 1410 case com.android.internal.R.styleable.TextView_editorExtras: 1411 try { 1412 setInputExtras(a.getResourceId(attr, 0)); 1413 } catch (XmlPullParserException e) { 1414 Log.w(LOG_TAG, "Failure reading input extras", e); 1415 } catch (IOException e) { 1416 Log.w(LOG_TAG, "Failure reading input extras", e); 1417 } 1418 break; 1419 1420 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1421 mCursorDrawableRes = a.getResourceId(attr, 0); 1422 break; 1423 1424 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1425 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1426 break; 1427 1428 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1429 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1430 break; 1431 1432 case com.android.internal.R.styleable.TextView_textSelectHandle: 1433 mTextSelectHandleRes = a.getResourceId(attr, 0); 1434 break; 1435 1436 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1437 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1438 break; 1439 1440 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1441 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1442 break; 1443 1444 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1445 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1446 break; 1447 1448 case com.android.internal.R.styleable.TextView_textIsSelectable: 1449 setTextIsSelectable(a.getBoolean(attr, false)); 1450 break; 1451 1452 case com.android.internal.R.styleable.TextView_breakStrategy: 1453 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1454 break; 1455 1456 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1457 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1458 break; 1459 1460 case com.android.internal.R.styleable.TextView_lineBreakStyle: 1461 mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE); 1462 break; 1463 1464 case com.android.internal.R.styleable.TextView_lineBreakWordStyle: 1465 mLineBreakWordStyle = a.getInt(attr, 1466 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); 1467 break; 1468 1469 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1470 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1471 break; 1472 1473 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1474 autoSizeStepGranularityInPx = a.getDimension(attr, 1475 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1476 break; 1477 1478 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1479 autoSizeMinTextSizeInPx = a.getDimension(attr, 1480 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1481 break; 1482 1483 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1484 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1485 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1486 break; 1487 1488 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1489 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1490 if (autoSizeStepSizeArrayResId > 0) { 1491 final TypedArray autoSizePresetTextSizes = a.getResources() 1492 .obtainTypedArray(autoSizeStepSizeArrayResId); 1493 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1494 autoSizePresetTextSizes.recycle(); 1495 } 1496 break; 1497 case com.android.internal.R.styleable.TextView_justificationMode: 1498 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1499 break; 1500 1501 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1502 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1503 break; 1504 1505 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1506 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1507 break; 1508 1509 case com.android.internal.R.styleable.TextView_lineHeight: 1510 lineHeight = a.getDimensionPixelSize(attr, -1); 1511 break; 1512 } 1513 } 1514 1515 a.recycle(); 1516 1517 BufferType bufferType = BufferType.EDITABLE; 1518 1519 final int variation = 1520 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1521 final boolean passwordInputType = variation 1522 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1523 final boolean webPasswordInputType = variation 1524 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1525 final boolean numberPasswordInputType = variation 1526 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1527 1528 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1529 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1530 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 1531 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; 1532 } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { 1533 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 1534 } else { 1535 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; 1536 } 1537 // TODO(b/179693024): Use a ChangeId instead. 1538 mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; 1539 1540 if (inputMethod != null) { 1541 Class<?> c; 1542 1543 try { 1544 c = Class.forName(inputMethod.toString()); 1545 } catch (ClassNotFoundException ex) { 1546 throw new RuntimeException(ex); 1547 } 1548 1549 try { 1550 createEditorIfNeeded(); 1551 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1552 } catch (InstantiationException ex) { 1553 throw new RuntimeException(ex); 1554 } catch (IllegalAccessException ex) { 1555 throw new RuntimeException(ex); 1556 } 1557 try { 1558 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1559 ? inputType 1560 : mEditor.mKeyListener.getInputType(); 1561 } catch (IncompatibleClassChangeError e) { 1562 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1563 } 1564 } else if (digits != null) { 1565 createEditorIfNeeded(); 1566 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1567 // If no input type was specified, we will default to generic 1568 // text, since we can't tell the IME about the set of digits 1569 // that was selected. 1570 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1571 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1572 } else if (inputType != EditorInfo.TYPE_NULL) { 1573 setInputType(inputType, true); 1574 // If set, the input type overrides what was set using the deprecated singleLine flag. 1575 singleLine = !isMultilineInputType(inputType); 1576 } else if (phone) { 1577 createEditorIfNeeded(); 1578 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1579 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1580 } else if (numeric != 0) { 1581 createEditorIfNeeded(); 1582 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1583 null, // locale 1584 (numeric & SIGNED) != 0, 1585 (numeric & DECIMAL) != 0); 1586 inputType = mEditor.mKeyListener.getInputType(); 1587 mEditor.mInputType = inputType; 1588 } else if (autotext || autocap != -1) { 1589 TextKeyListener.Capitalize cap; 1590 1591 inputType = EditorInfo.TYPE_CLASS_TEXT; 1592 1593 switch (autocap) { 1594 case 1: 1595 cap = TextKeyListener.Capitalize.SENTENCES; 1596 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1597 break; 1598 1599 case 2: 1600 cap = TextKeyListener.Capitalize.WORDS; 1601 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1602 break; 1603 1604 case 3: 1605 cap = TextKeyListener.Capitalize.CHARACTERS; 1606 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1607 break; 1608 1609 default: 1610 cap = TextKeyListener.Capitalize.NONE; 1611 break; 1612 } 1613 1614 createEditorIfNeeded(); 1615 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1616 mEditor.mInputType = inputType; 1617 } else if (editable) { 1618 createEditorIfNeeded(); 1619 mEditor.mKeyListener = TextKeyListener.getInstance(); 1620 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1621 } else if (isTextSelectable()) { 1622 // Prevent text changes from keyboard. 1623 if (mEditor != null) { 1624 mEditor.mKeyListener = null; 1625 mEditor.mInputType = EditorInfo.TYPE_NULL; 1626 } 1627 bufferType = BufferType.SPANNABLE; 1628 // So that selection can be changed using arrow keys and touch is handled. 1629 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1630 } else { 1631 if (mEditor != null) mEditor.mKeyListener = null; 1632 1633 switch (buffertype) { 1634 case 0: 1635 bufferType = BufferType.NORMAL; 1636 break; 1637 case 1: 1638 bufferType = BufferType.SPANNABLE; 1639 break; 1640 case 2: 1641 bufferType = BufferType.EDITABLE; 1642 break; 1643 } 1644 } 1645 1646 if (mEditor != null) { 1647 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1648 numberPasswordInputType); 1649 } 1650 1651 if (selectallonfocus) { 1652 createEditorIfNeeded(); 1653 mEditor.mSelectAllOnFocus = true; 1654 1655 if (bufferType == BufferType.NORMAL) { 1656 bufferType = BufferType.SPANNABLE; 1657 } 1658 } 1659 1660 // Set up the tint (if needed) before setting the drawables so that it 1661 // gets applied correctly. 1662 if (drawableTint != null || drawableTintMode != null) { 1663 if (mDrawables == null) { 1664 mDrawables = new Drawables(context); 1665 } 1666 if (drawableTint != null) { 1667 mDrawables.mTintList = drawableTint; 1668 mDrawables.mHasTint = true; 1669 } 1670 if (drawableTintMode != null) { 1671 mDrawables.mBlendMode = drawableTintMode; 1672 mDrawables.mHasTintMode = true; 1673 } 1674 } 1675 1676 // This call will save the initial left/right drawables 1677 setCompoundDrawablesWithIntrinsicBounds( 1678 drawableLeft, drawableTop, drawableRight, drawableBottom); 1679 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1680 setCompoundDrawablePadding(drawablePadding); 1681 1682 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1683 // of lines of height are unchanged for multi-line TextViews. 1684 setInputTypeSingleLine(singleLine); 1685 applySingleLine(singleLine, singleLine, singleLine, 1686 // Does not apply automated max length filter since length filter will be resolved 1687 // later in this function. 1688 false 1689 ); 1690 1691 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1692 ellipsize = ELLIPSIZE_END; 1693 } 1694 1695 switch (ellipsize) { 1696 case ELLIPSIZE_START: 1697 setEllipsize(TextUtils.TruncateAt.START); 1698 break; 1699 case ELLIPSIZE_MIDDLE: 1700 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1701 break; 1702 case ELLIPSIZE_END: 1703 setEllipsize(TextUtils.TruncateAt.END); 1704 break; 1705 case ELLIPSIZE_MARQUEE: 1706 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1707 setHorizontalFadingEdgeEnabled(true); 1708 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1709 } else { 1710 setHorizontalFadingEdgeEnabled(false); 1711 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1712 } 1713 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1714 break; 1715 } 1716 1717 final boolean isPassword = password || passwordInputType || webPasswordInputType 1718 || numberPasswordInputType; 1719 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1720 && (mEditor.mInputType 1721 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1722 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1723 if (isMonospaceEnforced) { 1724 attributes.mTypefaceIndex = MONOSPACE; 1725 } 1726 1727 mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; 1728 applyTextAppearance(attributes); 1729 1730 if (isPassword) { 1731 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1732 } 1733 1734 // For addressing b/145128646 1735 // For the performance reason, we limit characters for single line text field. 1736 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1737 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1738 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1739 } 1740 1741 if (mSingleLineLengthFilter != null) { 1742 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1743 } else if (maxlength >= 0) { 1744 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1745 } else { 1746 setFilters(NO_FILTERS); 1747 } 1748 1749 setText(text, bufferType); 1750 if (mText == null) { 1751 mText = ""; 1752 } 1753 if (mTransformed == null) { 1754 mTransformed = ""; 1755 } 1756 1757 if (textIsSetFromXml) { 1758 mTextSetFromXmlOrResourceId = true; 1759 } 1760 1761 if (hint != null) setHint(hint); 1762 1763 /* 1764 * Views are not normally clickable unless specified to be. 1765 * However, TextViews that have input or movement methods *are* 1766 * clickable by default. By setting clickable here, we implicitly set focusable as well 1767 * if not overridden by the developer. 1768 */ 1769 a = context.obtainStyledAttributes( 1770 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1771 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1772 boolean clickable = canInputOrMove || isClickable(); 1773 boolean longClickable = canInputOrMove || isLongClickable(); 1774 int focusable = getFocusable(); 1775 1776 n = a.getIndexCount(); 1777 for (int i = 0; i < n; i++) { 1778 int attr = a.getIndex(i); 1779 1780 switch (attr) { 1781 case com.android.internal.R.styleable.View_focusable: 1782 TypedValue val = new TypedValue(); 1783 if (a.getValue(attr, val)) { 1784 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1785 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1786 : val.data; 1787 } 1788 break; 1789 1790 case com.android.internal.R.styleable.View_clickable: 1791 clickable = a.getBoolean(attr, clickable); 1792 break; 1793 1794 case com.android.internal.R.styleable.View_longClickable: 1795 longClickable = a.getBoolean(attr, longClickable); 1796 break; 1797 } 1798 } 1799 a.recycle(); 1800 1801 // Some apps were relying on the undefined behavior of focusable winning over 1802 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1803 // when starting with EditText and setting only focusable=false). To keep those apps from 1804 // breaking, re-apply the focusable attribute here. 1805 if (focusable != getFocusable()) { 1806 setFocusable(focusable); 1807 } 1808 setClickable(clickable); 1809 setLongClickable(longClickable); 1810 1811 if (mEditor != null) mEditor.prepareCursorControllers(); 1812 1813 // If not explicitly specified this view is important for accessibility. 1814 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1815 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1816 } 1817 1818 if (supportsAutoSizeText()) { 1819 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1820 // If uniform auto-size has been specified but preset values have not been set then 1821 // replace the auto-size configuration values that have not been specified with the 1822 // defaults. 1823 if (!mHasPresetAutoSizeValues) { 1824 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1825 1826 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1827 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1828 TypedValue.COMPLEX_UNIT_SP, 1829 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1830 displayMetrics); 1831 } 1832 1833 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1834 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1835 TypedValue.COMPLEX_UNIT_SP, 1836 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1837 displayMetrics); 1838 } 1839 1840 if (autoSizeStepGranularityInPx 1841 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1842 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1843 } 1844 1845 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1846 autoSizeMaxTextSizeInPx, 1847 autoSizeStepGranularityInPx); 1848 } 1849 1850 setupAutoSizeText(); 1851 } 1852 } else { 1853 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1854 } 1855 1856 if (firstBaselineToTopHeight >= 0) { 1857 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1858 } 1859 if (lastBaselineToBottomHeight >= 0) { 1860 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1861 } 1862 if (lineHeight >= 0) { 1863 setLineHeight(lineHeight); 1864 } 1865 } 1866 1867 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1868 private void setTextInternal(@Nullable CharSequence text) { 1869 mText = text; 1870 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1871 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1872 } 1873 1874 /** 1875 * Specify whether this widget should automatically scale the text to try to perfectly fit 1876 * within the layout bounds by using the default auto-size configuration. 1877 * 1878 * @param autoSizeTextType the type of auto-size. Must be one of 1879 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1880 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1881 * 1882 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1883 * 1884 * @attr ref android.R.styleable#TextView_autoSizeTextType 1885 * 1886 * @see #getAutoSizeTextType() 1887 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1888 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1889 if (supportsAutoSizeText()) { 1890 switch (autoSizeTextType) { 1891 case AUTO_SIZE_TEXT_TYPE_NONE: 1892 clearAutoSizeConfiguration(); 1893 break; 1894 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1895 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1896 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1897 TypedValue.COMPLEX_UNIT_SP, 1898 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1899 displayMetrics); 1900 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1901 TypedValue.COMPLEX_UNIT_SP, 1902 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1903 displayMetrics); 1904 1905 validateAndSetAutoSizeTextTypeUniformConfiguration( 1906 autoSizeMinTextSizeInPx, 1907 autoSizeMaxTextSizeInPx, 1908 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1909 if (setupAutoSizeText()) { 1910 autoSizeText(); 1911 invalidate(); 1912 } 1913 break; 1914 default: 1915 throw new IllegalArgumentException( 1916 "Unknown auto-size text type: " + autoSizeTextType); 1917 } 1918 } 1919 } 1920 1921 /** 1922 * Specify whether this widget should automatically scale the text to try to perfectly fit 1923 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1924 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1925 * 1926 * @param autoSizeMinTextSize the minimum text size available for auto-size 1927 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1928 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1929 * the minimum and maximum text size in order to build the set of 1930 * text sizes the system uses to choose from when auto-sizing 1931 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1932 * possible dimension units 1933 * 1934 * @throws IllegalArgumentException if any of the configuration params are invalid. 1935 * 1936 * @attr ref android.R.styleable#TextView_autoSizeTextType 1937 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1938 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1939 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1940 * 1941 * @see #setAutoSizeTextTypeWithDefaults(int) 1942 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1943 * @see #getAutoSizeMinTextSize() 1944 * @see #getAutoSizeMaxTextSize() 1945 * @see #getAutoSizeStepGranularity() 1946 * @see #getAutoSizeTextAvailableSizes() 1947 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1948 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1949 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1950 if (supportsAutoSizeText()) { 1951 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1952 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1953 unit, autoSizeMinTextSize, displayMetrics); 1954 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1955 unit, autoSizeMaxTextSize, displayMetrics); 1956 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1957 unit, autoSizeStepGranularity, displayMetrics); 1958 1959 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1960 autoSizeMaxTextSizeInPx, 1961 autoSizeStepGranularityInPx); 1962 1963 if (setupAutoSizeText()) { 1964 autoSizeText(); 1965 invalidate(); 1966 } 1967 } 1968 } 1969 1970 /** 1971 * Specify whether this widget should automatically scale the text to try to perfectly fit 1972 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1973 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1974 * 1975 * @param presetSizes an {@code int} array of sizes in pixels 1976 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1977 * the possible dimension units 1978 * 1979 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1980 * 1981 * @attr ref android.R.styleable#TextView_autoSizeTextType 1982 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1983 * 1984 * @see #setAutoSizeTextTypeWithDefaults(int) 1985 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1986 * @see #getAutoSizeMinTextSize() 1987 * @see #getAutoSizeMaxTextSize() 1988 * @see #getAutoSizeTextAvailableSizes() 1989 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1990 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1991 if (supportsAutoSizeText()) { 1992 final int presetSizesLength = presetSizes.length; 1993 if (presetSizesLength > 0) { 1994 int[] presetSizesInPx = new int[presetSizesLength]; 1995 1996 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1997 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1998 } else { 1999 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2000 // Convert all to sizes to pixels. 2001 for (int i = 0; i < presetSizesLength; i++) { 2002 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 2003 presetSizes[i], displayMetrics)); 2004 } 2005 } 2006 2007 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 2008 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 2009 throw new IllegalArgumentException("None of the preset sizes is valid: " 2010 + Arrays.toString(presetSizes)); 2011 } 2012 } else { 2013 mHasPresetAutoSizeValues = false; 2014 } 2015 2016 if (setupAutoSizeText()) { 2017 autoSizeText(); 2018 invalidate(); 2019 } 2020 } 2021 } 2022 2023 /** 2024 * Returns the type of auto-size set for this widget. 2025 * 2026 * @return an {@code int} corresponding to one of the auto-size types: 2027 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2028 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2029 * 2030 * @attr ref android.R.styleable#TextView_autoSizeTextType 2031 * 2032 * @see #setAutoSizeTextTypeWithDefaults(int) 2033 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2034 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2035 */ 2036 @InspectableProperty(enumMapping = { 2037 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 2038 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 2039 }) 2040 @AutoSizeTextType getAutoSizeTextType()2041 public int getAutoSizeTextType() { 2042 return mAutoSizeTextType; 2043 } 2044 2045 /** 2046 * @return the current auto-size step granularity in pixels. 2047 * 2048 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2049 * 2050 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2051 */ 2052 @InspectableProperty getAutoSizeStepGranularity()2053 public int getAutoSizeStepGranularity() { 2054 return Math.round(mAutoSizeStepGranularityInPx); 2055 } 2056 2057 /** 2058 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 2059 * if auto-size has not been configured this function returns {@code -1}. 2060 * 2061 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2062 * 2063 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2064 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2065 */ 2066 @InspectableProperty getAutoSizeMinTextSize()2067 public int getAutoSizeMinTextSize() { 2068 return Math.round(mAutoSizeMinTextSizeInPx); 2069 } 2070 2071 /** 2072 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 2073 * if auto-size has not been configured this function returns {@code -1}. 2074 * 2075 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2076 * 2077 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2078 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2079 */ 2080 @InspectableProperty getAutoSizeMaxTextSize()2081 public int getAutoSizeMaxTextSize() { 2082 return Math.round(mAutoSizeMaxTextSizeInPx); 2083 } 2084 2085 /** 2086 * @return the current auto-size {@code int} sizes array (in pixels). 2087 * 2088 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2089 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2090 */ getAutoSizeTextAvailableSizes()2091 public int[] getAutoSizeTextAvailableSizes() { 2092 return mAutoSizeTextSizesInPx; 2093 } 2094 setupAutoSizeUniformPresetSizes(TypedArray textSizes)2095 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2096 final int textSizesLength = textSizes.length(); 2097 final int[] parsedSizes = new int[textSizesLength]; 2098 2099 if (textSizesLength > 0) { 2100 for (int i = 0; i < textSizesLength; i++) { 2101 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2102 } 2103 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2104 setupAutoSizeUniformPresetSizesConfiguration(); 2105 } 2106 } 2107 setupAutoSizeUniformPresetSizesConfiguration()2108 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2109 final int sizesLength = mAutoSizeTextSizesInPx.length; 2110 mHasPresetAutoSizeValues = sizesLength > 0; 2111 if (mHasPresetAutoSizeValues) { 2112 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2113 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2114 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2115 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2116 } 2117 return mHasPresetAutoSizeValues; 2118 } 2119 2120 /** 2121 * If all params are valid then save the auto-size configuration. 2122 * 2123 * @throws IllegalArgumentException if any of the params are invalid 2124 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2125 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2126 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2127 // First validate. 2128 if (autoSizeMinTextSizeInPx <= 0) { 2129 throw new IllegalArgumentException("Minimum auto-size text size (" 2130 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2131 } 2132 2133 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2134 throw new IllegalArgumentException("Maximum auto-size text size (" 2135 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2136 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2137 } 2138 2139 if (autoSizeStepGranularityInPx <= 0) { 2140 throw new IllegalArgumentException("The auto-size step granularity (" 2141 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2142 } 2143 2144 // All good, persist the configuration. 2145 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2146 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2147 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2148 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2149 mHasPresetAutoSizeValues = false; 2150 } 2151 clearAutoSizeConfiguration()2152 private void clearAutoSizeConfiguration() { 2153 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2154 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2155 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2156 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2157 mAutoSizeTextSizesInPx = EmptyArray.INT; 2158 mNeedsAutoSizeText = false; 2159 } 2160 2161 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2162 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2163 final int presetValuesLength = presetValues.length; 2164 if (presetValuesLength == 0) { 2165 return presetValues; 2166 } 2167 Arrays.sort(presetValues); 2168 2169 final IntArray uniqueValidSizes = new IntArray(); 2170 for (int i = 0; i < presetValuesLength; i++) { 2171 final int currentPresetValue = presetValues[i]; 2172 2173 if (currentPresetValue > 0 2174 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2175 uniqueValidSizes.add(currentPresetValue); 2176 } 2177 } 2178 2179 return presetValuesLength == uniqueValidSizes.size() 2180 ? presetValues 2181 : uniqueValidSizes.toArray(); 2182 } 2183 setupAutoSizeText()2184 private boolean setupAutoSizeText() { 2185 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2186 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2187 // not have a predefined set of sizes or if the current sizes array is empty. 2188 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2189 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2190 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2191 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2192 for (int i = 0; i < autoSizeValuesLength; i++) { 2193 autoSizeTextSizesInPx[i] = Math.round( 2194 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2195 } 2196 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2197 } 2198 2199 mNeedsAutoSizeText = true; 2200 } else { 2201 mNeedsAutoSizeText = false; 2202 } 2203 2204 return mNeedsAutoSizeText; 2205 } 2206 parseDimensionArray(TypedArray dimens)2207 private int[] parseDimensionArray(TypedArray dimens) { 2208 if (dimens == null) { 2209 return null; 2210 } 2211 int[] result = new int[dimens.length()]; 2212 for (int i = 0; i < result.length; i++) { 2213 result[i] = dimens.getDimensionPixelSize(i, 0); 2214 } 2215 return result; 2216 } 2217 2218 /** 2219 * @hide 2220 */ 2221 @TestApi 2222 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2223 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 2224 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2225 if (resultCode == Activity.RESULT_OK && data != null) { 2226 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2227 if (result != null) { 2228 if (isTextEditable()) { 2229 ClipData clip = ClipData.newPlainText("", result); 2230 ContentInfo payload = 2231 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); 2232 performReceiveContent(payload); 2233 if (mEditor != null) { 2234 mEditor.refreshTextActionMode(); 2235 } 2236 } else { 2237 if (result.length() > 0) { 2238 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2239 .show(); 2240 } 2241 } 2242 } 2243 } else if (mSpannable != null) { 2244 // Reset the selection. 2245 Selection.setSelection(mSpannable, getSelectionEnd()); 2246 } 2247 } 2248 } 2249 2250 /** 2251 * Sets the Typeface taking into account the given attributes. 2252 * 2253 * @param typeface a typeface 2254 * @param familyName family name string, e.g. "serif" 2255 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2256 * @param style a typeface style 2257 * @param weight a weight value for the Typeface or -1 if not specified. 2258 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2259 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2260 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2261 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2262 if (typeface == null && familyName != null) { 2263 // Lookup normal Typeface from system font map. 2264 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2265 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2266 } else if (typeface != null) { 2267 resolveStyleAndSetTypeface(typeface, style, weight); 2268 } else { // both typeface and familyName is null. 2269 switch (typefaceIndex) { 2270 case SANS: 2271 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2272 break; 2273 case SERIF: 2274 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2275 break; 2276 case MONOSPACE: 2277 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2278 break; 2279 case DEFAULT_TYPEFACE: 2280 default: 2281 resolveStyleAndSetTypeface(null, style, weight); 2282 break; 2283 } 2284 } 2285 } 2286 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2287 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2288 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2289 if (weight >= 0) { 2290 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2291 final boolean italic = (style & Typeface.ITALIC) != 0; 2292 setTypeface(Typeface.create(typeface, weight, italic)); 2293 } else { 2294 setTypeface(typeface, style); 2295 } 2296 } 2297 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2298 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2299 boolean hasRelativeDrawables = (start != null) || (end != null); 2300 if (hasRelativeDrawables) { 2301 Drawables dr = mDrawables; 2302 if (dr == null) { 2303 mDrawables = dr = new Drawables(getContext()); 2304 } 2305 mDrawables.mOverride = true; 2306 final Rect compoundRect = dr.mCompoundRect; 2307 int[] state = getDrawableState(); 2308 if (start != null) { 2309 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2310 start.setState(state); 2311 start.copyBounds(compoundRect); 2312 start.setCallback(this); 2313 2314 dr.mDrawableStart = start; 2315 dr.mDrawableSizeStart = compoundRect.width(); 2316 dr.mDrawableHeightStart = compoundRect.height(); 2317 } else { 2318 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2319 } 2320 if (end != null) { 2321 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2322 end.setState(state); 2323 end.copyBounds(compoundRect); 2324 end.setCallback(this); 2325 2326 dr.mDrawableEnd = end; 2327 dr.mDrawableSizeEnd = compoundRect.width(); 2328 dr.mDrawableHeightEnd = compoundRect.height(); 2329 } else { 2330 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2331 } 2332 resetResolvedDrawables(); 2333 resolveDrawables(); 2334 applyCompoundDrawableTint(); 2335 } 2336 } 2337 2338 @android.view.RemotableViewMethod 2339 @Override setEnabled(boolean enabled)2340 public void setEnabled(boolean enabled) { 2341 if (enabled == isEnabled()) { 2342 return; 2343 } 2344 2345 if (!enabled) { 2346 // Hide the soft input if the currently active TextView is disabled 2347 InputMethodManager imm = getInputMethodManager(); 2348 if (imm != null && imm.isActive(this)) { 2349 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2350 } 2351 } 2352 2353 super.setEnabled(enabled); 2354 2355 if (enabled) { 2356 // Make sure IME is updated with current editor info. 2357 InputMethodManager imm = getInputMethodManager(); 2358 if (imm != null) imm.restartInput(this); 2359 } 2360 2361 // Will change text color 2362 if (mEditor != null) { 2363 mEditor.invalidateTextDisplayList(); 2364 mEditor.prepareCursorControllers(); 2365 2366 // start or stop the cursor blinking as appropriate 2367 mEditor.makeBlink(); 2368 } 2369 } 2370 2371 /** 2372 * Sets the typeface and style in which the text should be displayed, 2373 * and turns on the fake bold and italic bits in the Paint if the 2374 * Typeface that you provided does not have all the bits in the 2375 * style that you specified. 2376 * 2377 * @attr ref android.R.styleable#TextView_typeface 2378 * @attr ref android.R.styleable#TextView_textStyle 2379 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2380 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2381 if (style > 0) { 2382 if (tf == null) { 2383 tf = Typeface.defaultFromStyle(style); 2384 } else { 2385 tf = Typeface.create(tf, style); 2386 } 2387 2388 setTypeface(tf); 2389 // now compute what (if any) algorithmic styling is needed 2390 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2391 int need = style & ~typefaceStyle; 2392 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2393 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2394 } else { 2395 mTextPaint.setFakeBoldText(false); 2396 mTextPaint.setTextSkewX(0); 2397 setTypeface(tf); 2398 } 2399 } 2400 2401 /** 2402 * Subclasses override this to specify that they have a KeyListener 2403 * by default even if not specifically called for in the XML options. 2404 */ getDefaultEditable()2405 protected boolean getDefaultEditable() { 2406 return false; 2407 } 2408 2409 /** 2410 * Subclasses override this to specify a default movement method. 2411 */ getDefaultMovementMethod()2412 protected MovementMethod getDefaultMovementMethod() { 2413 return null; 2414 } 2415 2416 /** 2417 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2418 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2419 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2420 * the return value from this method to Spannable or Editable, respectively. 2421 * 2422 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2423 * should make your own copy first.</p> 2424 * 2425 * @return The text displayed by the text view. 2426 * @attr ref android.R.styleable#TextView_text 2427 */ 2428 @ViewDebug.CapturedViewProperty 2429 @InspectableProperty getText()2430 public CharSequence getText() { 2431 if (mUseTextPaddingForUiTranslation) { 2432 ViewTranslationCallback callback = getViewTranslationCallback(); 2433 if (callback != null && callback instanceof TextViewTranslationCallback) { 2434 TextViewTranslationCallback defaultCallback = 2435 (TextViewTranslationCallback) callback; 2436 if (defaultCallback.isTextPaddingEnabled() 2437 && defaultCallback.isShowingTranslation()) { 2438 return defaultCallback.getPaddedText(mText, mTransformed); 2439 } 2440 } 2441 } 2442 return mText; 2443 } 2444 2445 /** 2446 * Returns the length, in characters, of the text managed by this TextView 2447 * @return The length of the text managed by the TextView in characters. 2448 */ length()2449 public int length() { 2450 return mText.length(); 2451 } 2452 2453 /** 2454 * Return the text that TextView is displaying as an Editable object. If the text is not 2455 * editable, null is returned. 2456 * 2457 * @see #getText 2458 */ getEditableText()2459 public Editable getEditableText() { 2460 return (mText instanceof Editable) ? (Editable) mText : null; 2461 } 2462 2463 /** 2464 * @hide 2465 */ 2466 @VisibleForTesting getTransformed()2467 public CharSequence getTransformed() { 2468 return mTransformed; 2469 } 2470 2471 /** 2472 * Gets the vertical distance between lines of text, in pixels. 2473 * Note that markup within the text can cause individual lines 2474 * to be taller or shorter than this height, and the layout may 2475 * contain additional first-or last-line padding. 2476 * @return The height of one standard line in pixels. 2477 */ 2478 @InspectableProperty getLineHeight()2479 public int getLineHeight() { 2480 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2481 } 2482 2483 /** 2484 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2485 * This value can be null if the text or width has recently changed. 2486 * @return The Layout that is currently being used to display the text. 2487 */ getLayout()2488 public final Layout getLayout() { 2489 return mLayout; 2490 } 2491 2492 /** 2493 * @return the {@link android.text.Layout} that is currently being used to 2494 * display the hint text. This can be null. 2495 */ 2496 @UnsupportedAppUsage getHintLayout()2497 final Layout getHintLayout() { 2498 return mHintLayout; 2499 } 2500 2501 /** 2502 * Retrieve the {@link android.content.UndoManager} that is currently associated 2503 * with this TextView. By default there is no associated UndoManager, so null 2504 * is returned. One can be associated with the TextView through 2505 * {@link #setUndoManager(android.content.UndoManager, String)} 2506 * 2507 * @hide 2508 */ getUndoManager()2509 public final UndoManager getUndoManager() { 2510 // TODO: Consider supporting a global undo manager. 2511 throw new UnsupportedOperationException("not implemented"); 2512 } 2513 2514 2515 /** 2516 * @hide 2517 */ 2518 @VisibleForTesting getEditorForTesting()2519 public final Editor getEditorForTesting() { 2520 return mEditor; 2521 } 2522 2523 /** 2524 * Associate an {@link android.content.UndoManager} with this TextView. Once 2525 * done, all edit operations on the TextView will result in appropriate 2526 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2527 * stack. 2528 * 2529 * @param undoManager The {@link android.content.UndoManager} to associate with 2530 * this TextView, or null to clear any existing association. 2531 * @param tag String tag identifying this particular TextView owner in the 2532 * UndoManager. This is used to keep the correct association with the 2533 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2534 * 2535 * @hide 2536 */ setUndoManager(UndoManager undoManager, String tag)2537 public final void setUndoManager(UndoManager undoManager, String tag) { 2538 // TODO: Consider supporting a global undo manager. An implementation will need to: 2539 // * createEditorIfNeeded() 2540 // * Promote to BufferType.EDITABLE if needed. 2541 // * Update the UndoManager and UndoOwner. 2542 // Likewise it will need to be able to restore the default UndoManager. 2543 throw new UnsupportedOperationException("not implemented"); 2544 } 2545 2546 /** 2547 * Gets the current {@link KeyListener} for the TextView. 2548 * This will frequently be null for non-EditText TextViews. 2549 * @return the current key listener for this TextView. 2550 * 2551 * @attr ref android.R.styleable#TextView_numeric 2552 * @attr ref android.R.styleable#TextView_digits 2553 * @attr ref android.R.styleable#TextView_phoneNumber 2554 * @attr ref android.R.styleable#TextView_inputMethod 2555 * @attr ref android.R.styleable#TextView_capitalize 2556 * @attr ref android.R.styleable#TextView_autoText 2557 */ getKeyListener()2558 public final KeyListener getKeyListener() { 2559 return mEditor == null ? null : mEditor.mKeyListener; 2560 } 2561 2562 /** 2563 * Sets the key listener to be used with this TextView. This can be null 2564 * to disallow user input. Note that this method has significant and 2565 * subtle interactions with soft keyboards and other input method: 2566 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2567 * for important details. Calling this method will replace the current 2568 * content type of the text view with the content type returned by the 2569 * key listener. 2570 * <p> 2571 * Be warned that if you want a TextView with a key listener or movement 2572 * method not to be focusable, or if you want a TextView without a 2573 * key listener or movement method to be focusable, you must call 2574 * {@link #setFocusable} again after calling this to get the focusability 2575 * back the way you want it. 2576 * 2577 * @attr ref android.R.styleable#TextView_numeric 2578 * @attr ref android.R.styleable#TextView_digits 2579 * @attr ref android.R.styleable#TextView_phoneNumber 2580 * @attr ref android.R.styleable#TextView_inputMethod 2581 * @attr ref android.R.styleable#TextView_capitalize 2582 * @attr ref android.R.styleable#TextView_autoText 2583 */ setKeyListener(KeyListener input)2584 public void setKeyListener(KeyListener input) { 2585 mListenerChanged = true; 2586 setKeyListenerOnly(input); 2587 fixFocusableAndClickableSettings(); 2588 2589 if (input != null) { 2590 createEditorIfNeeded(); 2591 setInputTypeFromEditor(); 2592 } else { 2593 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2594 } 2595 2596 InputMethodManager imm = getInputMethodManager(); 2597 if (imm != null) imm.restartInput(this); 2598 } 2599 setInputTypeFromEditor()2600 private void setInputTypeFromEditor() { 2601 try { 2602 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2603 } catch (IncompatibleClassChangeError e) { 2604 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2605 } 2606 // Change inputType, without affecting transformation. 2607 // No need to applySingleLine since mSingleLine is unchanged. 2608 setInputTypeSingleLine(mSingleLine); 2609 } 2610 setKeyListenerOnly(KeyListener input)2611 private void setKeyListenerOnly(KeyListener input) { 2612 if (mEditor == null && input == null) return; // null is the default value 2613 2614 createEditorIfNeeded(); 2615 if (mEditor.mKeyListener != input) { 2616 mEditor.mKeyListener = input; 2617 if (input != null && !(mText instanceof Editable)) { 2618 setText(mText); 2619 } 2620 2621 setFilters((Editable) mText, mFilters); 2622 } 2623 } 2624 2625 /** 2626 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2627 * which provides positioning, scrolling, and text selection functionality. 2628 * This will frequently be null for non-EditText TextViews. 2629 * @return the movement method being used for this TextView. 2630 * @see android.text.method.MovementMethod 2631 */ getMovementMethod()2632 public final MovementMethod getMovementMethod() { 2633 return mMovement; 2634 } 2635 2636 /** 2637 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2638 * for this TextView. This can be null to disallow using the arrow keys to move the 2639 * cursor or scroll the view. 2640 * <p> 2641 * Be warned that if you want a TextView with a key listener or movement 2642 * method not to be focusable, or if you want a TextView without a 2643 * key listener or movement method to be focusable, you must call 2644 * {@link #setFocusable} again after calling this to get the focusability 2645 * back the way you want it. 2646 */ setMovementMethod(MovementMethod movement)2647 public final void setMovementMethod(MovementMethod movement) { 2648 if (mMovement != movement) { 2649 mMovement = movement; 2650 2651 if (movement != null && mSpannable == null) { 2652 setText(mText); 2653 } 2654 2655 fixFocusableAndClickableSettings(); 2656 2657 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2658 // mMovement 2659 if (mEditor != null) mEditor.prepareCursorControllers(); 2660 } 2661 } 2662 fixFocusableAndClickableSettings()2663 private void fixFocusableAndClickableSettings() { 2664 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2665 setFocusable(FOCUSABLE); 2666 setClickable(true); 2667 setLongClickable(true); 2668 } else { 2669 setFocusable(FOCUSABLE_AUTO); 2670 setClickable(false); 2671 setLongClickable(false); 2672 } 2673 } 2674 2675 /** 2676 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2677 * This is frequently null, except for single-line and password fields. 2678 * @return the current transformation method for this TextView. 2679 * 2680 * @attr ref android.R.styleable#TextView_password 2681 * @attr ref android.R.styleable#TextView_singleLine 2682 */ getTransformationMethod()2683 public final TransformationMethod getTransformationMethod() { 2684 return mTransformation; 2685 } 2686 2687 /** 2688 * Sets the transformation that is applied to the text that this 2689 * TextView is displaying. 2690 * 2691 * @attr ref android.R.styleable#TextView_password 2692 * @attr ref android.R.styleable#TextView_singleLine 2693 */ setTransformationMethod(TransformationMethod method)2694 public final void setTransformationMethod(TransformationMethod method) { 2695 if (method == mTransformation) { 2696 // Avoid the setText() below if the transformation is 2697 // the same. 2698 return; 2699 } 2700 if (mTransformation != null) { 2701 if (mSpannable != null) { 2702 mSpannable.removeSpan(mTransformation); 2703 } 2704 } 2705 2706 mTransformation = method; 2707 2708 if (method instanceof TransformationMethod2) { 2709 TransformationMethod2 method2 = (TransformationMethod2) method; 2710 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2711 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2712 } else { 2713 mAllowTransformationLengthChange = false; 2714 } 2715 2716 setText(mText); 2717 2718 if (hasPasswordTransformationMethod()) { 2719 notifyViewAccessibilityStateChangedIfNeeded( 2720 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2721 } 2722 2723 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2724 // getTextDirectionHeuristic, needs reset 2725 mTextDir = getTextDirectionHeuristic(); 2726 } 2727 2728 /** 2729 * Returns the top padding of the view, plus space for the top 2730 * Drawable if any. 2731 */ getCompoundPaddingTop()2732 public int getCompoundPaddingTop() { 2733 final Drawables dr = mDrawables; 2734 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2735 return mPaddingTop; 2736 } else { 2737 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2738 } 2739 } 2740 2741 /** 2742 * Returns the bottom padding of the view, plus space for the bottom 2743 * Drawable if any. 2744 */ getCompoundPaddingBottom()2745 public int getCompoundPaddingBottom() { 2746 final Drawables dr = mDrawables; 2747 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2748 return mPaddingBottom; 2749 } else { 2750 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2751 } 2752 } 2753 2754 /** 2755 * Returns the left padding of the view, plus space for the left 2756 * Drawable if any. 2757 */ getCompoundPaddingLeft()2758 public int getCompoundPaddingLeft() { 2759 final Drawables dr = mDrawables; 2760 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2761 return mPaddingLeft; 2762 } else { 2763 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2764 } 2765 } 2766 2767 /** 2768 * Returns the right padding of the view, plus space for the right 2769 * Drawable if any. 2770 */ getCompoundPaddingRight()2771 public int getCompoundPaddingRight() { 2772 final Drawables dr = mDrawables; 2773 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2774 return mPaddingRight; 2775 } else { 2776 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2777 } 2778 } 2779 2780 /** 2781 * Returns the start padding of the view, plus space for the start 2782 * Drawable if any. 2783 */ getCompoundPaddingStart()2784 public int getCompoundPaddingStart() { 2785 resolveDrawables(); 2786 switch(getLayoutDirection()) { 2787 default: 2788 case LAYOUT_DIRECTION_LTR: 2789 return getCompoundPaddingLeft(); 2790 case LAYOUT_DIRECTION_RTL: 2791 return getCompoundPaddingRight(); 2792 } 2793 } 2794 2795 /** 2796 * Returns the end padding of the view, plus space for the end 2797 * Drawable if any. 2798 */ getCompoundPaddingEnd()2799 public int getCompoundPaddingEnd() { 2800 resolveDrawables(); 2801 switch(getLayoutDirection()) { 2802 default: 2803 case LAYOUT_DIRECTION_LTR: 2804 return getCompoundPaddingRight(); 2805 case LAYOUT_DIRECTION_RTL: 2806 return getCompoundPaddingLeft(); 2807 } 2808 } 2809 2810 /** 2811 * Returns the extended top padding of the view, including both the 2812 * top Drawable if any and any extra space to keep more than maxLines 2813 * of text from showing. It is only valid to call this after measuring. 2814 */ getExtendedPaddingTop()2815 public int getExtendedPaddingTop() { 2816 if (mMaxMode != LINES) { 2817 return getCompoundPaddingTop(); 2818 } 2819 2820 if (mLayout == null) { 2821 assumeLayout(); 2822 } 2823 2824 if (mLayout.getLineCount() <= mMaximum) { 2825 return getCompoundPaddingTop(); 2826 } 2827 2828 int top = getCompoundPaddingTop(); 2829 int bottom = getCompoundPaddingBottom(); 2830 int viewht = getHeight() - top - bottom; 2831 int layoutht = mLayout.getLineTop(mMaximum); 2832 2833 if (layoutht >= viewht) { 2834 return top; 2835 } 2836 2837 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2838 if (gravity == Gravity.TOP) { 2839 return top; 2840 } else if (gravity == Gravity.BOTTOM) { 2841 return top + viewht - layoutht; 2842 } else { // (gravity == Gravity.CENTER_VERTICAL) 2843 return top + (viewht - layoutht) / 2; 2844 } 2845 } 2846 2847 /** 2848 * Returns the extended bottom padding of the view, including both the 2849 * bottom Drawable if any and any extra space to keep more than maxLines 2850 * of text from showing. It is only valid to call this after measuring. 2851 */ getExtendedPaddingBottom()2852 public int getExtendedPaddingBottom() { 2853 if (mMaxMode != LINES) { 2854 return getCompoundPaddingBottom(); 2855 } 2856 2857 if (mLayout == null) { 2858 assumeLayout(); 2859 } 2860 2861 if (mLayout.getLineCount() <= mMaximum) { 2862 return getCompoundPaddingBottom(); 2863 } 2864 2865 int top = getCompoundPaddingTop(); 2866 int bottom = getCompoundPaddingBottom(); 2867 int viewht = getHeight() - top - bottom; 2868 int layoutht = mLayout.getLineTop(mMaximum); 2869 2870 if (layoutht >= viewht) { 2871 return bottom; 2872 } 2873 2874 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2875 if (gravity == Gravity.TOP) { 2876 return bottom + viewht - layoutht; 2877 } else if (gravity == Gravity.BOTTOM) { 2878 return bottom; 2879 } else { // (gravity == Gravity.CENTER_VERTICAL) 2880 return bottom + (viewht - layoutht) / 2; 2881 } 2882 } 2883 2884 /** 2885 * Returns the total left padding of the view, including the left 2886 * Drawable if any. 2887 */ getTotalPaddingLeft()2888 public int getTotalPaddingLeft() { 2889 return getCompoundPaddingLeft(); 2890 } 2891 2892 /** 2893 * Returns the total right padding of the view, including the right 2894 * Drawable if any. 2895 */ getTotalPaddingRight()2896 public int getTotalPaddingRight() { 2897 return getCompoundPaddingRight(); 2898 } 2899 2900 /** 2901 * Returns the total start padding of the view, including the start 2902 * Drawable if any. 2903 */ getTotalPaddingStart()2904 public int getTotalPaddingStart() { 2905 return getCompoundPaddingStart(); 2906 } 2907 2908 /** 2909 * Returns the total end padding of the view, including the end 2910 * Drawable if any. 2911 */ getTotalPaddingEnd()2912 public int getTotalPaddingEnd() { 2913 return getCompoundPaddingEnd(); 2914 } 2915 2916 /** 2917 * Returns the total top padding of the view, including the top 2918 * Drawable if any, the extra space to keep more than maxLines 2919 * from showing, and the vertical offset for gravity, if any. 2920 */ getTotalPaddingTop()2921 public int getTotalPaddingTop() { 2922 return getExtendedPaddingTop() + getVerticalOffset(true); 2923 } 2924 2925 /** 2926 * Returns the total bottom padding of the view, including the bottom 2927 * Drawable if any, the extra space to keep more than maxLines 2928 * from showing, and the vertical offset for gravity, if any. 2929 */ getTotalPaddingBottom()2930 public int getTotalPaddingBottom() { 2931 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2932 } 2933 2934 /** 2935 * Sets the Drawables (if any) to appear to the left of, above, to the 2936 * right of, and below the text. Use {@code null} if you do not want a 2937 * Drawable there. The Drawables must already have had 2938 * {@link Drawable#setBounds} called. 2939 * <p> 2940 * Calling this method will overwrite any Drawables previously set using 2941 * {@link #setCompoundDrawablesRelative} or related methods. 2942 * 2943 * @attr ref android.R.styleable#TextView_drawableLeft 2944 * @attr ref android.R.styleable#TextView_drawableTop 2945 * @attr ref android.R.styleable#TextView_drawableRight 2946 * @attr ref android.R.styleable#TextView_drawableBottom 2947 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2948 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2949 @Nullable Drawable right, @Nullable Drawable bottom) { 2950 Drawables dr = mDrawables; 2951 2952 // We're switching to absolute, discard relative. 2953 if (dr != null) { 2954 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2955 dr.mDrawableStart = null; 2956 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2957 dr.mDrawableEnd = null; 2958 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2959 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2960 } 2961 2962 final boolean drawables = left != null || top != null || right != null || bottom != null; 2963 if (!drawables) { 2964 // Clearing drawables... can we free the data structure? 2965 if (dr != null) { 2966 if (!dr.hasMetadata()) { 2967 mDrawables = null; 2968 } else { 2969 // We need to retain the last set padding, so just clear 2970 // out all of the fields in the existing structure. 2971 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2972 if (dr.mShowing[i] != null) { 2973 dr.mShowing[i].setCallback(null); 2974 } 2975 dr.mShowing[i] = null; 2976 } 2977 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2978 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2979 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2980 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2981 } 2982 } 2983 } else { 2984 if (dr == null) { 2985 mDrawables = dr = new Drawables(getContext()); 2986 } 2987 2988 mDrawables.mOverride = false; 2989 2990 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2991 dr.mShowing[Drawables.LEFT].setCallback(null); 2992 } 2993 dr.mShowing[Drawables.LEFT] = left; 2994 2995 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2996 dr.mShowing[Drawables.TOP].setCallback(null); 2997 } 2998 dr.mShowing[Drawables.TOP] = top; 2999 3000 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 3001 dr.mShowing[Drawables.RIGHT].setCallback(null); 3002 } 3003 dr.mShowing[Drawables.RIGHT] = right; 3004 3005 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3006 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3007 } 3008 dr.mShowing[Drawables.BOTTOM] = bottom; 3009 3010 final Rect compoundRect = dr.mCompoundRect; 3011 int[] state; 3012 3013 state = getDrawableState(); 3014 3015 if (left != null) { 3016 left.setState(state); 3017 left.copyBounds(compoundRect); 3018 left.setCallback(this); 3019 dr.mDrawableSizeLeft = compoundRect.width(); 3020 dr.mDrawableHeightLeft = compoundRect.height(); 3021 } else { 3022 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3023 } 3024 3025 if (right != null) { 3026 right.setState(state); 3027 right.copyBounds(compoundRect); 3028 right.setCallback(this); 3029 dr.mDrawableSizeRight = compoundRect.width(); 3030 dr.mDrawableHeightRight = compoundRect.height(); 3031 } else { 3032 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3033 } 3034 3035 if (top != null) { 3036 top.setState(state); 3037 top.copyBounds(compoundRect); 3038 top.setCallback(this); 3039 dr.mDrawableSizeTop = compoundRect.height(); 3040 dr.mDrawableWidthTop = compoundRect.width(); 3041 } else { 3042 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3043 } 3044 3045 if (bottom != null) { 3046 bottom.setState(state); 3047 bottom.copyBounds(compoundRect); 3048 bottom.setCallback(this); 3049 dr.mDrawableSizeBottom = compoundRect.height(); 3050 dr.mDrawableWidthBottom = compoundRect.width(); 3051 } else { 3052 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3053 } 3054 } 3055 3056 // Save initial left/right drawables 3057 if (dr != null) { 3058 dr.mDrawableLeftInitial = left; 3059 dr.mDrawableRightInitial = right; 3060 } 3061 3062 resetResolvedDrawables(); 3063 resolveDrawables(); 3064 applyCompoundDrawableTint(); 3065 invalidate(); 3066 requestLayout(); 3067 } 3068 3069 /** 3070 * Sets the Drawables (if any) to appear to the left of, above, to the 3071 * right of, and below the text. Use 0 if you do not want a Drawable there. 3072 * The Drawables' bounds will be set to their intrinsic bounds. 3073 * <p> 3074 * Calling this method will overwrite any Drawables previously set using 3075 * {@link #setCompoundDrawablesRelative} or related methods. 3076 * 3077 * @param left Resource identifier of the left Drawable. 3078 * @param top Resource identifier of the top Drawable. 3079 * @param right Resource identifier of the right Drawable. 3080 * @param bottom Resource identifier of the bottom Drawable. 3081 * 3082 * @attr ref android.R.styleable#TextView_drawableLeft 3083 * @attr ref android.R.styleable#TextView_drawableTop 3084 * @attr ref android.R.styleable#TextView_drawableRight 3085 * @attr ref android.R.styleable#TextView_drawableBottom 3086 */ 3087 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3088 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 3089 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 3090 final Context context = getContext(); 3091 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 3092 top != 0 ? context.getDrawable(top) : null, 3093 right != 0 ? context.getDrawable(right) : null, 3094 bottom != 0 ? context.getDrawable(bottom) : null); 3095 } 3096 3097 /** 3098 * Sets the Drawables (if any) to appear to the left of, above, to the 3099 * right of, and below the text. Use {@code null} if you do not want a 3100 * Drawable there. The Drawables' bounds will be set to their intrinsic 3101 * bounds. 3102 * <p> 3103 * Calling this method will overwrite any Drawables previously set using 3104 * {@link #setCompoundDrawablesRelative} or related methods. 3105 * 3106 * @attr ref android.R.styleable#TextView_drawableLeft 3107 * @attr ref android.R.styleable#TextView_drawableTop 3108 * @attr ref android.R.styleable#TextView_drawableRight 3109 * @attr ref android.R.styleable#TextView_drawableBottom 3110 */ 3111 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3112 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3113 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3114 3115 if (left != null) { 3116 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3117 } 3118 if (right != null) { 3119 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3120 } 3121 if (top != null) { 3122 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3123 } 3124 if (bottom != null) { 3125 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3126 } 3127 setCompoundDrawables(left, top, right, bottom); 3128 } 3129 3130 /** 3131 * Sets the Drawables (if any) to appear to the start of, above, to the end 3132 * of, and below the text. Use {@code null} if you do not want a Drawable 3133 * there. The Drawables must already have had {@link Drawable#setBounds} 3134 * called. 3135 * <p> 3136 * Calling this method will overwrite any Drawables previously set using 3137 * {@link #setCompoundDrawables} or related methods. 3138 * 3139 * @attr ref android.R.styleable#TextView_drawableStart 3140 * @attr ref android.R.styleable#TextView_drawableTop 3141 * @attr ref android.R.styleable#TextView_drawableEnd 3142 * @attr ref android.R.styleable#TextView_drawableBottom 3143 */ 3144 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3145 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3146 @Nullable Drawable end, @Nullable Drawable bottom) { 3147 Drawables dr = mDrawables; 3148 3149 // We're switching to relative, discard absolute. 3150 if (dr != null) { 3151 if (dr.mShowing[Drawables.LEFT] != null) { 3152 dr.mShowing[Drawables.LEFT].setCallback(null); 3153 } 3154 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3155 if (dr.mShowing[Drawables.RIGHT] != null) { 3156 dr.mShowing[Drawables.RIGHT].setCallback(null); 3157 } 3158 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3159 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3160 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3161 } 3162 3163 final boolean drawables = start != null || top != null 3164 || end != null || bottom != null; 3165 3166 if (!drawables) { 3167 // Clearing drawables... can we free the data structure? 3168 if (dr != null) { 3169 if (!dr.hasMetadata()) { 3170 mDrawables = null; 3171 } else { 3172 // We need to retain the last set padding, so just clear 3173 // out all of the fields in the existing structure. 3174 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3175 dr.mDrawableStart = null; 3176 if (dr.mShowing[Drawables.TOP] != null) { 3177 dr.mShowing[Drawables.TOP].setCallback(null); 3178 } 3179 dr.mShowing[Drawables.TOP] = null; 3180 if (dr.mDrawableEnd != null) { 3181 dr.mDrawableEnd.setCallback(null); 3182 } 3183 dr.mDrawableEnd = null; 3184 if (dr.mShowing[Drawables.BOTTOM] != null) { 3185 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3186 } 3187 dr.mShowing[Drawables.BOTTOM] = null; 3188 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3189 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3190 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3191 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3192 } 3193 } 3194 } else { 3195 if (dr == null) { 3196 mDrawables = dr = new Drawables(getContext()); 3197 } 3198 3199 mDrawables.mOverride = true; 3200 3201 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3202 dr.mDrawableStart.setCallback(null); 3203 } 3204 dr.mDrawableStart = start; 3205 3206 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3207 dr.mShowing[Drawables.TOP].setCallback(null); 3208 } 3209 dr.mShowing[Drawables.TOP] = top; 3210 3211 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3212 dr.mDrawableEnd.setCallback(null); 3213 } 3214 dr.mDrawableEnd = end; 3215 3216 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3217 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3218 } 3219 dr.mShowing[Drawables.BOTTOM] = bottom; 3220 3221 final Rect compoundRect = dr.mCompoundRect; 3222 int[] state; 3223 3224 state = getDrawableState(); 3225 3226 if (start != null) { 3227 start.setState(state); 3228 start.copyBounds(compoundRect); 3229 start.setCallback(this); 3230 dr.mDrawableSizeStart = compoundRect.width(); 3231 dr.mDrawableHeightStart = compoundRect.height(); 3232 } else { 3233 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3234 } 3235 3236 if (end != null) { 3237 end.setState(state); 3238 end.copyBounds(compoundRect); 3239 end.setCallback(this); 3240 dr.mDrawableSizeEnd = compoundRect.width(); 3241 dr.mDrawableHeightEnd = compoundRect.height(); 3242 } else { 3243 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3244 } 3245 3246 if (top != null) { 3247 top.setState(state); 3248 top.copyBounds(compoundRect); 3249 top.setCallback(this); 3250 dr.mDrawableSizeTop = compoundRect.height(); 3251 dr.mDrawableWidthTop = compoundRect.width(); 3252 } else { 3253 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3254 } 3255 3256 if (bottom != null) { 3257 bottom.setState(state); 3258 bottom.copyBounds(compoundRect); 3259 bottom.setCallback(this); 3260 dr.mDrawableSizeBottom = compoundRect.height(); 3261 dr.mDrawableWidthBottom = compoundRect.width(); 3262 } else { 3263 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3264 } 3265 } 3266 3267 resetResolvedDrawables(); 3268 resolveDrawables(); 3269 invalidate(); 3270 requestLayout(); 3271 } 3272 3273 /** 3274 * Sets the Drawables (if any) to appear to the start of, above, to the end 3275 * of, and below the text. Use 0 if you do not want a Drawable there. The 3276 * Drawables' bounds will be set to their intrinsic bounds. 3277 * <p> 3278 * Calling this method will overwrite any Drawables previously set using 3279 * {@link #setCompoundDrawables} or related methods. 3280 * 3281 * @param start Resource identifier of the start Drawable. 3282 * @param top Resource identifier of the top Drawable. 3283 * @param end Resource identifier of the end Drawable. 3284 * @param bottom Resource identifier of the bottom Drawable. 3285 * 3286 * @attr ref android.R.styleable#TextView_drawableStart 3287 * @attr ref android.R.styleable#TextView_drawableTop 3288 * @attr ref android.R.styleable#TextView_drawableEnd 3289 * @attr ref android.R.styleable#TextView_drawableBottom 3290 */ 3291 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3292 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3293 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3294 final Context context = getContext(); 3295 setCompoundDrawablesRelativeWithIntrinsicBounds( 3296 start != 0 ? context.getDrawable(start) : null, 3297 top != 0 ? context.getDrawable(top) : null, 3298 end != 0 ? context.getDrawable(end) : null, 3299 bottom != 0 ? context.getDrawable(bottom) : null); 3300 } 3301 3302 /** 3303 * Sets the Drawables (if any) to appear to the start of, above, to the end 3304 * of, and below the text. Use {@code null} if you do not want a Drawable 3305 * there. The Drawables' bounds will be set to their intrinsic bounds. 3306 * <p> 3307 * Calling this method will overwrite any Drawables previously set using 3308 * {@link #setCompoundDrawables} or related methods. 3309 * 3310 * @attr ref android.R.styleable#TextView_drawableStart 3311 * @attr ref android.R.styleable#TextView_drawableTop 3312 * @attr ref android.R.styleable#TextView_drawableEnd 3313 * @attr ref android.R.styleable#TextView_drawableBottom 3314 */ 3315 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3316 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3317 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3318 3319 if (start != null) { 3320 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3321 } 3322 if (end != null) { 3323 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3324 } 3325 if (top != null) { 3326 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3327 } 3328 if (bottom != null) { 3329 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3330 } 3331 setCompoundDrawablesRelative(start, top, end, bottom); 3332 } 3333 3334 /** 3335 * Returns drawables for the left, top, right, and bottom borders. 3336 * 3337 * @attr ref android.R.styleable#TextView_drawableLeft 3338 * @attr ref android.R.styleable#TextView_drawableTop 3339 * @attr ref android.R.styleable#TextView_drawableRight 3340 * @attr ref android.R.styleable#TextView_drawableBottom 3341 */ 3342 @NonNull getCompoundDrawables()3343 public Drawable[] getCompoundDrawables() { 3344 final Drawables dr = mDrawables; 3345 if (dr != null) { 3346 return dr.mShowing.clone(); 3347 } else { 3348 return new Drawable[] { null, null, null, null }; 3349 } 3350 } 3351 3352 /** 3353 * Returns drawables for the start, top, end, and bottom borders. 3354 * 3355 * @attr ref android.R.styleable#TextView_drawableStart 3356 * @attr ref android.R.styleable#TextView_drawableTop 3357 * @attr ref android.R.styleable#TextView_drawableEnd 3358 * @attr ref android.R.styleable#TextView_drawableBottom 3359 */ 3360 @NonNull getCompoundDrawablesRelative()3361 public Drawable[] getCompoundDrawablesRelative() { 3362 final Drawables dr = mDrawables; 3363 if (dr != null) { 3364 return new Drawable[] { 3365 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3366 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3367 }; 3368 } else { 3369 return new Drawable[] { null, null, null, null }; 3370 } 3371 } 3372 3373 /** 3374 * Sets the size of the padding between the compound drawables and 3375 * the text. 3376 * 3377 * @attr ref android.R.styleable#TextView_drawablePadding 3378 */ 3379 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3380 public void setCompoundDrawablePadding(int pad) { 3381 Drawables dr = mDrawables; 3382 if (pad == 0) { 3383 if (dr != null) { 3384 dr.mDrawablePadding = pad; 3385 } 3386 } else { 3387 if (dr == null) { 3388 mDrawables = dr = new Drawables(getContext()); 3389 } 3390 dr.mDrawablePadding = pad; 3391 } 3392 3393 invalidate(); 3394 requestLayout(); 3395 } 3396 3397 /** 3398 * Returns the padding between the compound drawables and the text. 3399 * 3400 * @attr ref android.R.styleable#TextView_drawablePadding 3401 */ 3402 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3403 public int getCompoundDrawablePadding() { 3404 final Drawables dr = mDrawables; 3405 return dr != null ? dr.mDrawablePadding : 0; 3406 } 3407 3408 /** 3409 * Applies a tint to the compound drawables. Does not modify the 3410 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3411 * <p> 3412 * Subsequent calls to 3413 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3414 * and related methods will automatically mutate the drawables and apply 3415 * the specified tint and tint mode using 3416 * {@link Drawable#setTintList(ColorStateList)}. 3417 * 3418 * @param tint the tint to apply, may be {@code null} to clear tint 3419 * 3420 * @attr ref android.R.styleable#TextView_drawableTint 3421 * @see #getCompoundDrawableTintList() 3422 * @see Drawable#setTintList(ColorStateList) 3423 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3424 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3425 if (mDrawables == null) { 3426 mDrawables = new Drawables(getContext()); 3427 } 3428 mDrawables.mTintList = tint; 3429 mDrawables.mHasTint = true; 3430 3431 applyCompoundDrawableTint(); 3432 } 3433 3434 /** 3435 * @return the tint applied to the compound drawables 3436 * @attr ref android.R.styleable#TextView_drawableTint 3437 * @see #setCompoundDrawableTintList(ColorStateList) 3438 */ 3439 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3440 public ColorStateList getCompoundDrawableTintList() { 3441 return mDrawables != null ? mDrawables.mTintList : null; 3442 } 3443 3444 /** 3445 * Specifies the blending mode used to apply the tint specified by 3446 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3447 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3448 * 3449 * @param tintMode the blending mode used to apply the tint, may be 3450 * {@code null} to clear tint 3451 * @attr ref android.R.styleable#TextView_drawableTintMode 3452 * @see #setCompoundDrawableTintList(ColorStateList) 3453 * @see Drawable#setTintMode(PorterDuff.Mode) 3454 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3455 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3456 setCompoundDrawableTintBlendMode(tintMode != null 3457 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3458 } 3459 3460 /** 3461 * Specifies the blending mode used to apply the tint specified by 3462 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3463 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3464 * 3465 * @param blendMode the blending mode used to apply the tint, may be 3466 * {@code null} to clear tint 3467 * @attr ref android.R.styleable#TextView_drawableTintMode 3468 * @see #setCompoundDrawableTintList(ColorStateList) 3469 * @see Drawable#setTintBlendMode(BlendMode) 3470 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3471 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3472 if (mDrawables == null) { 3473 mDrawables = new Drawables(getContext()); 3474 } 3475 mDrawables.mBlendMode = blendMode; 3476 mDrawables.mHasTintMode = true; 3477 3478 applyCompoundDrawableTint(); 3479 } 3480 3481 /** 3482 * Returns the blending mode used to apply the tint to the compound 3483 * drawables, if specified. 3484 * 3485 * @return the blending mode used to apply the tint to the compound 3486 * drawables 3487 * @attr ref android.R.styleable#TextView_drawableTintMode 3488 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3489 * 3490 */ 3491 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3492 public PorterDuff.Mode getCompoundDrawableTintMode() { 3493 BlendMode mode = getCompoundDrawableTintBlendMode(); 3494 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3495 } 3496 3497 /** 3498 * Returns the blending mode used to apply the tint to the compound 3499 * drawables, if specified. 3500 * 3501 * @return the blending mode used to apply the tint to the compound 3502 * drawables 3503 * @attr ref android.R.styleable#TextView_drawableTintMode 3504 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3505 */ 3506 @InspectableProperty(name = "drawableBlendMode", 3507 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3508 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3509 return mDrawables != null ? mDrawables.mBlendMode : null; 3510 } 3511 applyCompoundDrawableTint()3512 private void applyCompoundDrawableTint() { 3513 if (mDrawables == null) { 3514 return; 3515 } 3516 3517 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3518 final ColorStateList tintList = mDrawables.mTintList; 3519 final BlendMode blendMode = mDrawables.mBlendMode; 3520 final boolean hasTint = mDrawables.mHasTint; 3521 final boolean hasTintMode = mDrawables.mHasTintMode; 3522 final int[] state = getDrawableState(); 3523 3524 for (Drawable dr : mDrawables.mShowing) { 3525 if (dr == null) { 3526 continue; 3527 } 3528 3529 if (dr == mDrawables.mDrawableError) { 3530 // From a developer's perspective, the error drawable isn't 3531 // a compound drawable. Don't apply the generic compound 3532 // drawable tint to it. 3533 continue; 3534 } 3535 3536 dr.mutate(); 3537 3538 if (hasTint) { 3539 dr.setTintList(tintList); 3540 } 3541 3542 if (hasTintMode) { 3543 dr.setTintBlendMode(blendMode); 3544 } 3545 3546 // The drawable (or one of its children) may not have been 3547 // stateful before applying the tint, so let's try again. 3548 if (dr.isStateful()) { 3549 dr.setState(state); 3550 } 3551 } 3552 } 3553 } 3554 3555 /** 3556 * @inheritDoc 3557 * 3558 * @see #setFirstBaselineToTopHeight(int) 3559 * @see #setLastBaselineToBottomHeight(int) 3560 */ 3561 @Override setPadding(int left, int top, int right, int bottom)3562 public void setPadding(int left, int top, int right, int bottom) { 3563 if (left != mPaddingLeft 3564 || right != mPaddingRight 3565 || top != mPaddingTop 3566 || bottom != mPaddingBottom) { 3567 nullLayouts(); 3568 } 3569 3570 // the super call will requestLayout() 3571 super.setPadding(left, top, right, bottom); 3572 invalidate(); 3573 } 3574 3575 /** 3576 * @inheritDoc 3577 * 3578 * @see #setFirstBaselineToTopHeight(int) 3579 * @see #setLastBaselineToBottomHeight(int) 3580 */ 3581 @Override setPaddingRelative(int start, int top, int end, int bottom)3582 public void setPaddingRelative(int start, int top, int end, int bottom) { 3583 if (start != getPaddingStart() 3584 || end != getPaddingEnd() 3585 || top != mPaddingTop 3586 || bottom != mPaddingBottom) { 3587 nullLayouts(); 3588 } 3589 3590 // the super call will requestLayout() 3591 super.setPaddingRelative(start, top, end, bottom); 3592 invalidate(); 3593 } 3594 3595 /** 3596 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3597 * the distance between the top of the TextView and first line's baseline. 3598 * <p> 3599 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3600 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3601 * 3602 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3603 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3604 * Moreover since this function sets the top padding, if the height of the TextView is less than 3605 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3606 * down and bottom will be clipped. 3607 * 3608 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3609 * in pixels 3610 * 3611 * @see #getFirstBaselineToTopHeight() 3612 * @see #setLastBaselineToBottomHeight(int) 3613 * @see #setPadding(int, int, int, int) 3614 * @see #setPaddingRelative(int, int, int, int) 3615 * 3616 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3617 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3618 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3619 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3620 3621 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3622 final int fontMetricsTop; 3623 if (getIncludeFontPadding()) { 3624 fontMetricsTop = fontMetrics.top; 3625 } else { 3626 fontMetricsTop = fontMetrics.ascent; 3627 } 3628 3629 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3630 // in settings). At the moment, we don't. 3631 3632 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3633 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3634 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3635 } 3636 } 3637 3638 /** 3639 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3640 * the distance between the bottom of the TextView and the last line's baseline. 3641 * <p> 3642 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3643 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3644 * 3645 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3646 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3647 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3648 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3649 * clipped. 3650 * 3651 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3652 * in pixels 3653 * 3654 * @see #getLastBaselineToBottomHeight() 3655 * @see #setFirstBaselineToTopHeight(int) 3656 * @see #setPadding(int, int, int, int) 3657 * @see #setPaddingRelative(int, int, int, int) 3658 * 3659 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3660 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3661 public void setLastBaselineToBottomHeight( 3662 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3663 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3664 3665 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3666 final int fontMetricsBottom; 3667 if (getIncludeFontPadding()) { 3668 fontMetricsBottom = fontMetrics.bottom; 3669 } else { 3670 fontMetricsBottom = fontMetrics.descent; 3671 } 3672 3673 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3674 // in settings). At the moment, we don't. 3675 3676 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3677 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3678 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3679 } 3680 } 3681 3682 /** 3683 * Returns the distance between the first text baseline and the top of this TextView. 3684 * 3685 * @see #setFirstBaselineToTopHeight(int) 3686 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3687 */ 3688 @InspectableProperty getFirstBaselineToTopHeight()3689 public int getFirstBaselineToTopHeight() { 3690 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3691 } 3692 3693 /** 3694 * Returns the distance between the last text baseline and the bottom of this TextView. 3695 * 3696 * @see #setLastBaselineToBottomHeight(int) 3697 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3698 */ 3699 @InspectableProperty getLastBaselineToBottomHeight()3700 public int getLastBaselineToBottomHeight() { 3701 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3702 } 3703 3704 /** 3705 * Gets the autolink mask of the text. 3706 * 3707 * See {@link Linkify#ALL} and peers for possible values. 3708 * 3709 * @attr ref android.R.styleable#TextView_autoLink 3710 */ 3711 @InspectableProperty(name = "autoLink", flagMapping = { 3712 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3713 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3714 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3715 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3716 }) getAutoLinkMask()3717 public final int getAutoLinkMask() { 3718 return mAutoLinkMask; 3719 } 3720 3721 /** 3722 * Sets the Drawable corresponding to the selection handle used for 3723 * positioning the cursor within text. The Drawable defaults to the value 3724 * of the textSelectHandle attribute. 3725 * Note that any change applied to the handle Drawable will not be visible 3726 * until the handle is hidden and then drawn again. 3727 * 3728 * @see #setTextSelectHandle(int) 3729 * @attr ref android.R.styleable#TextView_textSelectHandle 3730 */ 3731 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3732 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3733 Preconditions.checkNotNull(textSelectHandle, 3734 "The text select handle should not be null."); 3735 mTextSelectHandle = textSelectHandle; 3736 mTextSelectHandleRes = 0; 3737 if (mEditor != null) { 3738 mEditor.loadHandleDrawables(true /* overwrite */); 3739 } 3740 } 3741 3742 /** 3743 * Sets the Drawable corresponding to the selection handle used for 3744 * positioning the cursor within text. The Drawable defaults to the value 3745 * of the textSelectHandle attribute. 3746 * Note that any change applied to the handle Drawable will not be visible 3747 * until the handle is hidden and then drawn again. 3748 * 3749 * @see #setTextSelectHandle(Drawable) 3750 * @attr ref android.R.styleable#TextView_textSelectHandle 3751 */ 3752 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3753 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3754 Preconditions.checkArgument(textSelectHandle != 0, 3755 "The text select handle should be a valid drawable resource id."); 3756 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3757 } 3758 3759 /** 3760 * Returns the Drawable corresponding to the selection handle used 3761 * for positioning the cursor within text. 3762 * Note that any change applied to the handle Drawable will not be visible 3763 * until the handle is hidden and then drawn again. 3764 * 3765 * @return the text select handle drawable 3766 * 3767 * @see #setTextSelectHandle(Drawable) 3768 * @see #setTextSelectHandle(int) 3769 * @attr ref android.R.styleable#TextView_textSelectHandle 3770 */ getTextSelectHandle()3771 @Nullable public Drawable getTextSelectHandle() { 3772 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3773 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3774 } 3775 return mTextSelectHandle; 3776 } 3777 3778 /** 3779 * Sets the Drawable corresponding to the left handle used 3780 * for selecting text. The Drawable defaults to the value of the 3781 * textSelectHandleLeft attribute. 3782 * Note that any change applied to the handle Drawable will not be visible 3783 * until the handle is hidden and then drawn again. 3784 * 3785 * @see #setTextSelectHandleLeft(int) 3786 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3787 */ 3788 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3789 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3790 Preconditions.checkNotNull(textSelectHandleLeft, 3791 "The left text select handle should not be null."); 3792 mTextSelectHandleLeft = textSelectHandleLeft; 3793 mTextSelectHandleLeftRes = 0; 3794 if (mEditor != null) { 3795 mEditor.loadHandleDrawables(true /* overwrite */); 3796 } 3797 } 3798 3799 /** 3800 * Sets the Drawable corresponding to the left handle used 3801 * for selecting text. The Drawable defaults to the value of the 3802 * textSelectHandleLeft attribute. 3803 * Note that any change applied to the handle Drawable will not be visible 3804 * until the handle is hidden and then drawn again. 3805 * 3806 * @see #setTextSelectHandleLeft(Drawable) 3807 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3808 */ 3809 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3810 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3811 Preconditions.checkArgument(textSelectHandleLeft != 0, 3812 "The text select left handle should be a valid drawable resource id."); 3813 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3814 } 3815 3816 /** 3817 * Returns the Drawable corresponding to the left handle used 3818 * for selecting text. 3819 * Note that any change applied to the handle Drawable will not be visible 3820 * until the handle is hidden and then drawn again. 3821 * 3822 * @return the left text selection handle drawable 3823 * 3824 * @see #setTextSelectHandleLeft(Drawable) 3825 * @see #setTextSelectHandleLeft(int) 3826 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3827 */ getTextSelectHandleLeft()3828 @Nullable public Drawable getTextSelectHandleLeft() { 3829 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3830 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3831 } 3832 return mTextSelectHandleLeft; 3833 } 3834 3835 /** 3836 * Sets the Drawable corresponding to the right handle used 3837 * for selecting text. The Drawable defaults to the value of the 3838 * textSelectHandleRight attribute. 3839 * Note that any change applied to the handle Drawable will not be visible 3840 * until the handle is hidden and then drawn again. 3841 * 3842 * @see #setTextSelectHandleRight(int) 3843 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3844 */ 3845 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3846 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3847 Preconditions.checkNotNull(textSelectHandleRight, 3848 "The right text select handle should not be null."); 3849 mTextSelectHandleRight = textSelectHandleRight; 3850 mTextSelectHandleRightRes = 0; 3851 if (mEditor != null) { 3852 mEditor.loadHandleDrawables(true /* overwrite */); 3853 } 3854 } 3855 3856 /** 3857 * Sets the Drawable corresponding to the right handle used 3858 * for selecting text. The Drawable defaults to the value of the 3859 * textSelectHandleRight attribute. 3860 * Note that any change applied to the handle Drawable will not be visible 3861 * until the handle is hidden and then drawn again. 3862 * 3863 * @see #setTextSelectHandleRight(Drawable) 3864 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3865 */ 3866 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3867 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3868 Preconditions.checkArgument(textSelectHandleRight != 0, 3869 "The text select right handle should be a valid drawable resource id."); 3870 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3871 } 3872 3873 /** 3874 * Returns the Drawable corresponding to the right handle used 3875 * for selecting text. 3876 * Note that any change applied to the handle Drawable will not be visible 3877 * until the handle is hidden and then drawn again. 3878 * 3879 * @return the right text selection handle drawable 3880 * 3881 * @see #setTextSelectHandleRight(Drawable) 3882 * @see #setTextSelectHandleRight(int) 3883 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3884 */ getTextSelectHandleRight()3885 @Nullable public Drawable getTextSelectHandleRight() { 3886 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3887 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3888 } 3889 return mTextSelectHandleRight; 3890 } 3891 3892 /** 3893 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3894 * value of the textCursorDrawable attribute. 3895 * Note that any change applied to the cursor Drawable will not be visible 3896 * until the cursor is hidden and then drawn again. 3897 * 3898 * @see #setTextCursorDrawable(int) 3899 * @attr ref android.R.styleable#TextView_textCursorDrawable 3900 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3901 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3902 mCursorDrawable = textCursorDrawable; 3903 mCursorDrawableRes = 0; 3904 if (mEditor != null) { 3905 mEditor.loadCursorDrawable(); 3906 } 3907 } 3908 3909 /** 3910 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3911 * value of the textCursorDrawable attribute. 3912 * Note that any change applied to the cursor Drawable will not be visible 3913 * until the cursor is hidden and then drawn again. 3914 * 3915 * @see #setTextCursorDrawable(Drawable) 3916 * @attr ref android.R.styleable#TextView_textCursorDrawable 3917 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3918 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3919 setTextCursorDrawable( 3920 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3921 } 3922 3923 /** 3924 * Returns the Drawable corresponding to the text cursor. 3925 * Note that any change applied to the cursor Drawable will not be visible 3926 * until the cursor is hidden and then drawn again. 3927 * 3928 * @return the text cursor drawable 3929 * 3930 * @see #setTextCursorDrawable(Drawable) 3931 * @see #setTextCursorDrawable(int) 3932 * @attr ref android.R.styleable#TextView_textCursorDrawable 3933 */ getTextCursorDrawable()3934 @Nullable public Drawable getTextCursorDrawable() { 3935 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3936 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3937 } 3938 return mCursorDrawable; 3939 } 3940 3941 /** 3942 * Sets the text appearance from the specified style resource. 3943 * <p> 3944 * Use a framework-defined {@code TextAppearance} style like 3945 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3946 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3947 * set of attributes that can be used in a custom style. 3948 * 3949 * @param resId the resource identifier of the style to apply 3950 * @attr ref android.R.styleable#TextView_textAppearance 3951 */ 3952 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3953 public void setTextAppearance(@StyleRes int resId) { 3954 setTextAppearance(mContext, resId); 3955 } 3956 3957 /** 3958 * Sets the text color, size, style, hint color, and highlight color 3959 * from the specified TextAppearance resource. 3960 * 3961 * @deprecated Use {@link #setTextAppearance(int)} instead. 3962 */ 3963 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3964 public void setTextAppearance(Context context, @StyleRes int resId) { 3965 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3966 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3967 readTextAppearance(context, ta, attributes, false /* styleArray */); 3968 ta.recycle(); 3969 applyTextAppearance(attributes); 3970 } 3971 3972 /** 3973 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3974 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3975 */ 3976 private static class TextAppearanceAttributes { 3977 int mTextColorHighlight = 0; 3978 ColorStateList mTextColor = null; 3979 ColorStateList mTextColorHint = null; 3980 ColorStateList mTextColorLink = null; 3981 int mTextSize = -1; 3982 int mTextSizeUnit = -1; 3983 LocaleList mTextLocales = null; 3984 String mFontFamily = null; 3985 Typeface mFontTypeface = null; 3986 boolean mFontFamilyExplicit = false; 3987 int mTypefaceIndex = -1; 3988 int mTextStyle = 0; 3989 int mFontWeight = -1; 3990 boolean mAllCaps = false; 3991 int mShadowColor = 0; 3992 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3993 boolean mHasElegant = false; 3994 boolean mElegant = false; 3995 boolean mHasFallbackLineSpacing = false; 3996 boolean mFallbackLineSpacing = false; 3997 boolean mHasLetterSpacing = false; 3998 float mLetterSpacing = 0; 3999 String mFontFeatureSettings = null; 4000 String mFontVariationSettings = null; 4001 boolean mHasLineBreakStyle = false; 4002 boolean mHasLineBreakWordStyle = false; 4003 int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 4004 int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 4005 4006 @Override toString()4007 public String toString() { 4008 return "TextAppearanceAttributes {\n" 4009 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 4010 + " mTextColor:" + mTextColor + "\n" 4011 + " mTextColorHint:" + mTextColorHint + "\n" 4012 + " mTextColorLink:" + mTextColorLink + "\n" 4013 + " mTextSize:" + mTextSize + "\n" 4014 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 4015 + " mTextLocales:" + mTextLocales + "\n" 4016 + " mFontFamily:" + mFontFamily + "\n" 4017 + " mFontTypeface:" + mFontTypeface + "\n" 4018 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 4019 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 4020 + " mTextStyle:" + mTextStyle + "\n" 4021 + " mFontWeight:" + mFontWeight + "\n" 4022 + " mAllCaps:" + mAllCaps + "\n" 4023 + " mShadowColor:" + mShadowColor + "\n" 4024 + " mShadowDx:" + mShadowDx + "\n" 4025 + " mShadowDy:" + mShadowDy + "\n" 4026 + " mShadowRadius:" + mShadowRadius + "\n" 4027 + " mHasElegant:" + mHasElegant + "\n" 4028 + " mElegant:" + mElegant + "\n" 4029 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 4030 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 4031 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 4032 + " mLetterSpacing:" + mLetterSpacing + "\n" 4033 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 4034 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 4035 + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" 4036 + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" 4037 + " mLineBreakStyle:" + mLineBreakStyle + "\n" 4038 + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" 4039 + "}"; 4040 } 4041 } 4042 4043 // Maps styleable attributes that exist both in TextView style and TextAppearance. 4044 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 4045 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4046 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 4047 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4048 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 4049 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4050 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 4051 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4052 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 4053 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4054 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 4055 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4056 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 4057 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4058 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 4059 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4060 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 4061 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4062 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4063 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4064 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4065 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4066 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4067 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4068 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4069 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4070 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4071 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4072 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4073 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4074 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4075 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4076 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4077 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4078 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4079 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4080 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4081 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4082 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4083 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4084 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4085 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4086 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, 4087 com.android.internal.R.styleable.TextAppearance_lineBreakStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4088 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, 4089 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); 4090 } 4091 4092 /** 4093 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 4094 * set. If the TypedArray contains a value that was already set in the given attributes, that 4095 * will be overridden. 4096 * 4097 * @param context The Context to be used 4098 * @param appearance The TypedArray to read properties from 4099 * @param attributes the TextAppearanceAttributes to fill in 4100 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 4101 * what attribute indexes will be used to read the properties. 4102 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4103 private void readTextAppearance(Context context, TypedArray appearance, 4104 TextAppearanceAttributes attributes, boolean styleArray) { 4105 final int n = appearance.getIndexCount(); 4106 for (int i = 0; i < n; i++) { 4107 final int attr = appearance.getIndex(i); 4108 int index = attr; 4109 // Translate style array index ids to TextAppearance ids. 4110 if (styleArray) { 4111 index = sAppearanceValues.get(attr, -1); 4112 if (index == -1) { 4113 // This value is not part of a Text Appearance and should be ignored. 4114 continue; 4115 } 4116 } 4117 switch (index) { 4118 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4119 attributes.mTextColorHighlight = 4120 appearance.getColor(attr, attributes.mTextColorHighlight); 4121 break; 4122 case com.android.internal.R.styleable.TextAppearance_textColor: 4123 attributes.mTextColor = appearance.getColorStateList(attr); 4124 break; 4125 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4126 attributes.mTextColorHint = appearance.getColorStateList(attr); 4127 break; 4128 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4129 attributes.mTextColorLink = appearance.getColorStateList(attr); 4130 break; 4131 case com.android.internal.R.styleable.TextAppearance_textSize: 4132 attributes.mTextSize = 4133 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4134 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4135 break; 4136 case com.android.internal.R.styleable.TextAppearance_textLocale: 4137 final String localeString = appearance.getString(attr); 4138 if (localeString != null) { 4139 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4140 if (!localeList.isEmpty()) { 4141 attributes.mTextLocales = localeList; 4142 } 4143 } 4144 break; 4145 case com.android.internal.R.styleable.TextAppearance_typeface: 4146 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4147 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4148 attributes.mFontFamily = null; 4149 } 4150 break; 4151 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4152 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4153 try { 4154 attributes.mFontTypeface = appearance.getFont(attr); 4155 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4156 // Expected if it is not a font resource. 4157 } 4158 } 4159 if (attributes.mFontTypeface == null) { 4160 attributes.mFontFamily = appearance.getString(attr); 4161 } 4162 attributes.mFontFamilyExplicit = true; 4163 break; 4164 case com.android.internal.R.styleable.TextAppearance_textStyle: 4165 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4166 break; 4167 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4168 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4169 break; 4170 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4171 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4172 break; 4173 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4174 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4175 break; 4176 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4177 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4178 break; 4179 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4180 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4181 break; 4182 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4183 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4184 break; 4185 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4186 attributes.mHasElegant = true; 4187 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4188 break; 4189 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4190 attributes.mHasFallbackLineSpacing = true; 4191 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4192 attributes.mFallbackLineSpacing); 4193 break; 4194 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4195 attributes.mHasLetterSpacing = true; 4196 attributes.mLetterSpacing = 4197 appearance.getFloat(attr, attributes.mLetterSpacing); 4198 break; 4199 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4200 attributes.mFontFeatureSettings = appearance.getString(attr); 4201 break; 4202 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4203 attributes.mFontVariationSettings = appearance.getString(attr); 4204 break; 4205 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: 4206 attributes.mHasLineBreakStyle = true; 4207 attributes.mLineBreakStyle = 4208 appearance.getInt(attr, attributes.mLineBreakStyle); 4209 break; 4210 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: 4211 attributes.mHasLineBreakWordStyle = true; 4212 attributes.mLineBreakWordStyle = 4213 appearance.getInt(attr, attributes.mLineBreakWordStyle); 4214 break; 4215 default: 4216 } 4217 } 4218 } 4219 applyTextAppearance(TextAppearanceAttributes attributes)4220 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4221 if (attributes.mTextColor != null) { 4222 setTextColor(attributes.mTextColor); 4223 } 4224 4225 if (attributes.mTextColorHint != null) { 4226 setHintTextColor(attributes.mTextColorHint); 4227 } 4228 4229 if (attributes.mTextColorLink != null) { 4230 setLinkTextColor(attributes.mTextColorLink); 4231 } 4232 4233 if (attributes.mTextColorHighlight != 0) { 4234 setHighlightColor(attributes.mTextColorHighlight); 4235 } 4236 4237 if (attributes.mTextSize != -1) { 4238 mTextSizeUnit = attributes.mTextSizeUnit; 4239 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4240 } 4241 4242 if (attributes.mTextLocales != null) { 4243 setTextLocales(attributes.mTextLocales); 4244 } 4245 4246 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4247 attributes.mFontFamily = null; 4248 } 4249 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4250 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4251 4252 if (attributes.mShadowColor != 0) { 4253 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4254 attributes.mShadowColor); 4255 } 4256 4257 if (attributes.mAllCaps) { 4258 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4259 } 4260 4261 if (attributes.mHasElegant) { 4262 setElegantTextHeight(attributes.mElegant); 4263 } 4264 4265 if (attributes.mHasFallbackLineSpacing) { 4266 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4267 } 4268 4269 if (attributes.mHasLetterSpacing) { 4270 setLetterSpacing(attributes.mLetterSpacing); 4271 } 4272 4273 if (attributes.mFontFeatureSettings != null) { 4274 setFontFeatureSettings(attributes.mFontFeatureSettings); 4275 } 4276 4277 if (attributes.mFontVariationSettings != null) { 4278 setFontVariationSettings(attributes.mFontVariationSettings); 4279 } 4280 4281 if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { 4282 updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, 4283 attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, 4284 attributes.mLineBreakWordStyle); 4285 } 4286 } 4287 4288 /** 4289 * Updates the LineBreakConfig from the TextAppearance. 4290 * 4291 * This method updates the given line configuration from the TextAppearance. This method will 4292 * request new layout if line break config has been changed. 4293 * 4294 * @param isLineBreakStyleSpecified true if the line break style is specified. 4295 * @param isLineBreakWordStyleSpecified true if the line break word style is specified. 4296 * @param lineBreakStyle the value of the line break style in the TextAppearance. 4297 * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. 4298 */ updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4299 private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, 4300 boolean isLineBreakWordStyleSpecified, 4301 @LineBreakConfig.LineBreakStyle int lineBreakStyle, 4302 @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 4303 boolean updated = false; 4304 if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) { 4305 mLineBreakStyle = lineBreakStyle; 4306 updated = true; 4307 } 4308 if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) { 4309 mLineBreakWordStyle = lineBreakWordStyle; 4310 updated = true; 4311 } 4312 if (updated && mLayout != null) { 4313 nullLayouts(); 4314 requestLayout(); 4315 invalidate(); 4316 } 4317 } 4318 /** 4319 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4320 * the first member of {@link #getTextLocales()}. 4321 * @return the default primary {@link Locale} of the text in this TextView. 4322 */ 4323 @NonNull getTextLocale()4324 public Locale getTextLocale() { 4325 return mTextPaint.getTextLocale(); 4326 } 4327 4328 /** 4329 * Get the default {@link LocaleList} of the text in this TextView. 4330 * @return the default {@link LocaleList} of the text in this TextView. 4331 */ 4332 @NonNull @Size(min = 1) getTextLocales()4333 public LocaleList getTextLocales() { 4334 return mTextPaint.getTextLocales(); 4335 } 4336 changeListenerLocaleTo(@ullable Locale locale)4337 private void changeListenerLocaleTo(@Nullable Locale locale) { 4338 if (mListenerChanged) { 4339 // If a listener has been explicitly set, don't change it. We may break something. 4340 return; 4341 } 4342 // The following null check is not absolutely necessary since all calling points of 4343 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4344 // here in case others would want to call this method in the future. 4345 if (mEditor != null) { 4346 KeyListener listener = mEditor.mKeyListener; 4347 if (listener instanceof DigitsKeyListener) { 4348 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4349 } else if (listener instanceof DateKeyListener) { 4350 listener = DateKeyListener.getInstance(locale); 4351 } else if (listener instanceof TimeKeyListener) { 4352 listener = TimeKeyListener.getInstance(locale); 4353 } else if (listener instanceof DateTimeKeyListener) { 4354 listener = DateTimeKeyListener.getInstance(locale); 4355 } else { 4356 return; 4357 } 4358 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4359 setKeyListenerOnly(listener); 4360 setInputTypeFromEditor(); 4361 if (wasPasswordType) { 4362 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4363 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4364 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4365 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4366 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4367 } 4368 } 4369 } 4370 } 4371 4372 /** 4373 * Set the default {@link Locale} of the text in this TextView to a one-member 4374 * {@link LocaleList} containing just the given Locale. 4375 * 4376 * @param locale the {@link Locale} for drawing text, must not be null. 4377 * 4378 * @see #setTextLocales 4379 */ setTextLocale(@onNull Locale locale)4380 public void setTextLocale(@NonNull Locale locale) { 4381 mLocalesChanged = true; 4382 mTextPaint.setTextLocale(locale); 4383 if (mLayout != null) { 4384 nullLayouts(); 4385 requestLayout(); 4386 invalidate(); 4387 } 4388 } 4389 4390 /** 4391 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4392 * 4393 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4394 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4395 * other aspects of text display, including line breaking. 4396 * 4397 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4398 * 4399 * @see Paint#setTextLocales 4400 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4401 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4402 mLocalesChanged = true; 4403 mTextPaint.setTextLocales(locales); 4404 if (mLayout != null) { 4405 nullLayouts(); 4406 requestLayout(); 4407 invalidate(); 4408 } 4409 } 4410 4411 @Override onConfigurationChanged(Configuration newConfig)4412 protected void onConfigurationChanged(Configuration newConfig) { 4413 super.onConfigurationChanged(newConfig); 4414 if (!mLocalesChanged) { 4415 mTextPaint.setTextLocales(LocaleList.getDefault()); 4416 if (mLayout != null) { 4417 nullLayouts(); 4418 requestLayout(); 4419 invalidate(); 4420 } 4421 } 4422 if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { 4423 mFontWeightAdjustment = newConfig.fontWeightAdjustment; 4424 setTypeface(getTypeface()); 4425 } 4426 } 4427 4428 /** 4429 * @return the size (in pixels) of the default text size in this TextView. 4430 */ 4431 @InspectableProperty 4432 @ViewDebug.ExportedProperty(category = "text") getTextSize()4433 public float getTextSize() { 4434 return mTextPaint.getTextSize(); 4435 } 4436 4437 /** 4438 * @return the size (in scaled pixels) of the default text size in this TextView. 4439 * @hide 4440 */ 4441 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4442 public float getScaledTextSize() { 4443 return mTextPaint.getTextSize() / mTextPaint.density; 4444 } 4445 4446 /** @hide */ 4447 @ViewDebug.ExportedProperty(category = "text", mapping = { 4448 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4449 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4450 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4451 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4452 }) getTypefaceStyle()4453 public int getTypefaceStyle() { 4454 Typeface typeface = mTextPaint.getTypeface(); 4455 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4456 } 4457 4458 /** 4459 * Set the default text size to the given value, interpreted as "scaled 4460 * pixel" units. This size is adjusted based on the current density and 4461 * user font size preference. 4462 * 4463 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4464 * 4465 * @param size The scaled pixel size. 4466 * 4467 * @attr ref android.R.styleable#TextView_textSize 4468 */ 4469 @android.view.RemotableViewMethod setTextSize(float size)4470 public void setTextSize(float size) { 4471 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4472 } 4473 4474 /** 4475 * Set the default text size to a given unit and value. See {@link 4476 * TypedValue} for the possible dimension units. 4477 * 4478 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4479 * 4480 * @param unit The desired dimension unit. 4481 * @param size The desired size in the given units. 4482 * 4483 * @attr ref android.R.styleable#TextView_textSize 4484 */ setTextSize(int unit, float size)4485 public void setTextSize(int unit, float size) { 4486 if (!isAutoSizeEnabled()) { 4487 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4488 } 4489 } 4490 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4491 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4492 Context c = getContext(); 4493 Resources r; 4494 4495 if (c == null) { 4496 r = Resources.getSystem(); 4497 } else { 4498 r = c.getResources(); 4499 } 4500 4501 mTextSizeUnit = unit; 4502 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4503 shouldRequestLayout); 4504 } 4505 4506 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setRawTextSize(float size, boolean shouldRequestLayout)4507 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4508 if (size != mTextPaint.getTextSize()) { 4509 mTextPaint.setTextSize(size); 4510 4511 if (shouldRequestLayout && mLayout != null) { 4512 // Do not auto-size right after setting the text size. 4513 mNeedsAutoSizeText = false; 4514 nullLayouts(); 4515 requestLayout(); 4516 invalidate(); 4517 } 4518 } 4519 } 4520 4521 /** 4522 * Gets the text size unit defined by the developer. It may be specified in resources or be 4523 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4524 * 4525 * @return the dimension type of the text size unit originally defined. 4526 * @see TypedValue#TYPE_DIMENSION 4527 */ getTextSizeUnit()4528 public int getTextSizeUnit() { 4529 return mTextSizeUnit; 4530 } 4531 4532 /** 4533 * Gets the extent by which text should be stretched horizontally. 4534 * This will usually be 1.0. 4535 * @return The horizontal scale factor. 4536 */ 4537 @InspectableProperty getTextScaleX()4538 public float getTextScaleX() { 4539 return mTextPaint.getTextScaleX(); 4540 } 4541 4542 /** 4543 * Sets the horizontal scale factor for text. The default value 4544 * is 1.0. Values greater than 1.0 stretch the text wider. 4545 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4546 * @param size The horizontal scale factor. 4547 * @attr ref android.R.styleable#TextView_textScaleX 4548 */ 4549 @android.view.RemotableViewMethod setTextScaleX(float size)4550 public void setTextScaleX(float size) { 4551 if (size != mTextPaint.getTextScaleX()) { 4552 mUserSetTextScaleX = true; 4553 mTextPaint.setTextScaleX(size); 4554 4555 if (mLayout != null) { 4556 nullLayouts(); 4557 requestLayout(); 4558 invalidate(); 4559 } 4560 } 4561 } 4562 4563 /** 4564 * Sets the typeface and style in which the text should be displayed. 4565 * Note that not all Typeface families actually have bold and italic 4566 * variants, so you may need to use 4567 * {@link #setTypeface(Typeface, int)} to get the appearance 4568 * that you actually want. 4569 * 4570 * @see #getTypeface() 4571 * 4572 * @attr ref android.R.styleable#TextView_fontFamily 4573 * @attr ref android.R.styleable#TextView_typeface 4574 * @attr ref android.R.styleable#TextView_textStyle 4575 */ setTypeface(@ullable Typeface tf)4576 public void setTypeface(@Nullable Typeface tf) { 4577 mOriginalTypeface = tf; 4578 if (mFontWeightAdjustment != 0 4579 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 4580 if (tf == null) { 4581 tf = Typeface.DEFAULT; 4582 } else { 4583 int newWeight = Math.min( 4584 Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), 4585 FontStyle.FONT_WEIGHT_MAX); 4586 int typefaceStyle = tf != null ? tf.getStyle() : 0; 4587 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; 4588 tf = Typeface.create(tf, newWeight, italic); 4589 } 4590 } 4591 if (mTextPaint.getTypeface() != tf) { 4592 mTextPaint.setTypeface(tf); 4593 4594 if (mLayout != null) { 4595 nullLayouts(); 4596 requestLayout(); 4597 invalidate(); 4598 } 4599 } 4600 } 4601 4602 /** 4603 * Gets the current {@link Typeface} that is used to style the text. 4604 * @return The current Typeface. 4605 * 4606 * @see #setTypeface(Typeface) 4607 * 4608 * @attr ref android.R.styleable#TextView_fontFamily 4609 * @attr ref android.R.styleable#TextView_typeface 4610 * @attr ref android.R.styleable#TextView_textStyle 4611 */ 4612 @InspectableProperty getTypeface()4613 public Typeface getTypeface() { 4614 return mOriginalTypeface; 4615 } 4616 4617 /** 4618 * Set the TextView's elegant height metrics flag. This setting selects font 4619 * variants that have not been compacted to fit Latin-based vertical 4620 * metrics, and also increases top and bottom bounds to provide more space. 4621 * 4622 * @param elegant set the paint's elegant metrics flag. 4623 * 4624 * @see #isElegantTextHeight() 4625 * @see Paint#isElegantTextHeight() 4626 * 4627 * @attr ref android.R.styleable#TextView_elegantTextHeight 4628 */ setElegantTextHeight(boolean elegant)4629 public void setElegantTextHeight(boolean elegant) { 4630 if (elegant != mTextPaint.isElegantTextHeight()) { 4631 mTextPaint.setElegantTextHeight(elegant); 4632 if (mLayout != null) { 4633 nullLayouts(); 4634 requestLayout(); 4635 invalidate(); 4636 } 4637 } 4638 } 4639 4640 /** 4641 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4642 * displaying the text (which is needed to avoid text from consecutive lines running into 4643 * each other). If set, fallback fonts that end up getting used can increase the ascent 4644 * and descent of the lines that they are used on. 4645 * <p/> 4646 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4647 * is typically much taller or deeper than Latin text. 4648 * 4649 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4650 * 4651 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4652 * 4653 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4654 */ setFallbackLineSpacing(boolean enabled)4655 public void setFallbackLineSpacing(boolean enabled) { 4656 int fallbackStrategy; 4657 if (enabled) { 4658 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 4659 fallbackStrategy = FALLBACK_LINE_SPACING_ALL; 4660 } else { 4661 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4662 } 4663 } else { 4664 fallbackStrategy = FALLBACK_LINE_SPACING_NONE; 4665 } 4666 if (mUseFallbackLineSpacing != fallbackStrategy) { 4667 mUseFallbackLineSpacing = fallbackStrategy; 4668 if (mLayout != null) { 4669 nullLayouts(); 4670 requestLayout(); 4671 invalidate(); 4672 } 4673 } 4674 } 4675 4676 /** 4677 * @return whether fallback line spacing is enabled, {@code true} by default 4678 * 4679 * @see #setFallbackLineSpacing(boolean) 4680 * 4681 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4682 */ 4683 @InspectableProperty isFallbackLineSpacing()4684 public boolean isFallbackLineSpacing() { 4685 return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; 4686 } 4687 isFallbackLineSpacingForBoringLayout()4688 private boolean isFallbackLineSpacingForBoringLayout() { 4689 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; 4690 } 4691 4692 // Package privte for accessing from Editor.java isFallbackLineSpacingForStaticLayout()4693 /* package */ boolean isFallbackLineSpacingForStaticLayout() { 4694 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL 4695 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4696 } 4697 4698 /** 4699 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4700 * variants that have not been compacted to fit Latin-based vertical 4701 * metrics, and also increases top and bottom bounds to provide more space. 4702 * @return {@code true} if the elegant height metrics flag is set. 4703 * 4704 * @see #setElegantTextHeight(boolean) 4705 * @see Paint#setElegantTextHeight(boolean) 4706 */ 4707 @InspectableProperty isElegantTextHeight()4708 public boolean isElegantTextHeight() { 4709 return mTextPaint.isElegantTextHeight(); 4710 } 4711 4712 /** 4713 * Gets the text letter-space value, which determines the spacing between characters. 4714 * The value returned is in ems. Normally, this value is 0.0. 4715 * @return The text letter-space value in ems. 4716 * 4717 * @see #setLetterSpacing(float) 4718 * @see Paint#setLetterSpacing 4719 */ 4720 @InspectableProperty getLetterSpacing()4721 public float getLetterSpacing() { 4722 return mTextPaint.getLetterSpacing(); 4723 } 4724 4725 /** 4726 * Sets text letter-spacing in em units. Typical values 4727 * for slight expansion will be around 0.05. Negative values tighten text. 4728 * 4729 * @see #getLetterSpacing() 4730 * @see Paint#getLetterSpacing 4731 * 4732 * @param letterSpacing A text letter-space value in ems. 4733 * @attr ref android.R.styleable#TextView_letterSpacing 4734 */ 4735 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4736 public void setLetterSpacing(float letterSpacing) { 4737 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4738 mTextPaint.setLetterSpacing(letterSpacing); 4739 4740 if (mLayout != null) { 4741 nullLayouts(); 4742 requestLayout(); 4743 invalidate(); 4744 } 4745 } 4746 } 4747 4748 /** 4749 * Returns the font feature settings. The format is the same as the CSS 4750 * font-feature-settings attribute: 4751 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4752 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4753 * 4754 * @return the currently set font feature settings. Default is null. 4755 * 4756 * @see #setFontFeatureSettings(String) 4757 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4758 */ 4759 @InspectableProperty 4760 @Nullable getFontFeatureSettings()4761 public String getFontFeatureSettings() { 4762 return mTextPaint.getFontFeatureSettings(); 4763 } 4764 4765 /** 4766 * Returns the font variation settings. 4767 * 4768 * @return the currently set font variation settings. Returns null if no variation is 4769 * specified. 4770 * 4771 * @see #setFontVariationSettings(String) 4772 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4773 */ 4774 @Nullable getFontVariationSettings()4775 public String getFontVariationSettings() { 4776 return mTextPaint.getFontVariationSettings(); 4777 } 4778 4779 /** 4780 * Sets the break strategy for breaking paragraphs into lines. The default value for 4781 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4782 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4783 * text "dancing" when being edited. 4784 * <p> 4785 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4786 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4787 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4788 * improves the structure of text layout however has performance impact and requires more time 4789 * to do the text layout.</p> 4790 * <p> 4791 * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is 4792 * evaluated in the ICU to identify the potential breakpoints. In 4793 * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line 4794 * break result. It aims to evaluate ICU's breakpoints and break the lines based on the 4795 * constraint. 4796 * </p> 4797 * 4798 * @attr ref android.R.styleable#TextView_breakStrategy 4799 * @see #getBreakStrategy() 4800 * @see #setHyphenationFrequency(int) 4801 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4802 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4803 mBreakStrategy = breakStrategy; 4804 if (mLayout != null) { 4805 nullLayouts(); 4806 requestLayout(); 4807 invalidate(); 4808 } 4809 } 4810 4811 /** 4812 * Gets the current strategy for breaking paragraphs into lines. 4813 * @return the current strategy for breaking paragraphs into lines. 4814 * 4815 * @attr ref android.R.styleable#TextView_breakStrategy 4816 * @see #setBreakStrategy(int) 4817 */ 4818 @InspectableProperty(enumMapping = { 4819 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4820 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4821 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4822 }) 4823 @Layout.BreakStrategy getBreakStrategy()4824 public int getBreakStrategy() { 4825 return mBreakStrategy; 4826 } 4827 4828 /** 4829 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4830 * The default value for both TextView and {@link EditText} is 4831 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4832 * is set from the theme. 4833 * <p/> 4834 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4835 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4836 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4837 * improves the structure of text layout however has performance impact and requires more time 4838 * to do the text layout. 4839 * <p/> 4840 * Note: Before Android Q, in the theme hyphenation frequency is set to 4841 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4842 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4843 * 4844 * @param hyphenationFrequency the hyphenation frequency to use, one of 4845 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4846 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4847 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4848 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4849 * @see #getHyphenationFrequency() 4850 * @see #getBreakStrategy() 4851 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4852 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4853 mHyphenationFrequency = hyphenationFrequency; 4854 if (mLayout != null) { 4855 nullLayouts(); 4856 requestLayout(); 4857 invalidate(); 4858 } 4859 } 4860 4861 /** 4862 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4863 * @return the current frequency of automatic hyphenation to be used when determining word 4864 * breaks. 4865 * 4866 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4867 * @see #setHyphenationFrequency(int) 4868 */ 4869 @InspectableProperty(enumMapping = { 4870 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4871 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4872 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4873 }) 4874 @Layout.HyphenationFrequency getHyphenationFrequency()4875 public int getHyphenationFrequency() { 4876 return mHyphenationFrequency; 4877 } 4878 4879 /** 4880 * Sets the line-break style for text wrapping. 4881 * 4882 * <p>Line-break style specifies the line-break strategies that can be used 4883 * for text wrapping. The line-break style affects rule-based line breaking 4884 * by specifying the strictness of line-breaking rules. 4885 * 4886 * <p>The following are types of line-break styles: 4887 * <ul> 4888 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE} 4889 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} 4890 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT} 4891 * </ul> 4892 * 4893 * <p>The default line-break style is 4894 * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no 4895 * line-breaking rules are used. 4896 * 4897 * <p>See the 4898 * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> 4899 * line-break property</a> for more information. 4900 * 4901 * @param lineBreakStyle The line-break style for the text. 4902 */ setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)4903 public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { 4904 if (mLineBreakStyle != lineBreakStyle) { 4905 mLineBreakStyle = lineBreakStyle; 4906 if (mLayout != null) { 4907 nullLayouts(); 4908 requestLayout(); 4909 invalidate(); 4910 } 4911 } 4912 } 4913 4914 /** 4915 * Sets the line-break word style for text wrapping. 4916 * 4917 * <p>The line-break word style affects dictionary-based line breaking by 4918 * providing phrase-based line-breaking opportunities. Use 4919 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify 4920 * phrase-based line breaking. 4921 * 4922 * <p>The default line-break word style is 4923 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that 4924 * no line-breaking word style is used. 4925 * 4926 * <p>See the 4927 * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external"> 4928 * word-break property</a> for more information. 4929 * 4930 * @param lineBreakWordStyle The line-break word style for the text. 4931 */ setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4932 public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 4933 if (mLineBreakWordStyle != lineBreakWordStyle) { 4934 mLineBreakWordStyle = lineBreakWordStyle; 4935 if (mLayout != null) { 4936 nullLayouts(); 4937 requestLayout(); 4938 invalidate(); 4939 } 4940 } 4941 } 4942 4943 /** 4944 * Gets the current line-break style for text wrapping. 4945 * 4946 * @return The line-break style to be used for text wrapping. 4947 */ getLineBreakStyle()4948 public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { 4949 return mLineBreakStyle; 4950 } 4951 4952 /** 4953 * Gets the current line-break word style for text wrapping. 4954 * 4955 * @return The line-break word style to be used for text wrapping. 4956 */ getLineBreakWordStyle()4957 public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { 4958 return mLineBreakWordStyle; 4959 } 4960 4961 /** 4962 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4963 * 4964 * @return a current {@link PrecomputedText.Params} 4965 * @see PrecomputedText 4966 */ getTextMetricsParams()4967 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4968 return new PrecomputedText.Params(new TextPaint(mTextPaint), 4969 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), 4970 getTextDirectionHeuristic(), 4971 mBreakStrategy, mHyphenationFrequency); 4972 } 4973 4974 /** 4975 * Apply the text layout parameter. 4976 * 4977 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4978 * @see PrecomputedText 4979 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4980 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4981 mTextPaint.set(params.getTextPaint()); 4982 mUserSetTextScaleX = true; 4983 mTextDir = params.getTextDirection(); 4984 mBreakStrategy = params.getBreakStrategy(); 4985 mHyphenationFrequency = params.getHyphenationFrequency(); 4986 LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); 4987 mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); 4988 mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); 4989 if (mLayout != null) { 4990 nullLayouts(); 4991 requestLayout(); 4992 invalidate(); 4993 } 4994 } 4995 4996 /** 4997 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4998 * last line is too short for justification, the last line will be displayed with the 4999 * alignment set by {@link android.view.View#setTextAlignment}. 5000 * 5001 * @see #getJustificationMode() 5002 */ 5003 @Layout.JustificationMode 5004 @android.view.RemotableViewMethod setJustificationMode(@ayout.JustificationMode int justificationMode)5005 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 5006 mJustificationMode = justificationMode; 5007 if (mLayout != null) { 5008 nullLayouts(); 5009 requestLayout(); 5010 invalidate(); 5011 } 5012 } 5013 5014 /** 5015 * @return true if currently paragraph justification mode. 5016 * 5017 * @see #setJustificationMode(int) 5018 */ 5019 @InspectableProperty(enumMapping = { 5020 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 5021 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 5022 }) getJustificationMode()5023 public @Layout.JustificationMode int getJustificationMode() { 5024 return mJustificationMode; 5025 } 5026 5027 /** 5028 * Sets font feature settings. The format is the same as the CSS 5029 * font-feature-settings attribute: 5030 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5031 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5032 * 5033 * @param fontFeatureSettings font feature settings represented as CSS compatible string 5034 * 5035 * @see #getFontFeatureSettings() 5036 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 5037 * 5038 * @attr ref android.R.styleable#TextView_fontFeatureSettings 5039 */ 5040 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)5041 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 5042 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 5043 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 5044 5045 if (mLayout != null) { 5046 nullLayouts(); 5047 requestLayout(); 5048 invalidate(); 5049 } 5050 } 5051 } 5052 5053 5054 /** 5055 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 5056 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 5057 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 5058 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 5059 * are invalid. If a specified axis name is not defined in the font, the settings will be 5060 * ignored. 5061 * 5062 * <p> 5063 * Examples, 5064 * <ul> 5065 * <li>Set font width to 150. 5066 * <pre> 5067 * <code> 5068 * TextView textView = (TextView) findViewById(R.id.textView); 5069 * textView.setFontVariationSettings("'wdth' 150"); 5070 * </code> 5071 * </pre> 5072 * </li> 5073 * 5074 * <li>Set the font slant to 20 degrees and ask for italic style. 5075 * <pre> 5076 * <code> 5077 * TextView textView = (TextView) findViewById(R.id.textView); 5078 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 5079 * </code> 5080 * </pre> 5081 * </p> 5082 * </li> 5083 * </ul> 5084 * 5085 * @param fontVariationSettings font variation settings. You can pass null or empty string as 5086 * no variation settings. 5087 * @return true if the given settings is effective to at least one font file underlying this 5088 * TextView. This function also returns true for empty settings string. Otherwise 5089 * returns false. 5090 * 5091 * @throws IllegalArgumentException If given string is not a valid font variation settings 5092 * format. 5093 * 5094 * @see #getFontVariationSettings() 5095 * @see FontVariationAxis 5096 * 5097 * @attr ref android.R.styleable#TextView_fontVariationSettings 5098 */ setFontVariationSettings(@ullable String fontVariationSettings)5099 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 5100 final String existingSettings = mTextPaint.getFontVariationSettings(); 5101 if (fontVariationSettings == existingSettings 5102 || (fontVariationSettings != null 5103 && fontVariationSettings.equals(existingSettings))) { 5104 return true; 5105 } 5106 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 5107 5108 if (effective && mLayout != null) { 5109 nullLayouts(); 5110 requestLayout(); 5111 invalidate(); 5112 } 5113 return effective; 5114 } 5115 5116 /** 5117 * Sets the text color for all the states (normal, selected, 5118 * focused) to be this color. 5119 * 5120 * @param color A color value in the form 0xAARRGGBB. 5121 * Do not pass a resource ID. To get a color value from a resource ID, call 5122 * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}. 5123 * 5124 * @see #setTextColor(ColorStateList) 5125 * @see #getTextColors() 5126 * 5127 * @attr ref android.R.styleable#TextView_textColor 5128 */ 5129 @android.view.RemotableViewMethod setTextColor(@olorInt int color)5130 public void setTextColor(@ColorInt int color) { 5131 mTextColor = ColorStateList.valueOf(color); 5132 updateTextColors(); 5133 } 5134 5135 /** 5136 * Sets the text color. 5137 * 5138 * @see #setTextColor(int) 5139 * @see #getTextColors() 5140 * @see #setHintTextColor(ColorStateList) 5141 * @see #setLinkTextColor(ColorStateList) 5142 * 5143 * @attr ref android.R.styleable#TextView_textColor 5144 */ 5145 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)5146 public void setTextColor(ColorStateList colors) { 5147 if (colors == null) { 5148 throw new NullPointerException(); 5149 } 5150 5151 mTextColor = colors; 5152 updateTextColors(); 5153 } 5154 5155 /** 5156 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 5157 * 5158 * @see #setTextColor(ColorStateList) 5159 * @see #setTextColor(int) 5160 * 5161 * @attr ref android.R.styleable#TextView_textColor 5162 */ 5163 @InspectableProperty(name = "textColor") getTextColors()5164 public final ColorStateList getTextColors() { 5165 return mTextColor; 5166 } 5167 5168 /** 5169 * Return the current color selected for normal text. 5170 * 5171 * @return Returns the current text color. 5172 */ 5173 @ColorInt getCurrentTextColor()5174 public final int getCurrentTextColor() { 5175 return mCurTextColor; 5176 } 5177 5178 /** 5179 * Sets the color used to display the selection highlight. 5180 * 5181 * @attr ref android.R.styleable#TextView_textColorHighlight 5182 */ 5183 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)5184 public void setHighlightColor(@ColorInt int color) { 5185 if (mHighlightColor != color) { 5186 mHighlightColor = color; 5187 invalidate(); 5188 } 5189 } 5190 5191 /** 5192 * @return the color used to display the selection highlight 5193 * 5194 * @see #setHighlightColor(int) 5195 * 5196 * @attr ref android.R.styleable#TextView_textColorHighlight 5197 */ 5198 @InspectableProperty(name = "textColorHighlight") 5199 @ColorInt getHighlightColor()5200 public int getHighlightColor() { 5201 return mHighlightColor; 5202 } 5203 5204 /** 5205 * Sets whether the soft input method will be made visible when this 5206 * TextView gets focused. The default is true. 5207 */ 5208 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)5209 public final void setShowSoftInputOnFocus(boolean show) { 5210 createEditorIfNeeded(); 5211 mEditor.mShowSoftInputOnFocus = show; 5212 } 5213 5214 /** 5215 * Returns whether the soft input method will be made visible when this 5216 * TextView gets focused. The default is true. 5217 */ getShowSoftInputOnFocus()5218 public final boolean getShowSoftInputOnFocus() { 5219 // When there is no Editor, return default true value 5220 return mEditor == null || mEditor.mShowSoftInputOnFocus; 5221 } 5222 5223 /** 5224 * Gives the text a shadow of the specified blur radius and color, the specified 5225 * distance from its drawn position. 5226 * <p> 5227 * The text shadow produced does not interact with the properties on view 5228 * that are responsible for real time shadows, 5229 * {@link View#getElevation() elevation} and 5230 * {@link View#getTranslationZ() translationZ}. 5231 * 5232 * @see Paint#setShadowLayer(float, float, float, int) 5233 * 5234 * @attr ref android.R.styleable#TextView_shadowColor 5235 * @attr ref android.R.styleable#TextView_shadowDx 5236 * @attr ref android.R.styleable#TextView_shadowDy 5237 * @attr ref android.R.styleable#TextView_shadowRadius 5238 */ setShadowLayer(float radius, float dx, float dy, int color)5239 public void setShadowLayer(float radius, float dx, float dy, int color) { 5240 mTextPaint.setShadowLayer(radius, dx, dy, color); 5241 5242 mShadowRadius = radius; 5243 mShadowDx = dx; 5244 mShadowDy = dy; 5245 mShadowColor = color; 5246 5247 // Will change text clip region 5248 if (mEditor != null) { 5249 mEditor.invalidateTextDisplayList(); 5250 mEditor.invalidateHandlesAndActionMode(); 5251 } 5252 invalidate(); 5253 } 5254 5255 /** 5256 * Gets the radius of the shadow layer. 5257 * 5258 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 5259 * 5260 * @see #setShadowLayer(float, float, float, int) 5261 * 5262 * @attr ref android.R.styleable#TextView_shadowRadius 5263 */ 5264 @InspectableProperty getShadowRadius()5265 public float getShadowRadius() { 5266 return mShadowRadius; 5267 } 5268 5269 /** 5270 * @return the horizontal offset of the shadow layer 5271 * 5272 * @see #setShadowLayer(float, float, float, int) 5273 * 5274 * @attr ref android.R.styleable#TextView_shadowDx 5275 */ 5276 @InspectableProperty getShadowDx()5277 public float getShadowDx() { 5278 return mShadowDx; 5279 } 5280 5281 /** 5282 * Gets the vertical offset of the shadow layer. 5283 * @return The vertical offset of the shadow layer. 5284 * 5285 * @see #setShadowLayer(float, float, float, int) 5286 * 5287 * @attr ref android.R.styleable#TextView_shadowDy 5288 */ 5289 @InspectableProperty getShadowDy()5290 public float getShadowDy() { 5291 return mShadowDy; 5292 } 5293 5294 /** 5295 * Gets the color of the shadow layer. 5296 * @return the color of the shadow layer 5297 * 5298 * @see #setShadowLayer(float, float, float, int) 5299 * 5300 * @attr ref android.R.styleable#TextView_shadowColor 5301 */ 5302 @InspectableProperty 5303 @ColorInt getShadowColor()5304 public int getShadowColor() { 5305 return mShadowColor; 5306 } 5307 5308 /** 5309 * Gets the {@link TextPaint} used for the text. 5310 * Use this only to consult the Paint's properties and not to change them. 5311 * @return The base paint used for the text. 5312 */ getPaint()5313 public TextPaint getPaint() { 5314 return mTextPaint; 5315 } 5316 5317 /** 5318 * Sets the autolink mask of the text. See {@link 5319 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5320 * possible values. 5321 * 5322 * <p class="note"><b>Note:</b> 5323 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5324 * is deprecated and should be avoided; see its documentation. 5325 * 5326 * @attr ref android.R.styleable#TextView_autoLink 5327 */ 5328 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5329 public final void setAutoLinkMask(int mask) { 5330 mAutoLinkMask = mask; 5331 } 5332 5333 /** 5334 * Sets whether the movement method will automatically be set to 5335 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5336 * set to nonzero and links are detected in {@link #setText}. 5337 * The default is true. 5338 * 5339 * @attr ref android.R.styleable#TextView_linksClickable 5340 */ 5341 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5342 public final void setLinksClickable(boolean whether) { 5343 mLinksClickable = whether; 5344 } 5345 5346 /** 5347 * Returns whether the movement method will automatically be set to 5348 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5349 * set to nonzero and links are detected in {@link #setText}. 5350 * The default is true. 5351 * 5352 * @attr ref android.R.styleable#TextView_linksClickable 5353 */ 5354 @InspectableProperty getLinksClickable()5355 public final boolean getLinksClickable() { 5356 return mLinksClickable; 5357 } 5358 5359 /** 5360 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5361 * (by {@link Linkify} or otherwise) if any. You can call 5362 * {@link URLSpan#getURL} on them to find where they link to 5363 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5364 * to find the region of the text they are attached to. 5365 */ getUrls()5366 public URLSpan[] getUrls() { 5367 if (mText instanceof Spanned) { 5368 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5369 } else { 5370 return new URLSpan[0]; 5371 } 5372 } 5373 5374 /** 5375 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5376 * TextView. 5377 * 5378 * @see #setHintTextColor(ColorStateList) 5379 * @see #getHintTextColors() 5380 * @see #setTextColor(int) 5381 * 5382 * @attr ref android.R.styleable#TextView_textColorHint 5383 */ 5384 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5385 public final void setHintTextColor(@ColorInt int color) { 5386 mHintTextColor = ColorStateList.valueOf(color); 5387 updateTextColors(); 5388 } 5389 5390 /** 5391 * Sets the color of the hint text. 5392 * 5393 * @see #getHintTextColors() 5394 * @see #setHintTextColor(int) 5395 * @see #setTextColor(ColorStateList) 5396 * @see #setLinkTextColor(ColorStateList) 5397 * 5398 * @attr ref android.R.styleable#TextView_textColorHint 5399 */ setHintTextColor(ColorStateList colors)5400 public final void setHintTextColor(ColorStateList colors) { 5401 mHintTextColor = colors; 5402 updateTextColors(); 5403 } 5404 5405 /** 5406 * @return the color of the hint text, for the different states of this TextView. 5407 * 5408 * @see #setHintTextColor(ColorStateList) 5409 * @see #setHintTextColor(int) 5410 * @see #setTextColor(ColorStateList) 5411 * @see #setLinkTextColor(ColorStateList) 5412 * 5413 * @attr ref android.R.styleable#TextView_textColorHint 5414 */ 5415 @InspectableProperty(name = "textColorHint") getHintTextColors()5416 public final ColorStateList getHintTextColors() { 5417 return mHintTextColor; 5418 } 5419 5420 /** 5421 * <p>Return the current color selected to paint the hint text.</p> 5422 * 5423 * @return Returns the current hint text color. 5424 */ 5425 @ColorInt getCurrentHintTextColor()5426 public final int getCurrentHintTextColor() { 5427 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5428 } 5429 5430 /** 5431 * Sets the color of links in the text. 5432 * 5433 * @see #setLinkTextColor(ColorStateList) 5434 * @see #getLinkTextColors() 5435 * 5436 * @attr ref android.R.styleable#TextView_textColorLink 5437 */ 5438 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5439 public final void setLinkTextColor(@ColorInt int color) { 5440 mLinkTextColor = ColorStateList.valueOf(color); 5441 updateTextColors(); 5442 } 5443 5444 /** 5445 * Sets the color of links in the text. 5446 * 5447 * @see #setLinkTextColor(int) 5448 * @see #getLinkTextColors() 5449 * @see #setTextColor(ColorStateList) 5450 * @see #setHintTextColor(ColorStateList) 5451 * 5452 * @attr ref android.R.styleable#TextView_textColorLink 5453 */ setLinkTextColor(ColorStateList colors)5454 public final void setLinkTextColor(ColorStateList colors) { 5455 mLinkTextColor = colors; 5456 updateTextColors(); 5457 } 5458 5459 /** 5460 * @return the list of colors used to paint the links in the text, for the different states of 5461 * this TextView 5462 * 5463 * @see #setLinkTextColor(ColorStateList) 5464 * @see #setLinkTextColor(int) 5465 * 5466 * @attr ref android.R.styleable#TextView_textColorLink 5467 */ 5468 @InspectableProperty(name = "textColorLink") getLinkTextColors()5469 public final ColorStateList getLinkTextColors() { 5470 return mLinkTextColor; 5471 } 5472 5473 /** 5474 * Sets the horizontal alignment of the text and the 5475 * vertical gravity that will be used when there is extra space 5476 * in the TextView beyond what is required for the text itself. 5477 * 5478 * @see android.view.Gravity 5479 * @attr ref android.R.styleable#TextView_gravity 5480 */ 5481 @android.view.RemotableViewMethod setGravity(int gravity)5482 public void setGravity(int gravity) { 5483 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5484 gravity |= Gravity.START; 5485 } 5486 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5487 gravity |= Gravity.TOP; 5488 } 5489 5490 boolean newLayout = false; 5491 5492 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5493 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5494 newLayout = true; 5495 } 5496 5497 if (gravity != mGravity) { 5498 invalidate(); 5499 } 5500 5501 mGravity = gravity; 5502 5503 if (mLayout != null && newLayout) { 5504 // XXX this is heavy-handed because no actual content changes. 5505 int want = mLayout.getWidth(); 5506 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5507 5508 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5509 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5510 } 5511 } 5512 5513 /** 5514 * Returns the horizontal and vertical alignment of this TextView. 5515 * 5516 * @see android.view.Gravity 5517 * @attr ref android.R.styleable#TextView_gravity 5518 */ 5519 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5520 public int getGravity() { 5521 return mGravity; 5522 } 5523 5524 /** 5525 * Gets the flags on the Paint being used to display the text. 5526 * @return The flags on the Paint being used to display the text. 5527 * @see Paint#getFlags 5528 */ getPaintFlags()5529 public int getPaintFlags() { 5530 return mTextPaint.getFlags(); 5531 } 5532 5533 /** 5534 * Sets flags on the Paint being used to display the text and 5535 * reflows the text if they are different from the old flags. 5536 * @see Paint#setFlags 5537 */ 5538 @android.view.RemotableViewMethod setPaintFlags(int flags)5539 public void setPaintFlags(int flags) { 5540 if (mTextPaint.getFlags() != flags) { 5541 mTextPaint.setFlags(flags); 5542 5543 if (mLayout != null) { 5544 nullLayouts(); 5545 requestLayout(); 5546 invalidate(); 5547 } 5548 } 5549 } 5550 5551 /** 5552 * Sets whether the text should be allowed to be wider than the 5553 * View is. If false, it will be wrapped to the width of the View. 5554 * 5555 * @attr ref android.R.styleable#TextView_scrollHorizontally 5556 */ setHorizontallyScrolling(boolean whether)5557 public void setHorizontallyScrolling(boolean whether) { 5558 if (mHorizontallyScrolling != whether) { 5559 mHorizontallyScrolling = whether; 5560 5561 if (mLayout != null) { 5562 nullLayouts(); 5563 requestLayout(); 5564 invalidate(); 5565 } 5566 } 5567 } 5568 5569 /** 5570 * Returns whether the text is allowed to be wider than the View. 5571 * If false, the text will be wrapped to the width of the View. 5572 * 5573 * @attr ref android.R.styleable#TextView_scrollHorizontally 5574 * @see #setHorizontallyScrolling(boolean) 5575 */ 5576 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5577 public final boolean isHorizontallyScrollable() { 5578 return mHorizontallyScrolling; 5579 } 5580 5581 /** 5582 * Returns whether the text is allowed to be wider than the View. 5583 * If false, the text will be wrapped to the width of the View. 5584 * 5585 * @attr ref android.R.styleable#TextView_scrollHorizontally 5586 * @hide 5587 */ 5588 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5589 public boolean getHorizontallyScrolling() { 5590 return mHorizontallyScrolling; 5591 } 5592 5593 /** 5594 * Sets the height of the TextView to be at least {@code minLines} tall. 5595 * <p> 5596 * This value is used for height calculation if LayoutParams does not force TextView to have an 5597 * exact height. Setting this value overrides other previous minimum height configurations such 5598 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5599 * this value to 1. 5600 * 5601 * @param minLines the minimum height of TextView in terms of number of lines 5602 * 5603 * @see #getMinLines() 5604 * @see #setLines(int) 5605 * 5606 * @attr ref android.R.styleable#TextView_minLines 5607 */ 5608 @android.view.RemotableViewMethod setMinLines(int minLines)5609 public void setMinLines(int minLines) { 5610 mMinimum = minLines; 5611 mMinMode = LINES; 5612 5613 requestLayout(); 5614 invalidate(); 5615 } 5616 5617 /** 5618 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5619 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5620 * 5621 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5622 * height is not defined in lines 5623 * 5624 * @see #setMinLines(int) 5625 * @see #setLines(int) 5626 * 5627 * @attr ref android.R.styleable#TextView_minLines 5628 */ 5629 @InspectableProperty getMinLines()5630 public int getMinLines() { 5631 return mMinMode == LINES ? mMinimum : -1; 5632 } 5633 5634 /** 5635 * Sets the height of the TextView to be at least {@code minPixels} tall. 5636 * <p> 5637 * This value is used for height calculation if LayoutParams does not force TextView to have an 5638 * exact height. Setting this value overrides previous minimum height configurations such as 5639 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5640 * <p> 5641 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5642 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5643 * used to decide the final height. 5644 * 5645 * @param minPixels the minimum height of TextView in terms of pixels 5646 * 5647 * @see #getMinHeight() 5648 * @see #setHeight(int) 5649 * 5650 * @attr ref android.R.styleable#TextView_minHeight 5651 */ 5652 @android.view.RemotableViewMethod setMinHeight(int minPixels)5653 public void setMinHeight(int minPixels) { 5654 mMinimum = minPixels; 5655 mMinMode = PIXELS; 5656 5657 requestLayout(); 5658 invalidate(); 5659 } 5660 5661 /** 5662 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5663 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5664 * 5665 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5666 * defined in pixels 5667 * 5668 * @see #setMinHeight(int) 5669 * @see #setHeight(int) 5670 * 5671 * @attr ref android.R.styleable#TextView_minHeight 5672 */ getMinHeight()5673 public int getMinHeight() { 5674 return mMinMode == PIXELS ? mMinimum : -1; 5675 } 5676 5677 /** 5678 * Sets the height of the TextView to be at most {@code maxLines} tall. 5679 * <p> 5680 * This value is used for height calculation if LayoutParams does not force TextView to have an 5681 * exact height. Setting this value overrides previous maximum height configurations such as 5682 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5683 * 5684 * @param maxLines the maximum height of TextView in terms of number of lines 5685 * 5686 * @see #getMaxLines() 5687 * @see #setLines(int) 5688 * 5689 * @attr ref android.R.styleable#TextView_maxLines 5690 */ 5691 @android.view.RemotableViewMethod setMaxLines(int maxLines)5692 public void setMaxLines(int maxLines) { 5693 mMaximum = maxLines; 5694 mMaxMode = LINES; 5695 5696 requestLayout(); 5697 invalidate(); 5698 } 5699 5700 /** 5701 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5702 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5703 * 5704 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5705 * is not defined in lines. 5706 * 5707 * @see #setMaxLines(int) 5708 * @see #setLines(int) 5709 * 5710 * @attr ref android.R.styleable#TextView_maxLines 5711 */ 5712 @InspectableProperty getMaxLines()5713 public int getMaxLines() { 5714 return mMaxMode == LINES ? mMaximum : -1; 5715 } 5716 5717 /** 5718 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5719 * <p> 5720 * This value is used for height calculation if LayoutParams does not force TextView to have an 5721 * exact height. Setting this value overrides previous maximum height configurations such as 5722 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5723 * 5724 * @param maxPixels the maximum height of TextView in terms of pixels 5725 * 5726 * @see #getMaxHeight() 5727 * @see #setHeight(int) 5728 * 5729 * @attr ref android.R.styleable#TextView_maxHeight 5730 */ 5731 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5732 public void setMaxHeight(int maxPixels) { 5733 mMaximum = maxPixels; 5734 mMaxMode = PIXELS; 5735 5736 requestLayout(); 5737 invalidate(); 5738 } 5739 5740 /** 5741 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5742 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5743 * 5744 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5745 * is not defined in pixels 5746 * 5747 * @see #setMaxHeight(int) 5748 * @see #setHeight(int) 5749 * 5750 * @attr ref android.R.styleable#TextView_maxHeight 5751 */ 5752 @InspectableProperty getMaxHeight()5753 public int getMaxHeight() { 5754 return mMaxMode == PIXELS ? mMaximum : -1; 5755 } 5756 5757 /** 5758 * Sets the height of the TextView to be exactly {@code lines} tall. 5759 * <p> 5760 * This value is used for height calculation if LayoutParams does not force TextView to have an 5761 * exact height. Setting this value overrides previous minimum/maximum height configurations 5762 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5763 * set this value to 1. 5764 * 5765 * @param lines the exact height of the TextView in terms of lines 5766 * 5767 * @see #setHeight(int) 5768 * 5769 * @attr ref android.R.styleable#TextView_lines 5770 */ 5771 @android.view.RemotableViewMethod setLines(int lines)5772 public void setLines(int lines) { 5773 mMaximum = mMinimum = lines; 5774 mMaxMode = mMinMode = LINES; 5775 5776 requestLayout(); 5777 invalidate(); 5778 } 5779 5780 /** 5781 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5782 * <p> 5783 * This value is used for height calculation if LayoutParams does not force TextView to have an 5784 * exact height. Setting this value overrides previous minimum/maximum height configurations 5785 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5786 * 5787 * @param pixels the exact height of the TextView in terms of pixels 5788 * 5789 * @see #setLines(int) 5790 * 5791 * @attr ref android.R.styleable#TextView_height 5792 */ 5793 @android.view.RemotableViewMethod setHeight(int pixels)5794 public void setHeight(int pixels) { 5795 mMaximum = mMinimum = pixels; 5796 mMaxMode = mMinMode = PIXELS; 5797 5798 requestLayout(); 5799 invalidate(); 5800 } 5801 5802 /** 5803 * Sets the width of the TextView to be at least {@code minEms} wide. 5804 * <p> 5805 * This value is used for width calculation if LayoutParams does not force TextView to have an 5806 * exact width. Setting this value overrides previous minimum width configurations such as 5807 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5808 * 5809 * @param minEms the minimum width of TextView in terms of ems 5810 * 5811 * @see #getMinEms() 5812 * @see #setEms(int) 5813 * 5814 * @attr ref android.R.styleable#TextView_minEms 5815 */ 5816 @android.view.RemotableViewMethod setMinEms(int minEms)5817 public void setMinEms(int minEms) { 5818 mMinWidth = minEms; 5819 mMinWidthMode = EMS; 5820 5821 requestLayout(); 5822 invalidate(); 5823 } 5824 5825 /** 5826 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5827 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5828 * 5829 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5830 * defined in ems 5831 * 5832 * @see #setMinEms(int) 5833 * @see #setEms(int) 5834 * 5835 * @attr ref android.R.styleable#TextView_minEms 5836 */ 5837 @InspectableProperty getMinEms()5838 public int getMinEms() { 5839 return mMinWidthMode == EMS ? mMinWidth : -1; 5840 } 5841 5842 /** 5843 * Sets the width of the TextView to be at least {@code minPixels} wide. 5844 * <p> 5845 * This value is used for width calculation if LayoutParams does not force TextView to have an 5846 * exact width. Setting this value overrides previous minimum width configurations such as 5847 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5848 * <p> 5849 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5850 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5851 * to decide the final width. 5852 * 5853 * @param minPixels the minimum width of TextView in terms of pixels 5854 * 5855 * @see #getMinWidth() 5856 * @see #setWidth(int) 5857 * 5858 * @attr ref android.R.styleable#TextView_minWidth 5859 */ 5860 @android.view.RemotableViewMethod setMinWidth(int minPixels)5861 public void setMinWidth(int minPixels) { 5862 mMinWidth = minPixels; 5863 mMinWidthMode = PIXELS; 5864 5865 requestLayout(); 5866 invalidate(); 5867 } 5868 5869 /** 5870 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5871 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5872 * 5873 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5874 * defined in pixels 5875 * 5876 * @see #setMinWidth(int) 5877 * @see #setWidth(int) 5878 * 5879 * @attr ref android.R.styleable#TextView_minWidth 5880 */ 5881 @InspectableProperty getMinWidth()5882 public int getMinWidth() { 5883 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5884 } 5885 5886 /** 5887 * Sets the width of the TextView to be at most {@code maxEms} wide. 5888 * <p> 5889 * This value is used for width calculation if LayoutParams does not force TextView to have an 5890 * exact width. Setting this value overrides previous maximum width configurations such as 5891 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5892 * 5893 * @param maxEms the maximum width of TextView in terms of ems 5894 * 5895 * @see #getMaxEms() 5896 * @see #setEms(int) 5897 * 5898 * @attr ref android.R.styleable#TextView_maxEms 5899 */ 5900 @android.view.RemotableViewMethod setMaxEms(int maxEms)5901 public void setMaxEms(int maxEms) { 5902 mMaxWidth = maxEms; 5903 mMaxWidthMode = EMS; 5904 5905 requestLayout(); 5906 invalidate(); 5907 } 5908 5909 /** 5910 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5911 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5912 * 5913 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5914 * defined in ems 5915 * 5916 * @see #setMaxEms(int) 5917 * @see #setEms(int) 5918 * 5919 * @attr ref android.R.styleable#TextView_maxEms 5920 */ 5921 @InspectableProperty getMaxEms()5922 public int getMaxEms() { 5923 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5924 } 5925 5926 /** 5927 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5928 * <p> 5929 * This value is used for width calculation if LayoutParams does not force TextView to have an 5930 * exact width. Setting this value overrides previous maximum width configurations such as 5931 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5932 * 5933 * @param maxPixels the maximum width of TextView in terms of pixels 5934 * 5935 * @see #getMaxWidth() 5936 * @see #setWidth(int) 5937 * 5938 * @attr ref android.R.styleable#TextView_maxWidth 5939 */ 5940 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5941 public void setMaxWidth(int maxPixels) { 5942 mMaxWidth = maxPixels; 5943 mMaxWidthMode = PIXELS; 5944 5945 requestLayout(); 5946 invalidate(); 5947 } 5948 5949 /** 5950 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5951 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5952 * 5953 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5954 * defined in pixels 5955 * 5956 * @see #setMaxWidth(int) 5957 * @see #setWidth(int) 5958 * 5959 * @attr ref android.R.styleable#TextView_maxWidth 5960 */ 5961 @InspectableProperty getMaxWidth()5962 public int getMaxWidth() { 5963 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5964 } 5965 5966 /** 5967 * Sets the width of the TextView to be exactly {@code ems} wide. 5968 * 5969 * This value is used for width calculation if LayoutParams does not force TextView to have an 5970 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5971 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5972 * 5973 * @param ems the exact width of the TextView in terms of ems 5974 * 5975 * @see #setWidth(int) 5976 * 5977 * @attr ref android.R.styleable#TextView_ems 5978 */ 5979 @android.view.RemotableViewMethod setEms(int ems)5980 public void setEms(int ems) { 5981 mMaxWidth = mMinWidth = ems; 5982 mMaxWidthMode = mMinWidthMode = EMS; 5983 5984 requestLayout(); 5985 invalidate(); 5986 } 5987 5988 /** 5989 * Sets the width of the TextView to be exactly {@code pixels} wide. 5990 * <p> 5991 * This value is used for width calculation if LayoutParams does not force TextView to have an 5992 * exact width. Setting this value overrides previous minimum/maximum width configurations 5993 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5994 * 5995 * @param pixels the exact width of the TextView in terms of pixels 5996 * 5997 * @see #setEms(int) 5998 * 5999 * @attr ref android.R.styleable#TextView_width 6000 */ 6001 @android.view.RemotableViewMethod setWidth(int pixels)6002 public void setWidth(int pixels) { 6003 mMaxWidth = mMinWidth = pixels; 6004 mMaxWidthMode = mMinWidthMode = PIXELS; 6005 6006 requestLayout(); 6007 invalidate(); 6008 } 6009 6010 /** 6011 * Sets line spacing for this TextView. Each line other than the last line will have its height 6012 * multiplied by {@code mult} and have {@code add} added to it. 6013 * 6014 * @param add The value in pixels that should be added to each line other than the last line. 6015 * This will be applied after the multiplier 6016 * @param mult The value by which each line height other than the last line will be multiplied 6017 * by 6018 * 6019 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6020 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6021 */ setLineSpacing(float add, float mult)6022 public void setLineSpacing(float add, float mult) { 6023 if (mSpacingAdd != add || mSpacingMult != mult) { 6024 mSpacingAdd = add; 6025 mSpacingMult = mult; 6026 6027 if (mLayout != null) { 6028 nullLayouts(); 6029 requestLayout(); 6030 invalidate(); 6031 } 6032 } 6033 } 6034 6035 /** 6036 * Gets the line spacing multiplier 6037 * 6038 * @return the value by which each line's height is multiplied to get its actual height. 6039 * 6040 * @see #setLineSpacing(float, float) 6041 * @see #getLineSpacingExtra() 6042 * 6043 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6044 */ 6045 @InspectableProperty getLineSpacingMultiplier()6046 public float getLineSpacingMultiplier() { 6047 return mSpacingMult; 6048 } 6049 6050 /** 6051 * Gets the line spacing extra space 6052 * 6053 * @return the extra space that is added to the height of each lines of this TextView. 6054 * 6055 * @see #setLineSpacing(float, float) 6056 * @see #getLineSpacingMultiplier() 6057 * 6058 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6059 */ 6060 @InspectableProperty getLineSpacingExtra()6061 public float getLineSpacingExtra() { 6062 return mSpacingAdd; 6063 } 6064 6065 /** 6066 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 6067 * between subsequent baselines in the TextView. 6068 * 6069 * @param lineHeight the line height in pixels 6070 * 6071 * @see #setLineSpacing(float, float) 6072 * @see #getLineSpacingExtra() 6073 * 6074 * @attr ref android.R.styleable#TextView_lineHeight 6075 */ 6076 @android.view.RemotableViewMethod setLineHeight(@x @ntRangefrom = 0) int lineHeight)6077 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 6078 Preconditions.checkArgumentNonnegative(lineHeight); 6079 6080 final int fontHeight = getPaint().getFontMetricsInt(null); 6081 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 6082 if (lineHeight != fontHeight) { 6083 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 6084 setLineSpacing(lineHeight - fontHeight, 1f); 6085 } 6086 } 6087 6088 /** 6089 * Convenience method to append the specified text to the TextView's 6090 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6091 * if it was not already editable. 6092 * 6093 * @param text text to be appended to the already displayed text 6094 */ append(CharSequence text)6095 public final void append(CharSequence text) { 6096 append(text, 0, text.length()); 6097 } 6098 6099 /** 6100 * Convenience method to append the specified text slice to the TextView's 6101 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6102 * if it was not already editable. 6103 * 6104 * @param text text to be appended to the already displayed text 6105 * @param start the index of the first character in the {@code text} 6106 * @param end the index of the character following the last character in the {@code text} 6107 * 6108 * @see Appendable#append(CharSequence, int, int) 6109 */ append(CharSequence text, int start, int end)6110 public void append(CharSequence text, int start, int end) { 6111 if (!(mText instanceof Editable)) { 6112 setText(mText, BufferType.EDITABLE); 6113 } 6114 6115 ((Editable) mText).append(text, start, end); 6116 6117 if (mAutoLinkMask != 0) { 6118 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 6119 // Do not change the movement method for text that support text selection as it 6120 // would prevent an arbitrary cursor displacement. 6121 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 6122 setMovementMethod(LinkMovementMethod.getInstance()); 6123 } 6124 } 6125 } 6126 updateTextColors()6127 private void updateTextColors() { 6128 boolean inval = false; 6129 final int[] drawableState = getDrawableState(); 6130 int color = mTextColor.getColorForState(drawableState, 0); 6131 if (color != mCurTextColor) { 6132 mCurTextColor = color; 6133 inval = true; 6134 } 6135 if (mLinkTextColor != null) { 6136 color = mLinkTextColor.getColorForState(drawableState, 0); 6137 if (color != mTextPaint.linkColor) { 6138 mTextPaint.linkColor = color; 6139 inval = true; 6140 } 6141 } 6142 if (mHintTextColor != null) { 6143 color = mHintTextColor.getColorForState(drawableState, 0); 6144 if (color != mCurHintTextColor) { 6145 mCurHintTextColor = color; 6146 if (mText.length() == 0) { 6147 inval = true; 6148 } 6149 } 6150 } 6151 if (inval) { 6152 // Text needs to be redrawn with the new color 6153 if (mEditor != null) mEditor.invalidateTextDisplayList(); 6154 invalidate(); 6155 } 6156 } 6157 6158 @Override drawableStateChanged()6159 protected void drawableStateChanged() { 6160 super.drawableStateChanged(); 6161 6162 if (mTextColor != null && mTextColor.isStateful() 6163 || (mHintTextColor != null && mHintTextColor.isStateful()) 6164 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 6165 updateTextColors(); 6166 } 6167 6168 if (mDrawables != null) { 6169 final int[] state = getDrawableState(); 6170 for (Drawable dr : mDrawables.mShowing) { 6171 if (dr != null && dr.isStateful() && dr.setState(state)) { 6172 invalidateDrawable(dr); 6173 } 6174 } 6175 } 6176 } 6177 6178 @Override drawableHotspotChanged(float x, float y)6179 public void drawableHotspotChanged(float x, float y) { 6180 super.drawableHotspotChanged(x, y); 6181 6182 if (mDrawables != null) { 6183 for (Drawable dr : mDrawables.mShowing) { 6184 if (dr != null) { 6185 dr.setHotspot(x, y); 6186 } 6187 } 6188 } 6189 } 6190 6191 @Override onSaveInstanceState()6192 public Parcelable onSaveInstanceState() { 6193 Parcelable superState = super.onSaveInstanceState(); 6194 6195 // Save state if we are forced to 6196 final boolean freezesText = getFreezesText(); 6197 boolean hasSelection = false; 6198 int start = -1; 6199 int end = -1; 6200 6201 if (mText != null) { 6202 start = getSelectionStart(); 6203 end = getSelectionEnd(); 6204 if (start >= 0 || end >= 0) { 6205 // Or save state if there is a selection 6206 hasSelection = true; 6207 } 6208 } 6209 6210 if (freezesText || hasSelection) { 6211 SavedState ss = new SavedState(superState); 6212 6213 if (freezesText) { 6214 if (mText instanceof Spanned) { 6215 final Spannable sp = new SpannableStringBuilder(mText); 6216 6217 if (mEditor != null) { 6218 removeMisspelledSpans(sp); 6219 sp.removeSpan(mEditor.mSuggestionRangeSpan); 6220 } 6221 6222 ss.text = sp; 6223 } else { 6224 ss.text = mText.toString(); 6225 } 6226 } 6227 6228 if (hasSelection) { 6229 // XXX Should also save the current scroll position! 6230 ss.selStart = start; 6231 ss.selEnd = end; 6232 } 6233 6234 if (isFocused() && start >= 0 && end >= 0) { 6235 ss.frozenWithFocus = true; 6236 } 6237 6238 ss.error = getError(); 6239 6240 if (mEditor != null) { 6241 ss.editorState = mEditor.saveInstanceState(); 6242 } 6243 return ss; 6244 } 6245 6246 return superState; 6247 } 6248 removeMisspelledSpans(Spannable spannable)6249 void removeMisspelledSpans(Spannable spannable) { 6250 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 6251 SuggestionSpan.class); 6252 for (int i = 0; i < suggestionSpans.length; i++) { 6253 int flags = suggestionSpans[i].getFlags(); 6254 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 6255 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 6256 spannable.removeSpan(suggestionSpans[i]); 6257 } 6258 } 6259 } 6260 6261 @Override onRestoreInstanceState(Parcelable state)6262 public void onRestoreInstanceState(Parcelable state) { 6263 if (!(state instanceof SavedState)) { 6264 super.onRestoreInstanceState(state); 6265 return; 6266 } 6267 6268 SavedState ss = (SavedState) state; 6269 super.onRestoreInstanceState(ss.getSuperState()); 6270 6271 // XXX restore buffer type too, as well as lots of other stuff 6272 if (ss.text != null) { 6273 setText(ss.text); 6274 } 6275 6276 if (ss.selStart >= 0 && ss.selEnd >= 0) { 6277 if (mSpannable != null) { 6278 int len = mText.length(); 6279 6280 if (ss.selStart > len || ss.selEnd > len) { 6281 String restored = ""; 6282 6283 if (ss.text != null) { 6284 restored = "(restored) "; 6285 } 6286 6287 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 6288 + " out of range for " + restored + "text " + mText); 6289 } else { 6290 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 6291 6292 if (ss.frozenWithFocus) { 6293 createEditorIfNeeded(); 6294 mEditor.mFrozenWithFocus = true; 6295 } 6296 } 6297 } 6298 } 6299 6300 if (ss.error != null) { 6301 final CharSequence error = ss.error; 6302 // Display the error later, after the first layout pass 6303 post(new Runnable() { 6304 public void run() { 6305 if (mEditor == null || !mEditor.mErrorWasChanged) { 6306 setError(error); 6307 } 6308 } 6309 }); 6310 } 6311 6312 if (ss.editorState != null) { 6313 createEditorIfNeeded(); 6314 mEditor.restoreInstanceState(ss.editorState); 6315 } 6316 } 6317 6318 /** 6319 * Control whether this text view saves its entire text contents when 6320 * freezing to an icicle, in addition to dynamic state such as cursor 6321 * position. By default this is false, not saving the text. Set to true 6322 * if the text in the text view is not being saved somewhere else in 6323 * persistent storage (such as in a content provider) so that if the 6324 * view is later thawed the user will not lose their data. For 6325 * {@link android.widget.EditText} it is always enabled, regardless of 6326 * the value of the attribute. 6327 * 6328 * @param freezesText Controls whether a frozen icicle should include the 6329 * entire text data: true to include it, false to not. 6330 * 6331 * @attr ref android.R.styleable#TextView_freezesText 6332 */ 6333 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)6334 public void setFreezesText(boolean freezesText) { 6335 mFreezesText = freezesText; 6336 } 6337 6338 /** 6339 * Return whether this text view is including its entire text contents 6340 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 6341 * 6342 * @return Returns true if text is included, false if it isn't. 6343 * 6344 * @see #setFreezesText 6345 */ 6346 @InspectableProperty getFreezesText()6347 public boolean getFreezesText() { 6348 return mFreezesText; 6349 } 6350 6351 /////////////////////////////////////////////////////////////////////////// 6352 6353 /** 6354 * Sets the Factory used to create new {@link Editable Editables}. 6355 * 6356 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6357 * 6358 * @see android.text.Editable.Factory 6359 * @see android.widget.TextView.BufferType#EDITABLE 6360 */ setEditableFactory(Editable.Factory factory)6361 public final void setEditableFactory(Editable.Factory factory) { 6362 mEditableFactory = factory; 6363 setText(mText); 6364 } 6365 6366 /** 6367 * Sets the Factory used to create new {@link Spannable Spannables}. 6368 * 6369 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6370 * 6371 * @see android.text.Spannable.Factory 6372 * @see android.widget.TextView.BufferType#SPANNABLE 6373 */ setSpannableFactory(Spannable.Factory factory)6374 public final void setSpannableFactory(Spannable.Factory factory) { 6375 mSpannableFactory = factory; 6376 setText(mText); 6377 } 6378 6379 /** 6380 * Sets the text to be displayed. TextView <em>does not</em> accept 6381 * HTML-like formatting, which you can do with text strings in XML resource files. 6382 * To style your strings, attach android.text.style.* objects to a 6383 * {@link android.text.SpannableString}, or see the 6384 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6385 * Available Resource Types</a> documentation for an example of setting 6386 * formatted text in the XML resource file. 6387 * <p/> 6388 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6389 * intermediate {@link Spannable Spannables}. Likewise it will use 6390 * {@link android.text.Editable.Factory} to create final or intermediate 6391 * {@link Editable Editables}. 6392 * 6393 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6394 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6395 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6396 * 6397 * @param text text to be displayed 6398 * 6399 * @attr ref android.R.styleable#TextView_text 6400 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6401 * parameters used to create the PrecomputedText mismatches 6402 * with this TextView. 6403 */ 6404 @android.view.RemotableViewMethod setText(CharSequence text)6405 public final void setText(CharSequence text) { 6406 setText(text, mBufferType); 6407 } 6408 6409 /** 6410 * Sets the text to be displayed but retains the cursor position. Same as 6411 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6412 * new text. 6413 * <p/> 6414 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6415 * intermediate {@link Spannable Spannables}. Likewise it will use 6416 * {@link android.text.Editable.Factory} to create final or intermediate 6417 * {@link Editable Editables}. 6418 * 6419 * @param text text to be displayed 6420 * 6421 * @see #setText(CharSequence) 6422 */ 6423 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6424 public final void setTextKeepState(CharSequence text) { 6425 setTextKeepState(text, mBufferType); 6426 } 6427 6428 /** 6429 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6430 * <p/> 6431 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6432 * intermediate {@link Spannable Spannables}. Likewise it will use 6433 * {@link android.text.Editable.Factory} to create final or intermediate 6434 * {@link Editable Editables}. 6435 * 6436 * Subclasses overriding this method should ensure that the following post condition holds, 6437 * in order to guarantee the safety of the view's measurement and layout operations: 6438 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6439 * will be different from {@code null}. 6440 * 6441 * @param text text to be displayed 6442 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6443 * stored as a static text, styleable/spannable text, or editable text 6444 * 6445 * @see #setText(CharSequence) 6446 * @see android.widget.TextView.BufferType 6447 * @see #setSpannableFactory(Spannable.Factory) 6448 * @see #setEditableFactory(Editable.Factory) 6449 * 6450 * @attr ref android.R.styleable#TextView_text 6451 * @attr ref android.R.styleable#TextView_bufferType 6452 */ setText(CharSequence text, BufferType type)6453 public void setText(CharSequence text, BufferType type) { 6454 setText(text, type, true, 0); 6455 6456 // drop any potential mCharWrappper leaks 6457 mCharWrapper = null; 6458 } 6459 6460 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6461 private void setText(CharSequence text, BufferType type, 6462 boolean notifyBefore, int oldlen) { 6463 mTextSetFromXmlOrResourceId = false; 6464 if (text == null) { 6465 text = ""; 6466 } 6467 6468 // If suggestions are not enabled, remove the suggestion spans from the text 6469 if (!isSuggestionsEnabled()) { 6470 text = removeSuggestionSpans(text); 6471 } 6472 6473 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6474 6475 if (text instanceof Spanned 6476 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6477 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6478 setHorizontalFadingEdgeEnabled(true); 6479 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6480 } else { 6481 setHorizontalFadingEdgeEnabled(false); 6482 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6483 } 6484 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6485 } 6486 6487 int n = mFilters.length; 6488 for (int i = 0; i < n; i++) { 6489 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6490 if (out != null) { 6491 text = out; 6492 } 6493 } 6494 6495 if (notifyBefore) { 6496 if (mText != null) { 6497 oldlen = mText.length(); 6498 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6499 } else { 6500 sendBeforeTextChanged("", 0, 0, text.length()); 6501 } 6502 } 6503 6504 boolean needEditableForNotification = false; 6505 6506 if (mListeners != null && mListeners.size() != 0) { 6507 needEditableForNotification = true; 6508 } 6509 6510 PrecomputedText precomputed = 6511 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6512 if (type == BufferType.EDITABLE || getKeyListener() != null 6513 || needEditableForNotification) { 6514 createEditorIfNeeded(); 6515 mEditor.forgetUndoRedo(); 6516 mEditor.scheduleRestartInputForSetText(); 6517 Editable t = mEditableFactory.newEditable(text); 6518 text = t; 6519 setFilters(t, mFilters); 6520 } else if (precomputed != null) { 6521 if (mTextDir == null) { 6522 mTextDir = getTextDirectionHeuristic(); 6523 } 6524 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6525 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6526 mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( 6527 mLineBreakStyle, mLineBreakWordStyle)); 6528 switch (checkResult) { 6529 case PrecomputedText.Params.UNUSABLE: 6530 throw new IllegalArgumentException( 6531 "PrecomputedText's Parameters don't match the parameters of this TextView." 6532 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6533 + "to override the settings of this TextView: " 6534 + "PrecomputedText: " + precomputed.getParams() 6535 + "TextView: " + getTextMetricsParams()); 6536 case PrecomputedText.Params.NEED_RECOMPUTE: 6537 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6538 break; 6539 case PrecomputedText.Params.USABLE: 6540 // pass through 6541 } 6542 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6543 text = mSpannableFactory.newSpannable(text); 6544 } else if (!(text instanceof CharWrapper)) { 6545 text = TextUtils.stringOrSpannedString(text); 6546 } 6547 6548 @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE; 6549 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 6550 a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText); 6551 } 6552 6553 if (mAutoLinkMask != 0) { 6554 Spannable s2; 6555 6556 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6557 s2 = (Spannable) text; 6558 } else { 6559 s2 = mSpannableFactory.newSpannable(text); 6560 } 6561 6562 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6563 text = s2; 6564 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6565 6566 /* 6567 * We must go ahead and set the text before changing the 6568 * movement method, because setMovementMethod() may call 6569 * setText() again to try to upgrade the buffer type. 6570 */ 6571 setTextInternal(text); 6572 if (a11yTextChangeType == AccessibilityUtils.NONE) { 6573 a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN; 6574 } 6575 6576 // Do not change the movement method for text that support text selection as it 6577 // would prevent an arbitrary cursor displacement. 6578 if (mLinksClickable && !textCanBeSelected()) { 6579 setMovementMethod(LinkMovementMethod.getInstance()); 6580 } 6581 } 6582 } 6583 6584 mBufferType = type; 6585 setTextInternal(text); 6586 6587 if (mTransformation == null) { 6588 mTransformed = text; 6589 } else { 6590 mTransformed = mTransformation.getTransformation(text, this); 6591 } 6592 if (mTransformed == null) { 6593 // Should not happen if the transformation method follows the non-null postcondition. 6594 mTransformed = ""; 6595 } 6596 6597 final int textLength = text.length(); 6598 6599 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6600 Spannable sp = (Spannable) text; 6601 6602 // Remove any ChangeWatchers that might have come from other TextViews. 6603 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6604 final int count = watchers.length; 6605 for (int i = 0; i < count; i++) { 6606 sp.removeSpan(watchers[i]); 6607 } 6608 6609 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6610 6611 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6612 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6613 6614 if (mEditor != null) mEditor.addSpanWatchers(sp); 6615 6616 if (mTransformation != null) { 6617 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6618 } 6619 6620 if (mMovement != null) { 6621 mMovement.initialize(this, (Spannable) text); 6622 6623 /* 6624 * Initializing the movement method will have set the 6625 * selection, so reset mSelectionMoved to keep that from 6626 * interfering with the normal on-focus selection-setting. 6627 */ 6628 if (mEditor != null) mEditor.mSelectionMoved = false; 6629 } 6630 } 6631 6632 if (mLayout != null) { 6633 checkForRelayout(); 6634 } 6635 6636 sendOnTextChanged(text, 0, oldlen, textLength); 6637 onTextChanged(text, 0, oldlen, textLength); 6638 6639 if (a11yTextChangeType == AccessibilityUtils.TEXT) { 6640 notifyViewAccessibilityStateChangedIfNeeded( 6641 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6642 } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) { 6643 notifyViewAccessibilityStateChangedIfNeeded( 6644 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 6645 } 6646 6647 if (needEditableForNotification) { 6648 sendAfterTextChanged((Editable) text); 6649 } else { 6650 notifyListeningManagersAfterTextChanged(); 6651 } 6652 6653 if (mEditor != null) { 6654 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6655 mEditor.prepareCursorControllers(); 6656 6657 mEditor.maybeFireScheduledRestartInputForSetText(); 6658 } 6659 } 6660 6661 /** 6662 * Sets the TextView to display the specified slice of the specified 6663 * char array. You must promise that you will not change the contents 6664 * of the array except for right before another call to setText(), 6665 * since the TextView has no way to know that the text 6666 * has changed and that it needs to invalidate and re-layout. 6667 * 6668 * @throws NullPointerException if text is null 6669 * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length 6670 * 6671 * @param text char array to be displayed 6672 * @param start start index in the char array 6673 * @param len length of char count after {@code start} 6674 */ setText( char[] text, int start, int len)6675 public final void setText(/* @NonNull */ char[] text, int start, int len) { 6676 int oldlen = 0; 6677 6678 if (start < 0 || len < 0 || start + len > text.length) { 6679 throw new IndexOutOfBoundsException(start + ", " + len); 6680 } 6681 6682 /* 6683 * We must do the before-notification here ourselves because if 6684 * the old text is a CharWrapper we destroy it before calling 6685 * into the normal path. 6686 */ 6687 if (mText != null) { 6688 oldlen = mText.length(); 6689 sendBeforeTextChanged(mText, 0, oldlen, len); 6690 } else { 6691 sendBeforeTextChanged("", 0, 0, len); 6692 } 6693 6694 if (mCharWrapper == null) { 6695 mCharWrapper = new CharWrapper(text, start, len); 6696 } else { 6697 mCharWrapper.set(text, start, len); 6698 } 6699 6700 setText(mCharWrapper, mBufferType, false, oldlen); 6701 } 6702 6703 /** 6704 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6705 * the cursor position. Same as 6706 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6707 * position (if any) is retained in the new text. 6708 * <p/> 6709 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6710 * intermediate {@link Spannable Spannables}. Likewise it will use 6711 * {@link android.text.Editable.Factory} to create final or intermediate 6712 * {@link Editable Editables}. 6713 * 6714 * @param text text to be displayed 6715 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6716 * stored as a static text, styleable/spannable text, or editable text 6717 * 6718 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6719 */ setTextKeepState(CharSequence text, BufferType type)6720 public final void setTextKeepState(CharSequence text, BufferType type) { 6721 int start = getSelectionStart(); 6722 int end = getSelectionEnd(); 6723 int len = text.length(); 6724 6725 setText(text, type); 6726 6727 if (start >= 0 || end >= 0) { 6728 if (mSpannable != null) { 6729 Selection.setSelection(mSpannable, 6730 Math.max(0, Math.min(start, len)), 6731 Math.max(0, Math.min(end, len))); 6732 } 6733 } 6734 } 6735 6736 /** 6737 * Sets the text to be displayed using a string resource identifier. 6738 * 6739 * @param resid the resource identifier of the string resource to be displayed 6740 * 6741 * @see #setText(CharSequence) 6742 * 6743 * @attr ref android.R.styleable#TextView_text 6744 */ 6745 @android.view.RemotableViewMethod setText(@tringRes int resid)6746 public final void setText(@StringRes int resid) { 6747 setText(getContext().getResources().getText(resid)); 6748 mTextSetFromXmlOrResourceId = true; 6749 mTextId = resid; 6750 } 6751 6752 /** 6753 * Sets the text to be displayed using a string resource identifier and the 6754 * {@link android.widget.TextView.BufferType}. 6755 * <p/> 6756 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6757 * intermediate {@link Spannable Spannables}. Likewise it will use 6758 * {@link android.text.Editable.Factory} to create final or intermediate 6759 * {@link Editable Editables}. 6760 * 6761 * @param resid the resource identifier of the string resource to be displayed 6762 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6763 * stored as a static text, styleable/spannable text, or editable text 6764 * 6765 * @see #setText(int) 6766 * @see #setText(CharSequence) 6767 * @see android.widget.TextView.BufferType 6768 * @see #setSpannableFactory(Spannable.Factory) 6769 * @see #setEditableFactory(Editable.Factory) 6770 * 6771 * @attr ref android.R.styleable#TextView_text 6772 * @attr ref android.R.styleable#TextView_bufferType 6773 */ setText(@tringRes int resid, BufferType type)6774 public final void setText(@StringRes int resid, BufferType type) { 6775 setText(getContext().getResources().getText(resid), type); 6776 mTextSetFromXmlOrResourceId = true; 6777 mTextId = resid; 6778 } 6779 6780 /** 6781 * Sets the text to be displayed when the text of the TextView is empty. 6782 * Null means to use the normal empty text. The hint does not currently 6783 * participate in determining the size of the view. 6784 * 6785 * @attr ref android.R.styleable#TextView_hint 6786 */ 6787 @android.view.RemotableViewMethod setHint(CharSequence hint)6788 public final void setHint(CharSequence hint) { 6789 setHintInternal(hint); 6790 6791 if (mEditor != null && isInputMethodTarget()) { 6792 mEditor.reportExtractedText(); 6793 } 6794 } 6795 setHintInternal(CharSequence hint)6796 private void setHintInternal(CharSequence hint) { 6797 mHint = TextUtils.stringOrSpannedString(hint); 6798 6799 if (mLayout != null) { 6800 checkForRelayout(); 6801 } 6802 6803 if (mText.length() == 0) { 6804 invalidate(); 6805 } 6806 6807 // Invalidate display list if hint is currently used 6808 if (mEditor != null && mText.length() == 0 && mHint != null) { 6809 mEditor.invalidateTextDisplayList(); 6810 } 6811 } 6812 6813 /** 6814 * Sets the text to be displayed when the text of the TextView is empty, 6815 * from a resource. 6816 * 6817 * @attr ref android.R.styleable#TextView_hint 6818 */ 6819 @android.view.RemotableViewMethod setHint(@tringRes int resid)6820 public final void setHint(@StringRes int resid) { 6821 mHintId = resid; 6822 setHint(getContext().getResources().getText(resid)); 6823 } 6824 6825 /** 6826 * Returns the hint that is displayed when the text of the TextView 6827 * is empty. 6828 * 6829 * @attr ref android.R.styleable#TextView_hint 6830 */ 6831 @InspectableProperty 6832 @ViewDebug.CapturedViewProperty getHint()6833 public CharSequence getHint() { 6834 return mHint; 6835 } 6836 6837 /** 6838 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6839 * line characters instead of letting it wrap onto multiple lines. 6840 * 6841 * @attr ref android.R.styleable#TextView_singleLine 6842 */ 6843 @InspectableProperty isSingleLine()6844 public boolean isSingleLine() { 6845 return mSingleLine; 6846 } 6847 isMultilineInputType(int type)6848 private static boolean isMultilineInputType(int type) { 6849 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6850 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6851 } 6852 6853 /** 6854 * Removes the suggestion spans. 6855 */ removeSuggestionSpans(CharSequence text)6856 CharSequence removeSuggestionSpans(CharSequence text) { 6857 if (text instanceof Spanned) { 6858 Spannable spannable; 6859 if (text instanceof Spannable) { 6860 spannable = (Spannable) text; 6861 } else { 6862 spannable = mSpannableFactory.newSpannable(text); 6863 } 6864 6865 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6866 if (spans.length == 0) { 6867 return text; 6868 } else { 6869 text = spannable; 6870 } 6871 6872 for (int i = 0; i < spans.length; i++) { 6873 spannable.removeSpan(spans[i]); 6874 } 6875 } 6876 return text; 6877 } 6878 6879 /** 6880 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6881 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6882 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6883 * then a soft keyboard will not be displayed for this text view. 6884 * 6885 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6886 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6887 * type. 6888 * 6889 * @see #getInputType() 6890 * @see #setRawInputType(int) 6891 * @see android.text.InputType 6892 * @attr ref android.R.styleable#TextView_inputType 6893 */ setInputType(int type)6894 public void setInputType(int type) { 6895 final boolean wasPassword = isPasswordInputType(getInputType()); 6896 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6897 setInputType(type, false); 6898 final boolean isPassword = isPasswordInputType(type); 6899 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6900 boolean forceUpdate = false; 6901 if (isPassword) { 6902 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6903 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6904 Typeface.NORMAL, -1 /* weight, not specifeid */); 6905 } else if (isVisiblePassword) { 6906 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6907 forceUpdate = true; 6908 } 6909 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6910 Typeface.NORMAL, -1 /* weight, not specified */); 6911 } else if (wasPassword || wasVisiblePassword) { 6912 // not in password mode, clean up typeface and transformation 6913 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6914 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6915 -1 /* weight, not specified */); 6916 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6917 forceUpdate = true; 6918 } 6919 } 6920 6921 boolean singleLine = !isMultilineInputType(type); 6922 6923 // We need to update the single line mode if it has changed or we 6924 // were previously in password mode. 6925 if (mSingleLine != singleLine || forceUpdate) { 6926 // Change single line mode, but only change the transformation if 6927 // we are not in password mode. 6928 applySingleLine(singleLine, !isPassword, true, true); 6929 } 6930 6931 if (!isSuggestionsEnabled()) { 6932 setTextInternal(removeSuggestionSpans(mText)); 6933 } 6934 6935 InputMethodManager imm = getInputMethodManager(); 6936 if (imm != null) imm.restartInput(this); 6937 } 6938 6939 /** 6940 * It would be better to rely on the input type for everything. A password inputType should have 6941 * a password transformation. We should hence use isPasswordInputType instead of this method. 6942 * 6943 * We should: 6944 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6945 * would install the correct transformation). 6946 * - Refuse the installation of a non-password transformation in setTransformation if the input 6947 * type is password. 6948 * 6949 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6950 * is useful since it matches what the user can see (obfuscated text or not). 6951 * 6952 * @return true if the current transformation method is of the password type. 6953 */ hasPasswordTransformationMethod()6954 boolean hasPasswordTransformationMethod() { 6955 return mTransformation instanceof PasswordTransformationMethod; 6956 } 6957 6958 /** 6959 * Returns true if the current inputType is any type of password. 6960 * 6961 * @hide 6962 */ isAnyPasswordInputType()6963 public boolean isAnyPasswordInputType() { 6964 final int inputType = getInputType(); 6965 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6966 } 6967 isPasswordInputType(int inputType)6968 static boolean isPasswordInputType(int inputType) { 6969 final int variation = 6970 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6971 return variation 6972 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6973 || variation 6974 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6975 || variation 6976 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6977 } 6978 isVisiblePasswordInputType(int inputType)6979 private static boolean isVisiblePasswordInputType(int inputType) { 6980 final int variation = 6981 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6982 return variation 6983 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6984 } 6985 6986 /** 6987 * Directly change the content type integer of the text view, without 6988 * modifying any other state. 6989 * @see #setInputType(int) 6990 * @see android.text.InputType 6991 * @attr ref android.R.styleable#TextView_inputType 6992 */ setRawInputType(int type)6993 public void setRawInputType(int type) { 6994 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6995 createEditorIfNeeded(); 6996 mEditor.mInputType = type; 6997 } 6998 6999 @Override getAutofillHints()7000 public String[] getAutofillHints() { 7001 String[] hints = super.getAutofillHints(); 7002 if (isAnyPasswordInputType()) { 7003 if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) { 7004 hints = ArrayUtils.appendElement(String.class, hints, 7005 AUTOFILL_HINT_PASSWORD_AUTO); 7006 } 7007 } 7008 return hints; 7009 } 7010 7011 /** 7012 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 7013 * a {@code Locale} object that can be used to customize key various listeners. 7014 * @see DateKeyListener#getInstance(Locale) 7015 * @see DateTimeKeyListener#getInstance(Locale) 7016 * @see DigitsKeyListener#getInstance(Locale) 7017 * @see TimeKeyListener#getInstance(Locale) 7018 */ 7019 @Nullable getCustomLocaleForKeyListenerOrNull()7020 private Locale getCustomLocaleForKeyListenerOrNull() { 7021 if (!mUseInternationalizedInput) { 7022 // If the application does not target O, stick to the previous behavior. 7023 return null; 7024 } 7025 final LocaleList locales = getImeHintLocales(); 7026 if (locales == null) { 7027 // If the application does not explicitly specify IME hint locale, also stick to the 7028 // previous behavior. 7029 return null; 7030 } 7031 return locales.get(0); 7032 } 7033 7034 @UnsupportedAppUsage setInputType(int type, boolean direct)7035 private void setInputType(int type, boolean direct) { 7036 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 7037 KeyListener input; 7038 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 7039 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 7040 TextKeyListener.Capitalize cap; 7041 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 7042 cap = TextKeyListener.Capitalize.CHARACTERS; 7043 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 7044 cap = TextKeyListener.Capitalize.WORDS; 7045 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 7046 cap = TextKeyListener.Capitalize.SENTENCES; 7047 } else { 7048 cap = TextKeyListener.Capitalize.NONE; 7049 } 7050 input = TextKeyListener.getInstance(autotext, cap); 7051 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 7052 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7053 input = DigitsKeyListener.getInstance( 7054 locale, 7055 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 7056 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 7057 if (locale != null) { 7058 // Override type, if necessary for i18n. 7059 int newType = input.getInputType(); 7060 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 7061 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 7062 // The class is different from the original class. So we need to override 7063 // 'type'. But we want to keep the password flag if it's there. 7064 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 7065 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 7066 } 7067 type = newType; 7068 } 7069 } 7070 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 7071 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7072 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 7073 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 7074 input = DateKeyListener.getInstance(locale); 7075 break; 7076 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 7077 input = TimeKeyListener.getInstance(locale); 7078 break; 7079 default: 7080 input = DateTimeKeyListener.getInstance(locale); 7081 break; 7082 } 7083 if (mUseInternationalizedInput) { 7084 type = input.getInputType(); // Override type, if necessary for i18n. 7085 } 7086 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 7087 input = DialerKeyListener.getInstance(); 7088 } else { 7089 input = TextKeyListener.getInstance(); 7090 } 7091 setRawInputType(type); 7092 mListenerChanged = false; 7093 if (direct) { 7094 createEditorIfNeeded(); 7095 mEditor.mKeyListener = input; 7096 } else { 7097 setKeyListenerOnly(input); 7098 } 7099 } 7100 7101 /** 7102 * Get the type of the editable content. 7103 * 7104 * @see #setInputType(int) 7105 * @see android.text.InputType 7106 */ 7107 @InspectableProperty(flagMapping = { 7108 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 7109 @FlagEntry( 7110 name = "text", 7111 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7112 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 7113 @FlagEntry( 7114 name = "textUri", 7115 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7116 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 7117 @FlagEntry( 7118 name = "textEmailAddress", 7119 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7120 target = InputType.TYPE_CLASS_TEXT 7121 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 7122 @FlagEntry( 7123 name = "textEmailSubject", 7124 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7125 target = InputType.TYPE_CLASS_TEXT 7126 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 7127 @FlagEntry( 7128 name = "textShortMessage", 7129 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7130 target = InputType.TYPE_CLASS_TEXT 7131 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 7132 @FlagEntry( 7133 name = "textLongMessage", 7134 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7135 target = InputType.TYPE_CLASS_TEXT 7136 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 7137 @FlagEntry( 7138 name = "textPersonName", 7139 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7140 target = InputType.TYPE_CLASS_TEXT 7141 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 7142 @FlagEntry( 7143 name = "textPostalAddress", 7144 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7145 target = InputType.TYPE_CLASS_TEXT 7146 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 7147 @FlagEntry( 7148 name = "textPassword", 7149 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7150 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 7151 @FlagEntry( 7152 name = "textVisiblePassword", 7153 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7154 target = InputType.TYPE_CLASS_TEXT 7155 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 7156 @FlagEntry( 7157 name = "textWebEditText", 7158 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7159 target = InputType.TYPE_CLASS_TEXT 7160 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 7161 @FlagEntry( 7162 name = "textFilter", 7163 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7164 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 7165 @FlagEntry( 7166 name = "textPhonetic", 7167 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7168 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 7169 @FlagEntry( 7170 name = "textWebEmailAddress", 7171 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7172 target = InputType.TYPE_CLASS_TEXT 7173 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 7174 @FlagEntry( 7175 name = "textWebPassword", 7176 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7177 target = InputType.TYPE_CLASS_TEXT 7178 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 7179 @FlagEntry( 7180 name = "number", 7181 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7182 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 7183 @FlagEntry( 7184 name = "numberPassword", 7185 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7186 target = InputType.TYPE_CLASS_NUMBER 7187 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 7188 @FlagEntry( 7189 name = "phone", 7190 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7191 target = InputType.TYPE_CLASS_PHONE), 7192 @FlagEntry( 7193 name = "datetime", 7194 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7195 target = InputType.TYPE_CLASS_DATETIME 7196 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 7197 @FlagEntry( 7198 name = "date", 7199 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7200 target = InputType.TYPE_CLASS_DATETIME 7201 | InputType.TYPE_DATETIME_VARIATION_DATE), 7202 @FlagEntry( 7203 name = "time", 7204 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7205 target = InputType.TYPE_CLASS_DATETIME 7206 | InputType.TYPE_DATETIME_VARIATION_TIME), 7207 @FlagEntry( 7208 name = "textCapCharacters", 7209 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7210 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 7211 @FlagEntry( 7212 name = "textCapWords", 7213 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7214 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 7215 @FlagEntry( 7216 name = "textCapSentences", 7217 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7218 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 7219 @FlagEntry( 7220 name = "textAutoCorrect", 7221 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7222 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 7223 @FlagEntry( 7224 name = "textAutoComplete", 7225 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7226 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 7227 @FlagEntry( 7228 name = "textMultiLine", 7229 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7230 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 7231 @FlagEntry( 7232 name = "textImeMultiLine", 7233 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7234 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 7235 @FlagEntry( 7236 name = "textNoSuggestions", 7237 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7238 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 7239 @FlagEntry( 7240 name = "numberSigned", 7241 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7242 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 7243 @FlagEntry( 7244 name = "numberDecimal", 7245 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7246 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 7247 }) getInputType()7248 public int getInputType() { 7249 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 7250 } 7251 7252 /** 7253 * Change the editor type integer associated with the text view, which 7254 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 7255 * when it has focus. 7256 * @see #getImeOptions 7257 * @see android.view.inputmethod.EditorInfo 7258 * @attr ref android.R.styleable#TextView_imeOptions 7259 */ setImeOptions(int imeOptions)7260 public void setImeOptions(int imeOptions) { 7261 createEditorIfNeeded(); 7262 mEditor.createInputContentTypeIfNeeded(); 7263 mEditor.mInputContentType.imeOptions = imeOptions; 7264 } 7265 7266 /** 7267 * Get the type of the Input Method Editor (IME). 7268 * @return the type of the IME 7269 * @see #setImeOptions(int) 7270 * @see EditorInfo 7271 */ 7272 @InspectableProperty(flagMapping = { 7273 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 7274 @FlagEntry( 7275 name = "actionUnspecified", 7276 mask = EditorInfo.IME_MASK_ACTION, 7277 target = EditorInfo.IME_ACTION_UNSPECIFIED), 7278 @FlagEntry( 7279 name = "actionNone", 7280 mask = EditorInfo.IME_MASK_ACTION, 7281 target = EditorInfo.IME_ACTION_NONE), 7282 @FlagEntry( 7283 name = "actionGo", 7284 mask = EditorInfo.IME_MASK_ACTION, 7285 target = EditorInfo.IME_ACTION_GO), 7286 @FlagEntry( 7287 name = "actionSearch", 7288 mask = EditorInfo.IME_MASK_ACTION, 7289 target = EditorInfo.IME_ACTION_SEARCH), 7290 @FlagEntry( 7291 name = "actionSend", 7292 mask = EditorInfo.IME_MASK_ACTION, 7293 target = EditorInfo.IME_ACTION_SEND), 7294 @FlagEntry( 7295 name = "actionNext", 7296 mask = EditorInfo.IME_MASK_ACTION, 7297 target = EditorInfo.IME_ACTION_NEXT), 7298 @FlagEntry( 7299 name = "actionDone", 7300 mask = EditorInfo.IME_MASK_ACTION, 7301 target = EditorInfo.IME_ACTION_DONE), 7302 @FlagEntry( 7303 name = "actionPrevious", 7304 mask = EditorInfo.IME_MASK_ACTION, 7305 target = EditorInfo.IME_ACTION_PREVIOUS), 7306 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 7307 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 7308 @FlagEntry( 7309 name = "flagNavigatePrevious", 7310 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 7311 @FlagEntry( 7312 name = "flagNoAccessoryAction", 7313 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 7314 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 7315 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 7316 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 7317 @FlagEntry( 7318 name = "flagNoPersonalizedLearning", 7319 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 7320 }) getImeOptions()7321 public int getImeOptions() { 7322 return mEditor != null && mEditor.mInputContentType != null 7323 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 7324 } 7325 7326 /** 7327 * Change the custom IME action associated with the text view, which 7328 * will be reported to an IME with {@link EditorInfo#actionLabel} 7329 * and {@link EditorInfo#actionId} when it has focus. 7330 * @see #getImeActionLabel 7331 * @see #getImeActionId 7332 * @see android.view.inputmethod.EditorInfo 7333 * @attr ref android.R.styleable#TextView_imeActionLabel 7334 * @attr ref android.R.styleable#TextView_imeActionId 7335 */ setImeActionLabel(CharSequence label, int actionId)7336 public void setImeActionLabel(CharSequence label, int actionId) { 7337 createEditorIfNeeded(); 7338 mEditor.createInputContentTypeIfNeeded(); 7339 mEditor.mInputContentType.imeActionLabel = label; 7340 mEditor.mInputContentType.imeActionId = actionId; 7341 } 7342 7343 /** 7344 * Get the IME action label previous set with {@link #setImeActionLabel}. 7345 * 7346 * @see #setImeActionLabel 7347 * @see android.view.inputmethod.EditorInfo 7348 */ 7349 @InspectableProperty getImeActionLabel()7350 public CharSequence getImeActionLabel() { 7351 return mEditor != null && mEditor.mInputContentType != null 7352 ? mEditor.mInputContentType.imeActionLabel : null; 7353 } 7354 7355 /** 7356 * Get the IME action ID previous set with {@link #setImeActionLabel}. 7357 * 7358 * @see #setImeActionLabel 7359 * @see android.view.inputmethod.EditorInfo 7360 */ 7361 @InspectableProperty getImeActionId()7362 public int getImeActionId() { 7363 return mEditor != null && mEditor.mInputContentType != null 7364 ? mEditor.mInputContentType.imeActionId : 0; 7365 } 7366 7367 /** 7368 * Set a special listener to be called when an action is performed 7369 * on the text view. This will be called when the enter key is pressed, 7370 * or when an action supplied to the IME is selected by the user. Setting 7371 * this means that the normal hard key event will not insert a newline 7372 * into the text view, even if it is multi-line; holding down the ALT 7373 * modifier will, however, allow the user to insert a newline character. 7374 */ setOnEditorActionListener(OnEditorActionListener l)7375 public void setOnEditorActionListener(OnEditorActionListener l) { 7376 createEditorIfNeeded(); 7377 mEditor.createInputContentTypeIfNeeded(); 7378 mEditor.mInputContentType.onEditorActionListener = l; 7379 } 7380 7381 /** 7382 * Called when an attached input method calls 7383 * {@link InputConnection#performEditorAction(int) 7384 * InputConnection.performEditorAction()} 7385 * for this text view. The default implementation will call your action 7386 * listener supplied to {@link #setOnEditorActionListener}, or perform 7387 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7388 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7389 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7390 * EditorInfo.IME_ACTION_DONE}. 7391 * 7392 * <p>For backwards compatibility, if no IME options have been set and the 7393 * text view would not normally advance focus on enter, then 7394 * the NEXT and DONE actions received here will be turned into an enter 7395 * key down/up pair to go through the normal key handling. 7396 * 7397 * @param actionCode The code of the action being performed. 7398 * 7399 * @see #setOnEditorActionListener 7400 */ onEditorAction(int actionCode)7401 public void onEditorAction(int actionCode) { 7402 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7403 if (ict != null) { 7404 if (ict.onEditorActionListener != null) { 7405 if (ict.onEditorActionListener.onEditorAction(this, 7406 actionCode, null)) { 7407 return; 7408 } 7409 } 7410 7411 // This is the handling for some default action. 7412 // Note that for backwards compatibility we don't do this 7413 // default handling if explicit ime options have not been given, 7414 // instead turning this into the normal enter key codes that an 7415 // app may be expecting. 7416 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7417 View v = focusSearch(FOCUS_FORWARD); 7418 if (v != null) { 7419 if (!v.requestFocus(FOCUS_FORWARD)) { 7420 throw new IllegalStateException("focus search returned a view " 7421 + "that wasn't able to take focus!"); 7422 } 7423 } 7424 return; 7425 7426 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7427 View v = focusSearch(FOCUS_BACKWARD); 7428 if (v != null) { 7429 if (!v.requestFocus(FOCUS_BACKWARD)) { 7430 throw new IllegalStateException("focus search returned a view " 7431 + "that wasn't able to take focus!"); 7432 } 7433 } 7434 return; 7435 7436 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7437 InputMethodManager imm = getInputMethodManager(); 7438 if (imm != null && imm.isActive(this)) { 7439 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7440 } 7441 return; 7442 } 7443 } 7444 7445 ViewRootImpl viewRootImpl = getViewRootImpl(); 7446 if (viewRootImpl != null) { 7447 long eventTime = SystemClock.uptimeMillis(); 7448 viewRootImpl.dispatchKeyFromIme( 7449 new KeyEvent(eventTime, eventTime, 7450 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7451 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7452 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7453 | KeyEvent.FLAG_EDITOR_ACTION)); 7454 viewRootImpl.dispatchKeyFromIme( 7455 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7456 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7457 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7458 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7459 | KeyEvent.FLAG_EDITOR_ACTION)); 7460 } 7461 } 7462 7463 /** 7464 * Set the private content type of the text, which is the 7465 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7466 * field that will be filled in when creating an input connection. 7467 * 7468 * @see #getPrivateImeOptions() 7469 * @see EditorInfo#privateImeOptions 7470 * @attr ref android.R.styleable#TextView_privateImeOptions 7471 */ setPrivateImeOptions(String type)7472 public void setPrivateImeOptions(String type) { 7473 createEditorIfNeeded(); 7474 mEditor.createInputContentTypeIfNeeded(); 7475 mEditor.mInputContentType.privateImeOptions = type; 7476 } 7477 7478 /** 7479 * Get the private type of the content. 7480 * 7481 * @see #setPrivateImeOptions(String) 7482 * @see EditorInfo#privateImeOptions 7483 */ 7484 @InspectableProperty getPrivateImeOptions()7485 public String getPrivateImeOptions() { 7486 return mEditor != null && mEditor.mInputContentType != null 7487 ? mEditor.mInputContentType.privateImeOptions : null; 7488 } 7489 7490 /** 7491 * Set the extra input data of the text, which is the 7492 * {@link EditorInfo#extras TextBoxAttribute.extras} 7493 * Bundle that will be filled in when creating an input connection. The 7494 * given integer is the resource identifier of an XML resource holding an 7495 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7496 * 7497 * @see #getInputExtras(boolean) 7498 * @see EditorInfo#extras 7499 * @attr ref android.R.styleable#TextView_editorExtras 7500 */ setInputExtras(@mlRes int xmlResId)7501 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7502 createEditorIfNeeded(); 7503 XmlResourceParser parser = getResources().getXml(xmlResId); 7504 mEditor.createInputContentTypeIfNeeded(); 7505 mEditor.mInputContentType.extras = new Bundle(); 7506 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7507 } 7508 7509 /** 7510 * Retrieve the input extras currently associated with the text view, which 7511 * can be viewed as well as modified. 7512 * 7513 * @param create If true, the extras will be created if they don't already 7514 * exist. Otherwise, null will be returned if none have been created. 7515 * @see #setInputExtras(int) 7516 * @see EditorInfo#extras 7517 * @attr ref android.R.styleable#TextView_editorExtras 7518 */ getInputExtras(boolean create)7519 public Bundle getInputExtras(boolean create) { 7520 if (mEditor == null && !create) return null; 7521 createEditorIfNeeded(); 7522 if (mEditor.mInputContentType == null) { 7523 if (!create) return null; 7524 mEditor.createInputContentTypeIfNeeded(); 7525 } 7526 if (mEditor.mInputContentType.extras == null) { 7527 if (!create) return null; 7528 mEditor.mInputContentType.extras = new Bundle(); 7529 } 7530 return mEditor.mInputContentType.extras; 7531 } 7532 7533 /** 7534 * Change "hint" locales associated with the text view, which will be reported to an IME with 7535 * {@link EditorInfo#hintLocales} when it has focus. 7536 * 7537 * Starting with Android O, this also causes internationalized listeners to be created (or 7538 * change locale) based on the first locale in the input locale list. 7539 * 7540 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7541 * call {@link InputMethodManager#restartInput(View)}.</p> 7542 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7543 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7544 * @see #getImeHintLocales() 7545 * @see android.view.inputmethod.EditorInfo#hintLocales 7546 */ setImeHintLocales(@ullable LocaleList hintLocales)7547 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7548 createEditorIfNeeded(); 7549 mEditor.createInputContentTypeIfNeeded(); 7550 mEditor.mInputContentType.imeHintLocales = hintLocales; 7551 if (mUseInternationalizedInput) { 7552 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7553 } 7554 } 7555 7556 /** 7557 * @return The current languages list "hint". {@code null} when no "hint" is available. 7558 * @see #setImeHintLocales(LocaleList) 7559 * @see android.view.inputmethod.EditorInfo#hintLocales 7560 */ 7561 @Nullable getImeHintLocales()7562 public LocaleList getImeHintLocales() { 7563 if (mEditor == null) { 7564 return null; 7565 } 7566 if (mEditor.mInputContentType == null) { 7567 return null; 7568 } 7569 return mEditor.mInputContentType.imeHintLocales; 7570 } 7571 7572 /** 7573 * Returns the error message that was set to be displayed with 7574 * {@link #setError}, or <code>null</code> if no error was set 7575 * or if it the error was cleared by the widget after user input. 7576 */ getError()7577 public CharSequence getError() { 7578 return mEditor == null ? null : mEditor.mError; 7579 } 7580 7581 /** 7582 * Sets the right-hand compound drawable of the TextView to the "error" 7583 * icon and sets an error message that will be displayed in a popup when 7584 * the TextView has focus. The icon and error message will be reset to 7585 * null when any key events cause changes to the TextView's text. If the 7586 * <code>error</code> is <code>null</code>, the error message and icon 7587 * will be cleared. 7588 */ 7589 @android.view.RemotableViewMethod setError(CharSequence error)7590 public void setError(CharSequence error) { 7591 if (error == null) { 7592 setError(null, null); 7593 } else { 7594 Drawable dr = getContext().getDrawable( 7595 com.android.internal.R.drawable.indicator_input_error); 7596 7597 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7598 setError(error, dr); 7599 } 7600 } 7601 7602 /** 7603 * Sets the right-hand compound drawable of the TextView to the specified 7604 * icon and sets an error message that will be displayed in a popup when 7605 * the TextView has focus. The icon and error message will be reset to 7606 * null when any key events cause changes to the TextView's text. The 7607 * drawable must already have had {@link Drawable#setBounds} set on it. 7608 * If the <code>error</code> is <code>null</code>, the error message will 7609 * be cleared (and you should provide a <code>null</code> icon as well). 7610 */ setError(CharSequence error, Drawable icon)7611 public void setError(CharSequence error, Drawable icon) { 7612 createEditorIfNeeded(); 7613 mEditor.setError(error, icon); 7614 notifyViewAccessibilityStateChangedIfNeeded( 7615 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7616 } 7617 7618 @Override setFrame(int l, int t, int r, int b)7619 protected boolean setFrame(int l, int t, int r, int b) { 7620 boolean result = super.setFrame(l, t, r, b); 7621 7622 if (mEditor != null) mEditor.setFrame(); 7623 7624 restartMarqueeIfNeeded(); 7625 7626 return result; 7627 } 7628 restartMarqueeIfNeeded()7629 private void restartMarqueeIfNeeded() { 7630 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7631 mRestartMarquee = false; 7632 startMarquee(); 7633 } 7634 } 7635 7636 /** 7637 * Sets the list of input filters that will be used if the buffer is 7638 * Editable. Has no effect otherwise. 7639 * 7640 * @attr ref android.R.styleable#TextView_maxLength 7641 */ setFilters(InputFilter[] filters)7642 public void setFilters(InputFilter[] filters) { 7643 if (filters == null) { 7644 throw new IllegalArgumentException(); 7645 } 7646 7647 mFilters = filters; 7648 7649 if (mText instanceof Editable) { 7650 setFilters((Editable) mText, filters); 7651 } 7652 } 7653 7654 /** 7655 * Sets the list of input filters on the specified Editable, 7656 * and includes mInput in the list if it is an InputFilter. 7657 */ setFilters(Editable e, InputFilter[] filters)7658 private void setFilters(Editable e, InputFilter[] filters) { 7659 if (mEditor != null) { 7660 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7661 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7662 int num = 0; 7663 if (undoFilter) num++; 7664 if (keyFilter) num++; 7665 if (num > 0) { 7666 InputFilter[] nf = new InputFilter[filters.length + num]; 7667 7668 System.arraycopy(filters, 0, nf, 0, filters.length); 7669 num = 0; 7670 if (undoFilter) { 7671 nf[filters.length] = mEditor.mUndoInputFilter; 7672 num++; 7673 } 7674 if (keyFilter) { 7675 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7676 } 7677 7678 e.setFilters(nf); 7679 return; 7680 } 7681 } 7682 e.setFilters(filters); 7683 } 7684 7685 /** 7686 * Returns the current list of input filters. 7687 * 7688 * @attr ref android.R.styleable#TextView_maxLength 7689 */ getFilters()7690 public InputFilter[] getFilters() { 7691 return mFilters; 7692 } 7693 7694 ///////////////////////////////////////////////////////////////////////// 7695 getBoxHeight(Layout l)7696 private int getBoxHeight(Layout l) { 7697 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7698 int padding = (l == mHintLayout) 7699 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7700 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7701 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7702 } 7703 7704 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7705 int getVerticalOffset(boolean forceNormal) { 7706 int voffset = 0; 7707 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7708 7709 Layout l = mLayout; 7710 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7711 l = mHintLayout; 7712 } 7713 7714 if (gravity != Gravity.TOP) { 7715 int boxht = getBoxHeight(l); 7716 int textht = l.getHeight(); 7717 7718 if (textht < boxht) { 7719 if (gravity == Gravity.BOTTOM) { 7720 voffset = boxht - textht; 7721 } else { // (gravity == Gravity.CENTER_VERTICAL) 7722 voffset = (boxht - textht) >> 1; 7723 } 7724 } 7725 } 7726 return voffset; 7727 } 7728 getBottomVerticalOffset(boolean forceNormal)7729 private int getBottomVerticalOffset(boolean forceNormal) { 7730 int voffset = 0; 7731 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7732 7733 Layout l = mLayout; 7734 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7735 l = mHintLayout; 7736 } 7737 7738 if (gravity != Gravity.BOTTOM) { 7739 int boxht = getBoxHeight(l); 7740 int textht = l.getHeight(); 7741 7742 if (textht < boxht) { 7743 if (gravity == Gravity.TOP) { 7744 voffset = boxht - textht; 7745 } else { // (gravity == Gravity.CENTER_VERTICAL) 7746 voffset = (boxht - textht) >> 1; 7747 } 7748 } 7749 } 7750 return voffset; 7751 } 7752 invalidateCursorPath()7753 void invalidateCursorPath() { 7754 if (mHighlightPathBogus) { 7755 invalidateCursor(); 7756 } else { 7757 final int horizontalPadding = getCompoundPaddingLeft(); 7758 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7759 7760 if (mEditor.mDrawableForCursor == null) { 7761 synchronized (TEMP_RECTF) { 7762 /* 7763 * The reason for this concern about the thickness of the 7764 * cursor and doing the floor/ceil on the coordinates is that 7765 * some EditTexts (notably textfields in the Browser) have 7766 * anti-aliased text where not all the characters are 7767 * necessarily at integer-multiple locations. This should 7768 * make sure the entire cursor gets invalidated instead of 7769 * sometimes missing half a pixel. 7770 */ 7771 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7772 if (thick < 1.0f) { 7773 thick = 1.0f; 7774 } 7775 7776 thick /= 2.0f; 7777 7778 // mHighlightPath is guaranteed to be non null at that point. 7779 mHighlightPath.computeBounds(TEMP_RECTF, false); 7780 7781 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7782 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7783 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7784 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7785 } 7786 } else { 7787 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7788 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7789 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7790 } 7791 } 7792 } 7793 invalidateCursor()7794 void invalidateCursor() { 7795 int where = getSelectionEnd(); 7796 7797 invalidateCursor(where, where, where); 7798 } 7799 invalidateCursor(int a, int b, int c)7800 private void invalidateCursor(int a, int b, int c) { 7801 if (a >= 0 || b >= 0 || c >= 0) { 7802 int start = Math.min(Math.min(a, b), c); 7803 int end = Math.max(Math.max(a, b), c); 7804 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7805 } 7806 } 7807 7808 /** 7809 * Invalidates the region of text enclosed between the start and end text offsets. 7810 */ invalidateRegion(int start, int end, boolean invalidateCursor)7811 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7812 if (mLayout == null) { 7813 invalidate(); 7814 } else { 7815 int lineStart = mLayout.getLineForOffset(start); 7816 int top = mLayout.getLineTop(lineStart); 7817 7818 // This is ridiculous, but the descent from the line above 7819 // can hang down into the line we really want to redraw, 7820 // so we have to invalidate part of the line above to make 7821 // sure everything that needs to be redrawn really is. 7822 // (But not the whole line above, because that would cause 7823 // the same problem with the descenders on the line above it!) 7824 if (lineStart > 0) { 7825 top -= mLayout.getLineDescent(lineStart - 1); 7826 } 7827 7828 int lineEnd; 7829 7830 if (start == end) { 7831 lineEnd = lineStart; 7832 } else { 7833 lineEnd = mLayout.getLineForOffset(end); 7834 } 7835 7836 int bottom = mLayout.getLineBottom(lineEnd); 7837 7838 // mEditor can be null in case selection is set programmatically. 7839 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7840 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7841 top = Math.min(top, bounds.top); 7842 bottom = Math.max(bottom, bounds.bottom); 7843 } 7844 7845 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7846 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7847 7848 int left, right; 7849 if (lineStart == lineEnd && !invalidateCursor) { 7850 left = (int) mLayout.getPrimaryHorizontal(start); 7851 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7852 left += compoundPaddingLeft; 7853 right += compoundPaddingLeft; 7854 } else { 7855 // Rectangle bounding box when the region spans several lines 7856 left = compoundPaddingLeft; 7857 right = getWidth() - getCompoundPaddingRight(); 7858 } 7859 7860 invalidate(mScrollX + left, verticalPadding + top, 7861 mScrollX + right, verticalPadding + bottom); 7862 } 7863 } 7864 registerForPreDraw()7865 private void registerForPreDraw() { 7866 if (!mPreDrawRegistered) { 7867 getViewTreeObserver().addOnPreDrawListener(this); 7868 mPreDrawRegistered = true; 7869 } 7870 } 7871 unregisterForPreDraw()7872 private void unregisterForPreDraw() { 7873 getViewTreeObserver().removeOnPreDrawListener(this); 7874 mPreDrawRegistered = false; 7875 mPreDrawListenerDetached = false; 7876 } 7877 7878 /** 7879 * {@inheritDoc} 7880 */ 7881 @Override onPreDraw()7882 public boolean onPreDraw() { 7883 if (mLayout == null) { 7884 assumeLayout(); 7885 } 7886 7887 if (mMovement != null) { 7888 /* This code also provides auto-scrolling when a cursor is moved using a 7889 * CursorController (insertion point or selection limits). 7890 * For selection, ensure start or end is visible depending on controller's state. 7891 */ 7892 int curs = getSelectionEnd(); 7893 // Do not create the controller if it is not already created. 7894 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7895 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7896 curs = getSelectionStart(); 7897 } 7898 7899 /* 7900 * TODO: This should really only keep the end in view if 7901 * it already was before the text changed. I'm not sure 7902 * of a good way to tell from here if it was. 7903 */ 7904 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7905 curs = mText.length(); 7906 } 7907 7908 if (curs >= 0) { 7909 bringPointIntoView(curs); 7910 } 7911 } else { 7912 bringTextIntoView(); 7913 } 7914 7915 // This has to be checked here since: 7916 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7917 // a screen rotation) since layout is not yet initialized at that point. 7918 if (mEditor != null && mEditor.mCreatedWithASelection) { 7919 mEditor.refreshTextActionMode(); 7920 mEditor.mCreatedWithASelection = false; 7921 } 7922 7923 unregisterForPreDraw(); 7924 7925 return true; 7926 } 7927 7928 @Override onAttachedToWindow()7929 protected void onAttachedToWindow() { 7930 super.onAttachedToWindow(); 7931 7932 if (mEditor != null) mEditor.onAttachedToWindow(); 7933 7934 if (mPreDrawListenerDetached) { 7935 getViewTreeObserver().addOnPreDrawListener(this); 7936 mPreDrawListenerDetached = false; 7937 } 7938 } 7939 7940 /** @hide */ 7941 @Override onDetachedFromWindowInternal()7942 protected void onDetachedFromWindowInternal() { 7943 if (mPreDrawRegistered) { 7944 getViewTreeObserver().removeOnPreDrawListener(this); 7945 mPreDrawListenerDetached = true; 7946 } 7947 7948 resetResolvedDrawables(); 7949 7950 if (mEditor != null) mEditor.onDetachedFromWindow(); 7951 7952 super.onDetachedFromWindowInternal(); 7953 } 7954 7955 @Override onScreenStateChanged(int screenState)7956 public void onScreenStateChanged(int screenState) { 7957 super.onScreenStateChanged(screenState); 7958 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7959 } 7960 7961 @Override isPaddingOffsetRequired()7962 protected boolean isPaddingOffsetRequired() { 7963 return mShadowRadius != 0 || mDrawables != null; 7964 } 7965 7966 @Override getLeftPaddingOffset()7967 protected int getLeftPaddingOffset() { 7968 return getCompoundPaddingLeft() - mPaddingLeft 7969 + (int) Math.min(0, mShadowDx - mShadowRadius); 7970 } 7971 7972 @Override getTopPaddingOffset()7973 protected int getTopPaddingOffset() { 7974 return (int) Math.min(0, mShadowDy - mShadowRadius); 7975 } 7976 7977 @Override getBottomPaddingOffset()7978 protected int getBottomPaddingOffset() { 7979 return (int) Math.max(0, mShadowDy + mShadowRadius); 7980 } 7981 7982 @Override getRightPaddingOffset()7983 protected int getRightPaddingOffset() { 7984 return -(getCompoundPaddingRight() - mPaddingRight) 7985 + (int) Math.max(0, mShadowDx + mShadowRadius); 7986 } 7987 7988 @Override verifyDrawable(@onNull Drawable who)7989 protected boolean verifyDrawable(@NonNull Drawable who) { 7990 final boolean verified = super.verifyDrawable(who); 7991 if (!verified && mDrawables != null) { 7992 for (Drawable dr : mDrawables.mShowing) { 7993 if (who == dr) { 7994 return true; 7995 } 7996 } 7997 } 7998 return verified; 7999 } 8000 8001 @Override jumpDrawablesToCurrentState()8002 public void jumpDrawablesToCurrentState() { 8003 super.jumpDrawablesToCurrentState(); 8004 if (mDrawables != null) { 8005 for (Drawable dr : mDrawables.mShowing) { 8006 if (dr != null) { 8007 dr.jumpToCurrentState(); 8008 } 8009 } 8010 } 8011 } 8012 8013 @Override invalidateDrawable(@onNull Drawable drawable)8014 public void invalidateDrawable(@NonNull Drawable drawable) { 8015 boolean handled = false; 8016 8017 if (verifyDrawable(drawable)) { 8018 final Rect dirty = drawable.getBounds(); 8019 int scrollX = mScrollX; 8020 int scrollY = mScrollY; 8021 8022 // IMPORTANT: The coordinates below are based on the coordinates computed 8023 // for each compound drawable in onDraw(). Make sure to update each section 8024 // accordingly. 8025 final TextView.Drawables drawables = mDrawables; 8026 if (drawables != null) { 8027 if (drawable == drawables.mShowing[Drawables.LEFT]) { 8028 final int compoundPaddingTop = getCompoundPaddingTop(); 8029 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8030 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8031 8032 scrollX += mPaddingLeft; 8033 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 8034 handled = true; 8035 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 8036 final int compoundPaddingTop = getCompoundPaddingTop(); 8037 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8038 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8039 8040 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 8041 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 8042 handled = true; 8043 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 8044 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8045 final int compoundPaddingRight = getCompoundPaddingRight(); 8046 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8047 8048 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 8049 scrollY += mPaddingTop; 8050 handled = true; 8051 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 8052 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8053 final int compoundPaddingRight = getCompoundPaddingRight(); 8054 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8055 8056 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 8057 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 8058 handled = true; 8059 } 8060 } 8061 8062 if (handled) { 8063 invalidate(dirty.left + scrollX, dirty.top + scrollY, 8064 dirty.right + scrollX, dirty.bottom + scrollY); 8065 } 8066 } 8067 8068 if (!handled) { 8069 super.invalidateDrawable(drawable); 8070 } 8071 } 8072 8073 @Override hasOverlappingRendering()8074 public boolean hasOverlappingRendering() { 8075 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 8076 return ((getBackground() != null && getBackground().getCurrent() != null) 8077 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 8078 || mShadowColor != 0); 8079 } 8080 8081 /** 8082 * 8083 * Returns the state of the {@code textIsSelectable} flag (See 8084 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 8085 * to allow users to select and copy text in a non-editable TextView, the content of an 8086 * {@link EditText} can always be selected, independently of the value of this flag. 8087 * <p> 8088 * 8089 * @return True if the text displayed in this TextView can be selected by the user. 8090 * 8091 * @attr ref android.R.styleable#TextView_textIsSelectable 8092 */ 8093 @InspectableProperty(name = "textIsSelectable") isTextSelectable()8094 public boolean isTextSelectable() { 8095 return mEditor == null ? false : mEditor.mTextIsSelectable; 8096 } 8097 8098 /** 8099 * Sets whether the content of this view is selectable by the user. The default is 8100 * {@code false}, meaning that the content is not selectable. 8101 * <p> 8102 * When you use a TextView to display a useful piece of information to the user (such as a 8103 * contact's address), make it selectable, so that the user can select and copy its 8104 * content. You can also use set the XML attribute 8105 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 8106 * <p> 8107 * When you call this method to set the value of {@code textIsSelectable}, it sets 8108 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 8109 * and {@code longClickable} to the same value. These flags correspond to the attributes 8110 * {@link android.R.styleable#View_focusable android:focusable}, 8111 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 8112 * {@link android.R.styleable#View_clickable android:clickable}, and 8113 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 8114 * flags to a state you had set previously, call one or more of the following methods: 8115 * {@link #setFocusable(boolean) setFocusable()}, 8116 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 8117 * {@link #setClickable(boolean) setClickable()} or 8118 * {@link #setLongClickable(boolean) setLongClickable()}. 8119 * 8120 * @param selectable Whether the content of this TextView should be selectable. 8121 */ setTextIsSelectable(boolean selectable)8122 public void setTextIsSelectable(boolean selectable) { 8123 if (!selectable && mEditor == null) return; // false is default value with no edit data 8124 8125 createEditorIfNeeded(); 8126 if (mEditor.mTextIsSelectable == selectable) return; 8127 8128 mEditor.mTextIsSelectable = selectable; 8129 setFocusableInTouchMode(selectable); 8130 setFocusable(FOCUSABLE_AUTO); 8131 setClickable(selectable); 8132 setLongClickable(selectable); 8133 8134 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 8135 8136 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 8137 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 8138 8139 // Called by setText above, but safer in case of future code changes 8140 mEditor.prepareCursorControllers(); 8141 } 8142 8143 @Override onCreateDrawableState(int extraSpace)8144 protected int[] onCreateDrawableState(int extraSpace) { 8145 final int[] drawableState; 8146 8147 if (mSingleLine) { 8148 drawableState = super.onCreateDrawableState(extraSpace); 8149 } else { 8150 drawableState = super.onCreateDrawableState(extraSpace + 1); 8151 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 8152 } 8153 8154 if (isTextSelectable()) { 8155 // Disable pressed state, which was introduced when TextView was made clickable. 8156 // Prevents text color change. 8157 // setClickable(false) would have a similar effect, but it also disables focus changes 8158 // and long press actions, which are both needed by text selection. 8159 final int length = drawableState.length; 8160 for (int i = 0; i < length; i++) { 8161 if (drawableState[i] == R.attr.state_pressed) { 8162 final int[] nonPressedState = new int[length - 1]; 8163 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 8164 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 8165 return nonPressedState; 8166 } 8167 } 8168 } 8169 8170 return drawableState; 8171 } 8172 8173 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getUpdatedHighlightPath()8174 private Path getUpdatedHighlightPath() { 8175 Path highlight = null; 8176 Paint highlightPaint = mHighlightPaint; 8177 8178 final int selStart = getSelectionStart(); 8179 final int selEnd = getSelectionEnd(); 8180 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 8181 if (selStart == selEnd) { 8182 if (mEditor != null && mEditor.shouldRenderCursor()) { 8183 if (mHighlightPathBogus) { 8184 if (mHighlightPath == null) mHighlightPath = new Path(); 8185 mHighlightPath.reset(); 8186 mLayout.getCursorPath(selStart, mHighlightPath, mText); 8187 mEditor.updateCursorPosition(); 8188 mHighlightPathBogus = false; 8189 } 8190 8191 // XXX should pass to skin instead of drawing directly 8192 highlightPaint.setColor(mCurTextColor); 8193 highlightPaint.setStyle(Paint.Style.STROKE); 8194 highlight = mHighlightPath; 8195 } 8196 } else { 8197 if (mHighlightPathBogus) { 8198 if (mHighlightPath == null) mHighlightPath = new Path(); 8199 mHighlightPath.reset(); 8200 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8201 mHighlightPathBogus = false; 8202 } 8203 8204 // XXX should pass to skin instead of drawing directly 8205 highlightPaint.setColor(mHighlightColor); 8206 highlightPaint.setStyle(Paint.Style.FILL); 8207 8208 highlight = mHighlightPath; 8209 } 8210 } 8211 return highlight; 8212 } 8213 8214 /** 8215 * @hide 8216 */ getHorizontalOffsetForDrawables()8217 public int getHorizontalOffsetForDrawables() { 8218 return 0; 8219 } 8220 8221 @Override onDraw(Canvas canvas)8222 protected void onDraw(Canvas canvas) { 8223 restartMarqueeIfNeeded(); 8224 8225 // Draw the background for this view 8226 super.onDraw(canvas); 8227 8228 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8229 final int compoundPaddingTop = getCompoundPaddingTop(); 8230 final int compoundPaddingRight = getCompoundPaddingRight(); 8231 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8232 final int scrollX = mScrollX; 8233 final int scrollY = mScrollY; 8234 final int right = mRight; 8235 final int left = mLeft; 8236 final int bottom = mBottom; 8237 final int top = mTop; 8238 final boolean isLayoutRtl = isLayoutRtl(); 8239 final int offset = getHorizontalOffsetForDrawables(); 8240 final int leftOffset = isLayoutRtl ? 0 : offset; 8241 final int rightOffset = isLayoutRtl ? offset : 0; 8242 8243 final Drawables dr = mDrawables; 8244 if (dr != null) { 8245 /* 8246 * Compound, not extended, because the icon is not clipped 8247 * if the text height is smaller. 8248 */ 8249 8250 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 8251 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 8252 8253 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8254 // Make sure to update invalidateDrawable() when changing this code. 8255 if (dr.mShowing[Drawables.LEFT] != null) { 8256 canvas.save(); 8257 canvas.translate(scrollX + mPaddingLeft + leftOffset, 8258 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 8259 dr.mShowing[Drawables.LEFT].draw(canvas); 8260 canvas.restore(); 8261 } 8262 8263 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8264 // Make sure to update invalidateDrawable() when changing this code. 8265 if (dr.mShowing[Drawables.RIGHT] != null) { 8266 canvas.save(); 8267 canvas.translate(scrollX + right - left - mPaddingRight 8268 - dr.mDrawableSizeRight - rightOffset, 8269 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 8270 dr.mShowing[Drawables.RIGHT].draw(canvas); 8271 canvas.restore(); 8272 } 8273 8274 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8275 // Make sure to update invalidateDrawable() when changing this code. 8276 if (dr.mShowing[Drawables.TOP] != null) { 8277 canvas.save(); 8278 canvas.translate(scrollX + compoundPaddingLeft 8279 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 8280 dr.mShowing[Drawables.TOP].draw(canvas); 8281 canvas.restore(); 8282 } 8283 8284 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8285 // Make sure to update invalidateDrawable() when changing this code. 8286 if (dr.mShowing[Drawables.BOTTOM] != null) { 8287 canvas.save(); 8288 canvas.translate(scrollX + compoundPaddingLeft 8289 + (hspace - dr.mDrawableWidthBottom) / 2, 8290 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 8291 dr.mShowing[Drawables.BOTTOM].draw(canvas); 8292 canvas.restore(); 8293 } 8294 } 8295 8296 int color = mCurTextColor; 8297 8298 if (mLayout == null) { 8299 assumeLayout(); 8300 } 8301 8302 Layout layout = mLayout; 8303 8304 if (mHint != null && mText.length() == 0) { 8305 if (mHintTextColor != null) { 8306 color = mCurHintTextColor; 8307 } 8308 8309 layout = mHintLayout; 8310 } 8311 8312 mTextPaint.setColor(color); 8313 mTextPaint.drawableState = getDrawableState(); 8314 8315 canvas.save(); 8316 /* Would be faster if we didn't have to do this. Can we chop the 8317 (displayable) text so that we don't need to do this ever? 8318 */ 8319 8320 int extendedPaddingTop = getExtendedPaddingTop(); 8321 int extendedPaddingBottom = getExtendedPaddingBottom(); 8322 8323 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8324 final int maxScrollY = mLayout.getHeight() - vspace; 8325 8326 float clipLeft = compoundPaddingLeft + scrollX; 8327 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 8328 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 8329 float clipBottom = bottom - top + scrollY 8330 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 8331 8332 if (mShadowRadius != 0) { 8333 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 8334 clipRight += Math.max(0, mShadowDx + mShadowRadius); 8335 8336 clipTop += Math.min(0, mShadowDy - mShadowRadius); 8337 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 8338 } 8339 8340 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 8341 8342 int voffsetText = 0; 8343 int voffsetCursor = 0; 8344 8345 // translate in by our padding 8346 /* shortcircuit calling getVerticaOffset() */ 8347 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8348 voffsetText = getVerticalOffset(false); 8349 voffsetCursor = getVerticalOffset(true); 8350 } 8351 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 8352 8353 final int layoutDirection = getLayoutDirection(); 8354 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8355 if (isMarqueeFadeEnabled()) { 8356 if (!mSingleLine && getLineCount() == 1 && canMarquee() 8357 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 8358 final int width = mRight - mLeft; 8359 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 8360 final float dx = mLayout.getLineRight(0) - (width - padding); 8361 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8362 } 8363 8364 if (mMarquee != null && mMarquee.isRunning()) { 8365 final float dx = -mMarquee.getScroll(); 8366 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8367 } 8368 } 8369 8370 final int cursorOffsetVertical = voffsetCursor - voffsetText; 8371 8372 Path highlight = getUpdatedHighlightPath(); 8373 if (mEditor != null) { 8374 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 8375 } else { 8376 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8377 } 8378 8379 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 8380 final float dx = mMarquee.getGhostOffset(); 8381 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8382 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8383 } 8384 8385 canvas.restore(); 8386 } 8387 8388 @Override getFocusedRect(Rect r)8389 public void getFocusedRect(Rect r) { 8390 if (mLayout == null) { 8391 super.getFocusedRect(r); 8392 return; 8393 } 8394 8395 int selEnd = getSelectionEnd(); 8396 if (selEnd < 0) { 8397 super.getFocusedRect(r); 8398 return; 8399 } 8400 8401 int selStart = getSelectionStart(); 8402 if (selStart < 0 || selStart >= selEnd) { 8403 int line = mLayout.getLineForOffset(selEnd); 8404 r.top = mLayout.getLineTop(line); 8405 r.bottom = mLayout.getLineBottom(line); 8406 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8407 r.right = r.left + 4; 8408 } else { 8409 int lineStart = mLayout.getLineForOffset(selStart); 8410 int lineEnd = mLayout.getLineForOffset(selEnd); 8411 r.top = mLayout.getLineTop(lineStart); 8412 r.bottom = mLayout.getLineBottom(lineEnd); 8413 if (lineStart == lineEnd) { 8414 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8415 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8416 } else { 8417 // Selection extends across multiple lines -- make the focused 8418 // rect cover the entire width. 8419 if (mHighlightPathBogus) { 8420 if (mHighlightPath == null) mHighlightPath = new Path(); 8421 mHighlightPath.reset(); 8422 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8423 mHighlightPathBogus = false; 8424 } 8425 synchronized (TEMP_RECTF) { 8426 mHighlightPath.computeBounds(TEMP_RECTF, true); 8427 r.left = (int) TEMP_RECTF.left - 1; 8428 r.right = (int) TEMP_RECTF.right + 1; 8429 } 8430 } 8431 } 8432 8433 // Adjust for padding and gravity. 8434 int paddingLeft = getCompoundPaddingLeft(); 8435 int paddingTop = getExtendedPaddingTop(); 8436 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8437 paddingTop += getVerticalOffset(false); 8438 } 8439 r.offset(paddingLeft, paddingTop); 8440 int paddingBottom = getExtendedPaddingBottom(); 8441 r.bottom += paddingBottom; 8442 } 8443 8444 /** 8445 * Return the number of lines of text, or 0 if the internal Layout has not 8446 * been built. 8447 */ getLineCount()8448 public int getLineCount() { 8449 return mLayout != null ? mLayout.getLineCount() : 0; 8450 } 8451 8452 /** 8453 * Return the baseline for the specified line (0...getLineCount() - 1) 8454 * If bounds is not null, return the top, left, right, bottom extents 8455 * of the specified line in it. If the internal Layout has not been built, 8456 * return 0 and set bounds to (0, 0, 0, 0) 8457 * @param line which line to examine (0..getLineCount() - 1) 8458 * @param bounds Optional. If not null, it returns the extent of the line 8459 * @return the Y-coordinate of the baseline 8460 */ getLineBounds(int line, Rect bounds)8461 public int getLineBounds(int line, Rect bounds) { 8462 if (mLayout == null) { 8463 if (bounds != null) { 8464 bounds.set(0, 0, 0, 0); 8465 } 8466 return 0; 8467 } else { 8468 int baseline = mLayout.getLineBounds(line, bounds); 8469 8470 int voffset = getExtendedPaddingTop(); 8471 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8472 voffset += getVerticalOffset(true); 8473 } 8474 if (bounds != null) { 8475 bounds.offset(getCompoundPaddingLeft(), voffset); 8476 } 8477 return baseline + voffset; 8478 } 8479 } 8480 8481 @Override getBaseline()8482 public int getBaseline() { 8483 if (mLayout == null) { 8484 return super.getBaseline(); 8485 } 8486 8487 return getBaselineOffset() + mLayout.getLineBaseline(0); 8488 } 8489 getBaselineOffset()8490 int getBaselineOffset() { 8491 int voffset = 0; 8492 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8493 voffset = getVerticalOffset(true); 8494 } 8495 8496 if (isLayoutModeOptical(mParent)) { 8497 voffset -= getOpticalInsets().top; 8498 } 8499 8500 return getExtendedPaddingTop() + voffset; 8501 } 8502 8503 /** 8504 * @hide 8505 */ 8506 @Override getFadeTop(boolean offsetRequired)8507 protected int getFadeTop(boolean offsetRequired) { 8508 if (mLayout == null) return 0; 8509 8510 int voffset = 0; 8511 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8512 voffset = getVerticalOffset(true); 8513 } 8514 8515 if (offsetRequired) voffset += getTopPaddingOffset(); 8516 8517 return getExtendedPaddingTop() + voffset; 8518 } 8519 8520 /** 8521 * @hide 8522 */ 8523 @Override getFadeHeight(boolean offsetRequired)8524 protected int getFadeHeight(boolean offsetRequired) { 8525 return mLayout != null ? mLayout.getHeight() : 0; 8526 } 8527 8528 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8529 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8530 if (mSpannable != null && mLinksClickable) { 8531 final float x = event.getX(pointerIndex); 8532 final float y = event.getY(pointerIndex); 8533 final int offset = getOffsetForPosition(x, y); 8534 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8535 ClickableSpan.class); 8536 if (clickables.length > 0) { 8537 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8538 } 8539 } 8540 if (isTextSelectable() || isTextEditable()) { 8541 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8542 } 8543 return super.onResolvePointerIcon(event, pointerIndex); 8544 } 8545 8546 @Override onKeyPreIme(int keyCode, KeyEvent event)8547 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8548 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8549 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8550 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8551 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8552 return true; 8553 } 8554 return super.onKeyPreIme(keyCode, event); 8555 } 8556 8557 /** 8558 * @hide 8559 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8560 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8561 // Do nothing unless mEditor is in text action mode. 8562 if (mEditor == null || mEditor.getTextActionMode() == null) { 8563 return false; 8564 } 8565 8566 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8567 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8568 if (state != null) { 8569 state.startTracking(event, this); 8570 } 8571 return true; 8572 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8573 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8574 if (state != null) { 8575 state.handleUpEvent(event); 8576 } 8577 if (event.isTracking() && !event.isCanceled()) { 8578 stopTextActionMode(); 8579 return true; 8580 } 8581 } 8582 return false; 8583 } 8584 8585 @Override onKeyDown(int keyCode, KeyEvent event)8586 public boolean onKeyDown(int keyCode, KeyEvent event) { 8587 final int which = doKeyDown(keyCode, event, null); 8588 if (which == KEY_EVENT_NOT_HANDLED) { 8589 return super.onKeyDown(keyCode, event); 8590 } 8591 8592 return true; 8593 } 8594 8595 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8596 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8597 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8598 final int which = doKeyDown(keyCode, down, event); 8599 if (which == KEY_EVENT_NOT_HANDLED) { 8600 // Go through default dispatching. 8601 return super.onKeyMultiple(keyCode, repeatCount, event); 8602 } 8603 if (which == KEY_EVENT_HANDLED) { 8604 // Consumed the whole thing. 8605 return true; 8606 } 8607 8608 repeatCount--; 8609 8610 // We are going to dispatch the remaining events to either the input 8611 // or movement method. To do this, we will just send a repeated stream 8612 // of down and up events until we have done the complete repeatCount. 8613 // It would be nice if those interfaces had an onKeyMultiple() method, 8614 // but adding that is a more complicated change. 8615 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8616 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8617 // mEditor and mEditor.mInput are not null from doKeyDown 8618 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8619 while (--repeatCount > 0) { 8620 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8621 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8622 } 8623 hideErrorIfUnchanged(); 8624 8625 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8626 // mMovement is not null from doKeyDown 8627 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8628 while (--repeatCount > 0) { 8629 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8630 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8631 } 8632 } 8633 8634 return true; 8635 } 8636 8637 /** 8638 * Returns true if pressing ENTER in this field advances focus instead 8639 * of inserting the character. This is true mostly in single-line fields, 8640 * but also in mail addresses and subjects which will display on multiple 8641 * lines but where it doesn't make sense to insert newlines. 8642 */ shouldAdvanceFocusOnEnter()8643 private boolean shouldAdvanceFocusOnEnter() { 8644 if (getKeyListener() == null) { 8645 return false; 8646 } 8647 8648 if (mSingleLine) { 8649 return true; 8650 } 8651 8652 if (mEditor != null 8653 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8654 == EditorInfo.TYPE_CLASS_TEXT) { 8655 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8656 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8657 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8658 return true; 8659 } 8660 } 8661 8662 return false; 8663 } 8664 isDirectionalNavigationKey(int keyCode)8665 private boolean isDirectionalNavigationKey(int keyCode) { 8666 switch(keyCode) { 8667 case KeyEvent.KEYCODE_DPAD_UP: 8668 case KeyEvent.KEYCODE_DPAD_DOWN: 8669 case KeyEvent.KEYCODE_DPAD_LEFT: 8670 case KeyEvent.KEYCODE_DPAD_RIGHT: 8671 return true; 8672 } 8673 return false; 8674 } 8675 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8676 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8677 if (!isEnabled()) { 8678 return KEY_EVENT_NOT_HANDLED; 8679 } 8680 8681 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8682 // While this shouldn't be necessary because any time we're preventing default movement we 8683 // should be restricting the focus to remain within this view, thus we'll also receive 8684 // the key up event, occasionally key up events will get dropped and we don't want to 8685 // prevent the user from traversing out of this on the next key down. 8686 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8687 mPreventDefaultMovement = false; 8688 } 8689 8690 switch (keyCode) { 8691 case KeyEvent.KEYCODE_ENTER: 8692 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8693 if (event.hasNoModifiers()) { 8694 // When mInputContentType is set, we know that we are 8695 // running in a "modern" cupcake environment, so don't need 8696 // to worry about the application trying to capture 8697 // enter key events. 8698 if (mEditor != null && mEditor.mInputContentType != null) { 8699 // If there is an action listener, given them a 8700 // chance to consume the event. 8701 if (mEditor.mInputContentType.onEditorActionListener != null 8702 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8703 this, EditorInfo.IME_NULL, event)) { 8704 mEditor.mInputContentType.enterDown = true; 8705 // We are consuming the enter key for them. 8706 return KEY_EVENT_HANDLED; 8707 } 8708 } 8709 8710 // If our editor should move focus when enter is pressed, or 8711 // this is a generated event from an IME action button, then 8712 // don't let it be inserted into the text. 8713 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8714 || shouldAdvanceFocusOnEnter()) { 8715 if (hasOnClickListeners()) { 8716 return KEY_EVENT_NOT_HANDLED; 8717 } 8718 return KEY_EVENT_HANDLED; 8719 } 8720 } 8721 break; 8722 8723 case KeyEvent.KEYCODE_DPAD_CENTER: 8724 if (event.hasNoModifiers()) { 8725 if (shouldAdvanceFocusOnEnter()) { 8726 return KEY_EVENT_NOT_HANDLED; 8727 } 8728 } 8729 break; 8730 8731 case KeyEvent.KEYCODE_TAB: 8732 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8733 // Tab is used to move focus. 8734 return KEY_EVENT_NOT_HANDLED; 8735 } 8736 break; 8737 8738 // Has to be done on key down (and not on key up) to correctly be intercepted. 8739 case KeyEvent.KEYCODE_BACK: 8740 if (mEditor != null && mEditor.getTextActionMode() != null) { 8741 stopTextActionMode(); 8742 return KEY_EVENT_HANDLED; 8743 } 8744 break; 8745 8746 case KeyEvent.KEYCODE_CUT: 8747 if (event.hasNoModifiers() && canCut()) { 8748 if (onTextContextMenuItem(ID_CUT)) { 8749 return KEY_EVENT_HANDLED; 8750 } 8751 } 8752 break; 8753 8754 case KeyEvent.KEYCODE_COPY: 8755 if (event.hasNoModifiers() && canCopy()) { 8756 if (onTextContextMenuItem(ID_COPY)) { 8757 return KEY_EVENT_HANDLED; 8758 } 8759 } 8760 break; 8761 8762 case KeyEvent.KEYCODE_PASTE: 8763 if (event.hasNoModifiers() && canPaste()) { 8764 if (onTextContextMenuItem(ID_PASTE)) { 8765 return KEY_EVENT_HANDLED; 8766 } 8767 } 8768 break; 8769 8770 case KeyEvent.KEYCODE_FORWARD_DEL: 8771 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8772 if (onTextContextMenuItem(ID_CUT)) { 8773 return KEY_EVENT_HANDLED; 8774 } 8775 } 8776 break; 8777 8778 case KeyEvent.KEYCODE_INSERT: 8779 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8780 if (onTextContextMenuItem(ID_COPY)) { 8781 return KEY_EVENT_HANDLED; 8782 } 8783 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8784 if (onTextContextMenuItem(ID_PASTE)) { 8785 return KEY_EVENT_HANDLED; 8786 } 8787 } 8788 break; 8789 } 8790 8791 if (mEditor != null && mEditor.mKeyListener != null) { 8792 boolean doDown = true; 8793 if (otherEvent != null) { 8794 try { 8795 beginBatchEdit(); 8796 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8797 otherEvent); 8798 hideErrorIfUnchanged(); 8799 doDown = false; 8800 if (handled) { 8801 return KEY_EVENT_HANDLED; 8802 } 8803 } catch (AbstractMethodError e) { 8804 // onKeyOther was added after 1.0, so if it isn't 8805 // implemented we need to try to dispatch as a regular down. 8806 } finally { 8807 endBatchEdit(); 8808 } 8809 } 8810 8811 if (doDown) { 8812 beginBatchEdit(); 8813 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8814 keyCode, event); 8815 endBatchEdit(); 8816 hideErrorIfUnchanged(); 8817 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8818 } 8819 } 8820 8821 // bug 650865: sometimes we get a key event before a layout. 8822 // don't try to move around if we don't know the layout. 8823 8824 if (mMovement != null && mLayout != null) { 8825 boolean doDown = true; 8826 if (otherEvent != null) { 8827 try { 8828 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8829 doDown = false; 8830 if (handled) { 8831 return KEY_EVENT_HANDLED; 8832 } 8833 } catch (AbstractMethodError e) { 8834 // onKeyOther was added after 1.0, so if it isn't 8835 // implemented we need to try to dispatch as a regular down. 8836 } 8837 } 8838 if (doDown) { 8839 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8840 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8841 mPreventDefaultMovement = true; 8842 } 8843 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8844 } 8845 } 8846 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8847 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8848 // to move focus with arrows. 8849 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8850 && isDirectionalNavigationKey(keyCode)) { 8851 return KEY_EVENT_HANDLED; 8852 } 8853 } 8854 8855 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8856 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8857 } 8858 8859 /** 8860 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8861 * can be recorded. 8862 * @hide 8863 */ resetErrorChangedFlag()8864 public void resetErrorChangedFlag() { 8865 /* 8866 * Keep track of what the error was before doing the input 8867 * so that if an input filter changed the error, we leave 8868 * that error showing. Otherwise, we take down whatever 8869 * error was showing when the user types something. 8870 */ 8871 if (mEditor != null) mEditor.mErrorWasChanged = false; 8872 } 8873 8874 /** 8875 * @hide 8876 */ hideErrorIfUnchanged()8877 public void hideErrorIfUnchanged() { 8878 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8879 setError(null, null); 8880 } 8881 } 8882 8883 @Override onKeyUp(int keyCode, KeyEvent event)8884 public boolean onKeyUp(int keyCode, KeyEvent event) { 8885 if (!isEnabled()) { 8886 return super.onKeyUp(keyCode, event); 8887 } 8888 8889 if (!KeyEvent.isModifierKey(keyCode)) { 8890 mPreventDefaultMovement = false; 8891 } 8892 8893 switch (keyCode) { 8894 case KeyEvent.KEYCODE_DPAD_CENTER: 8895 if (event.hasNoModifiers()) { 8896 /* 8897 * If there is a click listener, just call through to 8898 * super, which will invoke it. 8899 * 8900 * If there isn't a click listener, try to show the soft 8901 * input method. (It will also 8902 * call performClick(), but that won't do anything in 8903 * this case.) 8904 */ 8905 if (!hasOnClickListeners()) { 8906 if (mMovement != null && mText instanceof Editable 8907 && mLayout != null && onCheckIsTextEditor()) { 8908 InputMethodManager imm = getInputMethodManager(); 8909 viewClicked(imm); 8910 if (imm != null && getShowSoftInputOnFocus()) { 8911 imm.showSoftInput(this, 0); 8912 } 8913 } 8914 } 8915 } 8916 return super.onKeyUp(keyCode, event); 8917 8918 case KeyEvent.KEYCODE_ENTER: 8919 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8920 if (event.hasNoModifiers()) { 8921 if (mEditor != null && mEditor.mInputContentType != null 8922 && mEditor.mInputContentType.onEditorActionListener != null 8923 && mEditor.mInputContentType.enterDown) { 8924 mEditor.mInputContentType.enterDown = false; 8925 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8926 this, EditorInfo.IME_NULL, event)) { 8927 return true; 8928 } 8929 } 8930 8931 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8932 || shouldAdvanceFocusOnEnter()) { 8933 /* 8934 * If there is a click listener, just call through to 8935 * super, which will invoke it. 8936 * 8937 * If there isn't a click listener, try to advance focus, 8938 * but still call through to super, which will reset the 8939 * pressed state and longpress state. (It will also 8940 * call performClick(), but that won't do anything in 8941 * this case.) 8942 */ 8943 if (!hasOnClickListeners()) { 8944 View v = focusSearch(FOCUS_DOWN); 8945 8946 if (v != null) { 8947 if (!v.requestFocus(FOCUS_DOWN)) { 8948 throw new IllegalStateException("focus search returned a view " 8949 + "that wasn't able to take focus!"); 8950 } 8951 8952 /* 8953 * Return true because we handled the key; super 8954 * will return false because there was no click 8955 * listener. 8956 */ 8957 super.onKeyUp(keyCode, event); 8958 return true; 8959 } else if ((event.getFlags() 8960 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8961 // No target for next focus, but make sure the IME 8962 // if this came from it. 8963 InputMethodManager imm = getInputMethodManager(); 8964 if (imm != null && imm.isActive(this)) { 8965 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8966 } 8967 } 8968 } 8969 } 8970 return super.onKeyUp(keyCode, event); 8971 } 8972 break; 8973 } 8974 8975 if (mEditor != null && mEditor.mKeyListener != null) { 8976 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8977 return true; 8978 } 8979 } 8980 8981 if (mMovement != null && mLayout != null) { 8982 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8983 return true; 8984 } 8985 } 8986 8987 return super.onKeyUp(keyCode, event); 8988 } 8989 8990 @Override onCheckIsTextEditor()8991 public boolean onCheckIsTextEditor() { 8992 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8993 } 8994 hasEditorInFocusSearchDirection(@ocusRealDirection int direction)8995 private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) { 8996 final View nextView = focusSearch(direction); 8997 return nextView != null && nextView.onCheckIsTextEditor(); 8998 } 8999 9000 @Override onCreateInputConnection(EditorInfo outAttrs)9001 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 9002 if (onCheckIsTextEditor() && isEnabled()) { 9003 mEditor.createInputMethodStateIfNeeded(); 9004 outAttrs.inputType = getInputType(); 9005 if (mEditor.mInputContentType != null) { 9006 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 9007 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 9008 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 9009 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 9010 outAttrs.extras = mEditor.mInputContentType.extras; 9011 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 9012 } else { 9013 outAttrs.imeOptions = EditorInfo.IME_NULL; 9014 outAttrs.hintLocales = null; 9015 } 9016 if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) { 9017 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 9018 } 9019 if (hasEditorInFocusSearchDirection(FOCUS_UP)) { 9020 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 9021 } 9022 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 9023 == EditorInfo.IME_ACTION_UNSPECIFIED) { 9024 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 9025 // An action has not been set, but the enter key will move to 9026 // the next focus, so set the action to that. 9027 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 9028 } else { 9029 // An action has not been set, and there is no focus to move 9030 // to, so let's just supply a "done" action. 9031 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 9032 } 9033 if (!shouldAdvanceFocusOnEnter()) { 9034 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 9035 } 9036 } 9037 if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 9038 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; 9039 } 9040 if (isMultilineInputType(outAttrs.inputType)) { 9041 // Multi-line text editors should always show an enter key. 9042 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 9043 } 9044 outAttrs.hintText = mHint; 9045 outAttrs.targetInputMethodUser = mTextOperationUser; 9046 if (mText instanceof Editable) { 9047 InputConnection ic = new EditableInputConnection(this); 9048 outAttrs.initialSelStart = getSelectionStart(); 9049 outAttrs.initialSelEnd = getSelectionEnd(); 9050 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 9051 outAttrs.setInitialSurroundingText(mText); 9052 outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); 9053 return ic; 9054 } 9055 } 9056 return null; 9057 } 9058 9059 /** 9060 * If this TextView contains editable content, extract a portion of it 9061 * based on the information in <var>request</var> in to <var>outText</var>. 9062 * @return Returns true if the text was successfully extracted, else false. 9063 */ extractText(ExtractedTextRequest request, ExtractedText outText)9064 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 9065 createEditorIfNeeded(); 9066 return mEditor.extractText(request, outText); 9067 } 9068 9069 /** 9070 * This is used to remove all style-impacting spans from text before new 9071 * extracted text is being replaced into it, so that we don't have any 9072 * lingering spans applied during the replace. 9073 */ removeParcelableSpans(Spannable spannable, int start, int end)9074 static void removeParcelableSpans(Spannable spannable, int start, int end) { 9075 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 9076 int i = spans.length; 9077 while (i > 0) { 9078 i--; 9079 spannable.removeSpan(spans[i]); 9080 } 9081 } 9082 9083 /** 9084 * Apply to this text view the given extracted text, as previously 9085 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 9086 */ setExtractedText(ExtractedText text)9087 public void setExtractedText(ExtractedText text) { 9088 Editable content = getEditableText(); 9089 if (text.text != null) { 9090 if (content == null) { 9091 setText(text.text, TextView.BufferType.EDITABLE); 9092 } else { 9093 int start = 0; 9094 int end = content.length(); 9095 9096 if (text.partialStartOffset >= 0) { 9097 final int N = content.length(); 9098 start = text.partialStartOffset; 9099 if (start > N) start = N; 9100 end = text.partialEndOffset; 9101 if (end > N) end = N; 9102 } 9103 9104 removeParcelableSpans(content, start, end); 9105 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 9106 if (text.text instanceof Spanned) { 9107 // OK to copy spans only. 9108 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 9109 Object.class, content, start); 9110 } 9111 } else { 9112 content.replace(start, end, text.text); 9113 } 9114 } 9115 } 9116 9117 // Now set the selection position... make sure it is in range, to 9118 // avoid crashes. If this is a partial update, it is possible that 9119 // the underlying text may have changed, causing us problems here. 9120 // Also we just don't want to trust clients to do the right thing. 9121 Spannable sp = (Spannable) getText(); 9122 final int N = sp.length(); 9123 int start = text.selectionStart; 9124 if (start < 0) { 9125 start = 0; 9126 } else if (start > N) { 9127 start = N; 9128 } 9129 int end = text.selectionEnd; 9130 if (end < 0) { 9131 end = 0; 9132 } else if (end > N) { 9133 end = N; 9134 } 9135 Selection.setSelection(sp, start, end); 9136 9137 // Finally, update the selection mode. 9138 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 9139 MetaKeyKeyListener.startSelecting(this, sp); 9140 } else { 9141 MetaKeyKeyListener.stopSelecting(this, sp); 9142 } 9143 9144 setHintInternal(text.hint); 9145 } 9146 9147 /** 9148 * @hide 9149 */ setExtracting(ExtractedTextRequest req)9150 public void setExtracting(ExtractedTextRequest req) { 9151 if (mEditor.mInputMethodState != null) { 9152 mEditor.mInputMethodState.mExtractedTextRequest = req; 9153 } 9154 // This would stop a possible selection mode, but no such mode is started in case 9155 // extracted mode will start. Some text is selected though, and will trigger an action mode 9156 // in the extracted view. 9157 mEditor.hideCursorAndSpanControllers(); 9158 stopTextActionMode(); 9159 if (mEditor.mSelectionModifierCursorController != null) { 9160 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 9161 } 9162 } 9163 9164 /** 9165 * Called by the framework in response to a text completion from 9166 * the current input method, provided by it calling 9167 * {@link InputConnection#commitCompletion 9168 * InputConnection.commitCompletion()}. The default implementation does 9169 * nothing; text views that are supporting auto-completion should override 9170 * this to do their desired behavior. 9171 * 9172 * @param text The auto complete text the user has selected. 9173 */ onCommitCompletion(CompletionInfo text)9174 public void onCommitCompletion(CompletionInfo text) { 9175 // intentionally empty 9176 } 9177 9178 /** 9179 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 9180 * dictionary) from the current input method, provided by it calling 9181 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 9182 * The default implementation flashes the background of the corrected word to provide 9183 * feedback to the user. 9184 * 9185 * @param info The auto correct info about the text that was corrected. 9186 */ onCommitCorrection(CorrectionInfo info)9187 public void onCommitCorrection(CorrectionInfo info) { 9188 if (mEditor != null) mEditor.onCommitCorrection(info); 9189 } 9190 beginBatchEdit()9191 public void beginBatchEdit() { 9192 if (mEditor != null) mEditor.beginBatchEdit(); 9193 } 9194 endBatchEdit()9195 public void endBatchEdit() { 9196 if (mEditor != null) mEditor.endBatchEdit(); 9197 } 9198 9199 /** 9200 * Called by the framework in response to a request to begin a batch 9201 * of edit operations through a call to link {@link #beginBatchEdit()}. 9202 */ onBeginBatchEdit()9203 public void onBeginBatchEdit() { 9204 // intentionally empty 9205 } 9206 9207 /** 9208 * Called by the framework in response to a request to end a batch 9209 * of edit operations through a call to link {@link #endBatchEdit}. 9210 */ onEndBatchEdit()9211 public void onEndBatchEdit() { 9212 // intentionally empty 9213 } 9214 9215 /** @hide */ onPerformSpellCheck()9216 public void onPerformSpellCheck() { 9217 if (mEditor != null && mEditor.mSpellChecker != null) { 9218 mEditor.mSpellChecker.onPerformSpellCheck(); 9219 } 9220 } 9221 9222 /** 9223 * Called by the framework in response to a private command from the 9224 * current method, provided by it calling 9225 * {@link InputConnection#performPrivateCommand 9226 * InputConnection.performPrivateCommand()}. 9227 * 9228 * @param action The action name of the command. 9229 * @param data Any additional data for the command. This may be null. 9230 * @return Return true if you handled the command, else false. 9231 */ onPrivateIMECommand(String action, Bundle data)9232 public boolean onPrivateIMECommand(String action, Bundle data) { 9233 return false; 9234 } 9235 9236 /** @hide */ 9237 @VisibleForTesting 9238 @UnsupportedAppUsage nullLayouts()9239 public void nullLayouts() { 9240 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 9241 mSavedLayout = (BoringLayout) mLayout; 9242 } 9243 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 9244 mSavedHintLayout = (BoringLayout) mHintLayout; 9245 } 9246 9247 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 9248 9249 mBoring = mHintBoring = null; 9250 9251 // Since it depends on the value of mLayout 9252 if (mEditor != null) mEditor.prepareCursorControllers(); 9253 } 9254 9255 /** 9256 * Make a new Layout based on the already-measured size of the view, 9257 * on the assumption that it was measured correctly at some point. 9258 */ 9259 @UnsupportedAppUsage assumeLayout()9260 private void assumeLayout() { 9261 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9262 9263 if (width < 1) { 9264 width = 0; 9265 } 9266 9267 int physicalWidth = width; 9268 9269 if (mHorizontallyScrolling) { 9270 width = VERY_WIDE; 9271 } 9272 9273 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 9274 physicalWidth, false); 9275 } 9276 9277 @UnsupportedAppUsage getLayoutAlignment()9278 private Layout.Alignment getLayoutAlignment() { 9279 Layout.Alignment alignment; 9280 switch (getTextAlignment()) { 9281 case TEXT_ALIGNMENT_GRAVITY: 9282 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 9283 case Gravity.START: 9284 alignment = Layout.Alignment.ALIGN_NORMAL; 9285 break; 9286 case Gravity.END: 9287 alignment = Layout.Alignment.ALIGN_OPPOSITE; 9288 break; 9289 case Gravity.LEFT: 9290 alignment = Layout.Alignment.ALIGN_LEFT; 9291 break; 9292 case Gravity.RIGHT: 9293 alignment = Layout.Alignment.ALIGN_RIGHT; 9294 break; 9295 case Gravity.CENTER_HORIZONTAL: 9296 alignment = Layout.Alignment.ALIGN_CENTER; 9297 break; 9298 default: 9299 alignment = Layout.Alignment.ALIGN_NORMAL; 9300 break; 9301 } 9302 break; 9303 case TEXT_ALIGNMENT_TEXT_START: 9304 alignment = Layout.Alignment.ALIGN_NORMAL; 9305 break; 9306 case TEXT_ALIGNMENT_TEXT_END: 9307 alignment = Layout.Alignment.ALIGN_OPPOSITE; 9308 break; 9309 case TEXT_ALIGNMENT_CENTER: 9310 alignment = Layout.Alignment.ALIGN_CENTER; 9311 break; 9312 case TEXT_ALIGNMENT_VIEW_START: 9313 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 9314 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9315 break; 9316 case TEXT_ALIGNMENT_VIEW_END: 9317 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 9318 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9319 break; 9320 case TEXT_ALIGNMENT_INHERIT: 9321 // This should never happen as we have already resolved the text alignment 9322 // but better safe than sorry so we just fall through 9323 default: 9324 alignment = Layout.Alignment.ALIGN_NORMAL; 9325 break; 9326 } 9327 return alignment; 9328 } 9329 9330 /** 9331 * The width passed in is now the desired layout width, 9332 * not the full view width with padding. 9333 * {@hide} 9334 */ 9335 @VisibleForTesting 9336 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)9337 public void makeNewLayout(int wantWidth, int hintWidth, 9338 BoringLayout.Metrics boring, 9339 BoringLayout.Metrics hintBoring, 9340 int ellipsisWidth, boolean bringIntoView) { 9341 stopMarquee(); 9342 9343 // Update "old" cached values 9344 mOldMaximum = mMaximum; 9345 mOldMaxMode = mMaxMode; 9346 9347 mHighlightPathBogus = true; 9348 9349 if (wantWidth < 0) { 9350 wantWidth = 0; 9351 } 9352 if (hintWidth < 0) { 9353 hintWidth = 0; 9354 } 9355 9356 Layout.Alignment alignment = getLayoutAlignment(); 9357 final boolean testDirChange = mSingleLine && mLayout != null 9358 && (alignment == Layout.Alignment.ALIGN_NORMAL 9359 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 9360 int oldDir = 0; 9361 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 9362 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 9363 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 9364 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 9365 TruncateAt effectiveEllipsize = mEllipsize; 9366 if (mEllipsize == TruncateAt.MARQUEE 9367 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 9368 effectiveEllipsize = TruncateAt.END_SMALL; 9369 } 9370 9371 if (mTextDir == null) { 9372 mTextDir = getTextDirectionHeuristic(); 9373 } 9374 9375 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 9376 effectiveEllipsize, effectiveEllipsize == mEllipsize); 9377 if (switchEllipsize) { 9378 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 9379 ? TruncateAt.END : TruncateAt.MARQUEE; 9380 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 9381 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 9382 } 9383 9384 shouldEllipsize = mEllipsize != null; 9385 mHintLayout = null; 9386 9387 if (mHint != null) { 9388 if (shouldEllipsize) hintWidth = wantWidth; 9389 9390 if (hintBoring == UNKNOWN_BORING) { 9391 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9392 isFallbackLineSpacingForBoringLayout(), mHintBoring); 9393 if (hintBoring != null) { 9394 mHintBoring = hintBoring; 9395 } 9396 } 9397 9398 if (hintBoring != null) { 9399 if (hintBoring.width <= hintWidth 9400 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9401 if (mSavedHintLayout != null) { 9402 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9403 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9404 hintBoring, mIncludePad); 9405 } else { 9406 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9407 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9408 hintBoring, mIncludePad); 9409 } 9410 9411 mSavedHintLayout = (BoringLayout) mHintLayout; 9412 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9413 if (mSavedHintLayout != null) { 9414 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9415 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9416 hintBoring, mIncludePad, mEllipsize, 9417 ellipsisWidth); 9418 } else { 9419 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9420 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9421 hintBoring, mIncludePad, mEllipsize, 9422 ellipsisWidth); 9423 } 9424 } 9425 } 9426 // TODO: code duplication with makeSingleLayout() 9427 if (mHintLayout == null) { 9428 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9429 mHint.length(), mTextPaint, hintWidth) 9430 .setAlignment(alignment) 9431 .setTextDirection(mTextDir) 9432 .setLineSpacing(mSpacingAdd, mSpacingMult) 9433 .setIncludePad(mIncludePad) 9434 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 9435 .setBreakStrategy(mBreakStrategy) 9436 .setHyphenationFrequency(mHyphenationFrequency) 9437 .setJustificationMode(mJustificationMode) 9438 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9439 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 9440 mLineBreakStyle, mLineBreakWordStyle)); 9441 if (shouldEllipsize) { 9442 builder.setEllipsize(mEllipsize) 9443 .setEllipsizedWidth(ellipsisWidth); 9444 } 9445 mHintLayout = builder.build(); 9446 } 9447 } 9448 9449 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9450 registerForPreDraw(); 9451 } 9452 9453 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9454 if (!compressText(ellipsisWidth)) { 9455 final int height = mLayoutParams.height; 9456 // If the size of the view does not depend on the size of the text, try to 9457 // start the marquee immediately 9458 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9459 startMarquee(); 9460 } else { 9461 // Defer the start of the marquee until we know our width (see setFrame()) 9462 mRestartMarquee = true; 9463 } 9464 } 9465 } 9466 9467 // CursorControllers need a non-null mLayout 9468 if (mEditor != null) mEditor.prepareCursorControllers(); 9469 } 9470 9471 /** 9472 * Returns true if DynamicLayout is required 9473 * 9474 * @hide 9475 */ 9476 @VisibleForTesting useDynamicLayout()9477 public boolean useDynamicLayout() { 9478 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9479 } 9480 9481 /** 9482 * @hide 9483 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9484 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9485 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9486 boolean useSaved) { 9487 Layout result = null; 9488 if (useDynamicLayout()) { 9489 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9490 wantWidth) 9491 .setDisplayText(mTransformed) 9492 .setAlignment(alignment) 9493 .setTextDirection(mTextDir) 9494 .setLineSpacing(mSpacingAdd, mSpacingMult) 9495 .setIncludePad(mIncludePad) 9496 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 9497 .setBreakStrategy(mBreakStrategy) 9498 .setHyphenationFrequency(mHyphenationFrequency) 9499 .setJustificationMode(mJustificationMode) 9500 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9501 .setEllipsizedWidth(ellipsisWidth); 9502 result = builder.build(); 9503 } else { 9504 if (boring == UNKNOWN_BORING) { 9505 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 9506 isFallbackLineSpacingForBoringLayout(), mBoring); 9507 if (boring != null) { 9508 mBoring = boring; 9509 } 9510 } 9511 9512 if (boring != null) { 9513 if (boring.width <= wantWidth 9514 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9515 if (useSaved && mSavedLayout != null) { 9516 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9517 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9518 boring, mIncludePad); 9519 } else { 9520 result = BoringLayout.make(mTransformed, mTextPaint, 9521 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9522 boring, mIncludePad); 9523 } 9524 9525 if (useSaved) { 9526 mSavedLayout = (BoringLayout) result; 9527 } 9528 } else if (shouldEllipsize && boring.width <= wantWidth) { 9529 if (useSaved && mSavedLayout != null) { 9530 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9531 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9532 boring, mIncludePad, effectiveEllipsize, 9533 ellipsisWidth); 9534 } else { 9535 result = BoringLayout.make(mTransformed, mTextPaint, 9536 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9537 boring, mIncludePad, effectiveEllipsize, 9538 ellipsisWidth); 9539 } 9540 } 9541 } 9542 } 9543 if (result == null) { 9544 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9545 0, mTransformed.length(), mTextPaint, wantWidth) 9546 .setAlignment(alignment) 9547 .setTextDirection(mTextDir) 9548 .setLineSpacing(mSpacingAdd, mSpacingMult) 9549 .setIncludePad(mIncludePad) 9550 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 9551 .setBreakStrategy(mBreakStrategy) 9552 .setHyphenationFrequency(mHyphenationFrequency) 9553 .setJustificationMode(mJustificationMode) 9554 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9555 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 9556 mLineBreakStyle, mLineBreakWordStyle)); 9557 if (shouldEllipsize) { 9558 builder.setEllipsize(effectiveEllipsize) 9559 .setEllipsizedWidth(ellipsisWidth); 9560 } 9561 result = builder.build(); 9562 } 9563 return result; 9564 } 9565 9566 @UnsupportedAppUsage compressText(float width)9567 private boolean compressText(float width) { 9568 if (isHardwareAccelerated()) return false; 9569 9570 // Only compress the text if it hasn't been compressed by the previous pass 9571 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9572 && mTextPaint.getTextScaleX() == 1.0f) { 9573 final float textWidth = mLayout.getLineWidth(0); 9574 final float overflow = (textWidth + 1.0f - width) / width; 9575 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9576 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9577 post(new Runnable() { 9578 public void run() { 9579 requestLayout(); 9580 } 9581 }); 9582 return true; 9583 } 9584 } 9585 9586 return false; 9587 } 9588 desired(Layout layout)9589 private static int desired(Layout layout) { 9590 int n = layout.getLineCount(); 9591 CharSequence text = layout.getText(); 9592 float max = 0; 9593 9594 // if any line was wrapped, we can't use it. 9595 // but it's ok for the last line not to have a newline 9596 9597 for (int i = 0; i < n - 1; i++) { 9598 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9599 return -1; 9600 } 9601 } 9602 9603 for (int i = 0; i < n; i++) { 9604 max = Math.max(max, layout.getLineMax(i)); 9605 } 9606 9607 return (int) Math.ceil(max); 9608 } 9609 9610 /** 9611 * Set whether the TextView includes extra top and bottom padding to make 9612 * room for accents that go above the normal ascent and descent. 9613 * The default is true. 9614 * 9615 * @see #getIncludeFontPadding() 9616 * 9617 * @attr ref android.R.styleable#TextView_includeFontPadding 9618 */ setIncludeFontPadding(boolean includepad)9619 public void setIncludeFontPadding(boolean includepad) { 9620 if (mIncludePad != includepad) { 9621 mIncludePad = includepad; 9622 9623 if (mLayout != null) { 9624 nullLayouts(); 9625 requestLayout(); 9626 invalidate(); 9627 } 9628 } 9629 } 9630 9631 /** 9632 * Gets whether the TextView includes extra top and bottom padding to make 9633 * room for accents that go above the normal ascent and descent. 9634 * 9635 * @see #setIncludeFontPadding(boolean) 9636 * 9637 * @attr ref android.R.styleable#TextView_includeFontPadding 9638 */ 9639 @InspectableProperty getIncludeFontPadding()9640 public boolean getIncludeFontPadding() { 9641 return mIncludePad; 9642 } 9643 9644 /** @hide */ 9645 @VisibleForTesting 9646 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9647 9648 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9649 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9650 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9651 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9652 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9653 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9654 9655 int width; 9656 int height; 9657 9658 BoringLayout.Metrics boring = UNKNOWN_BORING; 9659 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9660 9661 if (mTextDir == null) { 9662 mTextDir = getTextDirectionHeuristic(); 9663 } 9664 9665 int des = -1; 9666 boolean fromexisting = false; 9667 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9668 ? (float) widthSize : Float.MAX_VALUE; 9669 9670 if (widthMode == MeasureSpec.EXACTLY) { 9671 // Parent has told us how big to be. So be it. 9672 width = widthSize; 9673 } else { 9674 if (mLayout != null && mEllipsize == null) { 9675 des = desired(mLayout); 9676 } 9677 9678 if (des < 0) { 9679 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 9680 isFallbackLineSpacingForBoringLayout(), mBoring); 9681 if (boring != null) { 9682 mBoring = boring; 9683 } 9684 } else { 9685 fromexisting = true; 9686 } 9687 9688 if (boring == null || boring == UNKNOWN_BORING) { 9689 if (des < 0) { 9690 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9691 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9692 } 9693 width = des; 9694 } else { 9695 width = boring.width; 9696 } 9697 9698 final Drawables dr = mDrawables; 9699 if (dr != null) { 9700 width = Math.max(width, dr.mDrawableWidthTop); 9701 width = Math.max(width, dr.mDrawableWidthBottom); 9702 } 9703 9704 if (mHint != null) { 9705 int hintDes = -1; 9706 int hintWidth; 9707 9708 if (mHintLayout != null && mEllipsize == null) { 9709 hintDes = desired(mHintLayout); 9710 } 9711 9712 if (hintDes < 0) { 9713 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9714 isFallbackLineSpacingForBoringLayout(), mHintBoring); 9715 if (hintBoring != null) { 9716 mHintBoring = hintBoring; 9717 } 9718 } 9719 9720 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9721 if (hintDes < 0) { 9722 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9723 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9724 } 9725 hintWidth = hintDes; 9726 } else { 9727 hintWidth = hintBoring.width; 9728 } 9729 9730 if (hintWidth > width) { 9731 width = hintWidth; 9732 } 9733 } 9734 9735 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9736 9737 if (mMaxWidthMode == EMS) { 9738 width = Math.min(width, mMaxWidth * getLineHeight()); 9739 } else { 9740 width = Math.min(width, mMaxWidth); 9741 } 9742 9743 if (mMinWidthMode == EMS) { 9744 width = Math.max(width, mMinWidth * getLineHeight()); 9745 } else { 9746 width = Math.max(width, mMinWidth); 9747 } 9748 9749 // Check against our minimum width 9750 width = Math.max(width, getSuggestedMinimumWidth()); 9751 9752 if (widthMode == MeasureSpec.AT_MOST) { 9753 width = Math.min(widthSize, width); 9754 } 9755 } 9756 9757 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9758 int unpaddedWidth = want; 9759 9760 if (mHorizontallyScrolling) want = VERY_WIDE; 9761 9762 int hintWant = want; 9763 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9764 9765 if (mLayout == null) { 9766 makeNewLayout(want, hintWant, boring, hintBoring, 9767 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9768 } else { 9769 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9770 || (mLayout.getEllipsizedWidth() 9771 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9772 9773 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9774 && (want > mLayout.getWidth()) 9775 && (mLayout instanceof BoringLayout 9776 || (fromexisting && des >= 0 && des <= want)); 9777 9778 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9779 9780 if (layoutChanged || maximumChanged) { 9781 if (!maximumChanged && widthChanged) { 9782 mLayout.increaseWidthTo(want); 9783 } else { 9784 makeNewLayout(want, hintWant, boring, hintBoring, 9785 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9786 } 9787 } else { 9788 // Nothing has changed 9789 } 9790 } 9791 9792 if (heightMode == MeasureSpec.EXACTLY) { 9793 // Parent has told us how big to be. So be it. 9794 height = heightSize; 9795 mDesiredHeightAtMeasure = -1; 9796 } else { 9797 int desired = getDesiredHeight(); 9798 9799 height = desired; 9800 mDesiredHeightAtMeasure = desired; 9801 9802 if (heightMode == MeasureSpec.AT_MOST) { 9803 height = Math.min(desired, heightSize); 9804 } 9805 } 9806 9807 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9808 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9809 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9810 } 9811 9812 /* 9813 * We didn't let makeNewLayout() register to bring the cursor into view, 9814 * so do it here if there is any possibility that it is needed. 9815 */ 9816 if (mMovement != null 9817 || mLayout.getWidth() > unpaddedWidth 9818 || mLayout.getHeight() > unpaddedHeight) { 9819 registerForPreDraw(); 9820 } else { 9821 scrollTo(0, 0); 9822 } 9823 9824 setMeasuredDimension(width, height); 9825 } 9826 9827 /** 9828 * Automatically computes and sets the text size. 9829 */ autoSizeText()9830 private void autoSizeText() { 9831 if (!isAutoSizeEnabled()) { 9832 return; 9833 } 9834 9835 if (mNeedsAutoSizeText) { 9836 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9837 return; 9838 } 9839 9840 final int availableWidth = mHorizontallyScrolling 9841 ? VERY_WIDE 9842 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9843 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9844 - getExtendedPaddingTop(); 9845 9846 if (availableWidth <= 0 || availableHeight <= 0) { 9847 return; 9848 } 9849 9850 synchronized (TEMP_RECTF) { 9851 TEMP_RECTF.setEmpty(); 9852 TEMP_RECTF.right = availableWidth; 9853 TEMP_RECTF.bottom = availableHeight; 9854 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9855 9856 if (optimalTextSize != getTextSize()) { 9857 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9858 false /* shouldRequestLayout */); 9859 9860 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9861 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9862 false /* bringIntoView */); 9863 } 9864 } 9865 } 9866 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9867 // after the next layout pass should set this to false. 9868 mNeedsAutoSizeText = true; 9869 } 9870 9871 /** 9872 * Performs a binary search to find the largest text size that will still fit within the size 9873 * available to this view. 9874 */ findLargestTextSizeWhichFits(RectF availableSpace)9875 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9876 final int sizesCount = mAutoSizeTextSizesInPx.length; 9877 if (sizesCount == 0) { 9878 throw new IllegalStateException("No available text sizes to choose from."); 9879 } 9880 9881 int bestSizeIndex = 0; 9882 int lowIndex = bestSizeIndex + 1; 9883 int highIndex = sizesCount - 1; 9884 int sizeToTryIndex; 9885 while (lowIndex <= highIndex) { 9886 sizeToTryIndex = (lowIndex + highIndex) / 2; 9887 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9888 bestSizeIndex = lowIndex; 9889 lowIndex = sizeToTryIndex + 1; 9890 } else { 9891 highIndex = sizeToTryIndex - 1; 9892 bestSizeIndex = highIndex; 9893 } 9894 } 9895 9896 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9897 } 9898 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9899 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9900 final CharSequence text = mTransformed != null 9901 ? mTransformed 9902 : getText(); 9903 final int maxLines = getMaxLines(); 9904 if (mTempTextPaint == null) { 9905 mTempTextPaint = new TextPaint(); 9906 } else { 9907 mTempTextPaint.reset(); 9908 } 9909 mTempTextPaint.set(getPaint()); 9910 mTempTextPaint.setTextSize(suggestedSizeInPx); 9911 9912 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9913 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9914 9915 layoutBuilder.setAlignment(getLayoutAlignment()) 9916 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9917 .setIncludePad(getIncludeFontPadding()) 9918 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 9919 .setBreakStrategy(getBreakStrategy()) 9920 .setHyphenationFrequency(getHyphenationFrequency()) 9921 .setJustificationMode(getJustificationMode()) 9922 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9923 .setTextDirection(getTextDirectionHeuristic()) 9924 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 9925 mLineBreakStyle, mLineBreakWordStyle)); 9926 9927 final StaticLayout layout = layoutBuilder.build(); 9928 9929 // Lines overflow. 9930 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9931 return false; 9932 } 9933 9934 // Height overflow. 9935 if (layout.getHeight() > availableSpace.bottom) { 9936 return false; 9937 } 9938 9939 return true; 9940 } 9941 getDesiredHeight()9942 private int getDesiredHeight() { 9943 return Math.max( 9944 getDesiredHeight(mLayout, true), 9945 getDesiredHeight(mHintLayout, mEllipsize != null)); 9946 } 9947 getDesiredHeight(Layout layout, boolean cap)9948 private int getDesiredHeight(Layout layout, boolean cap) { 9949 if (layout == null) { 9950 return 0; 9951 } 9952 9953 /* 9954 * Don't cap the hint to a certain number of lines. 9955 * (Do cap it, though, if we have a maximum pixel height.) 9956 */ 9957 int desired = layout.getHeight(cap); 9958 9959 final Drawables dr = mDrawables; 9960 if (dr != null) { 9961 desired = Math.max(desired, dr.mDrawableHeightLeft); 9962 desired = Math.max(desired, dr.mDrawableHeightRight); 9963 } 9964 9965 int linecount = layout.getLineCount(); 9966 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9967 desired += padding; 9968 9969 if (mMaxMode != LINES) { 9970 desired = Math.min(desired, mMaximum); 9971 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9972 || layout instanceof BoringLayout)) { 9973 desired = layout.getLineTop(mMaximum); 9974 9975 if (dr != null) { 9976 desired = Math.max(desired, dr.mDrawableHeightLeft); 9977 desired = Math.max(desired, dr.mDrawableHeightRight); 9978 } 9979 9980 desired += padding; 9981 linecount = mMaximum; 9982 } 9983 9984 if (mMinMode == LINES) { 9985 if (linecount < mMinimum) { 9986 desired += getLineHeight() * (mMinimum - linecount); 9987 } 9988 } else { 9989 desired = Math.max(desired, mMinimum); 9990 } 9991 9992 // Check against our minimum height 9993 desired = Math.max(desired, getSuggestedMinimumHeight()); 9994 9995 return desired; 9996 } 9997 9998 /** 9999 * Check whether a change to the existing text layout requires a 10000 * new view layout. 10001 */ checkForResize()10002 private void checkForResize() { 10003 boolean sizeChanged = false; 10004 10005 if (mLayout != null) { 10006 // Check if our width changed 10007 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 10008 sizeChanged = true; 10009 invalidate(); 10010 } 10011 10012 // Check if our height changed 10013 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 10014 int desiredHeight = getDesiredHeight(); 10015 10016 if (desiredHeight != this.getHeight()) { 10017 sizeChanged = true; 10018 } 10019 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 10020 if (mDesiredHeightAtMeasure >= 0) { 10021 int desiredHeight = getDesiredHeight(); 10022 10023 if (desiredHeight != mDesiredHeightAtMeasure) { 10024 sizeChanged = true; 10025 } 10026 } 10027 } 10028 } 10029 10030 if (sizeChanged) { 10031 requestLayout(); 10032 // caller will have already invalidated 10033 } 10034 } 10035 10036 /** 10037 * Check whether entirely new text requires a new view layout 10038 * or merely a new text layout. 10039 */ 10040 @UnsupportedAppUsage checkForRelayout()10041 private void checkForRelayout() { 10042 // If we have a fixed width, we can just swap in a new text layout 10043 // if the text height stays the same or if the view height is fixed. 10044 10045 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 10046 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 10047 && (mHint == null || mHintLayout != null) 10048 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 10049 // Static width, so try making a new text layout. 10050 10051 int oldht = mLayout.getHeight(); 10052 int want = mLayout.getWidth(); 10053 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 10054 10055 /* 10056 * No need to bring the text into view, since the size is not 10057 * changing (unless we do the requestLayout(), in which case it 10058 * will happen at measure). 10059 */ 10060 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 10061 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 10062 false); 10063 10064 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 10065 // In a fixed-height view, so use our new text layout. 10066 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 10067 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 10068 autoSizeText(); 10069 invalidate(); 10070 return; 10071 } 10072 10073 // Dynamic height, but height has stayed the same, 10074 // so use our new text layout. 10075 if (mLayout.getHeight() == oldht 10076 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 10077 autoSizeText(); 10078 invalidate(); 10079 return; 10080 } 10081 } 10082 10083 // We lose: the height has changed and we have a dynamic height. 10084 // Request a new view layout using our new text layout. 10085 requestLayout(); 10086 invalidate(); 10087 } else { 10088 // Dynamic width, so we have no choice but to request a new 10089 // view layout with a new text layout. 10090 nullLayouts(); 10091 requestLayout(); 10092 invalidate(); 10093 } 10094 } 10095 10096 @Override onLayout(boolean changed, int left, int top, int right, int bottom)10097 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 10098 super.onLayout(changed, left, top, right, bottom); 10099 if (mDeferScroll >= 0) { 10100 int curs = mDeferScroll; 10101 mDeferScroll = -1; 10102 bringPointIntoView(Math.min(curs, mText.length())); 10103 } 10104 // Call auto-size after the width and height have been calculated. 10105 autoSizeText(); 10106 } 10107 isShowingHint()10108 private boolean isShowingHint() { 10109 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 10110 } 10111 10112 /** 10113 * Returns true if anything changed. 10114 */ 10115 @UnsupportedAppUsage bringTextIntoView()10116 private boolean bringTextIntoView() { 10117 Layout layout = isShowingHint() ? mHintLayout : mLayout; 10118 int line = 0; 10119 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10120 line = layout.getLineCount() - 1; 10121 } 10122 10123 Layout.Alignment a = layout.getParagraphAlignment(line); 10124 int dir = layout.getParagraphDirection(line); 10125 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10126 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10127 int ht = layout.getHeight(); 10128 10129 int scrollx, scrolly; 10130 10131 // Convert to left, center, or right alignment. 10132 if (a == Layout.Alignment.ALIGN_NORMAL) { 10133 a = dir == Layout.DIR_LEFT_TO_RIGHT 10134 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 10135 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 10136 a = dir == Layout.DIR_LEFT_TO_RIGHT 10137 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 10138 } 10139 10140 if (a == Layout.Alignment.ALIGN_CENTER) { 10141 /* 10142 * Keep centered if possible, or, if it is too wide to fit, 10143 * keep leading edge in view. 10144 */ 10145 10146 int left = (int) Math.floor(layout.getLineLeft(line)); 10147 int right = (int) Math.ceil(layout.getLineRight(line)); 10148 10149 if (right - left < hspace) { 10150 scrollx = (right + left) / 2 - hspace / 2; 10151 } else { 10152 if (dir < 0) { 10153 scrollx = right - hspace; 10154 } else { 10155 scrollx = left; 10156 } 10157 } 10158 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 10159 int right = (int) Math.ceil(layout.getLineRight(line)); 10160 scrollx = right - hspace; 10161 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 10162 scrollx = (int) Math.floor(layout.getLineLeft(line)); 10163 } 10164 10165 if (ht < vspace) { 10166 scrolly = 0; 10167 } else { 10168 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10169 scrolly = ht - vspace; 10170 } else { 10171 scrolly = 0; 10172 } 10173 } 10174 10175 if (scrollx != mScrollX || scrolly != mScrollY) { 10176 scrollTo(scrollx, scrolly); 10177 return true; 10178 } else { 10179 return false; 10180 } 10181 } 10182 10183 /** 10184 * Move the point, specified by the offset, into the view if it is needed. 10185 * This has to be called after layout. Returns true if anything changed. 10186 */ bringPointIntoView(int offset)10187 public boolean bringPointIntoView(int offset) { 10188 if (isLayoutRequested()) { 10189 mDeferScroll = offset; 10190 return false; 10191 } 10192 boolean changed = false; 10193 10194 Layout layout = isShowingHint() ? mHintLayout : mLayout; 10195 10196 if (layout == null) return changed; 10197 10198 int line = layout.getLineForOffset(offset); 10199 10200 int grav; 10201 10202 switch (layout.getParagraphAlignment(line)) { 10203 case ALIGN_LEFT: 10204 grav = 1; 10205 break; 10206 case ALIGN_RIGHT: 10207 grav = -1; 10208 break; 10209 case ALIGN_NORMAL: 10210 grav = layout.getParagraphDirection(line); 10211 break; 10212 case ALIGN_OPPOSITE: 10213 grav = -layout.getParagraphDirection(line); 10214 break; 10215 case ALIGN_CENTER: 10216 default: 10217 grav = 0; 10218 break; 10219 } 10220 10221 // We only want to clamp the cursor to fit within the layout width 10222 // in left-to-right modes, because in a right to left alignment, 10223 // we want to scroll to keep the line-right on the screen, as other 10224 // lines are likely to have text flush with the right margin, which 10225 // we want to keep visible. 10226 // A better long-term solution would probably be to measure both 10227 // the full line and a blank-trimmed version, and, for example, use 10228 // the latter measurement for centering and right alignment, but for 10229 // the time being we only implement the cursor clamping in left to 10230 // right where it is most likely to be annoying. 10231 final boolean clamped = grav > 0; 10232 // FIXME: Is it okay to truncate this, or should we round? 10233 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 10234 final int top = layout.getLineTop(line); 10235 final int bottom = layout.getLineTop(line + 1); 10236 10237 int left = (int) Math.floor(layout.getLineLeft(line)); 10238 int right = (int) Math.ceil(layout.getLineRight(line)); 10239 int ht = layout.getHeight(); 10240 10241 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10242 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10243 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 10244 // If cursor has been clamped, make sure we don't scroll. 10245 right = Math.max(x, left + hspace); 10246 } 10247 10248 int hslack = (bottom - top) / 2; 10249 int vslack = hslack; 10250 10251 if (vslack > vspace / 4) { 10252 vslack = vspace / 4; 10253 } 10254 if (hslack > hspace / 4) { 10255 hslack = hspace / 4; 10256 } 10257 10258 int hs = mScrollX; 10259 int vs = mScrollY; 10260 10261 if (top - vs < vslack) { 10262 vs = top - vslack; 10263 } 10264 if (bottom - vs > vspace - vslack) { 10265 vs = bottom - (vspace - vslack); 10266 } 10267 if (ht - vs < vspace) { 10268 vs = ht - vspace; 10269 } 10270 if (0 - vs > 0) { 10271 vs = 0; 10272 } 10273 10274 if (grav != 0) { 10275 if (x - hs < hslack) { 10276 hs = x - hslack; 10277 } 10278 if (x - hs > hspace - hslack) { 10279 hs = x - (hspace - hslack); 10280 } 10281 } 10282 10283 if (grav < 0) { 10284 if (left - hs > 0) { 10285 hs = left; 10286 } 10287 if (right - hs < hspace) { 10288 hs = right - hspace; 10289 } 10290 } else if (grav > 0) { 10291 if (right - hs < hspace) { 10292 hs = right - hspace; 10293 } 10294 if (left - hs > 0) { 10295 hs = left; 10296 } 10297 } else /* grav == 0 */ { 10298 if (right - left <= hspace) { 10299 /* 10300 * If the entire text fits, center it exactly. 10301 */ 10302 hs = left - (hspace - (right - left)) / 2; 10303 } else if (x > right - hslack) { 10304 /* 10305 * If we are near the right edge, keep the right edge 10306 * at the edge of the view. 10307 */ 10308 hs = right - hspace; 10309 } else if (x < left + hslack) { 10310 /* 10311 * If we are near the left edge, keep the left edge 10312 * at the edge of the view. 10313 */ 10314 hs = left; 10315 } else if (left > hs) { 10316 /* 10317 * Is there whitespace visible at the left? Fix it if so. 10318 */ 10319 hs = left; 10320 } else if (right < hs + hspace) { 10321 /* 10322 * Is there whitespace visible at the right? Fix it if so. 10323 */ 10324 hs = right - hspace; 10325 } else { 10326 /* 10327 * Otherwise, float as needed. 10328 */ 10329 if (x - hs < hslack) { 10330 hs = x - hslack; 10331 } 10332 if (x - hs > hspace - hslack) { 10333 hs = x - (hspace - hslack); 10334 } 10335 } 10336 } 10337 10338 if (hs != mScrollX || vs != mScrollY) { 10339 if (mScroller == null) { 10340 scrollTo(hs, vs); 10341 } else { 10342 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 10343 int dx = hs - mScrollX; 10344 int dy = vs - mScrollY; 10345 10346 if (duration > ANIMATED_SCROLL_GAP) { 10347 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 10348 awakenScrollBars(mScroller.getDuration()); 10349 invalidate(); 10350 } else { 10351 if (!mScroller.isFinished()) { 10352 mScroller.abortAnimation(); 10353 } 10354 10355 scrollBy(dx, dy); 10356 } 10357 10358 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 10359 } 10360 10361 changed = true; 10362 } 10363 10364 if (isFocused()) { 10365 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 10366 // requestRectangleOnScreen() is in terms of content coordinates. 10367 10368 // The offsets here are to ensure the rectangle we are using is 10369 // within our view bounds, in case the cursor is on the far left 10370 // or right. If it isn't withing the bounds, then this request 10371 // will be ignored. 10372 if (mTempRect == null) mTempRect = new Rect(); 10373 mTempRect.set(x - 2, top, x + 2, bottom); 10374 getInterestingRect(mTempRect, line); 10375 mTempRect.offset(mScrollX, mScrollY); 10376 10377 if (requestRectangleOnScreen(mTempRect)) { 10378 changed = true; 10379 } 10380 } 10381 10382 return changed; 10383 } 10384 10385 /** 10386 * Move the cursor, if needed, so that it is at an offset that is visible 10387 * to the user. This will not move the cursor if it represents more than 10388 * one character (a selection range). This will only work if the 10389 * TextView contains spannable text; otherwise it will do nothing. 10390 * 10391 * @return True if the cursor was actually moved, false otherwise. 10392 */ moveCursorToVisibleOffset()10393 public boolean moveCursorToVisibleOffset() { 10394 if (!(mText instanceof Spannable)) { 10395 return false; 10396 } 10397 int start = getSelectionStart(); 10398 int end = getSelectionEnd(); 10399 if (start != end) { 10400 return false; 10401 } 10402 10403 // First: make sure the line is visible on screen: 10404 10405 int line = mLayout.getLineForOffset(start); 10406 10407 final int top = mLayout.getLineTop(line); 10408 final int bottom = mLayout.getLineTop(line + 1); 10409 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10410 int vslack = (bottom - top) / 2; 10411 if (vslack > vspace / 4) { 10412 vslack = vspace / 4; 10413 } 10414 final int vs = mScrollY; 10415 10416 if (top < (vs + vslack)) { 10417 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10418 } else if (bottom > (vspace + vs - vslack)) { 10419 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10420 } 10421 10422 // Next: make sure the character is visible on screen: 10423 10424 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10425 final int hs = mScrollX; 10426 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10427 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10428 10429 // line might contain bidirectional text 10430 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10431 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10432 10433 int newStart = start; 10434 if (newStart < lowChar) { 10435 newStart = lowChar; 10436 } else if (newStart > highChar) { 10437 newStart = highChar; 10438 } 10439 10440 if (newStart != start) { 10441 Selection.setSelection(mSpannable, newStart); 10442 return true; 10443 } 10444 10445 return false; 10446 } 10447 10448 @Override computeScroll()10449 public void computeScroll() { 10450 if (mScroller != null) { 10451 if (mScroller.computeScrollOffset()) { 10452 mScrollX = mScroller.getCurrX(); 10453 mScrollY = mScroller.getCurrY(); 10454 invalidateParentCaches(); 10455 postInvalidate(); // So we draw again 10456 } 10457 } 10458 } 10459 getInterestingRect(Rect r, int line)10460 private void getInterestingRect(Rect r, int line) { 10461 convertFromViewportToContentCoordinates(r); 10462 10463 // Rectangle can can be expanded on first and last line to take 10464 // padding into account. 10465 // TODO Take left/right padding into account too? 10466 if (line == 0) r.top -= getExtendedPaddingTop(); 10467 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10468 } 10469 convertFromViewportToContentCoordinates(Rect r)10470 private void convertFromViewportToContentCoordinates(Rect r) { 10471 final int horizontalOffset = viewportToContentHorizontalOffset(); 10472 r.left += horizontalOffset; 10473 r.right += horizontalOffset; 10474 10475 final int verticalOffset = viewportToContentVerticalOffset(); 10476 r.top += verticalOffset; 10477 r.bottom += verticalOffset; 10478 } 10479 viewportToContentHorizontalOffset()10480 int viewportToContentHorizontalOffset() { 10481 return getCompoundPaddingLeft() - mScrollX; 10482 } 10483 10484 @UnsupportedAppUsage viewportToContentVerticalOffset()10485 int viewportToContentVerticalOffset() { 10486 int offset = getExtendedPaddingTop() - mScrollY; 10487 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10488 offset += getVerticalOffset(false); 10489 } 10490 return offset; 10491 } 10492 10493 @Override debug(int depth)10494 public void debug(int depth) { 10495 super.debug(depth); 10496 10497 String output = debugIndent(depth); 10498 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10499 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10500 + "} "; 10501 10502 if (mText != null) { 10503 10504 output += "mText=\"" + mText + "\" "; 10505 if (mLayout != null) { 10506 output += "mLayout width=" + mLayout.getWidth() 10507 + " height=" + mLayout.getHeight(); 10508 } 10509 } else { 10510 output += "mText=NULL"; 10511 } 10512 Log.d(VIEW_LOG_TAG, output); 10513 } 10514 10515 /** 10516 * Convenience for {@link Selection#getSelectionStart}. 10517 */ 10518 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10519 public int getSelectionStart() { 10520 return Selection.getSelectionStart(getText()); 10521 } 10522 10523 /** 10524 * Convenience for {@link Selection#getSelectionEnd}. 10525 */ 10526 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10527 public int getSelectionEnd() { 10528 return Selection.getSelectionEnd(getText()); 10529 } 10530 10531 /** 10532 * Return true iff there is a selection of nonzero length inside this text view. 10533 */ hasSelection()10534 public boolean hasSelection() { 10535 final int selectionStart = getSelectionStart(); 10536 final int selectionEnd = getSelectionEnd(); 10537 10538 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10539 } 10540 getSelectedText()10541 String getSelectedText() { 10542 if (!hasSelection()) { 10543 return null; 10544 } 10545 10546 final int start = getSelectionStart(); 10547 final int end = getSelectionEnd(); 10548 return String.valueOf( 10549 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10550 } 10551 10552 /** 10553 * Sets the properties of this field (lines, horizontally scrolling, 10554 * transformation method) to be for a single-line input. 10555 * 10556 * @attr ref android.R.styleable#TextView_singleLine 10557 */ setSingleLine()10558 public void setSingleLine() { 10559 setSingleLine(true); 10560 } 10561 10562 /** 10563 * Sets the properties of this field to transform input to ALL CAPS 10564 * display. This may use a "small caps" formatting if available. 10565 * This setting will be ignored if this field is editable or selectable. 10566 * 10567 * This call replaces the current transformation method. Disabling this 10568 * will not necessarily restore the previous behavior from before this 10569 * was enabled. 10570 * 10571 * @see #setTransformationMethod(TransformationMethod) 10572 * @attr ref android.R.styleable#TextView_textAllCaps 10573 */ 10574 @android.view.RemotableViewMethod setAllCaps(boolean allCaps)10575 public void setAllCaps(boolean allCaps) { 10576 if (allCaps) { 10577 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10578 } else { 10579 setTransformationMethod(null); 10580 } 10581 } 10582 10583 /** 10584 * 10585 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10586 * @return Whether the current transformation method is for ALL CAPS. 10587 * 10588 * @see #setAllCaps(boolean) 10589 * @see #setTransformationMethod(TransformationMethod) 10590 */ 10591 @InspectableProperty(name = "textAllCaps") isAllCaps()10592 public boolean isAllCaps() { 10593 final TransformationMethod method = getTransformationMethod(); 10594 return method != null && method instanceof AllCapsTransformationMethod; 10595 } 10596 10597 /** 10598 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10599 * transformation method) to be for a single-line input; if false, restores these to the default 10600 * conditions. 10601 * 10602 * Note that the default conditions are not necessarily those that were in effect prior this 10603 * method, and you may want to reset these properties to your custom values. 10604 * 10605 * Note that due to performance reasons, by setting single line for the EditText, the maximum 10606 * text length is set to 5000 if no other character limitation are applied. 10607 * 10608 * @attr ref android.R.styleable#TextView_singleLine 10609 */ 10610 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10611 public void setSingleLine(boolean singleLine) { 10612 // Could be used, but may break backward compatibility. 10613 // if (mSingleLine == singleLine) return; 10614 setInputTypeSingleLine(singleLine); 10615 applySingleLine(singleLine, true, true, true); 10616 } 10617 10618 /** 10619 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10620 * @param singleLine 10621 */ setInputTypeSingleLine(boolean singleLine)10622 private void setInputTypeSingleLine(boolean singleLine) { 10623 if (mEditor != null 10624 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10625 == EditorInfo.TYPE_CLASS_TEXT) { 10626 if (singleLine) { 10627 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10628 } else { 10629 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10630 } 10631 } 10632 } 10633 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10634 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10635 boolean changeMaxLines, boolean changeMaxLength) { 10636 mSingleLine = singleLine; 10637 10638 if (singleLine) { 10639 setLines(1); 10640 setHorizontallyScrolling(true); 10641 if (applyTransformation) { 10642 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10643 } 10644 10645 if (!changeMaxLength) return; 10646 10647 // Single line length filter is only applicable editable text. 10648 if (mBufferType != BufferType.EDITABLE) return; 10649 10650 final InputFilter[] prevFilters = getFilters(); 10651 for (InputFilter filter: getFilters()) { 10652 // We don't add LengthFilter if already there. 10653 if (filter instanceof InputFilter.LengthFilter) return; 10654 } 10655 10656 if (mSingleLineLengthFilter == null) { 10657 mSingleLineLengthFilter = new InputFilter.LengthFilter( 10658 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 10659 } 10660 10661 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 10662 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 10663 newFilters[prevFilters.length] = mSingleLineLengthFilter; 10664 10665 setFilters(newFilters); 10666 10667 // Since filter doesn't apply to existing text, trigger filter by setting text. 10668 setText(getText()); 10669 } else { 10670 if (changeMaxLines) { 10671 setMaxLines(Integer.MAX_VALUE); 10672 } 10673 setHorizontallyScrolling(false); 10674 if (applyTransformation) { 10675 setTransformationMethod(null); 10676 } 10677 10678 if (!changeMaxLength) return; 10679 10680 // Single line length filter is only applicable editable text. 10681 if (mBufferType != BufferType.EDITABLE) return; 10682 10683 final InputFilter[] prevFilters = getFilters(); 10684 if (prevFilters.length == 0) return; 10685 10686 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 10687 // single line char limit filter. 10688 if (mSingleLineLengthFilter == null) return; 10689 10690 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 10691 // Since filter list is expected to be small and want to avoid unnecessary array 10692 // allocation, check if there is mSingleLengthFilter first. 10693 int targetIndex = -1; 10694 for (int i = 0; i < prevFilters.length; ++i) { 10695 if (prevFilters[i] == mSingleLineLengthFilter) { 10696 targetIndex = i; 10697 break; 10698 } 10699 } 10700 if (targetIndex == -1) return; // not found. Do nothing. 10701 10702 if (prevFilters.length == 1) { 10703 setFilters(NO_FILTERS); 10704 return; 10705 } 10706 10707 // Create new array which doesn't include mSingleLengthFilter. 10708 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 10709 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 10710 System.arraycopy( 10711 prevFilters, 10712 targetIndex + 1, 10713 newFilters, 10714 targetIndex, 10715 prevFilters.length - targetIndex - 1); 10716 setFilters(newFilters); 10717 mSingleLineLengthFilter = null; 10718 } 10719 } 10720 10721 /** 10722 * Causes words in the text that are longer than the view's width 10723 * to be ellipsized instead of broken in the middle. You may also 10724 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10725 * to constrain the text to a single line. Use <code>null</code> 10726 * to turn off ellipsizing. 10727 * 10728 * If {@link #setMaxLines} has been used to set two or more lines, 10729 * only {@link android.text.TextUtils.TruncateAt#END} and 10730 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10731 * (other ellipsizing types will not do anything). 10732 * 10733 * @attr ref android.R.styleable#TextView_ellipsize 10734 */ setEllipsize(TextUtils.TruncateAt where)10735 public void setEllipsize(TextUtils.TruncateAt where) { 10736 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10737 if (mEllipsize != where) { 10738 mEllipsize = where; 10739 10740 if (mLayout != null) { 10741 nullLayouts(); 10742 requestLayout(); 10743 invalidate(); 10744 } 10745 } 10746 } 10747 10748 /** 10749 * Sets how many times to repeat the marquee animation. Only applied if the 10750 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10751 * 10752 * @see #getMarqueeRepeatLimit() 10753 * 10754 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10755 */ setMarqueeRepeatLimit(int marqueeLimit)10756 public void setMarqueeRepeatLimit(int marqueeLimit) { 10757 mMarqueeRepeatLimit = marqueeLimit; 10758 } 10759 10760 /** 10761 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10762 * TextView has marquee enabled. 10763 * 10764 * @return the number of times the marquee animation is repeated. -1 if the animation 10765 * repeats indefinitely 10766 * 10767 * @see #setMarqueeRepeatLimit(int) 10768 * 10769 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10770 */ 10771 @InspectableProperty getMarqueeRepeatLimit()10772 public int getMarqueeRepeatLimit() { 10773 return mMarqueeRepeatLimit; 10774 } 10775 10776 /** 10777 * Returns where, if anywhere, words that are longer than the view 10778 * is wide should be ellipsized. 10779 */ 10780 @InspectableProperty 10781 @ViewDebug.ExportedProperty getEllipsize()10782 public TextUtils.TruncateAt getEllipsize() { 10783 return mEllipsize; 10784 } 10785 10786 /** 10787 * Set the TextView so that when it takes focus, all the text is 10788 * selected. 10789 * 10790 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10791 */ 10792 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10793 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10794 createEditorIfNeeded(); 10795 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10796 10797 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10798 setText(mText, BufferType.SPANNABLE); 10799 } 10800 } 10801 10802 /** 10803 * Set whether the cursor is visible. The default is true. Note that this property only 10804 * makes sense for editable TextView. If IME is consuming the input, the cursor will always be 10805 * invisible, visibility will be updated as the last state when IME does not consume 10806 * the input anymore. 10807 * 10808 * @see #isCursorVisible() 10809 * 10810 * @attr ref android.R.styleable#TextView_cursorVisible 10811 */ 10812 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10813 public void setCursorVisible(boolean visible) { 10814 mCursorVisibleFromAttr = visible; 10815 updateCursorVisibleInternal(); 10816 } 10817 10818 /** 10819 * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} 10820 * is {@code true}. Otherwise, make the cursor visible. 10821 * 10822 * @param imeConsumesInput {@code true} if IME is consuming the input 10823 * 10824 * @hide 10825 */ setImeConsumesInput(boolean imeConsumesInput)10826 public void setImeConsumesInput(boolean imeConsumesInput) { 10827 mImeIsConsumingInput = imeConsumesInput; 10828 updateCursorVisibleInternal(); 10829 } 10830 updateCursorVisibleInternal()10831 private void updateCursorVisibleInternal() { 10832 boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; 10833 if (visible && mEditor == null) return; // visible is the default value with no edit data 10834 createEditorIfNeeded(); 10835 if (mEditor.mCursorVisible != visible) { 10836 mEditor.mCursorVisible = visible; 10837 invalidate(); 10838 10839 mEditor.makeBlink(); 10840 10841 // InsertionPointCursorController depends on mCursorVisible 10842 mEditor.prepareCursorControllers(); 10843 } 10844 } 10845 10846 /** 10847 * @return whether or not the cursor is visible (assuming this TextView is editable). This 10848 * method may return {@code false} when the IME is consuming the input even if the 10849 * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} 10850 * is called. 10851 * 10852 * @see #setCursorVisible(boolean) 10853 * 10854 * @attr ref android.R.styleable#TextView_cursorVisible 10855 */ 10856 @InspectableProperty isCursorVisible()10857 public boolean isCursorVisible() { 10858 // true is the default value 10859 return mEditor == null ? true : mEditor.mCursorVisible; 10860 } 10861 10862 /** 10863 * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. 10864 * {@code true} is the default value. 10865 * 10866 * @see #setCursorVisible(boolean) 10867 * @hide 10868 */ isCursorVisibleFromAttr()10869 public boolean isCursorVisibleFromAttr() { 10870 return mCursorVisibleFromAttr; 10871 } 10872 canMarquee()10873 private boolean canMarquee() { 10874 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10875 return width > 0 && (mLayout.getLineWidth(0) > width 10876 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10877 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10878 } 10879 10880 /** 10881 * @hide 10882 */ 10883 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10884 protected void startMarquee() { 10885 // Do not ellipsize EditText 10886 if (getKeyListener() != null) return; 10887 10888 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10889 return; 10890 } 10891 10892 if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible() 10893 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { 10894 10895 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10896 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10897 final Layout tmp = mLayout; 10898 mLayout = mSavedMarqueeModeLayout; 10899 mSavedMarqueeModeLayout = tmp; 10900 setHorizontalFadingEdgeEnabled(true); 10901 requestLayout(); 10902 invalidate(); 10903 } 10904 10905 if (mMarquee == null) mMarquee = new Marquee(this); 10906 mMarquee.start(mMarqueeRepeatLimit); 10907 } 10908 } 10909 10910 /** 10911 * @hide 10912 */ stopMarquee()10913 protected void stopMarquee() { 10914 if (mMarquee != null && !mMarquee.isStopped()) { 10915 mMarquee.stop(); 10916 } 10917 10918 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10919 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10920 final Layout tmp = mSavedMarqueeModeLayout; 10921 mSavedMarqueeModeLayout = mLayout; 10922 mLayout = tmp; 10923 setHorizontalFadingEdgeEnabled(false); 10924 requestLayout(); 10925 invalidate(); 10926 } 10927 } 10928 10929 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10930 private void startStopMarquee(boolean start) { 10931 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10932 if (start) { 10933 startMarquee(); 10934 } else { 10935 stopMarquee(); 10936 } 10937 } 10938 } 10939 10940 /** 10941 * This method is called when the text is changed, in case any subclasses 10942 * would like to know. 10943 * 10944 * Within <code>text</code>, the <code>lengthAfter</code> characters 10945 * beginning at <code>start</code> have just replaced old text that had 10946 * length <code>lengthBefore</code>. It is an error to attempt to make 10947 * changes to <code>text</code> from this callback. 10948 * 10949 * @param text The text the TextView is displaying 10950 * @param start The offset of the start of the range of the text that was 10951 * modified 10952 * @param lengthBefore The length of the former text that has been replaced 10953 * @param lengthAfter The length of the replacement modified text 10954 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10955 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10956 // intentionally empty, template pattern method can be overridden by subclasses 10957 } 10958 10959 /** 10960 * This method is called when the selection has changed, in case any 10961 * subclasses would like to know. 10962 * </p> 10963 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 10964 * the accessibility subsystem about the selection change. 10965 * </p> 10966 * 10967 * @param selStart The new selection start location. 10968 * @param selEnd The new selection end location. 10969 */ 10970 @CallSuper onSelectionChanged(int selStart, int selEnd)10971 protected void onSelectionChanged(int selStart, int selEnd) { 10972 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10973 } 10974 10975 /** 10976 * Adds a TextWatcher to the list of those whose methods are called 10977 * whenever this TextView's text changes. 10978 * <p> 10979 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10980 * not called after {@link #setText} calls. Now, doing {@link #setText} 10981 * if there are any text changed listeners forces the buffer type to 10982 * Editable if it would not otherwise be and does call this method. 10983 */ addTextChangedListener(TextWatcher watcher)10984 public void addTextChangedListener(TextWatcher watcher) { 10985 if (mListeners == null) { 10986 mListeners = new ArrayList<TextWatcher>(); 10987 } 10988 10989 mListeners.add(watcher); 10990 } 10991 10992 /** 10993 * Removes the specified TextWatcher from the list of those whose 10994 * methods are called 10995 * whenever this TextView's text changes. 10996 */ removeTextChangedListener(TextWatcher watcher)10997 public void removeTextChangedListener(TextWatcher watcher) { 10998 if (mListeners != null) { 10999 int i = mListeners.indexOf(watcher); 11000 11001 if (i >= 0) { 11002 mListeners.remove(i); 11003 } 11004 } 11005 } 11006 sendBeforeTextChanged(CharSequence text, int start, int before, int after)11007 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 11008 if (mListeners != null) { 11009 final ArrayList<TextWatcher> list = mListeners; 11010 final int count = list.size(); 11011 for (int i = 0; i < count; i++) { 11012 list.get(i).beforeTextChanged(text, start, before, after); 11013 } 11014 } 11015 11016 // The spans that are inside or intersect the modified region no longer make sense 11017 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 11018 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 11019 } 11020 11021 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)11022 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 11023 if (!(mText instanceof Editable)) return; 11024 Editable text = (Editable) mText; 11025 11026 T[] spans = text.getSpans(start, end, type); 11027 ArrayList<T> spansToRemove = new ArrayList<>(); 11028 for (T span : spans) { 11029 final int spanStart = text.getSpanStart(span); 11030 final int spanEnd = text.getSpanEnd(span); 11031 if (spanEnd == start || spanStart == end) continue; 11032 spansToRemove.add(span); 11033 } 11034 for (T span : spansToRemove) { 11035 text.removeSpan(span); 11036 } 11037 } 11038 removeAdjacentSuggestionSpans(final int pos)11039 void removeAdjacentSuggestionSpans(final int pos) { 11040 if (!(mText instanceof Editable)) return; 11041 final Editable text = (Editable) mText; 11042 11043 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 11044 final int length = spans.length; 11045 for (int i = 0; i < length; i++) { 11046 final int spanStart = text.getSpanStart(spans[i]); 11047 final int spanEnd = text.getSpanEnd(spans[i]); 11048 if (spanEnd == pos || spanStart == pos) { 11049 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 11050 text.removeSpan(spans[i]); 11051 } 11052 } 11053 } 11054 } 11055 11056 /** 11057 * Not private so it can be called from an inner class without going 11058 * through a thunk. 11059 */ sendOnTextChanged(CharSequence text, int start, int before, int after)11060 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 11061 if (mListeners != null) { 11062 final ArrayList<TextWatcher> list = mListeners; 11063 final int count = list.size(); 11064 for (int i = 0; i < count; i++) { 11065 list.get(i).onTextChanged(text, start, before, after); 11066 } 11067 } 11068 11069 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 11070 } 11071 11072 /** 11073 * Not private so it can be called from an inner class without going 11074 * through a thunk. 11075 */ sendAfterTextChanged(Editable text)11076 void sendAfterTextChanged(Editable text) { 11077 if (mListeners != null) { 11078 final ArrayList<TextWatcher> list = mListeners; 11079 final int count = list.size(); 11080 for (int i = 0; i < count; i++) { 11081 list.get(i).afterTextChanged(text); 11082 } 11083 } 11084 11085 notifyListeningManagersAfterTextChanged(); 11086 11087 hideErrorIfUnchanged(); 11088 } 11089 11090 /** 11091 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 11092 * interested on text changes. 11093 */ notifyListeningManagersAfterTextChanged()11094 private void notifyListeningManagersAfterTextChanged() { 11095 11096 // Autofill 11097 if (isAutofillable()) { 11098 // It is important to not check whether the view is important for autofill 11099 // since the user can trigger autofill manually on not important views. 11100 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11101 if (afm != null) { 11102 if (android.view.autofill.Helper.sVerbose) { 11103 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 11104 } 11105 afm.notifyValueChanged(TextView.this); 11106 } 11107 } 11108 11109 notifyContentCaptureTextChanged(); 11110 } 11111 11112 /** 11113 * Notifies the ContentCapture service that the text of the view has changed (only if 11114 * ContentCapture has been notified of this view's existence already). 11115 * 11116 * @hide 11117 */ notifyContentCaptureTextChanged()11118 public void notifyContentCaptureTextChanged() { 11119 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 11120 // of using isLaidout(), so it's not called in cases where it's laid out but a 11121 // notifyAppeared was not sent. 11122 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 11123 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 11124 if (cm != null && cm.isContentCaptureEnabled()) { 11125 final ContentCaptureSession session = getContentCaptureSession(); 11126 if (session != null) { 11127 // TODO(b/111276913): pass flags when edited by user / add CTS test 11128 session.notifyViewTextChanged(getAutofillId(), getText()); 11129 } 11130 } 11131 } 11132 } 11133 isAutofillable()11134 private boolean isAutofillable() { 11135 // It is important to not check whether the view is important for autofill 11136 // since the user can trigger autofill manually on not important views. 11137 return getAutofillType() != AUTOFILL_TYPE_NONE; 11138 } 11139 updateAfterEdit()11140 void updateAfterEdit() { 11141 invalidate(); 11142 int curs = getSelectionStart(); 11143 11144 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11145 registerForPreDraw(); 11146 } 11147 11148 checkForResize(); 11149 11150 if (curs >= 0) { 11151 mHighlightPathBogus = true; 11152 if (mEditor != null) mEditor.makeBlink(); 11153 bringPointIntoView(curs); 11154 } 11155 } 11156 11157 /** 11158 * Not private so it can be called from an inner class without going 11159 * through a thunk. 11160 */ handleTextChanged(CharSequence buffer, int start, int before, int after)11161 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 11162 sLastCutCopyOrTextChangedTime = 0; 11163 11164 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 11165 if (ims == null || ims.mBatchEditNesting == 0) { 11166 updateAfterEdit(); 11167 } 11168 if (ims != null) { 11169 ims.mContentChanged = true; 11170 if (ims.mChangedStart < 0) { 11171 ims.mChangedStart = start; 11172 ims.mChangedEnd = start + before; 11173 } else { 11174 ims.mChangedStart = Math.min(ims.mChangedStart, start); 11175 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 11176 } 11177 ims.mChangedDelta += after - before; 11178 } 11179 resetErrorChangedFlag(); 11180 sendOnTextChanged(buffer, start, before, after); 11181 onTextChanged(buffer, start, before, after); 11182 } 11183 11184 /** 11185 * Not private so it can be called from an inner class without going 11186 * through a thunk. 11187 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)11188 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 11189 // XXX Make the start and end move together if this ends up 11190 // spending too much time invalidating. 11191 11192 boolean selChanged = false; 11193 int newSelStart = -1, newSelEnd = -1; 11194 11195 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 11196 11197 if (what == Selection.SELECTION_END) { 11198 selChanged = true; 11199 newSelEnd = newStart; 11200 11201 if (oldStart >= 0 || newStart >= 0) { 11202 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 11203 checkForResize(); 11204 registerForPreDraw(); 11205 if (mEditor != null) mEditor.makeBlink(); 11206 } 11207 } 11208 11209 if (what == Selection.SELECTION_START) { 11210 selChanged = true; 11211 newSelStart = newStart; 11212 11213 if (oldStart >= 0 || newStart >= 0) { 11214 int end = Selection.getSelectionEnd(buf); 11215 invalidateCursor(end, oldStart, newStart); 11216 } 11217 } 11218 11219 if (selChanged) { 11220 mHighlightPathBogus = true; 11221 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 11222 11223 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 11224 if (newSelStart < 0) { 11225 newSelStart = Selection.getSelectionStart(buf); 11226 } 11227 if (newSelEnd < 0) { 11228 newSelEnd = Selection.getSelectionEnd(buf); 11229 } 11230 11231 if (mEditor != null) { 11232 mEditor.refreshTextActionMode(); 11233 if (!hasSelection() 11234 && mEditor.getTextActionMode() == null && hasTransientState()) { 11235 // User generated selection has been removed. 11236 setHasTransientState(false); 11237 } 11238 } 11239 onSelectionChanged(newSelStart, newSelEnd); 11240 } 11241 } 11242 11243 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 11244 || what instanceof CharacterStyle) { 11245 if (ims == null || ims.mBatchEditNesting == 0) { 11246 invalidate(); 11247 mHighlightPathBogus = true; 11248 checkForResize(); 11249 } else { 11250 ims.mContentChanged = true; 11251 } 11252 if (mEditor != null) { 11253 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 11254 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 11255 mEditor.invalidateHandlesAndActionMode(); 11256 } 11257 } 11258 11259 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 11260 mHighlightPathBogus = true; 11261 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 11262 ims.mSelectionModeChanged = true; 11263 } 11264 11265 if (Selection.getSelectionStart(buf) >= 0) { 11266 if (ims == null || ims.mBatchEditNesting == 0) { 11267 invalidateCursor(); 11268 } else { 11269 ims.mCursorChanged = true; 11270 } 11271 } 11272 } 11273 11274 if (what instanceof ParcelableSpan) { 11275 // If this is a span that can be sent to a remote process, 11276 // the current extract editor would be interested in it. 11277 if (ims != null && ims.mExtractedTextRequest != null) { 11278 if (ims.mBatchEditNesting != 0) { 11279 if (oldStart >= 0) { 11280 if (ims.mChangedStart > oldStart) { 11281 ims.mChangedStart = oldStart; 11282 } 11283 if (ims.mChangedStart > oldEnd) { 11284 ims.mChangedStart = oldEnd; 11285 } 11286 } 11287 if (newStart >= 0) { 11288 if (ims.mChangedStart > newStart) { 11289 ims.mChangedStart = newStart; 11290 } 11291 if (ims.mChangedStart > newEnd) { 11292 ims.mChangedStart = newEnd; 11293 } 11294 } 11295 } else { 11296 if (DEBUG_EXTRACT) { 11297 Log.v(LOG_TAG, "Span change outside of batch: " 11298 + oldStart + "-" + oldEnd + "," 11299 + newStart + "-" + newEnd + " " + what); 11300 } 11301 ims.mContentChanged = true; 11302 } 11303 } 11304 } 11305 11306 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 11307 && what instanceof SpellCheckSpan) { 11308 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 11309 } 11310 } 11311 11312 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)11313 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 11314 if (isTemporarilyDetached()) { 11315 // If we are temporarily in the detach state, then do nothing. 11316 super.onFocusChanged(focused, direction, previouslyFocusedRect); 11317 return; 11318 } 11319 11320 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 11321 11322 if (focused) { 11323 if (mSpannable != null) { 11324 MetaKeyKeyListener.resetMetaState(mSpannable); 11325 } 11326 } 11327 11328 startStopMarquee(focused); 11329 11330 if (mTransformation != null) { 11331 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 11332 } 11333 11334 super.onFocusChanged(focused, direction, previouslyFocusedRect); 11335 } 11336 11337 @Override onWindowFocusChanged(boolean hasWindowFocus)11338 public void onWindowFocusChanged(boolean hasWindowFocus) { 11339 super.onWindowFocusChanged(hasWindowFocus); 11340 11341 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 11342 11343 startStopMarquee(hasWindowFocus); 11344 } 11345 11346 @Override onVisibilityChanged(View changedView, int visibility)11347 protected void onVisibilityChanged(View changedView, int visibility) { 11348 super.onVisibilityChanged(changedView, visibility); 11349 if (mEditor != null && visibility != VISIBLE) { 11350 mEditor.hideCursorAndSpanControllers(); 11351 stopTextActionMode(); 11352 } 11353 } 11354 11355 @Override onVisibilityAggregated(boolean isVisible)11356 public void onVisibilityAggregated(boolean isVisible) { 11357 super.onVisibilityAggregated(isVisible); 11358 startStopMarquee(isVisible); 11359 } 11360 11361 /** 11362 * Use {@link BaseInputConnection#removeComposingSpans 11363 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 11364 * state from this text view. 11365 */ clearComposingText()11366 public void clearComposingText() { 11367 if (mText instanceof Spannable) { 11368 BaseInputConnection.removeComposingSpans(mSpannable); 11369 } 11370 } 11371 11372 @Override setSelected(boolean selected)11373 public void setSelected(boolean selected) { 11374 boolean wasSelected = isSelected(); 11375 11376 super.setSelected(selected); 11377 11378 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 11379 if (selected) { 11380 startMarquee(); 11381 } else { 11382 stopMarquee(); 11383 } 11384 } 11385 } 11386 11387 /** 11388 * Called from onTouchEvent() to prevent the touches by secondary fingers. 11389 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 11390 * This method is a lock to avoid processing multiple fingers on both text view and handles. 11391 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 11392 * 11393 * @param event The motion event that is being handled and carries the pointer info. 11394 * @param fromHandleView true if the event is delivered to selection handle or insertion 11395 * handle; false if this event is delivered to TextView. 11396 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 11397 * event, otherwise false. 11398 * - Always returns true for the first finger. 11399 * - For secondary fingers, if the first or current finger is from TextView, returns false. 11400 * This is to make touch mutually exclusive between the TextView and the handles, but 11401 * not among the handles. 11402 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)11403 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 11404 boolean res = true; 11405 if (mPrimePointerId == NO_POINTER_ID) { 11406 mPrimePointerId = event.getPointerId(0); 11407 mIsPrimePointerFromHandleView = fromHandleView; 11408 } else if (mPrimePointerId != event.getPointerId(0)) { 11409 res = mIsPrimePointerFromHandleView && fromHandleView; 11410 } 11411 if (event.getActionMasked() == MotionEvent.ACTION_UP 11412 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 11413 mPrimePointerId = -1; 11414 } 11415 return res; 11416 } 11417 11418 @Override onTouchEvent(MotionEvent event)11419 public boolean onTouchEvent(MotionEvent event) { 11420 if (DEBUG_CURSOR) { 11421 logCursor("onTouchEvent", "%d: %s (%f,%f)", 11422 event.getSequenceNumber(), 11423 MotionEvent.actionToString(event.getActionMasked()), 11424 event.getX(), event.getY()); 11425 } 11426 final int action = event.getActionMasked(); 11427 if (mEditor != null) { 11428 if (!isFromPrimePointer(event, false)) { 11429 return true; 11430 } 11431 11432 mEditor.onTouchEvent(event); 11433 11434 if (mEditor.mInsertionPointCursorController != null 11435 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 11436 return true; 11437 } 11438 if (mEditor.mSelectionModifierCursorController != null 11439 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 11440 return true; 11441 } 11442 } 11443 11444 final boolean superResult = super.onTouchEvent(event); 11445 if (DEBUG_CURSOR) { 11446 logCursor("onTouchEvent", "superResult=%s", superResult); 11447 } 11448 11449 /* 11450 * Don't handle the release after a long press, because it will move the selection away from 11451 * whatever the menu action was trying to affect. If the long press should have triggered an 11452 * insertion action mode, we can now actually show it. 11453 */ 11454 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 11455 mEditor.mDiscardNextActionUp = false; 11456 if (DEBUG_CURSOR) { 11457 logCursor("onTouchEvent", "release after long press detected"); 11458 } 11459 if (mEditor.mIsInsertionActionModeStartPending) { 11460 mEditor.startInsertionActionMode(); 11461 mEditor.mIsInsertionActionModeStartPending = false; 11462 } 11463 return superResult; 11464 } 11465 11466 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 11467 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 11468 11469 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 11470 && mText instanceof Spannable && mLayout != null) { 11471 boolean handled = false; 11472 11473 if (mMovement != null) { 11474 handled |= mMovement.onTouchEvent(this, mSpannable, event); 11475 } 11476 11477 final boolean textIsSelectable = isTextSelectable(); 11478 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 11479 // The LinkMovementMethod which should handle taps on links has not been installed 11480 // on non editable text that support text selection. 11481 // We reproduce its behavior here to open links for these. 11482 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 11483 getSelectionEnd(), ClickableSpan.class); 11484 11485 if (links.length > 0) { 11486 links[0].onClick(this); 11487 handled = true; 11488 } 11489 } 11490 11491 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 11492 // Show the IME, except when selecting in read-only text. 11493 final InputMethodManager imm = getInputMethodManager(); 11494 viewClicked(imm); 11495 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null 11496 && !showAutofillDialog()) { 11497 imm.showSoftInput(this, 0); 11498 } 11499 11500 // The above condition ensures that the mEditor is not null 11501 mEditor.onTouchUpEvent(event); 11502 11503 handled = true; 11504 } 11505 11506 if (handled) { 11507 return true; 11508 } 11509 } 11510 11511 return superResult; 11512 } 11513 11514 /** 11515 * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. 11516 * If autofill suggestions are available when the user clicks on a field that supports filling 11517 * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area 11518 * to display the datasets, so it is easy for users to pay attention to the datasets and 11519 * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better 11520 * experience is not to show the IME if there is a fill dialog. 11521 */ showAutofillDialog()11522 private boolean showAutofillDialog() { 11523 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11524 if (afm != null) { 11525 return afm.showAutofillDialog(this); 11526 } 11527 return false; 11528 } 11529 11530 @Override onGenericMotionEvent(MotionEvent event)11531 public boolean onGenericMotionEvent(MotionEvent event) { 11532 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 11533 try { 11534 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 11535 return true; 11536 } 11537 } catch (AbstractMethodError ex) { 11538 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 11539 // Ignore its absence in case third party applications implemented the 11540 // interface directly. 11541 } 11542 } 11543 return super.onGenericMotionEvent(event); 11544 } 11545 11546 @Override onCreateContextMenu(ContextMenu menu)11547 protected void onCreateContextMenu(ContextMenu menu) { 11548 if (mEditor != null) { 11549 mEditor.onCreateContextMenu(menu); 11550 } 11551 } 11552 11553 @Override showContextMenu()11554 public boolean showContextMenu() { 11555 if (mEditor != null) { 11556 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 11557 } 11558 return super.showContextMenu(); 11559 } 11560 11561 @Override showContextMenu(float x, float y)11562 public boolean showContextMenu(float x, float y) { 11563 if (mEditor != null) { 11564 mEditor.setContextMenuAnchor(x, y); 11565 } 11566 return super.showContextMenu(x, y); 11567 } 11568 11569 /** 11570 * @return True iff this TextView contains a text that can be edited, or if this is 11571 * a selectable TextView. 11572 */ 11573 @UnsupportedAppUsage isTextEditable()11574 boolean isTextEditable() { 11575 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 11576 } 11577 11578 /** 11579 * Returns true, only while processing a touch gesture, if the initial 11580 * touch down event caused focus to move to the text view and as a result 11581 * its selection changed. Only valid while processing the touch gesture 11582 * of interest, in an editable text view. 11583 */ didTouchFocusSelect()11584 public boolean didTouchFocusSelect() { 11585 return mEditor != null && mEditor.mTouchFocusSelected; 11586 } 11587 11588 @Override cancelLongPress()11589 public void cancelLongPress() { 11590 super.cancelLongPress(); 11591 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 11592 } 11593 11594 @Override onTrackballEvent(MotionEvent event)11595 public boolean onTrackballEvent(MotionEvent event) { 11596 if (mMovement != null && mSpannable != null && mLayout != null) { 11597 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 11598 return true; 11599 } 11600 } 11601 11602 return super.onTrackballEvent(event); 11603 } 11604 11605 /** 11606 * Sets the Scroller used for producing a scrolling animation 11607 * 11608 * @param s A Scroller instance 11609 */ setScroller(Scroller s)11610 public void setScroller(Scroller s) { 11611 mScroller = s; 11612 } 11613 11614 @Override getLeftFadingEdgeStrength()11615 protected float getLeftFadingEdgeStrength() { 11616 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11617 final Marquee marquee = mMarquee; 11618 if (marquee.shouldDrawLeftFade()) { 11619 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11620 } else { 11621 return 0.0f; 11622 } 11623 } else if (getLineCount() == 1) { 11624 final float lineLeft = getLayout().getLineLeft(0); 11625 if (lineLeft > mScrollX) return 0.0f; 11626 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11627 } 11628 return super.getLeftFadingEdgeStrength(); 11629 } 11630 11631 @Override getRightFadingEdgeStrength()11632 protected float getRightFadingEdgeStrength() { 11633 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11634 final Marquee marquee = mMarquee; 11635 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11636 } else if (getLineCount() == 1) { 11637 final float rightEdge = mScrollX + 11638 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11639 final float lineRight = getLayout().getLineRight(0); 11640 if (lineRight < rightEdge) return 0.0f; 11641 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11642 } 11643 return super.getRightFadingEdgeStrength(); 11644 } 11645 11646 /** 11647 * Calculates the fading edge strength as the ratio of the distance between two 11648 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11649 * value for the distance calculation. 11650 * 11651 * @param position1 A horizontal position. 11652 * @param position2 A horizontal position. 11653 * @return Fading edge strength between [0.0f, 1.0f]. 11654 */ 11655 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11656 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11657 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11658 if (horizontalFadingEdgeLength == 0) return 0.0f; 11659 final float diff = Math.abs(position1 - position2); 11660 if (diff > horizontalFadingEdgeLength) return 1.0f; 11661 return diff / horizontalFadingEdgeLength; 11662 } 11663 isMarqueeFadeEnabled()11664 private boolean isMarqueeFadeEnabled() { 11665 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11666 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11667 } 11668 11669 @Override computeHorizontalScrollRange()11670 protected int computeHorizontalScrollRange() { 11671 if (mLayout != null) { 11672 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11673 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11674 } 11675 11676 return super.computeHorizontalScrollRange(); 11677 } 11678 11679 @Override computeVerticalScrollRange()11680 protected int computeVerticalScrollRange() { 11681 if (mLayout != null) { 11682 return mLayout.getHeight(); 11683 } 11684 return super.computeVerticalScrollRange(); 11685 } 11686 11687 @Override computeVerticalScrollExtent()11688 protected int computeVerticalScrollExtent() { 11689 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11690 } 11691 11692 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11693 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11694 super.findViewsWithText(outViews, searched, flags); 11695 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11696 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11697 String searchedLowerCase = searched.toString().toLowerCase(); 11698 String textLowerCase = mText.toString().toLowerCase(); 11699 if (textLowerCase.contains(searchedLowerCase)) { 11700 outViews.add(this); 11701 } 11702 } 11703 } 11704 11705 /** 11706 * Type of the text buffer that defines the characteristics of the text such as static, 11707 * styleable, or editable. 11708 */ 11709 public enum BufferType { 11710 NORMAL, SPANNABLE, EDITABLE 11711 } 11712 11713 /** 11714 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11715 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11716 * if TextView_textColor was not set directly. 11717 * 11718 * @removed 11719 */ getTextColors(Context context, TypedArray attrs)11720 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11721 if (attrs == null) { 11722 // Preserve behavior prior to removal of this API. 11723 throw new NullPointerException(); 11724 } 11725 11726 // It's not safe to use this method from apps. The parameter 'attrs' 11727 // must have been obtained using the TextView filter array which is not 11728 // available to the SDK. As such, we grab a default TypedArray with the 11729 // right filter instead here. 11730 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11731 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11732 if (colors == null) { 11733 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11734 if (ap != 0) { 11735 final TypedArray appearance = context.obtainStyledAttributes( 11736 ap, R.styleable.TextAppearance); 11737 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11738 appearance.recycle(); 11739 } 11740 } 11741 a.recycle(); 11742 11743 return colors; 11744 } 11745 11746 /** 11747 * Returns the default color from the TextView_textColor attribute from the 11748 * AttributeSet, if set, or the default color from the 11749 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11750 * TextView_textColor was not set directly. 11751 * 11752 * @removed 11753 */ getTextColor(Context context, TypedArray attrs, int def)11754 public static int getTextColor(Context context, TypedArray attrs, int def) { 11755 final ColorStateList colors = getTextColors(context, attrs); 11756 if (colors == null) { 11757 return def; 11758 } else { 11759 return colors.getDefaultColor(); 11760 } 11761 } 11762 11763 @Override onKeyShortcut(int keyCode, KeyEvent event)11764 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11765 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11766 // Handle Ctrl-only shortcuts. 11767 switch (keyCode) { 11768 case KeyEvent.KEYCODE_A: 11769 if (canSelectText()) { 11770 return onTextContextMenuItem(ID_SELECT_ALL); 11771 } 11772 break; 11773 case KeyEvent.KEYCODE_Z: 11774 if (canUndo()) { 11775 return onTextContextMenuItem(ID_UNDO); 11776 } 11777 break; 11778 case KeyEvent.KEYCODE_X: 11779 if (canCut()) { 11780 return onTextContextMenuItem(ID_CUT); 11781 } 11782 break; 11783 case KeyEvent.KEYCODE_C: 11784 if (canCopy()) { 11785 return onTextContextMenuItem(ID_COPY); 11786 } 11787 break; 11788 case KeyEvent.KEYCODE_V: 11789 if (canPaste()) { 11790 return onTextContextMenuItem(ID_PASTE); 11791 } 11792 break; 11793 } 11794 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11795 // Handle Ctrl-Shift shortcuts. 11796 switch (keyCode) { 11797 case KeyEvent.KEYCODE_Z: 11798 if (canRedo()) { 11799 return onTextContextMenuItem(ID_REDO); 11800 } 11801 break; 11802 case KeyEvent.KEYCODE_V: 11803 if (canPaste()) { 11804 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11805 } 11806 } 11807 } 11808 return super.onKeyShortcut(keyCode, event); 11809 } 11810 11811 /** 11812 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11813 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11814 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11815 * sufficient. 11816 */ canSelectText()11817 boolean canSelectText() { 11818 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11819 } 11820 11821 /** 11822 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11823 * The text must be spannable and the movement method must allow for arbitary selection. 11824 * 11825 * See also {@link #canSelectText()}. 11826 */ textCanBeSelected()11827 boolean textCanBeSelected() { 11828 // prepareCursorController() relies on this method. 11829 // If you change this condition, make sure prepareCursorController is called anywhere 11830 // the value of this condition might be changed. 11831 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11832 return isTextEditable() 11833 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11834 } 11835 11836 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11837 private Locale getTextServicesLocale(boolean allowNullLocale) { 11838 // Start fetching the text services locale asynchronously. 11839 updateTextServicesLocaleAsync(); 11840 // If !allowNullLocale and there is no cached text services locale, just return the default 11841 // locale. 11842 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11843 : mCurrentSpellCheckerLocaleCache; 11844 } 11845 11846 /** 11847 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11848 * this {@link TextView}. 11849 * 11850 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11851 * other apps may need to set this so that the system can user right user's resources and 11852 * services such as input methods and spell checkers.</p> 11853 * 11854 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11855 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11856 * @hide 11857 */ 11858 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11859 public final void setTextOperationUser(@Nullable UserHandle user) { 11860 if (Objects.equals(mTextOperationUser, user)) { 11861 return; 11862 } 11863 if (user != null && !Process.myUserHandle().equals(user)) { 11864 // Just for preventing people from accidentally using this hidden API without 11865 // the required permission. The same permission is also checked in the system server. 11866 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11867 != PackageManager.PERMISSION_GRANTED) { 11868 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11869 + " userId=" + user.getIdentifier() 11870 + " callingUserId" + UserHandle.myUserId()); 11871 } 11872 } 11873 mTextOperationUser = user; 11874 // Invalidate some resources 11875 mCurrentSpellCheckerLocaleCache = null; 11876 if (mEditor != null) { 11877 mEditor.onTextOperationUserChanged(); 11878 } 11879 } 11880 11881 @Nullable getTextServicesManagerForUser()11882 final TextServicesManager getTextServicesManagerForUser() { 11883 return getServiceManagerForUser("android", TextServicesManager.class); 11884 } 11885 11886 @Nullable getClipboardManagerForUser()11887 final ClipboardManager getClipboardManagerForUser() { 11888 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11889 } 11890 11891 @Nullable getTextClassificationManagerForUser()11892 final TextClassificationManager getTextClassificationManagerForUser() { 11893 return getServiceManagerForUser( 11894 getContext().getPackageName(), TextClassificationManager.class); 11895 } 11896 11897 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11898 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11899 if (mTextOperationUser == null) { 11900 return getContext().getSystemService(managerClazz); 11901 } 11902 try { 11903 Context context = getContext().createPackageContextAsUser( 11904 packageName, 0 /* flags */, mTextOperationUser); 11905 return context.getSystemService(managerClazz); 11906 } catch (PackageManager.NameNotFoundException e) { 11907 return null; 11908 } 11909 } 11910 11911 /** 11912 * Starts {@link Activity} as a text-operation user if it is specified with 11913 * {@link #setTextOperationUser(UserHandle)}. 11914 * 11915 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11916 * 11917 * @param intent The description of the activity to start. 11918 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11919 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11920 if (mTextOperationUser != null) { 11921 getContext().startActivityAsUser(intent, mTextOperationUser); 11922 } else { 11923 getContext().startActivity(intent); 11924 } 11925 } 11926 11927 /** 11928 * This is a temporary method. Future versions may support multi-locale text. 11929 * Caveat: This method may not return the latest text services locale, but this should be 11930 * acceptable and it's more important to make this method asynchronous. 11931 * 11932 * @return The locale that should be used for a word iterator 11933 * in this TextView, based on the current spell checker settings, 11934 * the current IME's locale, or the system default locale. 11935 * Please note that a word iterator in this TextView is different from another word iterator 11936 * used by SpellChecker.java of TextView. This method should be used for the former. 11937 * @hide 11938 */ 11939 // TODO: Support multi-locale 11940 // TODO: Update the text services locale immediately after the keyboard locale is switched 11941 // by catching intent of keyboard switch event getTextServicesLocale()11942 public Locale getTextServicesLocale() { 11943 return getTextServicesLocale(false /* allowNullLocale */); 11944 } 11945 11946 /** 11947 * @return {@code true} if this TextView is specialized for showing and interacting with the 11948 * extracted text in a full-screen input method. 11949 * @hide 11950 */ isInExtractedMode()11951 public boolean isInExtractedMode() { 11952 return false; 11953 } 11954 11955 /** 11956 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11957 * auto-size. 11958 */ isAutoSizeEnabled()11959 private boolean isAutoSizeEnabled() { 11960 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11961 } 11962 11963 /** 11964 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11965 * @hide 11966 */ supportsAutoSizeText()11967 protected boolean supportsAutoSizeText() { 11968 return true; 11969 } 11970 11971 /** 11972 * This is a temporary method. Future versions may support multi-locale text. 11973 * Caveat: This method may not return the latest spell checker locale, but this should be 11974 * acceptable and it's more important to make this method asynchronous. 11975 * 11976 * @return The locale that should be used for a spell checker in this TextView, 11977 * based on the current spell checker settings, the current IME's locale, or the system default 11978 * locale. 11979 * @hide 11980 */ getSpellCheckerLocale()11981 public Locale getSpellCheckerLocale() { 11982 return getTextServicesLocale(true /* allowNullLocale */); 11983 } 11984 updateTextServicesLocaleAsync()11985 private void updateTextServicesLocaleAsync() { 11986 // AsyncTask.execute() uses a serial executor which means we don't have 11987 // to lock around updateTextServicesLocaleLocked() to prevent it from 11988 // being executed n times in parallel. 11989 AsyncTask.execute(new Runnable() { 11990 @Override 11991 public void run() { 11992 updateTextServicesLocaleLocked(); 11993 } 11994 }); 11995 } 11996 11997 @UnsupportedAppUsage updateTextServicesLocaleLocked()11998 private void updateTextServicesLocaleLocked() { 11999 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 12000 if (textServicesManager == null) { 12001 return; 12002 } 12003 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 12004 final Locale locale; 12005 if (subtype != null) { 12006 locale = subtype.getLocaleObject(); 12007 } else { 12008 locale = null; 12009 } 12010 mCurrentSpellCheckerLocaleCache = locale; 12011 } 12012 onLocaleChanged()12013 void onLocaleChanged() { 12014 mEditor.onLocaleChanged(); 12015 } 12016 12017 /** 12018 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 12019 * Made available to achieve a consistent behavior. 12020 * @hide 12021 */ getWordIterator()12022 public WordIterator getWordIterator() { 12023 if (mEditor != null) { 12024 return mEditor.getWordIterator(); 12025 } else { 12026 return null; 12027 } 12028 } 12029 12030 /** @hide */ 12031 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)12032 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 12033 super.onPopulateAccessibilityEventInternal(event); 12034 12035 final CharSequence text = getTextForAccessibility(); 12036 if (!TextUtils.isEmpty(text)) { 12037 event.getText().add(text); 12038 } 12039 } 12040 12041 @Override getAccessibilityClassName()12042 public CharSequence getAccessibilityClassName() { 12043 return TextView.class.getName(); 12044 } 12045 12046 /** @hide */ 12047 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)12048 protected void onProvideStructure(@NonNull ViewStructure structure, 12049 @ViewStructureType int viewFor, int flags) { 12050 super.onProvideStructure(structure, viewFor, flags); 12051 12052 final boolean isPassword = hasPasswordTransformationMethod() 12053 || isPasswordInputType(getInputType()); 12054 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 12055 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 12056 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 12057 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 12058 } 12059 if (mTextId != Resources.ID_NULL) { 12060 try { 12061 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 12062 } catch (Resources.NotFoundException e) { 12063 if (android.view.autofill.Helper.sVerbose) { 12064 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 12065 + mTextId + ": " + e.getMessage()); 12066 } 12067 } 12068 } 12069 String[] mimeTypes = getReceiveContentMimeTypes(); 12070 if (mimeTypes == null && mEditor != null) { 12071 // If the app hasn't set a listener for receiving content on this view (ie, 12072 // getReceiveContentMimeTypes() returns null), check if it implements the 12073 // keyboard image API and, if possible, use those MIME types as fallback. 12074 // This fallback is only in place for autofill, not other mechanisms for 12075 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER 12076 // in TextViewOnReceiveContentListener for more info. 12077 mimeTypes = mEditor.getDefaultOnReceiveContentListener() 12078 .getFallbackMimeTypesForAutofill(this); 12079 } 12080 structure.setReceiveContentMimeTypes(mimeTypes); 12081 } 12082 12083 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 12084 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 12085 if (mLayout == null) { 12086 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 12087 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 12088 } 12089 assumeLayout(); 12090 } 12091 Layout layout = mLayout; 12092 final int lineCount = layout.getLineCount(); 12093 if (lineCount <= 1) { 12094 // Simple case: this is a single line. 12095 final CharSequence text = getText(); 12096 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 12097 structure.setText(text); 12098 } else { 12099 structure.setText(text, getSelectionStart(), getSelectionEnd()); 12100 } 12101 } else { 12102 // Complex case: multi-line, could be scrolled or within a scroll container 12103 // so some lines are not visible. 12104 final int[] tmpCords = new int[2]; 12105 getLocationInWindow(tmpCords); 12106 final int topWindowLocation = tmpCords[1]; 12107 View root = this; 12108 ViewParent viewParent = getParent(); 12109 while (viewParent instanceof View) { 12110 root = (View) viewParent; 12111 viewParent = root.getParent(); 12112 } 12113 final int windowHeight = root.getHeight(); 12114 final int topLine; 12115 final int bottomLine; 12116 if (topWindowLocation >= 0) { 12117 // The top of the view is fully within its window; start text at line 0. 12118 topLine = getLineAtCoordinateUnclamped(0); 12119 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 12120 } else { 12121 // The top of hte window has scrolled off the top of the window; figure out 12122 // the starting line for this. 12123 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 12124 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 12125 } 12126 // We want to return some contextual lines above/below the lines that are 12127 // actually visible. 12128 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 12129 if (expandedTopLine < 0) { 12130 expandedTopLine = 0; 12131 } 12132 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 12133 if (expandedBottomLine >= lineCount) { 12134 expandedBottomLine = lineCount - 1; 12135 } 12136 12137 // Convert lines into character offsets. 12138 int expandedTopChar = layout.getLineStart(expandedTopLine); 12139 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 12140 12141 // Take into account selection -- if there is a selection, we need to expand 12142 // the text we are returning to include that selection. 12143 final int selStart = getSelectionStart(); 12144 final int selEnd = getSelectionEnd(); 12145 if (selStart < selEnd) { 12146 if (selStart < expandedTopChar) { 12147 expandedTopChar = selStart; 12148 } 12149 if (selEnd > expandedBottomChar) { 12150 expandedBottomChar = selEnd; 12151 } 12152 } 12153 12154 // Get the text and trim it to the range we are reporting. 12155 CharSequence text = getText(); 12156 12157 if (text != null) { 12158 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 12159 // Cap the offsets to avoid an OOB exception. That can happen if the 12160 // displayed/layout text, on which these offsets are calculated, is longer 12161 // than the original text (such as when the view is translated by the 12162 // platform intelligence). 12163 // TODO(b/196433694): Figure out how to better handle the offset 12164 // calculations for this case (so we don't unnecessarily cutoff the original 12165 // text, for example). 12166 expandedTopChar = Math.min(expandedTopChar, text.length()); 12167 expandedBottomChar = Math.min(expandedBottomChar, text.length()); 12168 text = text.subSequence(expandedTopChar, expandedBottomChar); 12169 } 12170 12171 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 12172 structure.setText(text); 12173 } else { 12174 structure.setText(text, 12175 selStart - expandedTopChar, 12176 selEnd - expandedTopChar); 12177 12178 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 12179 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 12180 final int baselineOffset = getBaselineOffset(); 12181 for (int i = topLine; i <= bottomLine; i++) { 12182 lineOffsets[i - topLine] = layout.getLineStart(i); 12183 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 12184 } 12185 structure.setTextLines(lineOffsets, lineBaselines); 12186 } 12187 } 12188 } 12189 12190 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 12191 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 12192 // Extract style information that applies to the TextView as a whole. 12193 int style = 0; 12194 int typefaceStyle = getTypefaceStyle(); 12195 if ((typefaceStyle & Typeface.BOLD) != 0) { 12196 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 12197 } 12198 if ((typefaceStyle & Typeface.ITALIC) != 0) { 12199 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 12200 } 12201 12202 // Global styles can also be set via TextView.setPaintFlags(). 12203 int paintFlags = mTextPaint.getFlags(); 12204 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 12205 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 12206 } 12207 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 12208 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 12209 } 12210 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 12211 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 12212 } 12213 12214 // TextView does not have its own text background color. A background is either part 12215 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 12216 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 12217 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 12218 } 12219 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 12220 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 12221 structure.setMinTextEms(getMinEms()); 12222 structure.setMaxTextEms(getMaxEms()); 12223 int maxLength = -1; 12224 for (InputFilter filter: getFilters()) { 12225 if (filter instanceof InputFilter.LengthFilter) { 12226 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 12227 break; 12228 } 12229 } 12230 structure.setMaxTextLength(maxLength); 12231 } 12232 } 12233 if (mHintId != Resources.ID_NULL) { 12234 try { 12235 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 12236 } catch (Resources.NotFoundException e) { 12237 if (android.view.autofill.Helper.sVerbose) { 12238 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 12239 + mHintId + ": " + e.getMessage()); 12240 } 12241 } 12242 } 12243 structure.setHint(getHint()); 12244 structure.setInputType(getInputType()); 12245 } 12246 canRequestAutofill()12247 boolean canRequestAutofill() { 12248 if (!isAutofillable()) { 12249 return false; 12250 } 12251 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12252 if (afm != null) { 12253 return afm.isEnabled(); 12254 } 12255 return false; 12256 } 12257 requestAutofill()12258 private void requestAutofill() { 12259 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12260 if (afm != null) { 12261 afm.requestAutofill(this); 12262 } 12263 } 12264 12265 @Override autofill(AutofillValue value)12266 public void autofill(AutofillValue value) { 12267 if (!isTextEditable()) { 12268 Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); 12269 return; 12270 } 12271 if (!value.isText()) { 12272 Log.w(LOG_TAG, "value of type " + value.describeContents() 12273 + " cannot be autofilled into " + this); 12274 return; 12275 } 12276 final ClipData clip = ClipData.newPlainText("", value.getTextValue()); 12277 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 12278 performReceiveContent(payload); 12279 } 12280 12281 @Override getAutofillType()12282 public @AutofillType int getAutofillType() { 12283 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 12284 } 12285 12286 /** 12287 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 12288 * {@code char}s if longer. 12289 * 12290 * @return current text, {@code null} if the text is not editable 12291 * 12292 * @see View#getAutofillValue() 12293 */ 12294 @Override 12295 @Nullable getAutofillValue()12296 public AutofillValue getAutofillValue() { 12297 if (isTextEditable()) { 12298 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 12299 return AutofillValue.forText(text); 12300 } 12301 return null; 12302 } 12303 12304 /** @hide */ 12305 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)12306 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 12307 super.onInitializeAccessibilityEventInternal(event); 12308 12309 final boolean isPassword = hasPasswordTransformationMethod(); 12310 event.setPassword(isPassword); 12311 12312 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 12313 event.setFromIndex(Selection.getSelectionStart(mText)); 12314 event.setToIndex(Selection.getSelectionEnd(mText)); 12315 event.setItemCount(mText.length()); 12316 } 12317 } 12318 12319 /** @hide */ 12320 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)12321 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 12322 super.onInitializeAccessibilityNodeInfoInternal(info); 12323 12324 final boolean isPassword = hasPasswordTransformationMethod(); 12325 info.setPassword(isPassword); 12326 info.setText(getTextForAccessibility()); 12327 info.setHintText(mHint); 12328 info.setShowingHintText(isShowingHint()); 12329 12330 if (mBufferType == BufferType.EDITABLE) { 12331 info.setEditable(true); 12332 if (isEnabled()) { 12333 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 12334 } 12335 } 12336 12337 if (mEditor != null) { 12338 info.setInputType(mEditor.mInputType); 12339 12340 if (mEditor.mError != null) { 12341 info.setContentInvalid(true); 12342 info.setError(mEditor.mError); 12343 } 12344 // TextView will expose this action if it is editable and has focus. 12345 if (isTextEditable() && isFocused()) { 12346 CharSequence imeActionLabel = mContext.getResources().getString( 12347 com.android.internal.R.string.keyboardview_keycode_enter); 12348 if (getImeActionLabel() != null) { 12349 imeActionLabel = getImeActionLabel(); 12350 } 12351 AccessibilityNodeInfo.AccessibilityAction action = 12352 new AccessibilityNodeInfo.AccessibilityAction( 12353 R.id.accessibilityActionImeEnter, imeActionLabel); 12354 info.addAction(action); 12355 } 12356 } 12357 12358 if (!TextUtils.isEmpty(mText)) { 12359 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 12360 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 12361 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 12362 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 12363 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 12364 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 12365 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 12366 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 12367 info.setAvailableExtraData(Arrays.asList( 12368 EXTRA_DATA_RENDERING_INFO_KEY, 12369 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 12370 )); 12371 info.setTextSelectable(isTextSelectable() || isTextEditable()); 12372 } else { 12373 info.setAvailableExtraData(Arrays.asList( 12374 EXTRA_DATA_RENDERING_INFO_KEY 12375 )); 12376 } 12377 12378 if (isFocused()) { 12379 if (canCopy()) { 12380 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 12381 } 12382 if (canPaste()) { 12383 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 12384 } 12385 if (canCut()) { 12386 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 12387 } 12388 if (canReplace()) { 12389 info.addAction( 12390 AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS); 12391 } 12392 if (canShare()) { 12393 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 12394 ACCESSIBILITY_ACTION_SHARE, 12395 getResources().getString(com.android.internal.R.string.share))); 12396 } 12397 if (canProcessText()) { // also implies mEditor is not null. 12398 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 12399 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); 12400 } 12401 } 12402 12403 // Check for known input filter types. 12404 final int numFilters = mFilters.length; 12405 for (int i = 0; i < numFilters; i++) { 12406 final InputFilter filter = mFilters[i]; 12407 if (filter instanceof InputFilter.LengthFilter) { 12408 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 12409 } 12410 } 12411 12412 if (!isSingleLine()) { 12413 info.setMultiLine(true); 12414 } 12415 12416 // A view should not be exposed as clickable/long-clickable to a service because of a 12417 // LinkMovementMethod or because it has selectable and non-editable text. 12418 if ((info.isClickable() || info.isLongClickable()) 12419 && (mMovement instanceof LinkMovementMethod 12420 || (isTextSelectable() && !isTextEditable()))) { 12421 if (!hasOnClickListeners()) { 12422 info.setClickable(false); 12423 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 12424 } 12425 if (!hasOnLongClickListeners()) { 12426 info.setLongClickable(false); 12427 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 12428 } 12429 } 12430 } 12431 12432 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)12433 public void addExtraDataToAccessibilityNodeInfo( 12434 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 12435 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 12436 int positionInfoStartIndex = arguments.getInt( 12437 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 12438 int positionInfoLength = arguments.getInt( 12439 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 12440 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 12441 || (positionInfoStartIndex >= mText.length())) { 12442 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 12443 return; 12444 } 12445 RectF[] boundingRects = new RectF[positionInfoLength]; 12446 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 12447 populateCharacterBounds(builder, positionInfoStartIndex, 12448 positionInfoStartIndex + positionInfoLength, 12449 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 12450 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 12451 for (int i = 0; i < positionInfoLength; i++) { 12452 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 12453 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 12454 RectF bounds = cursorAnchorInfo 12455 .getCharacterBounds(positionInfoStartIndex + i); 12456 if (bounds != null) { 12457 mapRectFromViewToScreenCoords(bounds, true); 12458 boundingRects[i] = bounds; 12459 } 12460 } 12461 } 12462 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 12463 return; 12464 } 12465 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 12466 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 12467 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 12468 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 12469 extraRenderingInfo.setTextSizeInPx(getTextSize()); 12470 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 12471 info.setExtraRenderingInfo(extraRenderingInfo); 12472 } 12473 } 12474 12475 /** 12476 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 12477 * 12478 * @param builder The builder to populate 12479 * @param startIndex The starting character index to populate 12480 * @param endIndex The ending character index to populate 12481 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 12482 * content 12483 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 12484 * @hide 12485 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12486 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 12487 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 12488 float viewportToContentVerticalOffset) { 12489 final int minLine = mLayout.getLineForOffset(startIndex); 12490 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 12491 for (int line = minLine; line <= maxLine; ++line) { 12492 final int lineStart = mLayout.getLineStart(line); 12493 final int lineEnd = mLayout.getLineEnd(line); 12494 final int offsetStart = Math.max(lineStart, startIndex); 12495 final int offsetEnd = Math.min(lineEnd, endIndex); 12496 final boolean ltrLine = 12497 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 12498 final float[] widths = new float[offsetEnd - offsetStart]; 12499 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 12500 final float top = mLayout.getLineTop(line); 12501 final float bottom = mLayout.getLineBottom(line); 12502 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 12503 final float charWidth = widths[offset - offsetStart]; 12504 final boolean isRtl = mLayout.isRtlCharAt(offset); 12505 final float primary = mLayout.getPrimaryHorizontal(offset); 12506 final float secondary = mLayout.getSecondaryHorizontal(offset); 12507 // TODO: This doesn't work perfectly for text with custom styles and 12508 // TAB chars. 12509 final float left; 12510 final float right; 12511 if (ltrLine) { 12512 if (isRtl) { 12513 left = secondary - charWidth; 12514 right = secondary; 12515 } else { 12516 left = primary; 12517 right = primary + charWidth; 12518 } 12519 } else { 12520 if (!isRtl) { 12521 left = secondary; 12522 right = secondary + charWidth; 12523 } else { 12524 left = primary - charWidth; 12525 right = primary; 12526 } 12527 } 12528 // TODO: Check top-right and bottom-left as well. 12529 final float localLeft = left + viewportToContentHorizontalOffset; 12530 final float localRight = right + viewportToContentHorizontalOffset; 12531 final float localTop = top + viewportToContentVerticalOffset; 12532 final float localBottom = bottom + viewportToContentVerticalOffset; 12533 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 12534 final boolean isBottomRightVisible = 12535 isPositionVisible(localRight, localBottom); 12536 int characterBoundsFlags = 0; 12537 if (isTopLeftVisible || isBottomRightVisible) { 12538 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 12539 } 12540 if (!isTopLeftVisible || !isBottomRightVisible) { 12541 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 12542 } 12543 if (isRtl) { 12544 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 12545 } 12546 // Here offset is the index in Java chars. 12547 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 12548 localBottom, characterBoundsFlags); 12549 } 12550 } 12551 } 12552 12553 /** 12554 * @hide 12555 */ isPositionVisible(final float positionX, final float positionY)12556 public boolean isPositionVisible(final float positionX, final float positionY) { 12557 synchronized (TEMP_POSITION) { 12558 final float[] position = TEMP_POSITION; 12559 position[0] = positionX; 12560 position[1] = positionY; 12561 View view = this; 12562 12563 while (view != null) { 12564 if (view != this) { 12565 // Local scroll is already taken into account in positionX/Y 12566 position[0] -= view.getScrollX(); 12567 position[1] -= view.getScrollY(); 12568 } 12569 12570 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 12571 || position[1] > view.getHeight()) { 12572 return false; 12573 } 12574 12575 if (!view.getMatrix().isIdentity()) { 12576 view.getMatrix().mapPoints(position); 12577 } 12578 12579 position[0] += view.getLeft(); 12580 position[1] += view.getTop(); 12581 12582 final ViewParent parent = view.getParent(); 12583 if (parent instanceof View) { 12584 view = (View) parent; 12585 } else { 12586 // We've reached the ViewRoot, stop iterating 12587 view = null; 12588 } 12589 } 12590 } 12591 12592 // We've been able to walk up the view hierarchy and the position was never clipped 12593 return true; 12594 } 12595 12596 /** 12597 * Performs an accessibility action after it has been offered to the 12598 * delegate. 12599 * 12600 * @hide 12601 */ 12602 @Override performAccessibilityActionInternal(int action, Bundle arguments)12603 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 12604 if (mEditor != null) { 12605 if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) 12606 || mEditor.performSmartActionsAccessibilityAction(action)) { 12607 return true; 12608 } 12609 } 12610 switch (action) { 12611 case AccessibilityNodeInfo.ACTION_CLICK: { 12612 return performAccessibilityActionClick(arguments); 12613 } 12614 case AccessibilityNodeInfo.ACTION_COPY: { 12615 if (isFocused() && canCopy()) { 12616 if (onTextContextMenuItem(ID_COPY)) { 12617 return true; 12618 } 12619 } 12620 } return false; 12621 case AccessibilityNodeInfo.ACTION_PASTE: { 12622 if (isFocused() && canPaste()) { 12623 if (onTextContextMenuItem(ID_PASTE)) { 12624 return true; 12625 } 12626 } 12627 } return false; 12628 case AccessibilityNodeInfo.ACTION_CUT: { 12629 if (isFocused() && canCut()) { 12630 if (onTextContextMenuItem(ID_CUT)) { 12631 return true; 12632 } 12633 } 12634 } return false; 12635 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 12636 ensureIterableTextForAccessibilitySelectable(); 12637 CharSequence text = getIterableTextForAccessibility(); 12638 if (text == null) { 12639 return false; 12640 } 12641 final int start = (arguments != null) ? arguments.getInt( 12642 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 12643 final int end = (arguments != null) ? arguments.getInt( 12644 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 12645 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 12646 // No arguments clears the selection. 12647 if (start == end && end == -1) { 12648 Selection.removeSelection((Spannable) text); 12649 return true; 12650 } 12651 if (start >= 0 && start <= end && end <= text.length()) { 12652 requestFocusOnNonEditableSelectableText(); 12653 Selection.setSelection((Spannable) text, start, end); 12654 // Make sure selection mode is engaged. 12655 if (mEditor != null) { 12656 mEditor.startSelectionActionModeAsync(false); 12657 } 12658 return true; 12659 } 12660 } 12661 } return false; 12662 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 12663 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 12664 ensureIterableTextForAccessibilitySelectable(); 12665 return super.performAccessibilityActionInternal(action, arguments); 12666 } 12667 case ACCESSIBILITY_ACTION_SHARE: { 12668 if (isFocused() && canShare()) { 12669 if (onTextContextMenuItem(ID_SHARE)) { 12670 return true; 12671 } 12672 } 12673 } return false; 12674 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 12675 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 12676 return false; 12677 } 12678 CharSequence text = (arguments != null) ? arguments.getCharSequence( 12679 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 12680 setText(text); 12681 if (mText != null) { 12682 int updatedTextLength = mText.length(); 12683 if (updatedTextLength > 0) { 12684 Selection.setSelection(mSpannable, updatedTextLength); 12685 } 12686 } 12687 } return true; 12688 case R.id.accessibilityActionImeEnter: { 12689 if (isFocused() && isTextEditable()) { 12690 onEditorAction(getImeActionId()); 12691 } 12692 } return true; 12693 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 12694 if (isLongClickable()) { 12695 boolean handled; 12696 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 12697 mEditor.mIsBeingLongClickedByAccessibility = true; 12698 try { 12699 handled = performLongClick(); 12700 } finally { 12701 mEditor.mIsBeingLongClickedByAccessibility = false; 12702 } 12703 } else { 12704 handled = performLongClick(); 12705 } 12706 return handled; 12707 } 12708 } 12709 return false; 12710 default: { 12711 // New ids have static blocks to assign values, so they can't be used in a case 12712 // block. 12713 if (action == R.id.accessibilityActionShowTextSuggestions) { 12714 return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE); 12715 } 12716 return super.performAccessibilityActionInternal(action, arguments); 12717 } 12718 } 12719 } 12720 performAccessibilityActionClick(Bundle arguments)12721 private boolean performAccessibilityActionClick(Bundle arguments) { 12722 boolean handled = false; 12723 12724 if (!isEnabled()) { 12725 return false; 12726 } 12727 12728 if (isClickable() || isLongClickable()) { 12729 // Simulate View.onTouchEvent for an ACTION_UP event 12730 if (isFocusable() && !isFocused()) { 12731 requestFocus(); 12732 } 12733 12734 performClick(); 12735 handled = true; 12736 } 12737 12738 // Show the IME, except when selecting in read-only text. 12739 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12740 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12741 final InputMethodManager imm = getInputMethodManager(); 12742 viewClicked(imm); 12743 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12744 handled |= imm.showSoftInput(this, 0); 12745 } 12746 } 12747 12748 return handled; 12749 } 12750 requestFocusOnNonEditableSelectableText()12751 private void requestFocusOnNonEditableSelectableText() { 12752 if (!isTextEditable() && isTextSelectable()) { 12753 if (!isEnabled()) { 12754 return; 12755 } 12756 12757 if (isFocusable() && !isFocused()) { 12758 requestFocus(); 12759 } 12760 } 12761 } 12762 hasSpannableText()12763 private boolean hasSpannableText() { 12764 return mText != null && mText instanceof Spannable; 12765 } 12766 12767 /** @hide */ 12768 @Override sendAccessibilityEventInternal(int eventType)12769 public void sendAccessibilityEventInternal(int eventType) { 12770 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12771 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12772 } 12773 12774 super.sendAccessibilityEventInternal(eventType); 12775 } 12776 12777 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12778 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12779 // Do not send scroll events since first they are not interesting for 12780 // accessibility and second such events a generated too frequently. 12781 // For details see the implementation of bringTextIntoView(). 12782 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12783 return; 12784 } 12785 super.sendAccessibilityEventUnchecked(event); 12786 } 12787 12788 /** 12789 * Returns the text that should be exposed to accessibility services. 12790 * <p> 12791 * This approximates what is displayed visually. 12792 * 12793 * @return the text that should be exposed to accessibility services, may 12794 * be {@code null} if no text is set 12795 */ 12796 @Nullable 12797 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTextForAccessibility()12798 private CharSequence getTextForAccessibility() { 12799 // If the text is empty, we must be showing the hint text. 12800 if (TextUtils.isEmpty(mText)) { 12801 return mHint; 12802 } 12803 12804 // Otherwise, return whatever text is being displayed. 12805 return TextUtils.trimToParcelableSize(mTransformed); 12806 } 12807 isVisibleToAccessibility()12808 boolean isVisibleToAccessibility() { 12809 return AccessibilityManager.getInstance(mContext).isEnabled() 12810 && (isFocused() || (isSelected() && isShown())); 12811 } 12812 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12813 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12814 int fromIndex, int removedCount, int addedCount) { 12815 AccessibilityEvent event = 12816 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12817 event.setFromIndex(fromIndex); 12818 event.setRemovedCount(removedCount); 12819 event.setAddedCount(addedCount); 12820 event.setBeforeText(beforeText); 12821 sendAccessibilityEventUnchecked(event); 12822 } 12823 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)12824 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12825 int fromIndex, int toIndex) { 12826 AccessibilityEvent event = 12827 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12828 event.setFromIndex(fromIndex); 12829 event.setToIndex(toIndex); 12830 event.setBeforeText(beforeText); 12831 sendAccessibilityEventUnchecked(event); 12832 } 12833 getInputMethodManager()12834 private InputMethodManager getInputMethodManager() { 12835 return getContext().getSystemService(InputMethodManager.class); 12836 } 12837 12838 /** 12839 * Returns whether this text view is a current input method target. The 12840 * default implementation just checks with {@link InputMethodManager}. 12841 * @return True if the TextView is a current input method target; false otherwise. 12842 */ isInputMethodTarget()12843 public boolean isInputMethodTarget() { 12844 InputMethodManager imm = getInputMethodManager(); 12845 return imm != null && imm.isActive(this); 12846 } 12847 12848 static final int ID_SELECT_ALL = android.R.id.selectAll; 12849 static final int ID_UNDO = android.R.id.undo; 12850 static final int ID_REDO = android.R.id.redo; 12851 static final int ID_CUT = android.R.id.cut; 12852 static final int ID_COPY = android.R.id.copy; 12853 static final int ID_PASTE = android.R.id.paste; 12854 static final int ID_SHARE = android.R.id.shareText; 12855 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12856 static final int ID_REPLACE = android.R.id.replaceText; 12857 static final int ID_ASSIST = android.R.id.textAssist; 12858 static final int ID_AUTOFILL = android.R.id.autofill; 12859 12860 /** 12861 * Called when a context menu option for the text view is selected. Currently 12862 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12863 * {@link android.R.id#copy}, {@link android.R.id#paste}, 12864 * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or 12865 * {@link android.R.id#shareText}. 12866 * 12867 * @return true if the context menu item action was performed. 12868 */ onTextContextMenuItem(int id)12869 public boolean onTextContextMenuItem(int id) { 12870 int min = 0; 12871 int max = mText.length(); 12872 12873 if (isFocused()) { 12874 final int selStart = getSelectionStart(); 12875 final int selEnd = getSelectionEnd(); 12876 12877 min = Math.max(0, Math.min(selStart, selEnd)); 12878 max = Math.max(0, Math.max(selStart, selEnd)); 12879 } 12880 12881 switch (id) { 12882 case ID_SELECT_ALL: 12883 final boolean hadSelection = hasSelection(); 12884 selectAllText(); 12885 if (mEditor != null && hadSelection) { 12886 mEditor.invalidateActionModeAsync(); 12887 } 12888 return true; 12889 12890 case ID_UNDO: 12891 if (mEditor != null) { 12892 mEditor.undo(); 12893 } 12894 return true; // Returns true even if nothing was undone. 12895 12896 case ID_REDO: 12897 if (mEditor != null) { 12898 mEditor.redo(); 12899 } 12900 return true; // Returns true even if nothing was undone. 12901 12902 case ID_PASTE: 12903 paste(true /* withFormatting */); 12904 return true; 12905 12906 case ID_PASTE_AS_PLAIN_TEXT: 12907 paste(false /* withFormatting */); 12908 return true; 12909 12910 case ID_CUT: 12911 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12912 if (setPrimaryClip(cutData)) { 12913 deleteText_internal(min, max); 12914 } else { 12915 Toast.makeText(getContext(), 12916 com.android.internal.R.string.failed_to_copy_to_clipboard, 12917 Toast.LENGTH_SHORT).show(); 12918 } 12919 return true; 12920 12921 case ID_COPY: 12922 // For link action mode in a non-selectable/non-focusable TextView, 12923 // make sure that we set the appropriate min/max. 12924 final int selStart = getSelectionStart(); 12925 final int selEnd = getSelectionEnd(); 12926 min = Math.max(0, Math.min(selStart, selEnd)); 12927 max = Math.max(0, Math.max(selStart, selEnd)); 12928 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12929 if (setPrimaryClip(copyData)) { 12930 stopTextActionMode(); 12931 } else { 12932 Toast.makeText(getContext(), 12933 com.android.internal.R.string.failed_to_copy_to_clipboard, 12934 Toast.LENGTH_SHORT).show(); 12935 } 12936 return true; 12937 12938 case ID_REPLACE: 12939 if (mEditor != null) { 12940 mEditor.replace(); 12941 } 12942 return true; 12943 12944 case ID_SHARE: 12945 shareSelectedText(); 12946 return true; 12947 12948 case ID_AUTOFILL: 12949 requestAutofill(); 12950 stopTextActionMode(); 12951 return true; 12952 } 12953 return false; 12954 } 12955 12956 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTransformedText(int start, int end)12957 CharSequence getTransformedText(int start, int end) { 12958 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12959 } 12960 12961 @Override performLongClick()12962 public boolean performLongClick() { 12963 if (DEBUG_CURSOR) { 12964 logCursor("performLongClick", null); 12965 } 12966 12967 boolean handled = false; 12968 boolean performedHapticFeedback = false; 12969 12970 if (mEditor != null) { 12971 mEditor.mIsBeingLongClicked = true; 12972 } 12973 12974 if (super.performLongClick()) { 12975 handled = true; 12976 performedHapticFeedback = true; 12977 } 12978 12979 if (mEditor != null) { 12980 handled |= mEditor.performLongClick(handled); 12981 mEditor.mIsBeingLongClicked = false; 12982 } 12983 12984 if (handled) { 12985 if (!performedHapticFeedback) { 12986 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12987 } 12988 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12989 } else { 12990 MetricsLogger.action( 12991 mContext, 12992 MetricsEvent.TEXT_LONGPRESS, 12993 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12994 } 12995 12996 return handled; 12997 } 12998 12999 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)13000 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 13001 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 13002 if (mEditor != null) { 13003 mEditor.onScrollChanged(); 13004 } 13005 } 13006 13007 /** 13008 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 13009 * by the IME or by the spell checker as the user types. This is done by adding 13010 * {@link SuggestionSpan}s to the text. 13011 * 13012 * When suggestions are enabled (default), this list of suggestions will be displayed when the 13013 * user asks for them on these parts of the text. This value depends on the inputType of this 13014 * TextView. 13015 * 13016 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 13017 * 13018 * In addition, the type variation must be one of 13019 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 13020 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 13021 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 13022 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 13023 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 13024 * 13025 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 13026 * 13027 * @return true if the suggestions popup window is enabled, based on the inputType. 13028 */ isSuggestionsEnabled()13029 public boolean isSuggestionsEnabled() { 13030 if (mEditor == null) return false; 13031 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 13032 return false; 13033 } 13034 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 13035 13036 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 13037 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 13038 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 13039 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 13040 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 13041 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 13042 } 13043 13044 /** 13045 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 13046 * selection is initiated in this View. 13047 * 13048 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 13049 * Paste, Replace and Share actions, depending on what this View supports. 13050 * 13051 * <p>A custom implementation can add new entries in the default menu in its 13052 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 13053 * method. The default actions can also be removed from the menu using 13054 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 13055 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 13056 * {@link android.R.id#pasteAsPlainText} (starting at API level 23), 13057 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 13058 * 13059 * <p>Returning false from 13060 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 13061 * will prevent the action mode from being started. 13062 * 13063 * <p>Action click events should be handled by the custom implementation of 13064 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 13065 * android.view.MenuItem)}. 13066 * 13067 * <p>Note that text selection mode is not started when a TextView receives focus and the 13068 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 13069 * that case, to allow for quick replacement. 13070 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)13071 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 13072 createEditorIfNeeded(); 13073 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 13074 } 13075 13076 /** 13077 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 13078 * 13079 * @return The current custom selection callback. 13080 */ getCustomSelectionActionModeCallback()13081 public ActionMode.Callback getCustomSelectionActionModeCallback() { 13082 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 13083 } 13084 13085 /** 13086 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 13087 * insertion is initiated in this View. 13088 * The standard implementation populates the menu with a subset of Select All, 13089 * Paste and Replace actions, depending on what this View supports. 13090 * 13091 * <p>A custom implementation can add new entries in the default menu in its 13092 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 13093 * android.view.Menu)} method. The default actions can also be removed from the menu using 13094 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 13095 * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API 13096 * level 23) or {@link android.R.id#replaceText} ids as parameters.</p> 13097 * 13098 * <p>Returning false from 13099 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 13100 * android.view.Menu)} will prevent the action mode from being started.</p> 13101 * 13102 * <p>Action click events should be handled by the custom implementation of 13103 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 13104 * android.view.MenuItem)}.</p> 13105 * 13106 * <p>Note that text insertion mode is not started when a TextView receives focus and the 13107 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 13108 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)13109 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 13110 createEditorIfNeeded(); 13111 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 13112 } 13113 13114 /** 13115 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 13116 * 13117 * @return The current custom insertion callback. 13118 */ getCustomInsertionActionModeCallback()13119 public ActionMode.Callback getCustomInsertionActionModeCallback() { 13120 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 13121 } 13122 13123 /** 13124 * Sets the {@link TextClassifier} for this TextView. 13125 */ setTextClassifier(@ullable TextClassifier textClassifier)13126 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 13127 mTextClassifier = textClassifier; 13128 } 13129 13130 /** 13131 * Returns the {@link TextClassifier} used by this TextView. 13132 * If no TextClassifier has been set, this TextView uses the default set by the 13133 * {@link TextClassificationManager}. 13134 */ 13135 @NonNull getTextClassifier()13136 public TextClassifier getTextClassifier() { 13137 if (mTextClassifier == null) { 13138 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 13139 if (tcm != null) { 13140 return tcm.getTextClassifier(); 13141 } 13142 return TextClassifier.NO_OP; 13143 } 13144 return mTextClassifier; 13145 } 13146 13147 /** 13148 * Returns a session-aware text classifier. 13149 * This method creates one if none already exists or the current one is destroyed. 13150 */ 13151 @NonNull getTextClassificationSession()13152 TextClassifier getTextClassificationSession() { 13153 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 13154 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 13155 if (tcm != null) { 13156 final String widgetType; 13157 if (isTextEditable()) { 13158 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 13159 } else if (isTextSelectable()) { 13160 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 13161 } else { 13162 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 13163 } 13164 mTextClassificationContext = new TextClassificationContext.Builder( 13165 mContext.getPackageName(), widgetType) 13166 .build(); 13167 if (mTextClassifier != null) { 13168 mTextClassificationSession = tcm.createTextClassificationSession( 13169 mTextClassificationContext, mTextClassifier); 13170 } else { 13171 mTextClassificationSession = tcm.createTextClassificationSession( 13172 mTextClassificationContext); 13173 } 13174 } else { 13175 mTextClassificationSession = TextClassifier.NO_OP; 13176 } 13177 } 13178 return mTextClassificationSession; 13179 } 13180 13181 /** 13182 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 13183 * @see #getTextClassificationSession() 13184 */ 13185 @Nullable getTextClassificationContext()13186 TextClassificationContext getTextClassificationContext() { 13187 return mTextClassificationContext; 13188 } 13189 13190 /** 13191 * Returns true if this TextView uses a no-op TextClassifier. 13192 */ usesNoOpTextClassifier()13193 boolean usesNoOpTextClassifier() { 13194 return getTextClassifier() == TextClassifier.NO_OP; 13195 } 13196 13197 /** 13198 * Starts an ActionMode for the specified TextLinkSpan. 13199 * 13200 * @return Whether or not we're attempting to start the action mode. 13201 * @hide 13202 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)13203 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 13204 Preconditions.checkNotNull(clickedSpan); 13205 13206 if (!(mText instanceof Spanned)) { 13207 return false; 13208 } 13209 13210 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 13211 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 13212 13213 if (start < 0 || end > mText.length() || start >= end) { 13214 return false; 13215 } 13216 13217 createEditorIfNeeded(); 13218 mEditor.startLinkActionModeAsync(start, end); 13219 return true; 13220 } 13221 13222 /** 13223 * Handles a click on the specified TextLinkSpan. 13224 * 13225 * @return Whether or not the click is being handled. 13226 * @hide 13227 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)13228 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 13229 Preconditions.checkNotNull(clickedSpan); 13230 if (mText instanceof Spanned) { 13231 final Spanned spanned = (Spanned) mText; 13232 final int start = spanned.getSpanStart(clickedSpan); 13233 final int end = spanned.getSpanEnd(clickedSpan); 13234 if (start >= 0 && end <= mText.length() && start < end) { 13235 final TextClassification.Request request = new TextClassification.Request.Builder( 13236 mText, start, end) 13237 .setDefaultLocales(getTextLocales()) 13238 .build(); 13239 final Supplier<TextClassification> supplier = () -> 13240 getTextClassificationSession().classifyText(request); 13241 final Consumer<TextClassification> consumer = classification -> { 13242 if (classification != null) { 13243 if (!classification.getActions().isEmpty()) { 13244 try { 13245 classification.getActions().get(0).getActionIntent().send(); 13246 } catch (PendingIntent.CanceledException e) { 13247 Log.e(LOG_TAG, "Error sending PendingIntent", e); 13248 } 13249 } else { 13250 Log.d(LOG_TAG, "No link action to perform"); 13251 } 13252 } else { 13253 // classification == null 13254 Log.d(LOG_TAG, "Timeout while classifying text"); 13255 } 13256 }; 13257 CompletableFuture.supplyAsync(supplier) 13258 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 13259 .thenAccept(consumer); 13260 return true; 13261 } 13262 } 13263 return false; 13264 } 13265 13266 /** 13267 * @hide 13268 */ 13269 @UnsupportedAppUsage stopTextActionMode()13270 protected void stopTextActionMode() { 13271 if (mEditor != null) { 13272 mEditor.stopTextActionMode(); 13273 } 13274 } 13275 13276 /** @hide */ hideFloatingToolbar(int durationMs)13277 public void hideFloatingToolbar(int durationMs) { 13278 if (mEditor != null) { 13279 mEditor.hideFloatingToolbar(durationMs); 13280 } 13281 } 13282 canUndo()13283 boolean canUndo() { 13284 return mEditor != null && mEditor.canUndo(); 13285 } 13286 canRedo()13287 boolean canRedo() { 13288 return mEditor != null && mEditor.canRedo(); 13289 } 13290 canCut()13291 boolean canCut() { 13292 if (hasPasswordTransformationMethod()) { 13293 return false; 13294 } 13295 13296 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 13297 && mEditor.mKeyListener != null) { 13298 return true; 13299 } 13300 13301 return false; 13302 } 13303 canCopy()13304 boolean canCopy() { 13305 if (hasPasswordTransformationMethod()) { 13306 return false; 13307 } 13308 13309 if (mText.length() > 0 && hasSelection() && mEditor != null) { 13310 return true; 13311 } 13312 13313 return false; 13314 } 13315 canReplace()13316 boolean canReplace() { 13317 if (hasPasswordTransformationMethod()) { 13318 return false; 13319 } 13320 13321 return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null) 13322 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); 13323 } 13324 canShare()13325 boolean canShare() { 13326 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 13327 return false; 13328 } 13329 return canCopy(); 13330 } 13331 isDeviceProvisioned()13332 boolean isDeviceProvisioned() { 13333 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 13334 mDeviceProvisionedState = Settings.Global.getInt( 13335 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 13336 ? DEVICE_PROVISIONED_YES 13337 : DEVICE_PROVISIONED_NO; 13338 } 13339 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 13340 } 13341 13342 @UnsupportedAppUsage canPaste()13343 boolean canPaste() { 13344 return (mText instanceof Editable 13345 && mEditor != null && mEditor.mKeyListener != null 13346 && getSelectionStart() >= 0 13347 && getSelectionEnd() >= 0 13348 && getClipboardManagerForUser().hasPrimaryClip()); 13349 } 13350 canPasteAsPlainText()13351 boolean canPasteAsPlainText() { 13352 if (!canPaste()) { 13353 return false; 13354 } 13355 13356 final ClipDescription description = 13357 getClipboardManagerForUser().getPrimaryClipDescription(); 13358 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 13359 return (isPlainType && description.isStyledText()) 13360 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 13361 } 13362 canProcessText()13363 boolean canProcessText() { 13364 if (getId() == View.NO_ID) { 13365 return false; 13366 } 13367 return canShare(); 13368 } 13369 canSelectAllText()13370 boolean canSelectAllText() { 13371 return canSelectText() && !hasPasswordTransformationMethod() 13372 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 13373 } 13374 selectAllText()13375 boolean selectAllText() { 13376 if (mEditor != null) { 13377 // Hide the toolbar before changing the selection to avoid flickering. 13378 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 13379 } 13380 final int length = mText.length(); 13381 Selection.setSelection(mSpannable, 0, length); 13382 return length > 0; 13383 } 13384 paste(boolean withFormatting)13385 private void paste(boolean withFormatting) { 13386 ClipboardManager clipboard = getClipboardManagerForUser(); 13387 ClipData clip = clipboard.getPrimaryClip(); 13388 if (clip == null) { 13389 return; 13390 } 13391 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) 13392 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) 13393 .build(); 13394 performReceiveContent(payload); 13395 sLastCutCopyOrTextChangedTime = 0; 13396 } 13397 shareSelectedText()13398 private void shareSelectedText() { 13399 String selectedText = getSelectedText(); 13400 if (selectedText != null && !selectedText.isEmpty()) { 13401 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 13402 sharingIntent.setType("text/plain"); 13403 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 13404 selectedText = TextUtils.trimToParcelableSize(selectedText); 13405 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 13406 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 13407 Selection.setSelection(mSpannable, getSelectionEnd()); 13408 } 13409 } 13410 13411 @CheckResult setPrimaryClip(ClipData clip)13412 private boolean setPrimaryClip(ClipData clip) { 13413 ClipboardManager clipboard = getClipboardManagerForUser(); 13414 try { 13415 clipboard.setPrimaryClip(clip); 13416 } catch (Throwable t) { 13417 return false; 13418 } 13419 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 13420 return true; 13421 } 13422 13423 /** 13424 * Get the character offset closest to the specified absolute position. A typical use case is to 13425 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 13426 * 13427 * @param x The horizontal absolute position of a point on screen 13428 * @param y The vertical absolute position of a point on screen 13429 * @return the character offset for the character whose position is closest to the specified 13430 * position. Returns -1 if there is no layout. 13431 */ getOffsetForPosition(float x, float y)13432 public int getOffsetForPosition(float x, float y) { 13433 if (getLayout() == null) return -1; 13434 final int line = getLineAtCoordinate(y); 13435 final int offset = getOffsetAtCoordinate(line, x); 13436 return offset; 13437 } 13438 convertToLocalHorizontalCoordinate(float x)13439 float convertToLocalHorizontalCoordinate(float x) { 13440 x -= getTotalPaddingLeft(); 13441 // Clamp the position to inside of the view. 13442 x = Math.max(0.0f, x); 13443 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 13444 x += getScrollX(); 13445 return x; 13446 } 13447 13448 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getLineAtCoordinate(float y)13449 int getLineAtCoordinate(float y) { 13450 y -= getTotalPaddingTop(); 13451 // Clamp the position to inside of the view. 13452 y = Math.max(0.0f, y); 13453 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 13454 y += getScrollY(); 13455 return getLayout().getLineForVertical((int) y); 13456 } 13457 getLineAtCoordinateUnclamped(float y)13458 int getLineAtCoordinateUnclamped(float y) { 13459 y -= getTotalPaddingTop(); 13460 y += getScrollY(); 13461 return getLayout().getLineForVertical((int) y); 13462 } 13463 getOffsetAtCoordinate(int line, float x)13464 int getOffsetAtCoordinate(int line, float x) { 13465 x = convertToLocalHorizontalCoordinate(x); 13466 return getLayout().getOffsetForHorizontal(line, x); 13467 } 13468 13469 /** 13470 * Handles drag events sent by the system following a call to 13471 * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) 13472 * startDragAndDrop()}. 13473 * 13474 * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent} 13475 * implementation. 13476 * 13477 * <p>If this text view is editable, accepts all drag actions (returns true for an 13478 * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all 13479 * subsequent drag events). While the drag is in progress, updates the cursor position 13480 * to follow the touch location. Once a drop event is received, handles content insertion 13481 * via {@link #performReceiveContent}. 13482 * 13483 * @param event The {@link android.view.DragEvent} sent by the system. 13484 * The {@link android.view.DragEvent#getAction()} method returns an action type constant 13485 * defined in DragEvent, indicating the type of drag event represented by this object. 13486 * @return Returns true if this text view is editable and delegates to super otherwise. 13487 * See {@link View#onDragEvent}. 13488 */ 13489 @Override onDragEvent(DragEvent event)13490 public boolean onDragEvent(DragEvent event) { 13491 if (mEditor == null || !mEditor.hasInsertionController()) { 13492 // If this TextView is not editable, defer to the default View implementation. This 13493 // will check for the presence of an OnReceiveContentListener and accept/reject 13494 // drag events depending on whether the listener is/isn't set. 13495 return super.onDragEvent(event); 13496 } 13497 switch (event.getAction()) { 13498 case DragEvent.ACTION_DRAG_STARTED: 13499 return true; 13500 13501 case DragEvent.ACTION_DRAG_ENTERED: 13502 TextView.this.requestFocus(); 13503 return true; 13504 13505 case DragEvent.ACTION_DRAG_LOCATION: 13506 if (mText instanceof Spannable) { 13507 final int offset = getOffsetForPosition(event.getX(), event.getY()); 13508 Selection.setSelection(mSpannable, offset); 13509 } 13510 return true; 13511 13512 case DragEvent.ACTION_DROP: 13513 if (mEditor != null) mEditor.onDrop(event); 13514 return true; 13515 13516 case DragEvent.ACTION_DRAG_ENDED: 13517 case DragEvent.ACTION_DRAG_EXITED: 13518 default: 13519 return true; 13520 } 13521 } 13522 isInBatchEditMode()13523 boolean isInBatchEditMode() { 13524 if (mEditor == null) return false; 13525 final Editor.InputMethodState ims = mEditor.mInputMethodState; 13526 if (ims != null) { 13527 return ims.mBatchEditNesting > 0; 13528 } 13529 return mEditor.mInBatchEditControllers; 13530 } 13531 13532 @Override onRtlPropertiesChanged(int layoutDirection)13533 public void onRtlPropertiesChanged(int layoutDirection) { 13534 super.onRtlPropertiesChanged(layoutDirection); 13535 13536 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 13537 if (mTextDir != newTextDir) { 13538 mTextDir = newTextDir; 13539 if (mLayout != null) { 13540 checkForRelayout(); 13541 } 13542 } 13543 } 13544 13545 /** 13546 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 13547 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 13548 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 13549 * return value may not be the same as the one TextView uses if the View's layout direction is 13550 * not resolved or detached from parent root view. 13551 */ getTextDirectionHeuristic()13552 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 13553 if (hasPasswordTransformationMethod()) { 13554 // passwords fields should be LTR 13555 return TextDirectionHeuristics.LTR; 13556 } 13557 13558 if (mEditor != null 13559 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 13560 == EditorInfo.TYPE_CLASS_PHONE) { 13561 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 13562 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 13563 // RTL digits. 13564 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 13565 final String zero = symbols.getDigitStrings()[0]; 13566 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 13567 // direction. 13568 final int firstCodepoint = zero.codePointAt(0); 13569 final byte digitDirection = Character.getDirectionality(firstCodepoint); 13570 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 13571 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 13572 return TextDirectionHeuristics.RTL; 13573 } else { 13574 return TextDirectionHeuristics.LTR; 13575 } 13576 } 13577 13578 // Always need to resolve layout direction first 13579 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 13580 13581 // Now, we can select the heuristic 13582 switch (getTextDirection()) { 13583 default: 13584 case TEXT_DIRECTION_FIRST_STRONG: 13585 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 13586 TextDirectionHeuristics.FIRSTSTRONG_LTR); 13587 case TEXT_DIRECTION_ANY_RTL: 13588 return TextDirectionHeuristics.ANYRTL_LTR; 13589 case TEXT_DIRECTION_LTR: 13590 return TextDirectionHeuristics.LTR; 13591 case TEXT_DIRECTION_RTL: 13592 return TextDirectionHeuristics.RTL; 13593 case TEXT_DIRECTION_LOCALE: 13594 return TextDirectionHeuristics.LOCALE; 13595 case TEXT_DIRECTION_FIRST_STRONG_LTR: 13596 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 13597 case TEXT_DIRECTION_FIRST_STRONG_RTL: 13598 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 13599 } 13600 } 13601 13602 /** 13603 * @hide 13604 */ 13605 @Override onResolveDrawables(int layoutDirection)13606 public void onResolveDrawables(int layoutDirection) { 13607 // No need to resolve twice 13608 if (mLastLayoutDirection == layoutDirection) { 13609 return; 13610 } 13611 mLastLayoutDirection = layoutDirection; 13612 13613 // Resolve drawables 13614 if (mDrawables != null) { 13615 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 13616 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 13617 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 13618 applyCompoundDrawableTint(); 13619 } 13620 } 13621 } 13622 13623 /** 13624 * Prepares a drawable for display by propagating layout direction and 13625 * drawable state. 13626 * 13627 * @param dr the drawable to prepare 13628 */ prepareDrawableForDisplay(@ullable Drawable dr)13629 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 13630 if (dr == null) { 13631 return; 13632 } 13633 13634 dr.setLayoutDirection(getLayoutDirection()); 13635 13636 if (dr.isStateful()) { 13637 dr.setState(getDrawableState()); 13638 dr.jumpToCurrentState(); 13639 } 13640 } 13641 13642 /** 13643 * @hide 13644 */ resetResolvedDrawables()13645 protected void resetResolvedDrawables() { 13646 super.resetResolvedDrawables(); 13647 mLastLayoutDirection = -1; 13648 } 13649 13650 /** 13651 * @hide 13652 */ viewClicked(InputMethodManager imm)13653 protected void viewClicked(InputMethodManager imm) { 13654 if (imm != null) { 13655 imm.viewClicked(this); 13656 } 13657 } 13658 13659 /** 13660 * Deletes the range of text [start, end[. 13661 * @hide 13662 */ 13663 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) deleteText_internal(int start, int end)13664 protected void deleteText_internal(int start, int end) { 13665 ((Editable) mText).delete(start, end); 13666 } 13667 13668 /** 13669 * Replaces the range of text [start, end[ by replacement text 13670 * @hide 13671 */ replaceText_internal(int start, int end, CharSequence text)13672 protected void replaceText_internal(int start, int end, CharSequence text) { 13673 ((Editable) mText).replace(start, end, text); 13674 } 13675 13676 /** 13677 * Sets a span on the specified range of text 13678 * @hide 13679 */ setSpan_internal(Object span, int start, int end, int flags)13680 protected void setSpan_internal(Object span, int start, int end, int flags) { 13681 ((Editable) mText).setSpan(span, start, end, flags); 13682 } 13683 13684 /** 13685 * Moves the cursor to the specified offset position in text 13686 * @hide 13687 */ setCursorPosition_internal(int start, int end)13688 protected void setCursorPosition_internal(int start, int end) { 13689 Selection.setSelection(((Editable) mText), start, end); 13690 } 13691 13692 /** 13693 * An Editor should be created as soon as any of the editable-specific fields (grouped 13694 * inside the Editor object) is assigned to a non-default value. 13695 * This method will create the Editor if needed. 13696 * 13697 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 13698 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 13699 * Editor for backward compatibility, as soon as one of these fields is assigned. 13700 * 13701 * Also note that for performance reasons, the mEditor is created when needed, but not 13702 * reset when no more edit-specific fields are needed. 13703 */ 13704 @UnsupportedAppUsage createEditorIfNeeded()13705 private void createEditorIfNeeded() { 13706 if (mEditor == null) { 13707 mEditor = new Editor(this); 13708 } 13709 } 13710 13711 /** 13712 * @hide 13713 */ 13714 @Override 13715 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getIterableTextForAccessibility()13716 public CharSequence getIterableTextForAccessibility() { 13717 return mText; 13718 } 13719 ensureIterableTextForAccessibilitySelectable()13720 private void ensureIterableTextForAccessibilitySelectable() { 13721 if (!(mText instanceof Spannable)) { 13722 setText(mText, BufferType.SPANNABLE); 13723 } 13724 } 13725 13726 /** 13727 * @hide 13728 */ 13729 @Override getIteratorForGranularity(int granularity)13730 public TextSegmentIterator getIteratorForGranularity(int granularity) { 13731 switch (granularity) { 13732 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 13733 Spannable text = (Spannable) getIterableTextForAccessibility(); 13734 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13735 AccessibilityIterators.LineTextSegmentIterator iterator = 13736 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 13737 iterator.initialize(text, getLayout()); 13738 return iterator; 13739 } 13740 } break; 13741 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 13742 Spannable text = (Spannable) getIterableTextForAccessibility(); 13743 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13744 AccessibilityIterators.PageTextSegmentIterator iterator = 13745 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 13746 iterator.initialize(this); 13747 return iterator; 13748 } 13749 } break; 13750 } 13751 return super.getIteratorForGranularity(granularity); 13752 } 13753 13754 /** 13755 * @hide 13756 */ 13757 @Override getAccessibilitySelectionStart()13758 public int getAccessibilitySelectionStart() { 13759 return getSelectionStart(); 13760 } 13761 13762 /** 13763 * @hide 13764 */ isAccessibilitySelectionExtendable()13765 public boolean isAccessibilitySelectionExtendable() { 13766 return true; 13767 } 13768 13769 /** 13770 * @hide 13771 */ prepareForExtendedAccessibilitySelection()13772 public void prepareForExtendedAccessibilitySelection() { 13773 requestFocusOnNonEditableSelectableText(); 13774 } 13775 13776 /** 13777 * @hide 13778 */ 13779 @Override getAccessibilitySelectionEnd()13780 public int getAccessibilitySelectionEnd() { 13781 return getSelectionEnd(); 13782 } 13783 13784 /** 13785 * @hide 13786 */ 13787 @Override setAccessibilitySelection(int start, int end)13788 public void setAccessibilitySelection(int start, int end) { 13789 if (getAccessibilitySelectionStart() == start 13790 && getAccessibilitySelectionEnd() == end) { 13791 return; 13792 } 13793 CharSequence text = getIterableTextForAccessibility(); 13794 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13795 Selection.setSelection((Spannable) text, start, end); 13796 } else { 13797 Selection.removeSelection((Spannable) text); 13798 } 13799 // Hide all selection controllers used for adjusting selection 13800 // since we are doing so explicitlty by other means and these 13801 // controllers interact with how selection behaves. 13802 if (mEditor != null) { 13803 mEditor.hideCursorAndSpanControllers(); 13804 mEditor.stopTextActionMode(); 13805 } 13806 } 13807 13808 /** @hide */ 13809 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13810 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13811 super.encodeProperties(stream); 13812 13813 TruncateAt ellipsize = getEllipsize(); 13814 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13815 stream.addProperty("text:textSize", getTextSize()); 13816 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13817 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13818 stream.addProperty("text:selectionStart", getSelectionStart()); 13819 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13820 stream.addProperty("text:curTextColor", mCurTextColor); 13821 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 13822 stream.addProperty("text:gravity", mGravity); 13823 } 13824 13825 /** 13826 * User interface state that is stored by TextView for implementing 13827 * {@link View#onSaveInstanceState}. 13828 */ 13829 public static class SavedState extends BaseSavedState { 13830 int selStart = -1; 13831 int selEnd = -1; 13832 @UnsupportedAppUsage 13833 CharSequence text; 13834 boolean frozenWithFocus; 13835 CharSequence error; 13836 ParcelableParcel editorState; // Optional state from Editor. 13837 SavedState(Parcelable superState)13838 SavedState(Parcelable superState) { 13839 super(superState); 13840 } 13841 13842 @Override writeToParcel(Parcel out, int flags)13843 public void writeToParcel(Parcel out, int flags) { 13844 super.writeToParcel(out, flags); 13845 out.writeInt(selStart); 13846 out.writeInt(selEnd); 13847 out.writeInt(frozenWithFocus ? 1 : 0); 13848 TextUtils.writeToParcel(text, out, flags); 13849 13850 if (error == null) { 13851 out.writeInt(0); 13852 } else { 13853 out.writeInt(1); 13854 TextUtils.writeToParcel(error, out, flags); 13855 } 13856 13857 if (editorState == null) { 13858 out.writeInt(0); 13859 } else { 13860 out.writeInt(1); 13861 editorState.writeToParcel(out, flags); 13862 } 13863 } 13864 13865 @Override toString()13866 public String toString() { 13867 String str = "TextView.SavedState{" 13868 + Integer.toHexString(System.identityHashCode(this)) 13869 + " start=" + selStart + " end=" + selEnd; 13870 if (text != null) { 13871 str += " text=" + text; 13872 } 13873 return str + "}"; 13874 } 13875 13876 @SuppressWarnings("hiding") 13877 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13878 new Parcelable.Creator<SavedState>() { 13879 public SavedState createFromParcel(Parcel in) { 13880 return new SavedState(in); 13881 } 13882 13883 public SavedState[] newArray(int size) { 13884 return new SavedState[size]; 13885 } 13886 }; 13887 SavedState(Parcel in)13888 private SavedState(Parcel in) { 13889 super(in); 13890 selStart = in.readInt(); 13891 selEnd = in.readInt(); 13892 frozenWithFocus = (in.readInt() != 0); 13893 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13894 13895 if (in.readInt() != 0) { 13896 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13897 } 13898 13899 if (in.readInt() != 0) { 13900 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13901 } 13902 } 13903 } 13904 13905 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13906 @NonNull 13907 private char[] mChars; 13908 private int mStart, mLength; 13909 CharWrapper(@onNull char[] chars, int start, int len)13910 CharWrapper(@NonNull char[] chars, int start, int len) { 13911 mChars = chars; 13912 mStart = start; 13913 mLength = len; 13914 } 13915 set(@onNull char[] chars, int start, int len)13916 /* package */ void set(@NonNull char[] chars, int start, int len) { 13917 mChars = chars; 13918 mStart = start; 13919 mLength = len; 13920 } 13921 length()13922 public int length() { 13923 return mLength; 13924 } 13925 charAt(int off)13926 public char charAt(int off) { 13927 return mChars[off + mStart]; 13928 } 13929 13930 @Override toString()13931 public String toString() { 13932 return new String(mChars, mStart, mLength); 13933 } 13934 subSequence(int start, int end)13935 public CharSequence subSequence(int start, int end) { 13936 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13937 throw new IndexOutOfBoundsException(start + ", " + end); 13938 } 13939 13940 return new String(mChars, start + mStart, end - start); 13941 } 13942 getChars(int start, int end, char[] buf, int off)13943 public void getChars(int start, int end, char[] buf, int off) { 13944 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13945 throw new IndexOutOfBoundsException(start + ", " + end); 13946 } 13947 13948 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13949 } 13950 13951 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13952 public void drawText(BaseCanvas c, int start, int end, 13953 float x, float y, Paint p) { 13954 c.drawText(mChars, start + mStart, end - start, x, y, p); 13955 } 13956 13957 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13958 public void drawTextRun(BaseCanvas c, int start, int end, 13959 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13960 int count = end - start; 13961 int contextCount = contextEnd - contextStart; 13962 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13963 contextCount, x, y, isRtl, p); 13964 } 13965 measureText(int start, int end, Paint p)13966 public float measureText(int start, int end, Paint p) { 13967 return p.measureText(mChars, start + mStart, end - start); 13968 } 13969 getTextWidths(int start, int end, float[] widths, Paint p)13970 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13971 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13972 } 13973 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13974 public float getTextRunAdvances(int start, int end, int contextStart, 13975 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13976 Paint p) { 13977 int count = end - start; 13978 int contextCount = contextEnd - contextStart; 13979 return p.getTextRunAdvances(mChars, start + mStart, count, 13980 contextStart + mStart, contextCount, isRtl, advances, 13981 advancesIndex); 13982 } 13983 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13984 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13985 int offset, int cursorOpt, Paint p) { 13986 int contextCount = contextEnd - contextStart; 13987 return p.getTextRunCursor(mChars, contextStart + mStart, 13988 contextCount, isRtl, offset + mStart, cursorOpt); 13989 } 13990 } 13991 13992 private static final class Marquee { 13993 // TODO: Add an option to configure this 13994 private static final float MARQUEE_DELTA_MAX = 0.07f; 13995 private static final int MARQUEE_DELAY = 1200; 13996 private static final int MARQUEE_DP_PER_SECOND = 30; 13997 13998 private static final byte MARQUEE_STOPPED = 0x0; 13999 private static final byte MARQUEE_STARTING = 0x1; 14000 private static final byte MARQUEE_RUNNING = 0x2; 14001 14002 private final WeakReference<TextView> mView; 14003 private final Choreographer mChoreographer; 14004 14005 private byte mStatus = MARQUEE_STOPPED; 14006 private final float mPixelsPerMs; 14007 private float mMaxScroll; 14008 private float mMaxFadeScroll; 14009 private float mGhostStart; 14010 private float mGhostOffset; 14011 private float mFadeStop; 14012 private int mRepeatLimit; 14013 14014 private float mScroll; 14015 private long mLastAnimationMs; 14016 Marquee(TextView v)14017 Marquee(TextView v) { 14018 final float density = v.getContext().getResources().getDisplayMetrics().density; 14019 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 14020 mView = new WeakReference<TextView>(v); 14021 mChoreographer = Choreographer.getInstance(); 14022 } 14023 14024 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 14025 @Override 14026 public void doFrame(long frameTimeNanos) { 14027 tick(); 14028 } 14029 }; 14030 14031 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 14032 @Override 14033 public void doFrame(long frameTimeNanos) { 14034 mStatus = MARQUEE_RUNNING; 14035 mLastAnimationMs = mChoreographer.getFrameTime(); 14036 tick(); 14037 } 14038 }; 14039 14040 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 14041 @Override 14042 public void doFrame(long frameTimeNanos) { 14043 if (mStatus == MARQUEE_RUNNING) { 14044 if (mRepeatLimit >= 0) { 14045 mRepeatLimit--; 14046 } 14047 start(mRepeatLimit); 14048 } 14049 } 14050 }; 14051 tick()14052 void tick() { 14053 if (mStatus != MARQUEE_RUNNING) { 14054 return; 14055 } 14056 14057 mChoreographer.removeFrameCallback(mTickCallback); 14058 14059 final TextView textView = mView.get(); 14060 if (textView != null && textView.isAggregatedVisible() 14061 && (textView.isFocused() || textView.isSelected())) { 14062 long currentMs = mChoreographer.getFrameTime(); 14063 long deltaMs = currentMs - mLastAnimationMs; 14064 mLastAnimationMs = currentMs; 14065 float deltaPx = deltaMs * mPixelsPerMs; 14066 mScroll += deltaPx; 14067 if (mScroll > mMaxScroll) { 14068 mScroll = mMaxScroll; 14069 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 14070 } else { 14071 mChoreographer.postFrameCallback(mTickCallback); 14072 } 14073 textView.invalidate(); 14074 } 14075 } 14076 stop()14077 void stop() { 14078 mStatus = MARQUEE_STOPPED; 14079 mChoreographer.removeFrameCallback(mStartCallback); 14080 mChoreographer.removeFrameCallback(mRestartCallback); 14081 mChoreographer.removeFrameCallback(mTickCallback); 14082 resetScroll(); 14083 } 14084 resetScroll()14085 private void resetScroll() { 14086 mScroll = 0.0f; 14087 final TextView textView = mView.get(); 14088 if (textView != null) textView.invalidate(); 14089 } 14090 start(int repeatLimit)14091 void start(int repeatLimit) { 14092 if (repeatLimit == 0) { 14093 stop(); 14094 return; 14095 } 14096 mRepeatLimit = repeatLimit; 14097 final TextView textView = mView.get(); 14098 if (textView != null && textView.mLayout != null) { 14099 mStatus = MARQUEE_STARTING; 14100 mScroll = 0.0f; 14101 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 14102 - textView.getCompoundPaddingRight(); 14103 final float lineWidth = textView.mLayout.getLineWidth(0); 14104 final float gap = textWidth / 3.0f; 14105 mGhostStart = lineWidth - textWidth + gap; 14106 mMaxScroll = mGhostStart + textWidth; 14107 mGhostOffset = lineWidth + gap; 14108 mFadeStop = lineWidth + textWidth / 6.0f; 14109 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 14110 14111 textView.invalidate(); 14112 mChoreographer.postFrameCallback(mStartCallback); 14113 } 14114 } 14115 getGhostOffset()14116 float getGhostOffset() { 14117 return mGhostOffset; 14118 } 14119 getScroll()14120 float getScroll() { 14121 return mScroll; 14122 } 14123 getMaxFadeScroll()14124 float getMaxFadeScroll() { 14125 return mMaxFadeScroll; 14126 } 14127 shouldDrawLeftFade()14128 boolean shouldDrawLeftFade() { 14129 return mScroll <= mFadeStop; 14130 } 14131 shouldDrawGhost()14132 boolean shouldDrawGhost() { 14133 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 14134 } 14135 isRunning()14136 boolean isRunning() { 14137 return mStatus == MARQUEE_RUNNING; 14138 } 14139 isStopped()14140 boolean isStopped() { 14141 return mStatus == MARQUEE_STOPPED; 14142 } 14143 } 14144 14145 private class ChangeWatcher implements TextWatcher, SpanWatcher { 14146 14147 private CharSequence mBeforeText; 14148 beforeTextChanged(CharSequence buffer, int start, int before, int after)14149 public void beforeTextChanged(CharSequence buffer, int start, 14150 int before, int after) { 14151 if (DEBUG_EXTRACT) { 14152 Log.v(LOG_TAG, "beforeTextChanged start=" + start 14153 + " before=" + before + " after=" + after + ": " + buffer); 14154 } 14155 14156 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 14157 mBeforeText = mTransformed.toString(); 14158 } 14159 14160 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 14161 } 14162 onTextChanged(CharSequence buffer, int start, int before, int after)14163 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 14164 if (DEBUG_EXTRACT) { 14165 Log.v(LOG_TAG, "onTextChanged start=" + start 14166 + " before=" + before + " after=" + after + ": " + buffer); 14167 } 14168 TextView.this.handleTextChanged(buffer, start, before, after); 14169 14170 if (isVisibleToAccessibility()) { 14171 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 14172 mBeforeText = null; 14173 } 14174 } 14175 afterTextChanged(Editable buffer)14176 public void afterTextChanged(Editable buffer) { 14177 if (DEBUG_EXTRACT) { 14178 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 14179 } 14180 TextView.this.sendAfterTextChanged(buffer); 14181 14182 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 14183 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 14184 } 14185 } 14186 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)14187 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 14188 if (DEBUG_EXTRACT) { 14189 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 14190 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 14191 } 14192 TextView.this.spanChange(buf, what, s, st, e, en); 14193 } 14194 onSpanAdded(Spannable buf, Object what, int s, int e)14195 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 14196 if (DEBUG_EXTRACT) { 14197 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 14198 } 14199 TextView.this.spanChange(buf, what, -1, s, -1, e); 14200 } 14201 onSpanRemoved(Spannable buf, Object what, int s, int e)14202 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 14203 if (DEBUG_EXTRACT) { 14204 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 14205 } 14206 TextView.this.spanChange(buf, what, s, -1, e, -1); 14207 } 14208 } 14209 14210 /** @hide */ 14211 @Override onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)14212 public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, 14213 @NonNull EditorInfo editorInfo, @Nullable Handler handler) { 14214 if (mEditor != null) { 14215 mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, 14216 editorInfo); 14217 } 14218 } 14219 14220 /** @hide */ 14221 @Override onInputConnectionClosedInternal()14222 public void onInputConnectionClosedInternal() { 14223 if (mEditor != null) { 14224 mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); 14225 } 14226 } 14227 14228 /** 14229 * Default {@link TextView} implementation for receiving content. Apps wishing to provide 14230 * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. 14231 * 14232 * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in 14233 * content without acting on it). 14234 * 14235 * <p>For editable TextViews the default behavior is to insert text into the view, coercing 14236 * non-text content to text as needed. The MIME types "text/plain" and "text/html" have 14237 * well-defined behavior for this, while other MIME types have reasonable fallback behavior 14238 * (see {@link ClipData.Item#coerceToStyledText}). 14239 * 14240 * @param payload The content to insert and related metadata. 14241 * 14242 * @return The portion of the passed-in content that was not handled (may be all, some, or none 14243 * of the passed-in content). 14244 */ 14245 @Nullable 14246 @Override onReceiveContent(@onNull ContentInfo payload)14247 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 14248 if (mEditor != null) { 14249 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 14250 } 14251 return payload; 14252 } 14253 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)14254 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 14255 if (msgFormat == null) { 14256 Log.d(LOG_TAG, location); 14257 } else { 14258 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 14259 } 14260 } 14261 14262 /** 14263 * Collects a {@link ViewTranslationRequest} which represents the content to be translated in 14264 * the view. 14265 * 14266 * <p>NOTE: When overriding the method, it should not collect a request to translate this 14267 * TextView if it is displaying a password. 14268 * 14269 * @param supportedFormats the supported translation format. The value could be {@link 14270 * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. 14271 * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest} 14272 * which contains the information to be translated. 14273 */ 14274 @Override onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)14275 public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, 14276 @NonNull Consumer<ViewTranslationRequest> requestsCollector) { 14277 if (supportedFormats == null || supportedFormats.length == 0) { 14278 if (UiTranslationController.DEBUG) { 14279 Log.w(LOG_TAG, "Do not provide the support translation formats."); 14280 } 14281 return; 14282 } 14283 ViewTranslationRequest.Builder requestBuilder = 14284 new ViewTranslationRequest.Builder(getAutofillId()); 14285 // Support Text translation 14286 if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { 14287 if (mText == null || mText.length() == 0) { 14288 if (UiTranslationController.DEBUG) { 14289 Log.w(LOG_TAG, "Cannot create translation request for the empty text."); 14290 } 14291 return; 14292 } 14293 boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); 14294 if (isTextEditable() || isPassword) { 14295 Log.w(LOG_TAG, "Cannot create translation request. editable = " 14296 + isTextEditable() + ", isPassword = " + isPassword); 14297 return; 14298 } 14299 // TODO(b/176488462): apply the view's important for translation 14300 requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, 14301 TranslationRequestValue.forText(mText)); 14302 if (!TextUtils.isEmpty(getContentDescription())) { 14303 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, 14304 TranslationRequestValue.forText(getContentDescription())); 14305 } 14306 } 14307 requestsCollector.accept(requestBuilder.build()); 14308 } 14309 } 14310