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.compat.annotation.UnsupportedAppUsage; 52 import android.content.ClipData; 53 import android.content.ClipDescription; 54 import android.content.ClipboardManager; 55 import android.content.Context; 56 import android.content.Intent; 57 import android.content.UndoManager; 58 import android.content.pm.PackageManager; 59 import android.content.res.ColorStateList; 60 import android.content.res.CompatibilityInfo; 61 import android.content.res.Configuration; 62 import android.content.res.Resources; 63 import android.content.res.TypedArray; 64 import android.content.res.XmlResourceParser; 65 import android.graphics.BaseCanvas; 66 import android.graphics.BlendMode; 67 import android.graphics.Canvas; 68 import android.graphics.Insets; 69 import android.graphics.Paint; 70 import android.graphics.Paint.FontMetricsInt; 71 import android.graphics.Path; 72 import android.graphics.PorterDuff; 73 import android.graphics.Rect; 74 import android.graphics.RectF; 75 import android.graphics.Typeface; 76 import android.graphics.drawable.Drawable; 77 import android.graphics.fonts.FontStyle; 78 import android.graphics.fonts.FontVariationAxis; 79 import android.icu.text.DecimalFormatSymbols; 80 import android.os.AsyncTask; 81 import android.os.Build; 82 import android.os.Build.VERSION_CODES; 83 import android.os.Bundle; 84 import android.os.Handler; 85 import android.os.LocaleList; 86 import android.os.Parcel; 87 import android.os.Parcelable; 88 import android.os.ParcelableParcel; 89 import android.os.Process; 90 import android.os.SystemClock; 91 import android.os.UserHandle; 92 import android.provider.Settings; 93 import android.text.BoringLayout; 94 import android.text.DynamicLayout; 95 import android.text.Editable; 96 import android.text.GetChars; 97 import android.text.GraphicsOperations; 98 import android.text.InputFilter; 99 import android.text.InputType; 100 import android.text.Layout; 101 import android.text.ParcelableSpan; 102 import android.text.PrecomputedText; 103 import android.text.Selection; 104 import android.text.SpanWatcher; 105 import android.text.Spannable; 106 import android.text.SpannableStringBuilder; 107 import android.text.Spanned; 108 import android.text.SpannedString; 109 import android.text.StaticLayout; 110 import android.text.TextDirectionHeuristic; 111 import android.text.TextDirectionHeuristics; 112 import android.text.TextPaint; 113 import android.text.TextUtils; 114 import android.text.TextUtils.TruncateAt; 115 import android.text.TextWatcher; 116 import android.text.method.AllCapsTransformationMethod; 117 import android.text.method.ArrowKeyMovementMethod; 118 import android.text.method.DateKeyListener; 119 import android.text.method.DateTimeKeyListener; 120 import android.text.method.DialerKeyListener; 121 import android.text.method.DigitsKeyListener; 122 import android.text.method.KeyListener; 123 import android.text.method.LinkMovementMethod; 124 import android.text.method.MetaKeyKeyListener; 125 import android.text.method.MovementMethod; 126 import android.text.method.PasswordTransformationMethod; 127 import android.text.method.SingleLineTransformationMethod; 128 import android.text.method.TextKeyListener; 129 import android.text.method.TimeKeyListener; 130 import android.text.method.TransformationMethod; 131 import android.text.method.TransformationMethod2; 132 import android.text.method.WordIterator; 133 import android.text.style.CharacterStyle; 134 import android.text.style.ClickableSpan; 135 import android.text.style.ParagraphStyle; 136 import android.text.style.SpellCheckSpan; 137 import android.text.style.SuggestionSpan; 138 import android.text.style.URLSpan; 139 import android.text.style.UpdateAppearance; 140 import android.text.util.Linkify; 141 import android.util.AttributeSet; 142 import android.util.DisplayMetrics; 143 import android.util.IntArray; 144 import android.util.Log; 145 import android.util.SparseIntArray; 146 import android.util.TypedValue; 147 import android.view.AccessibilityIterators.TextSegmentIterator; 148 import android.view.ActionMode; 149 import android.view.Choreographer; 150 import android.view.ContentInfo; 151 import android.view.ContextMenu; 152 import android.view.DragEvent; 153 import android.view.Gravity; 154 import android.view.HapticFeedbackConstants; 155 import android.view.InputDevice; 156 import android.view.KeyCharacterMap; 157 import android.view.KeyEvent; 158 import android.view.MotionEvent; 159 import android.view.PointerIcon; 160 import android.view.View; 161 import android.view.ViewConfiguration; 162 import android.view.ViewDebug; 163 import android.view.ViewGroup.LayoutParams; 164 import android.view.ViewHierarchyEncoder; 165 import android.view.ViewParent; 166 import android.view.ViewRootImpl; 167 import android.view.ViewStructure; 168 import android.view.ViewTreeObserver; 169 import android.view.accessibility.AccessibilityEvent; 170 import android.view.accessibility.AccessibilityManager; 171 import android.view.accessibility.AccessibilityNodeInfo; 172 import android.view.animation.AnimationUtils; 173 import android.view.autofill.AutofillManager; 174 import android.view.autofill.AutofillValue; 175 import android.view.contentcapture.ContentCaptureManager; 176 import android.view.contentcapture.ContentCaptureSession; 177 import android.view.inputmethod.BaseInputConnection; 178 import android.view.inputmethod.CompletionInfo; 179 import android.view.inputmethod.CorrectionInfo; 180 import android.view.inputmethod.CursorAnchorInfo; 181 import android.view.inputmethod.EditorInfo; 182 import android.view.inputmethod.ExtractedText; 183 import android.view.inputmethod.ExtractedTextRequest; 184 import android.view.inputmethod.InputConnection; 185 import android.view.inputmethod.InputMethodManager; 186 import android.view.inspector.InspectableProperty; 187 import android.view.inspector.InspectableProperty.EnumEntry; 188 import android.view.inspector.InspectableProperty.FlagEntry; 189 import android.view.textclassifier.TextClassification; 190 import android.view.textclassifier.TextClassificationContext; 191 import android.view.textclassifier.TextClassificationManager; 192 import android.view.textclassifier.TextClassifier; 193 import android.view.textclassifier.TextLinks; 194 import android.view.textservice.SpellCheckerSubtype; 195 import android.view.textservice.TextServicesManager; 196 import android.view.translation.TranslationRequestValue; 197 import android.view.translation.TranslationSpec; 198 import android.view.translation.UiTranslationController; 199 import android.view.translation.ViewTranslationCallback; 200 import android.view.translation.ViewTranslationRequest; 201 import android.widget.RemoteViews.RemoteView; 202 203 import com.android.internal.annotations.VisibleForTesting; 204 import com.android.internal.logging.MetricsLogger; 205 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 206 import com.android.internal.util.ArrayUtils; 207 import com.android.internal.util.FastMath; 208 import com.android.internal.util.Preconditions; 209 import com.android.internal.widget.EditableInputConnection; 210 211 import libcore.util.EmptyArray; 212 213 import org.xmlpull.v1.XmlPullParserException; 214 215 import java.io.IOException; 216 import java.lang.annotation.Retention; 217 import java.lang.annotation.RetentionPolicy; 218 import java.lang.ref.WeakReference; 219 import java.util.ArrayList; 220 import java.util.Arrays; 221 import java.util.Locale; 222 import java.util.Objects; 223 import java.util.concurrent.CompletableFuture; 224 import java.util.concurrent.TimeUnit; 225 import java.util.function.Consumer; 226 import java.util.function.Supplier; 227 228 /** 229 * A user interface element that displays text to the user. 230 * To provide user-editable text, see {@link EditText}. 231 * <p> 232 * The following code sample shows a typical use, with an XML layout 233 * and code to modify the contents of the text view: 234 * </p> 235 236 * <pre> 237 * <LinearLayout 238 xmlns:android="http://schemas.android.com/apk/res/android" 239 android:layout_width="match_parent" 240 android:layout_height="match_parent"> 241 * <TextView 242 * android:id="@+id/text_view_id" 243 * android:layout_height="wrap_content" 244 * android:layout_width="wrap_content" 245 * android:text="@string/hello" /> 246 * </LinearLayout> 247 * </pre> 248 * <p> 249 * This code sample demonstrates how to modify the contents of the text view 250 * defined in the previous XML layout: 251 * </p> 252 * <pre> 253 * public class MainActivity extends Activity { 254 * 255 * protected void onCreate(Bundle savedInstanceState) { 256 * super.onCreate(savedInstanceState); 257 * setContentView(R.layout.activity_main); 258 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 259 * helloTextView.setText(R.string.user_greeting); 260 * } 261 * } 262 * </pre> 263 * <p> 264 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 265 * </p> 266 * <p> 267 * <b>XML attributes</b> 268 * <p> 269 * See {@link android.R.styleable#TextView TextView Attributes}, 270 * {@link android.R.styleable#View View Attributes} 271 * 272 * @attr ref android.R.styleable#TextView_text 273 * @attr ref android.R.styleable#TextView_bufferType 274 * @attr ref android.R.styleable#TextView_hint 275 * @attr ref android.R.styleable#TextView_textColor 276 * @attr ref android.R.styleable#TextView_textColorHighlight 277 * @attr ref android.R.styleable#TextView_textColorHint 278 * @attr ref android.R.styleable#TextView_textAppearance 279 * @attr ref android.R.styleable#TextView_textColorLink 280 * @attr ref android.R.styleable#TextView_textFontWeight 281 * @attr ref android.R.styleable#TextView_textSize 282 * @attr ref android.R.styleable#TextView_textScaleX 283 * @attr ref android.R.styleable#TextView_fontFamily 284 * @attr ref android.R.styleable#TextView_typeface 285 * @attr ref android.R.styleable#TextView_textStyle 286 * @attr ref android.R.styleable#TextView_cursorVisible 287 * @attr ref android.R.styleable#TextView_maxLines 288 * @attr ref android.R.styleable#TextView_maxHeight 289 * @attr ref android.R.styleable#TextView_lines 290 * @attr ref android.R.styleable#TextView_height 291 * @attr ref android.R.styleable#TextView_minLines 292 * @attr ref android.R.styleable#TextView_minHeight 293 * @attr ref android.R.styleable#TextView_maxEms 294 * @attr ref android.R.styleable#TextView_maxWidth 295 * @attr ref android.R.styleable#TextView_ems 296 * @attr ref android.R.styleable#TextView_width 297 * @attr ref android.R.styleable#TextView_minEms 298 * @attr ref android.R.styleable#TextView_minWidth 299 * @attr ref android.R.styleable#TextView_gravity 300 * @attr ref android.R.styleable#TextView_scrollHorizontally 301 * @attr ref android.R.styleable#TextView_password 302 * @attr ref android.R.styleable#TextView_singleLine 303 * @attr ref android.R.styleable#TextView_selectAllOnFocus 304 * @attr ref android.R.styleable#TextView_includeFontPadding 305 * @attr ref android.R.styleable#TextView_maxLength 306 * @attr ref android.R.styleable#TextView_shadowColor 307 * @attr ref android.R.styleable#TextView_shadowDx 308 * @attr ref android.R.styleable#TextView_shadowDy 309 * @attr ref android.R.styleable#TextView_shadowRadius 310 * @attr ref android.R.styleable#TextView_autoLink 311 * @attr ref android.R.styleable#TextView_linksClickable 312 * @attr ref android.R.styleable#TextView_numeric 313 * @attr ref android.R.styleable#TextView_digits 314 * @attr ref android.R.styleable#TextView_phoneNumber 315 * @attr ref android.R.styleable#TextView_inputMethod 316 * @attr ref android.R.styleable#TextView_capitalize 317 * @attr ref android.R.styleable#TextView_autoText 318 * @attr ref android.R.styleable#TextView_editable 319 * @attr ref android.R.styleable#TextView_freezesText 320 * @attr ref android.R.styleable#TextView_ellipsize 321 * @attr ref android.R.styleable#TextView_drawableTop 322 * @attr ref android.R.styleable#TextView_drawableBottom 323 * @attr ref android.R.styleable#TextView_drawableRight 324 * @attr ref android.R.styleable#TextView_drawableLeft 325 * @attr ref android.R.styleable#TextView_drawableStart 326 * @attr ref android.R.styleable#TextView_drawableEnd 327 * @attr ref android.R.styleable#TextView_drawablePadding 328 * @attr ref android.R.styleable#TextView_drawableTint 329 * @attr ref android.R.styleable#TextView_drawableTintMode 330 * @attr ref android.R.styleable#TextView_lineSpacingExtra 331 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 332 * @attr ref android.R.styleable#TextView_justificationMode 333 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 334 * @attr ref android.R.styleable#TextView_inputType 335 * @attr ref android.R.styleable#TextView_imeOptions 336 * @attr ref android.R.styleable#TextView_privateImeOptions 337 * @attr ref android.R.styleable#TextView_imeActionLabel 338 * @attr ref android.R.styleable#TextView_imeActionId 339 * @attr ref android.R.styleable#TextView_editorExtras 340 * @attr ref android.R.styleable#TextView_elegantTextHeight 341 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 342 * @attr ref android.R.styleable#TextView_letterSpacing 343 * @attr ref android.R.styleable#TextView_fontFeatureSettings 344 * @attr ref android.R.styleable#TextView_fontVariationSettings 345 * @attr ref android.R.styleable#TextView_breakStrategy 346 * @attr ref android.R.styleable#TextView_hyphenationFrequency 347 * @attr ref android.R.styleable#TextView_autoSizeTextType 348 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 349 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 350 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 351 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 352 * @attr ref android.R.styleable#TextView_textCursorDrawable 353 * @attr ref android.R.styleable#TextView_textSelectHandle 354 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 355 * @attr ref android.R.styleable#TextView_textSelectHandleRight 356 * @attr ref android.R.styleable#TextView_allowUndo 357 * @attr ref android.R.styleable#TextView_enabled 358 */ 359 @RemoteView 360 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 361 static final String LOG_TAG = "TextView"; 362 static final boolean DEBUG_EXTRACT = false; 363 static final boolean DEBUG_CURSOR = false; 364 365 private static final float[] TEMP_POSITION = new float[2]; 366 367 // Enum for the "typeface" XML parameter. 368 // TODO: How can we get this from the XML instead of hardcoding it here? 369 /** @hide */ 370 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 371 @Retention(RetentionPolicy.SOURCE) 372 public @interface XMLTypefaceAttr{} 373 private static final int DEFAULT_TYPEFACE = -1; 374 private static final int SANS = 1; 375 private static final int SERIF = 2; 376 private static final int MONOSPACE = 3; 377 378 // Enum for the "ellipsize" XML parameter. 379 private static final int ELLIPSIZE_NOT_SET = -1; 380 private static final int ELLIPSIZE_NONE = 0; 381 private static final int ELLIPSIZE_START = 1; 382 private static final int ELLIPSIZE_MIDDLE = 2; 383 private static final int ELLIPSIZE_END = 3; 384 private static final int ELLIPSIZE_MARQUEE = 4; 385 386 // Bitfield for the "numeric" XML parameter. 387 // TODO: How can we get this from the XML instead of hardcoding it here? 388 private static final int SIGNED = 2; 389 private static final int DECIMAL = 4; 390 391 /** 392 * Draw marquee text with fading edges as usual 393 */ 394 private static final int MARQUEE_FADE_NORMAL = 0; 395 396 /** 397 * Draw marquee text as ellipsize end while inactive instead of with the fade. 398 * (Useful for devices where the fade can be expensive if overdone) 399 */ 400 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 401 402 /** 403 * Draw marquee text with fading edges because it is currently active/animating. 404 */ 405 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 406 407 @UnsupportedAppUsage 408 private static final int LINES = 1; 409 private static final int EMS = LINES; 410 private static final int PIXELS = 2; 411 412 // Maximum text length for single line input. 413 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 414 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 415 416 private static final RectF TEMP_RECTF = new RectF(); 417 418 /** @hide */ 419 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 420 private static final int ANIMATED_SCROLL_GAP = 250; 421 422 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 423 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 424 425 private static final int CHANGE_WATCHER_PRIORITY = 100; 426 427 // New state used to change background based on whether this TextView is multiline. 428 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 429 430 // Accessibility action to share selected text. 431 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 432 433 /** 434 * @hide 435 */ 436 // Accessibility action start id for "process text" actions. 437 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 438 439 /** 440 * @hide 441 */ 442 @TestApi 443 public static final int PROCESS_TEXT_REQUEST_CODE = 100; 444 445 /** 446 * Return code of {@link #doKeyDown}. 447 */ 448 private static final int KEY_EVENT_NOT_HANDLED = 0; 449 private static final int KEY_EVENT_HANDLED = -1; 450 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 451 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 452 453 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 454 455 // System wide time for last cut, copy or text changed action. 456 static long sLastCutCopyOrTextChangedTime; 457 458 private ColorStateList mTextColor; 459 private ColorStateList mHintTextColor; 460 private ColorStateList mLinkTextColor; 461 @ViewDebug.ExportedProperty(category = "text") 462 463 /** 464 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 465 */ 466 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 467 private int mCurTextColor; 468 469 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 470 private int mCurHintTextColor; 471 private boolean mFreezesText; 472 473 @UnsupportedAppUsage 474 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 475 @UnsupportedAppUsage 476 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 477 478 @UnsupportedAppUsage 479 private float mShadowRadius; 480 @UnsupportedAppUsage 481 private float mShadowDx; 482 @UnsupportedAppUsage 483 private float mShadowDy; 484 private int mShadowColor; 485 486 private boolean mPreDrawRegistered; 487 private boolean mPreDrawListenerDetached; 488 489 private TextClassifier mTextClassifier; 490 private TextClassifier mTextClassificationSession; 491 private TextClassificationContext mTextClassificationContext; 492 493 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 494 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 495 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 496 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 497 // into by the user holding the movement key down) then we shouldn't prevent the focus from 498 // changing. 499 private boolean mPreventDefaultMovement; 500 501 private TextUtils.TruncateAt mEllipsize; 502 503 // A flag to indicate the cursor was hidden by IME. 504 private boolean mImeIsConsumingInput; 505 506 // Whether cursor is visible without regard to {@link mImeConsumesInput}. 507 // {@code true} is the default value. 508 private boolean mCursorVisibleFromAttr = true; 509 510 static class Drawables { 511 static final int LEFT = 0; 512 static final int TOP = 1; 513 static final int RIGHT = 2; 514 static final int BOTTOM = 3; 515 516 static final int DRAWABLE_NONE = -1; 517 static final int DRAWABLE_RIGHT = 0; 518 static final int DRAWABLE_LEFT = 1; 519 520 final Rect mCompoundRect = new Rect(); 521 522 final Drawable[] mShowing = new Drawable[4]; 523 524 ColorStateList mTintList; 525 BlendMode mBlendMode; 526 boolean mHasTint; 527 boolean mHasTintMode; 528 529 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 530 Drawable mDrawableLeftInitial, mDrawableRightInitial; 531 532 boolean mIsRtlCompatibilityMode; 533 boolean mOverride; 534 535 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 536 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 537 538 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 539 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 540 541 int mDrawablePadding; 542 543 int mDrawableSaved = DRAWABLE_NONE; 544 Drawables(Context context)545 public Drawables(Context context) { 546 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 547 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 548 || !context.getApplicationInfo().hasRtlSupport(); 549 mOverride = false; 550 } 551 552 /** 553 * @return {@code true} if this object contains metadata that needs to 554 * be retained, {@code false} otherwise 555 */ 556 public boolean hasMetadata() { 557 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 558 } 559 560 /** 561 * Updates the list of displayed drawables to account for the current 562 * layout direction. 563 * 564 * @param layoutDirection the current layout direction 565 * @return {@code true} if the displayed drawables changed 566 */ 567 public boolean resolveWithLayoutDirection(int layoutDirection) { 568 final Drawable previousLeft = mShowing[Drawables.LEFT]; 569 final Drawable previousRight = mShowing[Drawables.RIGHT]; 570 571 // First reset "left" and "right" drawables to their initial values 572 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 573 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 574 575 if (mIsRtlCompatibilityMode) { 576 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 577 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 578 mShowing[Drawables.LEFT] = mDrawableStart; 579 mDrawableSizeLeft = mDrawableSizeStart; 580 mDrawableHeightLeft = mDrawableHeightStart; 581 } 582 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 583 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 584 mShowing[Drawables.RIGHT] = mDrawableEnd; 585 mDrawableSizeRight = mDrawableSizeEnd; 586 mDrawableHeightRight = mDrawableHeightEnd; 587 } 588 } else { 589 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 590 // drawable if and only if they have been defined 591 switch(layoutDirection) { 592 case LAYOUT_DIRECTION_RTL: 593 if (mOverride) { 594 mShowing[Drawables.RIGHT] = mDrawableStart; 595 mDrawableSizeRight = mDrawableSizeStart; 596 mDrawableHeightRight = mDrawableHeightStart; 597 598 mShowing[Drawables.LEFT] = mDrawableEnd; 599 mDrawableSizeLeft = mDrawableSizeEnd; 600 mDrawableHeightLeft = mDrawableHeightEnd; 601 } 602 break; 603 604 case LAYOUT_DIRECTION_LTR: 605 default: 606 if (mOverride) { 607 mShowing[Drawables.LEFT] = mDrawableStart; 608 mDrawableSizeLeft = mDrawableSizeStart; 609 mDrawableHeightLeft = mDrawableHeightStart; 610 611 mShowing[Drawables.RIGHT] = mDrawableEnd; 612 mDrawableSizeRight = mDrawableSizeEnd; 613 mDrawableHeightRight = mDrawableHeightEnd; 614 } 615 break; 616 } 617 } 618 619 applyErrorDrawableIfNeeded(layoutDirection); 620 621 return mShowing[Drawables.LEFT] != previousLeft 622 || mShowing[Drawables.RIGHT] != previousRight; 623 } 624 625 public void setErrorDrawable(Drawable dr, TextView tv) { 626 if (mDrawableError != dr && mDrawableError != null) { 627 mDrawableError.setCallback(null); 628 } 629 mDrawableError = dr; 630 631 if (mDrawableError != null) { 632 final Rect compoundRect = mCompoundRect; 633 final int[] state = tv.getDrawableState(); 634 635 mDrawableError.setState(state); 636 mDrawableError.copyBounds(compoundRect); 637 mDrawableError.setCallback(tv); 638 mDrawableSizeError = compoundRect.width(); 639 mDrawableHeightError = compoundRect.height(); 640 } else { 641 mDrawableSizeError = mDrawableHeightError = 0; 642 } 643 } 644 645 private void applyErrorDrawableIfNeeded(int layoutDirection) { 646 // first restore the initial state if needed 647 switch (mDrawableSaved) { 648 case DRAWABLE_LEFT: 649 mShowing[Drawables.LEFT] = mDrawableTemp; 650 mDrawableSizeLeft = mDrawableSizeTemp; 651 mDrawableHeightLeft = mDrawableHeightTemp; 652 break; 653 case DRAWABLE_RIGHT: 654 mShowing[Drawables.RIGHT] = mDrawableTemp; 655 mDrawableSizeRight = mDrawableSizeTemp; 656 mDrawableHeightRight = mDrawableHeightTemp; 657 break; 658 case DRAWABLE_NONE: 659 default: 660 } 661 // then, if needed, assign the Error drawable to the correct location 662 if (mDrawableError != null) { 663 switch(layoutDirection) { 664 case LAYOUT_DIRECTION_RTL: 665 mDrawableSaved = DRAWABLE_LEFT; 666 667 mDrawableTemp = mShowing[Drawables.LEFT]; 668 mDrawableSizeTemp = mDrawableSizeLeft; 669 mDrawableHeightTemp = mDrawableHeightLeft; 670 671 mShowing[Drawables.LEFT] = mDrawableError; 672 mDrawableSizeLeft = mDrawableSizeError; 673 mDrawableHeightLeft = mDrawableHeightError; 674 break; 675 case LAYOUT_DIRECTION_LTR: 676 default: 677 mDrawableSaved = DRAWABLE_RIGHT; 678 679 mDrawableTemp = mShowing[Drawables.RIGHT]; 680 mDrawableSizeTemp = mDrawableSizeRight; 681 mDrawableHeightTemp = mDrawableHeightRight; 682 683 mShowing[Drawables.RIGHT] = mDrawableError; 684 mDrawableSizeRight = mDrawableSizeError; 685 mDrawableHeightRight = mDrawableHeightError; 686 break; 687 } 688 } 689 } 690 } 691 692 @UnsupportedAppUsage 693 Drawables mDrawables; 694 695 @UnsupportedAppUsage 696 private CharWrapper mCharWrapper; 697 698 @UnsupportedAppUsage(trackingBug = 124050217) 699 private Marquee mMarquee; 700 @UnsupportedAppUsage 701 private boolean mRestartMarquee; 702 703 private int mMarqueeRepeatLimit = 3; 704 705 private int mLastLayoutDirection = -1; 706 707 /** 708 * On some devices the fading edges add a performance penalty if used 709 * extensively in the same layout. This mode indicates how the marquee 710 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 711 */ 712 @UnsupportedAppUsage 713 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 714 715 /** 716 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 717 * the layout that should be used when the mode switches. 718 */ 719 @UnsupportedAppUsage 720 private Layout mSavedMarqueeModeLayout; 721 722 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 723 @ViewDebug.ExportedProperty(category = "text") 724 @UnsupportedAppUsage 725 private @Nullable CharSequence mText; 726 private @Nullable Spannable mSpannable; 727 private @Nullable PrecomputedText mPrecomputed; 728 729 @UnsupportedAppUsage 730 private CharSequence mTransformed; 731 @UnsupportedAppUsage 732 private BufferType mBufferType = BufferType.NORMAL; 733 734 private CharSequence mHint; 735 @UnsupportedAppUsage 736 private Layout mHintLayout; 737 738 private MovementMethod mMovement; 739 740 private TransformationMethod mTransformation; 741 @UnsupportedAppUsage 742 private boolean mAllowTransformationLengthChange; 743 @UnsupportedAppUsage 744 private ChangeWatcher mChangeWatcher; 745 746 @UnsupportedAppUsage(trackingBug = 123769451) 747 private ArrayList<TextWatcher> mListeners; 748 749 // display attributes 750 @UnsupportedAppUsage 751 private final TextPaint mTextPaint; 752 @UnsupportedAppUsage 753 private boolean mUserSetTextScaleX; 754 @UnsupportedAppUsage 755 private Layout mLayout; 756 private boolean mLocalesChanged = false; 757 private int mTextSizeUnit = -1; 758 759 // This is used to reflect the current user preference for changing font weight and making text 760 // more bold. 761 private int mFontWeightAdjustment; 762 private Typeface mOriginalTypeface; 763 764 // True if setKeyListener() has been explicitly called 765 private boolean mListenerChanged = false; 766 // True if internationalized input should be used for numbers and date and time. 767 private final boolean mUseInternationalizedInput; 768 // True if fallback fonts that end up getting used should be allowed to affect line spacing. 769 /* package */ boolean mUseFallbackLineSpacing; 770 // True if the view text can be padded for compat reasons, when the view is translated. 771 private final boolean mUseTextPaddingForUiTranslation; 772 773 @ViewDebug.ExportedProperty(category = "text") 774 @UnsupportedAppUsage 775 private int mGravity = Gravity.TOP | Gravity.START; 776 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 777 private boolean mHorizontallyScrolling; 778 779 private int mAutoLinkMask; 780 private boolean mLinksClickable = true; 781 782 @UnsupportedAppUsage 783 private float mSpacingMult = 1.0f; 784 @UnsupportedAppUsage 785 private float mSpacingAdd = 0.0f; 786 787 private int mBreakStrategy; 788 private int mHyphenationFrequency; 789 private int mJustificationMode; 790 791 @UnsupportedAppUsage 792 private int mMaximum = Integer.MAX_VALUE; 793 @UnsupportedAppUsage 794 private int mMaxMode = LINES; 795 @UnsupportedAppUsage 796 private int mMinimum = 0; 797 @UnsupportedAppUsage 798 private int mMinMode = LINES; 799 800 @UnsupportedAppUsage 801 private int mOldMaximum = mMaximum; 802 @UnsupportedAppUsage 803 private int mOldMaxMode = mMaxMode; 804 805 @UnsupportedAppUsage 806 private int mMaxWidth = Integer.MAX_VALUE; 807 @UnsupportedAppUsage 808 private int mMaxWidthMode = PIXELS; 809 @UnsupportedAppUsage 810 private int mMinWidth = 0; 811 @UnsupportedAppUsage 812 private int mMinWidthMode = PIXELS; 813 814 @UnsupportedAppUsage 815 private boolean mSingleLine; 816 @UnsupportedAppUsage 817 private int mDesiredHeightAtMeasure = -1; 818 @UnsupportedAppUsage 819 private boolean mIncludePad = true; 820 private int mDeferScroll = -1; 821 822 // tmp primitives, so we don't alloc them on each draw 823 private Rect mTempRect; 824 private long mLastScroll; 825 private Scroller mScroller; 826 private TextPaint mTempTextPaint; 827 828 @UnsupportedAppUsage 829 private BoringLayout.Metrics mBoring; 830 @UnsupportedAppUsage 831 private BoringLayout.Metrics mHintBoring; 832 @UnsupportedAppUsage 833 private BoringLayout mSavedLayout; 834 @UnsupportedAppUsage 835 private BoringLayout mSavedHintLayout; 836 837 @UnsupportedAppUsage 838 private TextDirectionHeuristic mTextDir; 839 840 private InputFilter[] mFilters = NO_FILTERS; 841 842 /** 843 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 844 * the same as {@link Process#myUserHandle()}. 845 * 846 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 847 * other apps may need to set this so that the system can use right user's resources and 848 * services such as input methods and spell checkers.</p> 849 * 850 * @see #setTextOperationUser(UserHandle) 851 */ 852 @Nullable 853 private UserHandle mTextOperationUser; 854 855 private volatile Locale mCurrentSpellCheckerLocaleCache; 856 857 // It is possible to have a selection even when mEditor is null (programmatically set, like when 858 // a link is pressed). These highlight-related fields do not go in mEditor. 859 @UnsupportedAppUsage 860 int mHighlightColor = 0x6633B5E5; 861 private Path mHighlightPath; 862 @UnsupportedAppUsage 863 private final Paint mHighlightPaint; 864 @UnsupportedAppUsage 865 private boolean mHighlightPathBogus = true; 866 867 // Although these fields are specific to editable text, they are not added to Editor because 868 // they are defined by the TextView's style and are theme-dependent. 869 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 870 int mCursorDrawableRes; 871 private Drawable mCursorDrawable; 872 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 873 // by removing it, but we would break apps targeting <= P that use it by reflection. 874 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 875 int mTextSelectHandleLeftRes; 876 private Drawable mTextSelectHandleLeft; 877 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 878 // by removing it, but we would break apps targeting <= P that use it by reflection. 879 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 880 int mTextSelectHandleRightRes; 881 private Drawable mTextSelectHandleRight; 882 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 883 // by removing it, but we would break apps targeting <= P that use it by reflection. 884 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 885 int mTextSelectHandleRes; 886 private Drawable mTextSelectHandle; 887 int mTextEditSuggestionItemLayout; 888 int mTextEditSuggestionContainerLayout; 889 int mTextEditSuggestionHighlightStyle; 890 891 private static final int NO_POINTER_ID = -1; 892 /** 893 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 894 * TextView and the handle views which are rendered on popup windows. 895 */ 896 private int mPrimePointerId = NO_POINTER_ID; 897 898 /** 899 * Whether the prime pointer is from the event delivered to selection handle or insertion 900 * handle. 901 */ 902 private boolean mIsPrimePointerFromHandleView; 903 904 /** 905 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 906 * See {@link #createEditorIfNeeded()}. 907 */ 908 @UnsupportedAppUsage 909 private Editor mEditor; 910 911 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 912 private static final int DEVICE_PROVISIONED_NO = 1; 913 private static final int DEVICE_PROVISIONED_YES = 2; 914 915 /** 916 * Some special options such as sharing selected text should only be shown if the device 917 * is provisioned. Only check the provisioned state once for a given view instance. 918 */ 919 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 920 921 /** 922 * The TextView does not auto-size text (default). 923 */ 924 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 925 926 /** 927 * The TextView scales text size both horizontally and vertically to fit within the 928 * container. 929 */ 930 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 931 932 /** @hide */ 933 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 934 AUTO_SIZE_TEXT_TYPE_NONE, 935 AUTO_SIZE_TEXT_TYPE_UNIFORM 936 }) 937 @Retention(RetentionPolicy.SOURCE) 938 public @interface AutoSizeTextType {} 939 // Default minimum size for auto-sizing text in scaled pixels. 940 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 941 // Default maximum size for auto-sizing text in scaled pixels. 942 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 943 // Default value for the step size in pixels. 944 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 945 // Use this to specify that any of the auto-size configuration int values have not been set. 946 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 947 // Auto-size text type. 948 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 949 // Specify if auto-size text is needed. 950 private boolean mNeedsAutoSizeText = false; 951 // Step size for auto-sizing in pixels. 952 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 953 // Minimum text size for auto-sizing in pixels. 954 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 955 // Maximum text size for auto-sizing in pixels. 956 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 957 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 958 // when auto-sizing text. 959 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 960 // Specifies whether auto-size should use the provided auto size steps set or if it should 961 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 962 // mAutoSizeStepGranularityInPx. 963 private boolean mHasPresetAutoSizeValues = false; 964 965 // Autofill-related attributes 966 // 967 // Indicates whether the text was set statically or dynamically, so it can be used to 968 // sanitize autofill requests. 969 private boolean mTextSetFromXmlOrResourceId = false; 970 // Resource id used to set the text. 971 private @StringRes int mTextId = Resources.ID_NULL; 972 // Resource id used to set the hint. 973 private @StringRes int mHintId = Resources.ID_NULL; 974 // 975 // End of autofill-related attributes 976 977 /** 978 * Kick-start the font cache for the zygote process (to pay the cost of 979 * initializing freetype for our default font only once). 980 * @hide 981 */ 982 public static void preloadFontCache() { 983 if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 984 return; 985 } 986 Paint p = new Paint(); 987 p.setAntiAlias(true); 988 // Ensure that the Typeface is loaded here. 989 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 990 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 991 // since Paint.measureText can not be called without Typeface static initializer. 992 p.setTypeface(Typeface.DEFAULT); 993 // We don't care about the result, just the side-effect of measuring. 994 p.measureText("H"); 995 } 996 997 /** 998 * Interface definition for a callback to be invoked when an action is 999 * performed on the editor. 1000 */ 1001 public interface OnEditorActionListener { 1002 /** 1003 * Called when an action is being performed. 1004 * 1005 * @param v The view that was clicked. 1006 * @param actionId Identifier of the action. This will be either the 1007 * identifier you supplied, or {@link EditorInfo#IME_NULL 1008 * EditorInfo.IME_NULL} if being called due to the enter key 1009 * being pressed. 1010 * @param event If triggered by an enter key, this is the event; 1011 * otherwise, this is null. 1012 * @return Return true if you have consumed the action, else false. 1013 */ 1014 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 1015 } 1016 1017 public TextView(Context context) { 1018 this(context, null); 1019 } 1020 1021 public TextView(Context context, @Nullable AttributeSet attrs) { 1022 this(context, attrs, com.android.internal.R.attr.textViewStyle); 1023 } 1024 1025 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 1026 this(context, attrs, defStyleAttr, 0); 1027 } 1028 1029 @SuppressWarnings("deprecation") 1030 public TextView( 1031 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1032 super(context, attrs, defStyleAttr, defStyleRes); 1033 1034 // TextView is important by default, unless app developer overrode attribute. 1035 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1036 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1037 } 1038 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1039 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1040 } 1041 1042 setTextInternal(""); 1043 1044 final Resources res = getResources(); 1045 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1046 1047 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1048 mTextPaint.density = res.getDisplayMetrics().density; 1049 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1050 1051 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1052 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1053 1054 mMovement = getDefaultMovementMethod(); 1055 1056 mTransformation = null; 1057 1058 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1059 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1060 attributes.mTextSize = 15; 1061 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1062 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1063 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1064 1065 final Resources.Theme theme = context.getTheme(); 1066 1067 /* 1068 * Look the appearance up without checking first if it exists because 1069 * almost every TextView has one and it greatly simplifies the logic 1070 * to be able to parse the appearance first and then let specific tags 1071 * for this View override it. 1072 */ 1073 TypedArray a = theme.obtainStyledAttributes(attrs, 1074 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1075 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1076 attrs, a, defStyleAttr, defStyleRes); 1077 TypedArray appearance = null; 1078 int ap = a.getResourceId( 1079 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1080 a.recycle(); 1081 if (ap != -1) { 1082 appearance = theme.obtainStyledAttributes( 1083 ap, com.android.internal.R.styleable.TextAppearance); 1084 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1085 null, appearance, 0, ap); 1086 } 1087 if (appearance != null) { 1088 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1089 attributes.mFontFamilyExplicit = false; 1090 appearance.recycle(); 1091 } 1092 1093 boolean editable = getDefaultEditable(); 1094 CharSequence inputMethod = null; 1095 int numeric = 0; 1096 CharSequence digits = null; 1097 boolean phone = false; 1098 boolean autotext = false; 1099 int autocap = -1; 1100 int buffertype = 0; 1101 boolean selectallonfocus = false; 1102 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1103 drawableBottom = null, drawableStart = null, drawableEnd = null; 1104 ColorStateList drawableTint = null; 1105 BlendMode drawableTintMode = null; 1106 int drawablePadding = 0; 1107 int ellipsize = ELLIPSIZE_NOT_SET; 1108 boolean singleLine = false; 1109 int maxlength = -1; 1110 CharSequence text = ""; 1111 CharSequence hint = null; 1112 boolean password = false; 1113 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1114 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1115 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1116 int inputType = EditorInfo.TYPE_NULL; 1117 a = theme.obtainStyledAttributes( 1118 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1119 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1120 defStyleAttr, defStyleRes); 1121 int firstBaselineToTopHeight = -1; 1122 int lastBaselineToBottomHeight = -1; 1123 int lineHeight = -1; 1124 1125 readTextAppearance(context, a, attributes, true /* styleArray */); 1126 1127 int n = a.getIndexCount(); 1128 1129 // Must set id in a temporary variable because it will be reset by setText() 1130 boolean textIsSetFromXml = false; 1131 for (int i = 0; i < n; i++) { 1132 int attr = a.getIndex(i); 1133 1134 switch (attr) { 1135 case com.android.internal.R.styleable.TextView_editable: 1136 editable = a.getBoolean(attr, editable); 1137 break; 1138 1139 case com.android.internal.R.styleable.TextView_inputMethod: 1140 inputMethod = a.getText(attr); 1141 break; 1142 1143 case com.android.internal.R.styleable.TextView_numeric: 1144 numeric = a.getInt(attr, numeric); 1145 break; 1146 1147 case com.android.internal.R.styleable.TextView_digits: 1148 digits = a.getText(attr); 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_phoneNumber: 1152 phone = a.getBoolean(attr, phone); 1153 break; 1154 1155 case com.android.internal.R.styleable.TextView_autoText: 1156 autotext = a.getBoolean(attr, autotext); 1157 break; 1158 1159 case com.android.internal.R.styleable.TextView_capitalize: 1160 autocap = a.getInt(attr, autocap); 1161 break; 1162 1163 case com.android.internal.R.styleable.TextView_bufferType: 1164 buffertype = a.getInt(attr, buffertype); 1165 break; 1166 1167 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1168 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1169 break; 1170 1171 case com.android.internal.R.styleable.TextView_autoLink: 1172 mAutoLinkMask = a.getInt(attr, 0); 1173 break; 1174 1175 case com.android.internal.R.styleable.TextView_linksClickable: 1176 mLinksClickable = a.getBoolean(attr, true); 1177 break; 1178 1179 case com.android.internal.R.styleable.TextView_drawableLeft: 1180 drawableLeft = a.getDrawable(attr); 1181 break; 1182 1183 case com.android.internal.R.styleable.TextView_drawableTop: 1184 drawableTop = a.getDrawable(attr); 1185 break; 1186 1187 case com.android.internal.R.styleable.TextView_drawableRight: 1188 drawableRight = a.getDrawable(attr); 1189 break; 1190 1191 case com.android.internal.R.styleable.TextView_drawableBottom: 1192 drawableBottom = a.getDrawable(attr); 1193 break; 1194 1195 case com.android.internal.R.styleable.TextView_drawableStart: 1196 drawableStart = a.getDrawable(attr); 1197 break; 1198 1199 case com.android.internal.R.styleable.TextView_drawableEnd: 1200 drawableEnd = a.getDrawable(attr); 1201 break; 1202 1203 case com.android.internal.R.styleable.TextView_drawableTint: 1204 drawableTint = a.getColorStateList(attr); 1205 break; 1206 1207 case com.android.internal.R.styleable.TextView_drawableTintMode: 1208 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1209 drawableTintMode); 1210 break; 1211 1212 case com.android.internal.R.styleable.TextView_drawablePadding: 1213 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1214 break; 1215 1216 case com.android.internal.R.styleable.TextView_maxLines: 1217 setMaxLines(a.getInt(attr, -1)); 1218 break; 1219 1220 case com.android.internal.R.styleable.TextView_maxHeight: 1221 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1222 break; 1223 1224 case com.android.internal.R.styleable.TextView_lines: 1225 setLines(a.getInt(attr, -1)); 1226 break; 1227 1228 case com.android.internal.R.styleable.TextView_height: 1229 setHeight(a.getDimensionPixelSize(attr, -1)); 1230 break; 1231 1232 case com.android.internal.R.styleable.TextView_minLines: 1233 setMinLines(a.getInt(attr, -1)); 1234 break; 1235 1236 case com.android.internal.R.styleable.TextView_minHeight: 1237 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1238 break; 1239 1240 case com.android.internal.R.styleable.TextView_maxEms: 1241 setMaxEms(a.getInt(attr, -1)); 1242 break; 1243 1244 case com.android.internal.R.styleable.TextView_maxWidth: 1245 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1246 break; 1247 1248 case com.android.internal.R.styleable.TextView_ems: 1249 setEms(a.getInt(attr, -1)); 1250 break; 1251 1252 case com.android.internal.R.styleable.TextView_width: 1253 setWidth(a.getDimensionPixelSize(attr, -1)); 1254 break; 1255 1256 case com.android.internal.R.styleable.TextView_minEms: 1257 setMinEms(a.getInt(attr, -1)); 1258 break; 1259 1260 case com.android.internal.R.styleable.TextView_minWidth: 1261 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1262 break; 1263 1264 case com.android.internal.R.styleable.TextView_gravity: 1265 setGravity(a.getInt(attr, -1)); 1266 break; 1267 1268 case com.android.internal.R.styleable.TextView_hint: 1269 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1270 hint = a.getText(attr); 1271 break; 1272 1273 case com.android.internal.R.styleable.TextView_text: 1274 textIsSetFromXml = true; 1275 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1276 text = a.getText(attr); 1277 break; 1278 1279 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1280 if (a.getBoolean(attr, false)) { 1281 setHorizontallyScrolling(true); 1282 } 1283 break; 1284 1285 case com.android.internal.R.styleable.TextView_singleLine: 1286 singleLine = a.getBoolean(attr, singleLine); 1287 break; 1288 1289 case com.android.internal.R.styleable.TextView_ellipsize: 1290 ellipsize = a.getInt(attr, ellipsize); 1291 break; 1292 1293 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1294 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1295 break; 1296 1297 case com.android.internal.R.styleable.TextView_includeFontPadding: 1298 if (!a.getBoolean(attr, true)) { 1299 setIncludeFontPadding(false); 1300 } 1301 break; 1302 1303 case com.android.internal.R.styleable.TextView_cursorVisible: 1304 if (!a.getBoolean(attr, true)) { 1305 setCursorVisible(false); 1306 } 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_maxLength: 1310 maxlength = a.getInt(attr, -1); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_textScaleX: 1314 setTextScaleX(a.getFloat(attr, 1.0f)); 1315 break; 1316 1317 case com.android.internal.R.styleable.TextView_freezesText: 1318 mFreezesText = a.getBoolean(attr, false); 1319 break; 1320 1321 case com.android.internal.R.styleable.TextView_enabled: 1322 setEnabled(a.getBoolean(attr, isEnabled())); 1323 break; 1324 1325 case com.android.internal.R.styleable.TextView_password: 1326 password = a.getBoolean(attr, password); 1327 break; 1328 1329 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1330 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1331 break; 1332 1333 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1334 mSpacingMult = a.getFloat(attr, mSpacingMult); 1335 break; 1336 1337 case com.android.internal.R.styleable.TextView_inputType: 1338 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1339 break; 1340 1341 case com.android.internal.R.styleable.TextView_allowUndo: 1342 createEditorIfNeeded(); 1343 mEditor.mAllowUndo = a.getBoolean(attr, true); 1344 break; 1345 1346 case com.android.internal.R.styleable.TextView_imeOptions: 1347 createEditorIfNeeded(); 1348 mEditor.createInputContentTypeIfNeeded(); 1349 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1350 mEditor.mInputContentType.imeOptions); 1351 break; 1352 1353 case com.android.internal.R.styleable.TextView_imeActionLabel: 1354 createEditorIfNeeded(); 1355 mEditor.createInputContentTypeIfNeeded(); 1356 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1357 break; 1358 1359 case com.android.internal.R.styleable.TextView_imeActionId: 1360 createEditorIfNeeded(); 1361 mEditor.createInputContentTypeIfNeeded(); 1362 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1363 mEditor.mInputContentType.imeActionId); 1364 break; 1365 1366 case com.android.internal.R.styleable.TextView_privateImeOptions: 1367 setPrivateImeOptions(a.getString(attr)); 1368 break; 1369 1370 case com.android.internal.R.styleable.TextView_editorExtras: 1371 try { 1372 setInputExtras(a.getResourceId(attr, 0)); 1373 } catch (XmlPullParserException e) { 1374 Log.w(LOG_TAG, "Failure reading input extras", e); 1375 } catch (IOException e) { 1376 Log.w(LOG_TAG, "Failure reading input extras", e); 1377 } 1378 break; 1379 1380 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1381 mCursorDrawableRes = a.getResourceId(attr, 0); 1382 break; 1383 1384 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1385 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1386 break; 1387 1388 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1389 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1390 break; 1391 1392 case com.android.internal.R.styleable.TextView_textSelectHandle: 1393 mTextSelectHandleRes = a.getResourceId(attr, 0); 1394 break; 1395 1396 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1397 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1398 break; 1399 1400 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1401 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1402 break; 1403 1404 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1405 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1406 break; 1407 1408 case com.android.internal.R.styleable.TextView_textIsSelectable: 1409 setTextIsSelectable(a.getBoolean(attr, false)); 1410 break; 1411 1412 case com.android.internal.R.styleable.TextView_breakStrategy: 1413 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1414 break; 1415 1416 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1417 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1418 break; 1419 1420 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1421 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1422 break; 1423 1424 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1425 autoSizeStepGranularityInPx = a.getDimension(attr, 1426 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1427 break; 1428 1429 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1430 autoSizeMinTextSizeInPx = a.getDimension(attr, 1431 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1432 break; 1433 1434 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1435 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1436 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1437 break; 1438 1439 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1440 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1441 if (autoSizeStepSizeArrayResId > 0) { 1442 final TypedArray autoSizePresetTextSizes = a.getResources() 1443 .obtainTypedArray(autoSizeStepSizeArrayResId); 1444 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1445 autoSizePresetTextSizes.recycle(); 1446 } 1447 break; 1448 case com.android.internal.R.styleable.TextView_justificationMode: 1449 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1450 break; 1451 1452 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1453 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1454 break; 1455 1456 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1457 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1458 break; 1459 1460 case com.android.internal.R.styleable.TextView_lineHeight: 1461 lineHeight = a.getDimensionPixelSize(attr, -1); 1462 break; 1463 } 1464 } 1465 1466 a.recycle(); 1467 1468 BufferType bufferType = BufferType.EDITABLE; 1469 1470 final int variation = 1471 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1472 final boolean passwordInputType = variation 1473 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1474 final boolean webPasswordInputType = variation 1475 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1476 final boolean numberPasswordInputType = variation 1477 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1478 1479 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1480 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1481 mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; 1482 // TODO(b/179693024): Use a ChangeId instead. 1483 mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; 1484 1485 if (inputMethod != null) { 1486 Class<?> c; 1487 1488 try { 1489 c = Class.forName(inputMethod.toString()); 1490 } catch (ClassNotFoundException ex) { 1491 throw new RuntimeException(ex); 1492 } 1493 1494 try { 1495 createEditorIfNeeded(); 1496 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1497 } catch (InstantiationException ex) { 1498 throw new RuntimeException(ex); 1499 } catch (IllegalAccessException ex) { 1500 throw new RuntimeException(ex); 1501 } 1502 try { 1503 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1504 ? inputType 1505 : mEditor.mKeyListener.getInputType(); 1506 } catch (IncompatibleClassChangeError e) { 1507 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1508 } 1509 } else if (digits != null) { 1510 createEditorIfNeeded(); 1511 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1512 // If no input type was specified, we will default to generic 1513 // text, since we can't tell the IME about the set of digits 1514 // that was selected. 1515 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1516 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1517 } else if (inputType != EditorInfo.TYPE_NULL) { 1518 setInputType(inputType, true); 1519 // If set, the input type overrides what was set using the deprecated singleLine flag. 1520 singleLine = !isMultilineInputType(inputType); 1521 } else if (phone) { 1522 createEditorIfNeeded(); 1523 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1524 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1525 } else if (numeric != 0) { 1526 createEditorIfNeeded(); 1527 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1528 null, // locale 1529 (numeric & SIGNED) != 0, 1530 (numeric & DECIMAL) != 0); 1531 inputType = mEditor.mKeyListener.getInputType(); 1532 mEditor.mInputType = inputType; 1533 } else if (autotext || autocap != -1) { 1534 TextKeyListener.Capitalize cap; 1535 1536 inputType = EditorInfo.TYPE_CLASS_TEXT; 1537 1538 switch (autocap) { 1539 case 1: 1540 cap = TextKeyListener.Capitalize.SENTENCES; 1541 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1542 break; 1543 1544 case 2: 1545 cap = TextKeyListener.Capitalize.WORDS; 1546 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1547 break; 1548 1549 case 3: 1550 cap = TextKeyListener.Capitalize.CHARACTERS; 1551 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1552 break; 1553 1554 default: 1555 cap = TextKeyListener.Capitalize.NONE; 1556 break; 1557 } 1558 1559 createEditorIfNeeded(); 1560 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1561 mEditor.mInputType = inputType; 1562 } else if (editable) { 1563 createEditorIfNeeded(); 1564 mEditor.mKeyListener = TextKeyListener.getInstance(); 1565 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1566 } else if (isTextSelectable()) { 1567 // Prevent text changes from keyboard. 1568 if (mEditor != null) { 1569 mEditor.mKeyListener = null; 1570 mEditor.mInputType = EditorInfo.TYPE_NULL; 1571 } 1572 bufferType = BufferType.SPANNABLE; 1573 // So that selection can be changed using arrow keys and touch is handled. 1574 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1575 } else { 1576 if (mEditor != null) mEditor.mKeyListener = null; 1577 1578 switch (buffertype) { 1579 case 0: 1580 bufferType = BufferType.NORMAL; 1581 break; 1582 case 1: 1583 bufferType = BufferType.SPANNABLE; 1584 break; 1585 case 2: 1586 bufferType = BufferType.EDITABLE; 1587 break; 1588 } 1589 } 1590 1591 if (mEditor != null) { 1592 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1593 numberPasswordInputType); 1594 } 1595 1596 if (selectallonfocus) { 1597 createEditorIfNeeded(); 1598 mEditor.mSelectAllOnFocus = true; 1599 1600 if (bufferType == BufferType.NORMAL) { 1601 bufferType = BufferType.SPANNABLE; 1602 } 1603 } 1604 1605 // Set up the tint (if needed) before setting the drawables so that it 1606 // gets applied correctly. 1607 if (drawableTint != null || drawableTintMode != null) { 1608 if (mDrawables == null) { 1609 mDrawables = new Drawables(context); 1610 } 1611 if (drawableTint != null) { 1612 mDrawables.mTintList = drawableTint; 1613 mDrawables.mHasTint = true; 1614 } 1615 if (drawableTintMode != null) { 1616 mDrawables.mBlendMode = drawableTintMode; 1617 mDrawables.mHasTintMode = true; 1618 } 1619 } 1620 1621 // This call will save the initial left/right drawables 1622 setCompoundDrawablesWithIntrinsicBounds( 1623 drawableLeft, drawableTop, drawableRight, drawableBottom); 1624 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1625 setCompoundDrawablePadding(drawablePadding); 1626 1627 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1628 // of lines of height are unchanged for multi-line TextViews. 1629 setInputTypeSingleLine(singleLine); 1630 applySingleLine(singleLine, singleLine, singleLine, 1631 // Does not apply automated max length filter since length filter will be resolved 1632 // later in this function. 1633 false 1634 ); 1635 1636 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1637 ellipsize = ELLIPSIZE_END; 1638 } 1639 1640 switch (ellipsize) { 1641 case ELLIPSIZE_START: 1642 setEllipsize(TextUtils.TruncateAt.START); 1643 break; 1644 case ELLIPSIZE_MIDDLE: 1645 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1646 break; 1647 case ELLIPSIZE_END: 1648 setEllipsize(TextUtils.TruncateAt.END); 1649 break; 1650 case ELLIPSIZE_MARQUEE: 1651 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1652 setHorizontalFadingEdgeEnabled(true); 1653 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1654 } else { 1655 setHorizontalFadingEdgeEnabled(false); 1656 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1657 } 1658 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1659 break; 1660 } 1661 1662 final boolean isPassword = password || passwordInputType || webPasswordInputType 1663 || numberPasswordInputType; 1664 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1665 && (mEditor.mInputType 1666 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1667 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1668 if (isMonospaceEnforced) { 1669 attributes.mTypefaceIndex = MONOSPACE; 1670 } 1671 1672 mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; 1673 applyTextAppearance(attributes); 1674 1675 if (isPassword) { 1676 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1677 } 1678 1679 // For addressing b/145128646 1680 // For the performance reason, we limit characters for single line text field. 1681 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1682 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1683 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1684 } 1685 1686 if (mSingleLineLengthFilter != null) { 1687 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1688 } else if (maxlength >= 0) { 1689 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1690 } else { 1691 setFilters(NO_FILTERS); 1692 } 1693 1694 setText(text, bufferType); 1695 if (mText == null) { 1696 mText = ""; 1697 } 1698 if (mTransformed == null) { 1699 mTransformed = ""; 1700 } 1701 1702 if (textIsSetFromXml) { 1703 mTextSetFromXmlOrResourceId = true; 1704 } 1705 1706 if (hint != null) setHint(hint); 1707 1708 /* 1709 * Views are not normally clickable unless specified to be. 1710 * However, TextViews that have input or movement methods *are* 1711 * clickable by default. By setting clickable here, we implicitly set focusable as well 1712 * if not overridden by the developer. 1713 */ 1714 a = context.obtainStyledAttributes( 1715 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1716 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1717 boolean clickable = canInputOrMove || isClickable(); 1718 boolean longClickable = canInputOrMove || isLongClickable(); 1719 int focusable = getFocusable(); 1720 1721 n = a.getIndexCount(); 1722 for (int i = 0; i < n; i++) { 1723 int attr = a.getIndex(i); 1724 1725 switch (attr) { 1726 case com.android.internal.R.styleable.View_focusable: 1727 TypedValue val = new TypedValue(); 1728 if (a.getValue(attr, val)) { 1729 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1730 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1731 : val.data; 1732 } 1733 break; 1734 1735 case com.android.internal.R.styleable.View_clickable: 1736 clickable = a.getBoolean(attr, clickable); 1737 break; 1738 1739 case com.android.internal.R.styleable.View_longClickable: 1740 longClickable = a.getBoolean(attr, longClickable); 1741 break; 1742 } 1743 } 1744 a.recycle(); 1745 1746 // Some apps were relying on the undefined behavior of focusable winning over 1747 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1748 // when starting with EditText and setting only focusable=false). To keep those apps from 1749 // breaking, re-apply the focusable attribute here. 1750 if (focusable != getFocusable()) { 1751 setFocusable(focusable); 1752 } 1753 setClickable(clickable); 1754 setLongClickable(longClickable); 1755 1756 if (mEditor != null) mEditor.prepareCursorControllers(); 1757 1758 // If not explicitly specified this view is important for accessibility. 1759 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1760 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1761 } 1762 1763 if (supportsAutoSizeText()) { 1764 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1765 // If uniform auto-size has been specified but preset values have not been set then 1766 // replace the auto-size configuration values that have not been specified with the 1767 // defaults. 1768 if (!mHasPresetAutoSizeValues) { 1769 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1770 1771 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1772 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1773 TypedValue.COMPLEX_UNIT_SP, 1774 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1775 displayMetrics); 1776 } 1777 1778 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1779 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1780 TypedValue.COMPLEX_UNIT_SP, 1781 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1782 displayMetrics); 1783 } 1784 1785 if (autoSizeStepGranularityInPx 1786 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1787 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1788 } 1789 1790 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1791 autoSizeMaxTextSizeInPx, 1792 autoSizeStepGranularityInPx); 1793 } 1794 1795 setupAutoSizeText(); 1796 } 1797 } else { 1798 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1799 } 1800 1801 if (firstBaselineToTopHeight >= 0) { 1802 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1803 } 1804 if (lastBaselineToBottomHeight >= 0) { 1805 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1806 } 1807 if (lineHeight >= 0) { 1808 setLineHeight(lineHeight); 1809 } 1810 } 1811 1812 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1813 private void setTextInternal(@Nullable CharSequence text) { 1814 mText = text; 1815 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1816 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1817 } 1818 1819 /** 1820 * Specify whether this widget should automatically scale the text to try to perfectly fit 1821 * within the layout bounds by using the default auto-size configuration. 1822 * 1823 * @param autoSizeTextType the type of auto-size. Must be one of 1824 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1825 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1826 * 1827 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1828 * 1829 * @attr ref android.R.styleable#TextView_autoSizeTextType 1830 * 1831 * @see #getAutoSizeTextType() 1832 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1833 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1834 if (supportsAutoSizeText()) { 1835 switch (autoSizeTextType) { 1836 case AUTO_SIZE_TEXT_TYPE_NONE: 1837 clearAutoSizeConfiguration(); 1838 break; 1839 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1840 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1841 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1842 TypedValue.COMPLEX_UNIT_SP, 1843 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1844 displayMetrics); 1845 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1846 TypedValue.COMPLEX_UNIT_SP, 1847 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1848 displayMetrics); 1849 1850 validateAndSetAutoSizeTextTypeUniformConfiguration( 1851 autoSizeMinTextSizeInPx, 1852 autoSizeMaxTextSizeInPx, 1853 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1854 if (setupAutoSizeText()) { 1855 autoSizeText(); 1856 invalidate(); 1857 } 1858 break; 1859 default: 1860 throw new IllegalArgumentException( 1861 "Unknown auto-size text type: " + autoSizeTextType); 1862 } 1863 } 1864 } 1865 1866 /** 1867 * Specify whether this widget should automatically scale the text to try to perfectly fit 1868 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1869 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1870 * 1871 * @param autoSizeMinTextSize the minimum text size available for auto-size 1872 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1873 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1874 * the minimum and maximum text size in order to build the set of 1875 * text sizes the system uses to choose from when auto-sizing 1876 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1877 * possible dimension units 1878 * 1879 * @throws IllegalArgumentException if any of the configuration params are invalid. 1880 * 1881 * @attr ref android.R.styleable#TextView_autoSizeTextType 1882 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1883 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1884 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1885 * 1886 * @see #setAutoSizeTextTypeWithDefaults(int) 1887 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1888 * @see #getAutoSizeMinTextSize() 1889 * @see #getAutoSizeMaxTextSize() 1890 * @see #getAutoSizeStepGranularity() 1891 * @see #getAutoSizeTextAvailableSizes() 1892 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1893 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1894 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1895 if (supportsAutoSizeText()) { 1896 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1897 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1898 unit, autoSizeMinTextSize, displayMetrics); 1899 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1900 unit, autoSizeMaxTextSize, displayMetrics); 1901 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1902 unit, autoSizeStepGranularity, displayMetrics); 1903 1904 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1905 autoSizeMaxTextSizeInPx, 1906 autoSizeStepGranularityInPx); 1907 1908 if (setupAutoSizeText()) { 1909 autoSizeText(); 1910 invalidate(); 1911 } 1912 } 1913 } 1914 1915 /** 1916 * Specify whether this widget should automatically scale the text to try to perfectly fit 1917 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1918 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1919 * 1920 * @param presetSizes an {@code int} array of sizes in pixels 1921 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1922 * the possible dimension units 1923 * 1924 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1925 * 1926 * @attr ref android.R.styleable#TextView_autoSizeTextType 1927 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1928 * 1929 * @see #setAutoSizeTextTypeWithDefaults(int) 1930 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1931 * @see #getAutoSizeMinTextSize() 1932 * @see #getAutoSizeMaxTextSize() 1933 * @see #getAutoSizeTextAvailableSizes() 1934 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1935 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1936 if (supportsAutoSizeText()) { 1937 final int presetSizesLength = presetSizes.length; 1938 if (presetSizesLength > 0) { 1939 int[] presetSizesInPx = new int[presetSizesLength]; 1940 1941 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1942 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1943 } else { 1944 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1945 // Convert all to sizes to pixels. 1946 for (int i = 0; i < presetSizesLength; i++) { 1947 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 1948 presetSizes[i], displayMetrics)); 1949 } 1950 } 1951 1952 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1953 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1954 throw new IllegalArgumentException("None of the preset sizes is valid: " 1955 + Arrays.toString(presetSizes)); 1956 } 1957 } else { 1958 mHasPresetAutoSizeValues = false; 1959 } 1960 1961 if (setupAutoSizeText()) { 1962 autoSizeText(); 1963 invalidate(); 1964 } 1965 } 1966 } 1967 1968 /** 1969 * Returns the type of auto-size set for this widget. 1970 * 1971 * @return an {@code int} corresponding to one of the auto-size types: 1972 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1973 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1974 * 1975 * @attr ref android.R.styleable#TextView_autoSizeTextType 1976 * 1977 * @see #setAutoSizeTextTypeWithDefaults(int) 1978 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1979 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1980 */ 1981 @InspectableProperty(enumMapping = { 1982 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 1983 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 1984 }) 1985 @AutoSizeTextType getAutoSizeTextType()1986 public int getAutoSizeTextType() { 1987 return mAutoSizeTextType; 1988 } 1989 1990 /** 1991 * @return the current auto-size step granularity in pixels. 1992 * 1993 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1994 * 1995 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1996 */ 1997 @InspectableProperty getAutoSizeStepGranularity()1998 public int getAutoSizeStepGranularity() { 1999 return Math.round(mAutoSizeStepGranularityInPx); 2000 } 2001 2002 /** 2003 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 2004 * if auto-size has not been configured this function returns {@code -1}. 2005 * 2006 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2007 * 2008 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2009 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2010 */ 2011 @InspectableProperty getAutoSizeMinTextSize()2012 public int getAutoSizeMinTextSize() { 2013 return Math.round(mAutoSizeMinTextSizeInPx); 2014 } 2015 2016 /** 2017 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 2018 * if auto-size has not been configured this function returns {@code -1}. 2019 * 2020 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2021 * 2022 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2023 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2024 */ 2025 @InspectableProperty getAutoSizeMaxTextSize()2026 public int getAutoSizeMaxTextSize() { 2027 return Math.round(mAutoSizeMaxTextSizeInPx); 2028 } 2029 2030 /** 2031 * @return the current auto-size {@code int} sizes array (in pixels). 2032 * 2033 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2034 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2035 */ getAutoSizeTextAvailableSizes()2036 public int[] getAutoSizeTextAvailableSizes() { 2037 return mAutoSizeTextSizesInPx; 2038 } 2039 setupAutoSizeUniformPresetSizes(TypedArray textSizes)2040 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2041 final int textSizesLength = textSizes.length(); 2042 final int[] parsedSizes = new int[textSizesLength]; 2043 2044 if (textSizesLength > 0) { 2045 for (int i = 0; i < textSizesLength; i++) { 2046 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2047 } 2048 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2049 setupAutoSizeUniformPresetSizesConfiguration(); 2050 } 2051 } 2052 setupAutoSizeUniformPresetSizesConfiguration()2053 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2054 final int sizesLength = mAutoSizeTextSizesInPx.length; 2055 mHasPresetAutoSizeValues = sizesLength > 0; 2056 if (mHasPresetAutoSizeValues) { 2057 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2058 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2059 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2060 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2061 } 2062 return mHasPresetAutoSizeValues; 2063 } 2064 2065 /** 2066 * If all params are valid then save the auto-size configuration. 2067 * 2068 * @throws IllegalArgumentException if any of the params are invalid 2069 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2070 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2071 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2072 // First validate. 2073 if (autoSizeMinTextSizeInPx <= 0) { 2074 throw new IllegalArgumentException("Minimum auto-size text size (" 2075 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2076 } 2077 2078 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2079 throw new IllegalArgumentException("Maximum auto-size text size (" 2080 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2081 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2082 } 2083 2084 if (autoSizeStepGranularityInPx <= 0) { 2085 throw new IllegalArgumentException("The auto-size step granularity (" 2086 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2087 } 2088 2089 // All good, persist the configuration. 2090 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2091 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2092 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2093 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2094 mHasPresetAutoSizeValues = false; 2095 } 2096 clearAutoSizeConfiguration()2097 private void clearAutoSizeConfiguration() { 2098 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2099 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2100 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2101 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2102 mAutoSizeTextSizesInPx = EmptyArray.INT; 2103 mNeedsAutoSizeText = false; 2104 } 2105 2106 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2107 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2108 final int presetValuesLength = presetValues.length; 2109 if (presetValuesLength == 0) { 2110 return presetValues; 2111 } 2112 Arrays.sort(presetValues); 2113 2114 final IntArray uniqueValidSizes = new IntArray(); 2115 for (int i = 0; i < presetValuesLength; i++) { 2116 final int currentPresetValue = presetValues[i]; 2117 2118 if (currentPresetValue > 0 2119 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2120 uniqueValidSizes.add(currentPresetValue); 2121 } 2122 } 2123 2124 return presetValuesLength == uniqueValidSizes.size() 2125 ? presetValues 2126 : uniqueValidSizes.toArray(); 2127 } 2128 setupAutoSizeText()2129 private boolean setupAutoSizeText() { 2130 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2131 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2132 // not have a predefined set of sizes or if the current sizes array is empty. 2133 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2134 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2135 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2136 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2137 for (int i = 0; i < autoSizeValuesLength; i++) { 2138 autoSizeTextSizesInPx[i] = Math.round( 2139 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2140 } 2141 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2142 } 2143 2144 mNeedsAutoSizeText = true; 2145 } else { 2146 mNeedsAutoSizeText = false; 2147 } 2148 2149 return mNeedsAutoSizeText; 2150 } 2151 parseDimensionArray(TypedArray dimens)2152 private int[] parseDimensionArray(TypedArray dimens) { 2153 if (dimens == null) { 2154 return null; 2155 } 2156 int[] result = new int[dimens.length()]; 2157 for (int i = 0; i < result.length; i++) { 2158 result[i] = dimens.getDimensionPixelSize(i, 0); 2159 } 2160 return result; 2161 } 2162 2163 /** 2164 * @hide 2165 */ 2166 @TestApi 2167 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2168 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 2169 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2170 if (resultCode == Activity.RESULT_OK && data != null) { 2171 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2172 if (result != null) { 2173 if (isTextEditable()) { 2174 ClipData clip = ClipData.newPlainText("", result); 2175 ContentInfo payload = 2176 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); 2177 performReceiveContent(payload); 2178 if (mEditor != null) { 2179 mEditor.refreshTextActionMode(); 2180 } 2181 } else { 2182 if (result.length() > 0) { 2183 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2184 .show(); 2185 } 2186 } 2187 } 2188 } else if (mSpannable != null) { 2189 // Reset the selection. 2190 Selection.setSelection(mSpannable, getSelectionEnd()); 2191 } 2192 } 2193 } 2194 2195 /** 2196 * Sets the Typeface taking into account the given attributes. 2197 * 2198 * @param typeface a typeface 2199 * @param familyName family name string, e.g. "serif" 2200 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2201 * @param style a typeface style 2202 * @param weight a weight value for the Typeface or -1 if not specified. 2203 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2204 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2205 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2206 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2207 if (typeface == null && familyName != null) { 2208 // Lookup normal Typeface from system font map. 2209 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2210 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2211 } else if (typeface != null) { 2212 resolveStyleAndSetTypeface(typeface, style, weight); 2213 } else { // both typeface and familyName is null. 2214 switch (typefaceIndex) { 2215 case SANS: 2216 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2217 break; 2218 case SERIF: 2219 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2220 break; 2221 case MONOSPACE: 2222 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2223 break; 2224 case DEFAULT_TYPEFACE: 2225 default: 2226 resolveStyleAndSetTypeface(null, style, weight); 2227 break; 2228 } 2229 } 2230 } 2231 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2232 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2233 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2234 if (weight >= 0) { 2235 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2236 final boolean italic = (style & Typeface.ITALIC) != 0; 2237 setTypeface(Typeface.create(typeface, weight, italic)); 2238 } else { 2239 setTypeface(typeface, style); 2240 } 2241 } 2242 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2243 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2244 boolean hasRelativeDrawables = (start != null) || (end != null); 2245 if (hasRelativeDrawables) { 2246 Drawables dr = mDrawables; 2247 if (dr == null) { 2248 mDrawables = dr = new Drawables(getContext()); 2249 } 2250 mDrawables.mOverride = true; 2251 final Rect compoundRect = dr.mCompoundRect; 2252 int[] state = getDrawableState(); 2253 if (start != null) { 2254 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2255 start.setState(state); 2256 start.copyBounds(compoundRect); 2257 start.setCallback(this); 2258 2259 dr.mDrawableStart = start; 2260 dr.mDrawableSizeStart = compoundRect.width(); 2261 dr.mDrawableHeightStart = compoundRect.height(); 2262 } else { 2263 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2264 } 2265 if (end != null) { 2266 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2267 end.setState(state); 2268 end.copyBounds(compoundRect); 2269 end.setCallback(this); 2270 2271 dr.mDrawableEnd = end; 2272 dr.mDrawableSizeEnd = compoundRect.width(); 2273 dr.mDrawableHeightEnd = compoundRect.height(); 2274 } else { 2275 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2276 } 2277 resetResolvedDrawables(); 2278 resolveDrawables(); 2279 applyCompoundDrawableTint(); 2280 } 2281 } 2282 2283 @android.view.RemotableViewMethod 2284 @Override setEnabled(boolean enabled)2285 public void setEnabled(boolean enabled) { 2286 if (enabled == isEnabled()) { 2287 return; 2288 } 2289 2290 if (!enabled) { 2291 // Hide the soft input if the currently active TextView is disabled 2292 InputMethodManager imm = getInputMethodManager(); 2293 if (imm != null && imm.isActive(this)) { 2294 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2295 } 2296 } 2297 2298 super.setEnabled(enabled); 2299 2300 if (enabled) { 2301 // Make sure IME is updated with current editor info. 2302 InputMethodManager imm = getInputMethodManager(); 2303 if (imm != null) imm.restartInput(this); 2304 } 2305 2306 // Will change text color 2307 if (mEditor != null) { 2308 mEditor.invalidateTextDisplayList(); 2309 mEditor.prepareCursorControllers(); 2310 2311 // start or stop the cursor blinking as appropriate 2312 mEditor.makeBlink(); 2313 } 2314 } 2315 2316 /** 2317 * Sets the typeface and style in which the text should be displayed, 2318 * and turns on the fake bold and italic bits in the Paint if the 2319 * Typeface that you provided does not have all the bits in the 2320 * style that you specified. 2321 * 2322 * @attr ref android.R.styleable#TextView_typeface 2323 * @attr ref android.R.styleable#TextView_textStyle 2324 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2325 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2326 if (style > 0) { 2327 if (tf == null) { 2328 tf = Typeface.defaultFromStyle(style); 2329 } else { 2330 tf = Typeface.create(tf, style); 2331 } 2332 2333 setTypeface(tf); 2334 // now compute what (if any) algorithmic styling is needed 2335 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2336 int need = style & ~typefaceStyle; 2337 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2338 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2339 } else { 2340 mTextPaint.setFakeBoldText(false); 2341 mTextPaint.setTextSkewX(0); 2342 setTypeface(tf); 2343 } 2344 } 2345 2346 /** 2347 * Subclasses override this to specify that they have a KeyListener 2348 * by default even if not specifically called for in the XML options. 2349 */ getDefaultEditable()2350 protected boolean getDefaultEditable() { 2351 return false; 2352 } 2353 2354 /** 2355 * Subclasses override this to specify a default movement method. 2356 */ getDefaultMovementMethod()2357 protected MovementMethod getDefaultMovementMethod() { 2358 return null; 2359 } 2360 2361 /** 2362 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2363 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2364 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2365 * the return value from this method to Spannable or Editable, respectively. 2366 * 2367 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2368 * should make your own copy first.</p> 2369 * 2370 * @return The text displayed by the text view. 2371 * @attr ref android.R.styleable#TextView_text 2372 */ 2373 @ViewDebug.CapturedViewProperty 2374 @InspectableProperty getText()2375 public CharSequence getText() { 2376 if (mUseTextPaddingForUiTranslation) { 2377 ViewTranslationCallback callback = getViewTranslationCallback(); 2378 if (callback != null && callback instanceof TextViewTranslationCallback) { 2379 TextViewTranslationCallback defaultCallback = 2380 (TextViewTranslationCallback) callback; 2381 if (defaultCallback.isTextPaddingEnabled() 2382 && defaultCallback.isShowingTranslation()) { 2383 return defaultCallback.getPaddedText(mText, mTransformed); 2384 } 2385 } 2386 } 2387 return mText; 2388 } 2389 2390 /** 2391 * Returns the length, in characters, of the text managed by this TextView 2392 * @return The length of the text managed by the TextView in characters. 2393 */ length()2394 public int length() { 2395 return mText.length(); 2396 } 2397 2398 /** 2399 * Return the text that TextView is displaying as an Editable object. If the text is not 2400 * editable, null is returned. 2401 * 2402 * @see #getText 2403 */ getEditableText()2404 public Editable getEditableText() { 2405 return (mText instanceof Editable) ? (Editable) mText : null; 2406 } 2407 2408 /** 2409 * @hide 2410 */ 2411 @VisibleForTesting getTransformed()2412 public CharSequence getTransformed() { 2413 return mTransformed; 2414 } 2415 2416 /** 2417 * Gets the vertical distance between lines of text, in pixels. 2418 * Note that markup within the text can cause individual lines 2419 * to be taller or shorter than this height, and the layout may 2420 * contain additional first-or last-line padding. 2421 * @return The height of one standard line in pixels. 2422 */ 2423 @InspectableProperty getLineHeight()2424 public int getLineHeight() { 2425 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2426 } 2427 2428 /** 2429 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2430 * This value can be null if the text or width has recently changed. 2431 * @return The Layout that is currently being used to display the text. 2432 */ getLayout()2433 public final Layout getLayout() { 2434 return mLayout; 2435 } 2436 2437 /** 2438 * @return the {@link android.text.Layout} that is currently being used to 2439 * display the hint text. This can be null. 2440 */ 2441 @UnsupportedAppUsage getHintLayout()2442 final Layout getHintLayout() { 2443 return mHintLayout; 2444 } 2445 2446 /** 2447 * Retrieve the {@link android.content.UndoManager} that is currently associated 2448 * with this TextView. By default there is no associated UndoManager, so null 2449 * is returned. One can be associated with the TextView through 2450 * {@link #setUndoManager(android.content.UndoManager, String)} 2451 * 2452 * @hide 2453 */ getUndoManager()2454 public final UndoManager getUndoManager() { 2455 // TODO: Consider supporting a global undo manager. 2456 throw new UnsupportedOperationException("not implemented"); 2457 } 2458 2459 2460 /** 2461 * @hide 2462 */ 2463 @VisibleForTesting getEditorForTesting()2464 public final Editor getEditorForTesting() { 2465 return mEditor; 2466 } 2467 2468 /** 2469 * Associate an {@link android.content.UndoManager} with this TextView. Once 2470 * done, all edit operations on the TextView will result in appropriate 2471 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2472 * stack. 2473 * 2474 * @param undoManager The {@link android.content.UndoManager} to associate with 2475 * this TextView, or null to clear any existing association. 2476 * @param tag String tag identifying this particular TextView owner in the 2477 * UndoManager. This is used to keep the correct association with the 2478 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2479 * 2480 * @hide 2481 */ setUndoManager(UndoManager undoManager, String tag)2482 public final void setUndoManager(UndoManager undoManager, String tag) { 2483 // TODO: Consider supporting a global undo manager. An implementation will need to: 2484 // * createEditorIfNeeded() 2485 // * Promote to BufferType.EDITABLE if needed. 2486 // * Update the UndoManager and UndoOwner. 2487 // Likewise it will need to be able to restore the default UndoManager. 2488 throw new UnsupportedOperationException("not implemented"); 2489 } 2490 2491 /** 2492 * Gets the current {@link KeyListener} for the TextView. 2493 * This will frequently be null for non-EditText TextViews. 2494 * @return the current key listener for this TextView. 2495 * 2496 * @attr ref android.R.styleable#TextView_numeric 2497 * @attr ref android.R.styleable#TextView_digits 2498 * @attr ref android.R.styleable#TextView_phoneNumber 2499 * @attr ref android.R.styleable#TextView_inputMethod 2500 * @attr ref android.R.styleable#TextView_capitalize 2501 * @attr ref android.R.styleable#TextView_autoText 2502 */ getKeyListener()2503 public final KeyListener getKeyListener() { 2504 return mEditor == null ? null : mEditor.mKeyListener; 2505 } 2506 2507 /** 2508 * Sets the key listener to be used with this TextView. This can be null 2509 * to disallow user input. Note that this method has significant and 2510 * subtle interactions with soft keyboards and other input method: 2511 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2512 * for important details. Calling this method will replace the current 2513 * content type of the text view with the content type returned by the 2514 * key listener. 2515 * <p> 2516 * Be warned that if you want a TextView with a key listener or movement 2517 * method not to be focusable, or if you want a TextView without a 2518 * key listener or movement method to be focusable, you must call 2519 * {@link #setFocusable} again after calling this to get the focusability 2520 * back the way you want it. 2521 * 2522 * @attr ref android.R.styleable#TextView_numeric 2523 * @attr ref android.R.styleable#TextView_digits 2524 * @attr ref android.R.styleable#TextView_phoneNumber 2525 * @attr ref android.R.styleable#TextView_inputMethod 2526 * @attr ref android.R.styleable#TextView_capitalize 2527 * @attr ref android.R.styleable#TextView_autoText 2528 */ setKeyListener(KeyListener input)2529 public void setKeyListener(KeyListener input) { 2530 mListenerChanged = true; 2531 setKeyListenerOnly(input); 2532 fixFocusableAndClickableSettings(); 2533 2534 if (input != null) { 2535 createEditorIfNeeded(); 2536 setInputTypeFromEditor(); 2537 } else { 2538 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2539 } 2540 2541 InputMethodManager imm = getInputMethodManager(); 2542 if (imm != null) imm.restartInput(this); 2543 } 2544 setInputTypeFromEditor()2545 private void setInputTypeFromEditor() { 2546 try { 2547 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2548 } catch (IncompatibleClassChangeError e) { 2549 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2550 } 2551 // Change inputType, without affecting transformation. 2552 // No need to applySingleLine since mSingleLine is unchanged. 2553 setInputTypeSingleLine(mSingleLine); 2554 } 2555 setKeyListenerOnly(KeyListener input)2556 private void setKeyListenerOnly(KeyListener input) { 2557 if (mEditor == null && input == null) return; // null is the default value 2558 2559 createEditorIfNeeded(); 2560 if (mEditor.mKeyListener != input) { 2561 mEditor.mKeyListener = input; 2562 if (input != null && !(mText instanceof Editable)) { 2563 setText(mText); 2564 } 2565 2566 setFilters((Editable) mText, mFilters); 2567 } 2568 } 2569 2570 /** 2571 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2572 * which provides positioning, scrolling, and text selection functionality. 2573 * This will frequently be null for non-EditText TextViews. 2574 * @return the movement method being used for this TextView. 2575 * @see android.text.method.MovementMethod 2576 */ getMovementMethod()2577 public final MovementMethod getMovementMethod() { 2578 return mMovement; 2579 } 2580 2581 /** 2582 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2583 * for this TextView. This can be null to disallow using the arrow keys to move the 2584 * cursor or scroll the view. 2585 * <p> 2586 * Be warned that if you want a TextView with a key listener or movement 2587 * method not to be focusable, or if you want a TextView without a 2588 * key listener or movement method to be focusable, you must call 2589 * {@link #setFocusable} again after calling this to get the focusability 2590 * back the way you want it. 2591 */ setMovementMethod(MovementMethod movement)2592 public final void setMovementMethod(MovementMethod movement) { 2593 if (mMovement != movement) { 2594 mMovement = movement; 2595 2596 if (movement != null && mSpannable == null) { 2597 setText(mText); 2598 } 2599 2600 fixFocusableAndClickableSettings(); 2601 2602 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2603 // mMovement 2604 if (mEditor != null) mEditor.prepareCursorControllers(); 2605 } 2606 } 2607 fixFocusableAndClickableSettings()2608 private void fixFocusableAndClickableSettings() { 2609 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2610 setFocusable(FOCUSABLE); 2611 setClickable(true); 2612 setLongClickable(true); 2613 } else { 2614 setFocusable(FOCUSABLE_AUTO); 2615 setClickable(false); 2616 setLongClickable(false); 2617 } 2618 } 2619 2620 /** 2621 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2622 * This is frequently null, except for single-line and password fields. 2623 * @return the current transformation method for this TextView. 2624 * 2625 * @attr ref android.R.styleable#TextView_password 2626 * @attr ref android.R.styleable#TextView_singleLine 2627 */ getTransformationMethod()2628 public final TransformationMethod getTransformationMethod() { 2629 return mTransformation; 2630 } 2631 2632 /** 2633 * Sets the transformation that is applied to the text that this 2634 * TextView is displaying. 2635 * 2636 * @attr ref android.R.styleable#TextView_password 2637 * @attr ref android.R.styleable#TextView_singleLine 2638 */ setTransformationMethod(TransformationMethod method)2639 public final void setTransformationMethod(TransformationMethod method) { 2640 if (method == mTransformation) { 2641 // Avoid the setText() below if the transformation is 2642 // the same. 2643 return; 2644 } 2645 if (mTransformation != null) { 2646 if (mSpannable != null) { 2647 mSpannable.removeSpan(mTransformation); 2648 } 2649 } 2650 2651 mTransformation = method; 2652 2653 if (method instanceof TransformationMethod2) { 2654 TransformationMethod2 method2 = (TransformationMethod2) method; 2655 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2656 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2657 } else { 2658 mAllowTransformationLengthChange = false; 2659 } 2660 2661 setText(mText); 2662 2663 if (hasPasswordTransformationMethod()) { 2664 notifyViewAccessibilityStateChangedIfNeeded( 2665 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2666 } 2667 2668 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2669 // getTextDirectionHeuristic, needs reset 2670 mTextDir = getTextDirectionHeuristic(); 2671 } 2672 2673 /** 2674 * Returns the top padding of the view, plus space for the top 2675 * Drawable if any. 2676 */ getCompoundPaddingTop()2677 public int getCompoundPaddingTop() { 2678 final Drawables dr = mDrawables; 2679 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2680 return mPaddingTop; 2681 } else { 2682 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2683 } 2684 } 2685 2686 /** 2687 * Returns the bottom padding of the view, plus space for the bottom 2688 * Drawable if any. 2689 */ getCompoundPaddingBottom()2690 public int getCompoundPaddingBottom() { 2691 final Drawables dr = mDrawables; 2692 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2693 return mPaddingBottom; 2694 } else { 2695 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2696 } 2697 } 2698 2699 /** 2700 * Returns the left padding of the view, plus space for the left 2701 * Drawable if any. 2702 */ getCompoundPaddingLeft()2703 public int getCompoundPaddingLeft() { 2704 final Drawables dr = mDrawables; 2705 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2706 return mPaddingLeft; 2707 } else { 2708 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2709 } 2710 } 2711 2712 /** 2713 * Returns the right padding of the view, plus space for the right 2714 * Drawable if any. 2715 */ getCompoundPaddingRight()2716 public int getCompoundPaddingRight() { 2717 final Drawables dr = mDrawables; 2718 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2719 return mPaddingRight; 2720 } else { 2721 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2722 } 2723 } 2724 2725 /** 2726 * Returns the start padding of the view, plus space for the start 2727 * Drawable if any. 2728 */ getCompoundPaddingStart()2729 public int getCompoundPaddingStart() { 2730 resolveDrawables(); 2731 switch(getLayoutDirection()) { 2732 default: 2733 case LAYOUT_DIRECTION_LTR: 2734 return getCompoundPaddingLeft(); 2735 case LAYOUT_DIRECTION_RTL: 2736 return getCompoundPaddingRight(); 2737 } 2738 } 2739 2740 /** 2741 * Returns the end padding of the view, plus space for the end 2742 * Drawable if any. 2743 */ getCompoundPaddingEnd()2744 public int getCompoundPaddingEnd() { 2745 resolveDrawables(); 2746 switch(getLayoutDirection()) { 2747 default: 2748 case LAYOUT_DIRECTION_LTR: 2749 return getCompoundPaddingRight(); 2750 case LAYOUT_DIRECTION_RTL: 2751 return getCompoundPaddingLeft(); 2752 } 2753 } 2754 2755 /** 2756 * Returns the extended top padding of the view, including both the 2757 * top Drawable if any and any extra space to keep more than maxLines 2758 * of text from showing. It is only valid to call this after measuring. 2759 */ getExtendedPaddingTop()2760 public int getExtendedPaddingTop() { 2761 if (mMaxMode != LINES) { 2762 return getCompoundPaddingTop(); 2763 } 2764 2765 if (mLayout == null) { 2766 assumeLayout(); 2767 } 2768 2769 if (mLayout.getLineCount() <= mMaximum) { 2770 return getCompoundPaddingTop(); 2771 } 2772 2773 int top = getCompoundPaddingTop(); 2774 int bottom = getCompoundPaddingBottom(); 2775 int viewht = getHeight() - top - bottom; 2776 int layoutht = mLayout.getLineTop(mMaximum); 2777 2778 if (layoutht >= viewht) { 2779 return top; 2780 } 2781 2782 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2783 if (gravity == Gravity.TOP) { 2784 return top; 2785 } else if (gravity == Gravity.BOTTOM) { 2786 return top + viewht - layoutht; 2787 } else { // (gravity == Gravity.CENTER_VERTICAL) 2788 return top + (viewht - layoutht) / 2; 2789 } 2790 } 2791 2792 /** 2793 * Returns the extended bottom padding of the view, including both the 2794 * bottom Drawable if any and any extra space to keep more than maxLines 2795 * of text from showing. It is only valid to call this after measuring. 2796 */ getExtendedPaddingBottom()2797 public int getExtendedPaddingBottom() { 2798 if (mMaxMode != LINES) { 2799 return getCompoundPaddingBottom(); 2800 } 2801 2802 if (mLayout == null) { 2803 assumeLayout(); 2804 } 2805 2806 if (mLayout.getLineCount() <= mMaximum) { 2807 return getCompoundPaddingBottom(); 2808 } 2809 2810 int top = getCompoundPaddingTop(); 2811 int bottom = getCompoundPaddingBottom(); 2812 int viewht = getHeight() - top - bottom; 2813 int layoutht = mLayout.getLineTop(mMaximum); 2814 2815 if (layoutht >= viewht) { 2816 return bottom; 2817 } 2818 2819 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2820 if (gravity == Gravity.TOP) { 2821 return bottom + viewht - layoutht; 2822 } else if (gravity == Gravity.BOTTOM) { 2823 return bottom; 2824 } else { // (gravity == Gravity.CENTER_VERTICAL) 2825 return bottom + (viewht - layoutht) / 2; 2826 } 2827 } 2828 2829 /** 2830 * Returns the total left padding of the view, including the left 2831 * Drawable if any. 2832 */ getTotalPaddingLeft()2833 public int getTotalPaddingLeft() { 2834 return getCompoundPaddingLeft(); 2835 } 2836 2837 /** 2838 * Returns the total right padding of the view, including the right 2839 * Drawable if any. 2840 */ getTotalPaddingRight()2841 public int getTotalPaddingRight() { 2842 return getCompoundPaddingRight(); 2843 } 2844 2845 /** 2846 * Returns the total start padding of the view, including the start 2847 * Drawable if any. 2848 */ getTotalPaddingStart()2849 public int getTotalPaddingStart() { 2850 return getCompoundPaddingStart(); 2851 } 2852 2853 /** 2854 * Returns the total end padding of the view, including the end 2855 * Drawable if any. 2856 */ getTotalPaddingEnd()2857 public int getTotalPaddingEnd() { 2858 return getCompoundPaddingEnd(); 2859 } 2860 2861 /** 2862 * Returns the total top padding of the view, including the top 2863 * Drawable if any, the extra space to keep more than maxLines 2864 * from showing, and the vertical offset for gravity, if any. 2865 */ getTotalPaddingTop()2866 public int getTotalPaddingTop() { 2867 return getExtendedPaddingTop() + getVerticalOffset(true); 2868 } 2869 2870 /** 2871 * Returns the total bottom padding of the view, including the bottom 2872 * Drawable if any, the extra space to keep more than maxLines 2873 * from showing, and the vertical offset for gravity, if any. 2874 */ getTotalPaddingBottom()2875 public int getTotalPaddingBottom() { 2876 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2877 } 2878 2879 /** 2880 * Sets the Drawables (if any) to appear to the left of, above, to the 2881 * right of, and below the text. Use {@code null} if you do not want a 2882 * Drawable there. The Drawables must already have had 2883 * {@link Drawable#setBounds} called. 2884 * <p> 2885 * Calling this method will overwrite any Drawables previously set using 2886 * {@link #setCompoundDrawablesRelative} or related methods. 2887 * 2888 * @attr ref android.R.styleable#TextView_drawableLeft 2889 * @attr ref android.R.styleable#TextView_drawableTop 2890 * @attr ref android.R.styleable#TextView_drawableRight 2891 * @attr ref android.R.styleable#TextView_drawableBottom 2892 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2893 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2894 @Nullable Drawable right, @Nullable Drawable bottom) { 2895 Drawables dr = mDrawables; 2896 2897 // We're switching to absolute, discard relative. 2898 if (dr != null) { 2899 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2900 dr.mDrawableStart = null; 2901 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2902 dr.mDrawableEnd = null; 2903 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2904 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2905 } 2906 2907 final boolean drawables = left != null || top != null || right != null || bottom != null; 2908 if (!drawables) { 2909 // Clearing drawables... can we free the data structure? 2910 if (dr != null) { 2911 if (!dr.hasMetadata()) { 2912 mDrawables = null; 2913 } else { 2914 // We need to retain the last set padding, so just clear 2915 // out all of the fields in the existing structure. 2916 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2917 if (dr.mShowing[i] != null) { 2918 dr.mShowing[i].setCallback(null); 2919 } 2920 dr.mShowing[i] = null; 2921 } 2922 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2923 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2924 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2925 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2926 } 2927 } 2928 } else { 2929 if (dr == null) { 2930 mDrawables = dr = new Drawables(getContext()); 2931 } 2932 2933 mDrawables.mOverride = false; 2934 2935 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2936 dr.mShowing[Drawables.LEFT].setCallback(null); 2937 } 2938 dr.mShowing[Drawables.LEFT] = left; 2939 2940 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2941 dr.mShowing[Drawables.TOP].setCallback(null); 2942 } 2943 dr.mShowing[Drawables.TOP] = top; 2944 2945 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2946 dr.mShowing[Drawables.RIGHT].setCallback(null); 2947 } 2948 dr.mShowing[Drawables.RIGHT] = right; 2949 2950 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2951 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2952 } 2953 dr.mShowing[Drawables.BOTTOM] = bottom; 2954 2955 final Rect compoundRect = dr.mCompoundRect; 2956 int[] state; 2957 2958 state = getDrawableState(); 2959 2960 if (left != null) { 2961 left.setState(state); 2962 left.copyBounds(compoundRect); 2963 left.setCallback(this); 2964 dr.mDrawableSizeLeft = compoundRect.width(); 2965 dr.mDrawableHeightLeft = compoundRect.height(); 2966 } else { 2967 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2968 } 2969 2970 if (right != null) { 2971 right.setState(state); 2972 right.copyBounds(compoundRect); 2973 right.setCallback(this); 2974 dr.mDrawableSizeRight = compoundRect.width(); 2975 dr.mDrawableHeightRight = compoundRect.height(); 2976 } else { 2977 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2978 } 2979 2980 if (top != null) { 2981 top.setState(state); 2982 top.copyBounds(compoundRect); 2983 top.setCallback(this); 2984 dr.mDrawableSizeTop = compoundRect.height(); 2985 dr.mDrawableWidthTop = compoundRect.width(); 2986 } else { 2987 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2988 } 2989 2990 if (bottom != null) { 2991 bottom.setState(state); 2992 bottom.copyBounds(compoundRect); 2993 bottom.setCallback(this); 2994 dr.mDrawableSizeBottom = compoundRect.height(); 2995 dr.mDrawableWidthBottom = compoundRect.width(); 2996 } else { 2997 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2998 } 2999 } 3000 3001 // Save initial left/right drawables 3002 if (dr != null) { 3003 dr.mDrawableLeftInitial = left; 3004 dr.mDrawableRightInitial = right; 3005 } 3006 3007 resetResolvedDrawables(); 3008 resolveDrawables(); 3009 applyCompoundDrawableTint(); 3010 invalidate(); 3011 requestLayout(); 3012 } 3013 3014 /** 3015 * Sets the Drawables (if any) to appear to the left of, above, to the 3016 * right of, and below the text. Use 0 if you do not want a Drawable there. 3017 * The Drawables' bounds will be set to their intrinsic bounds. 3018 * <p> 3019 * Calling this method will overwrite any Drawables previously set using 3020 * {@link #setCompoundDrawablesRelative} or related methods. 3021 * 3022 * @param left Resource identifier of the left Drawable. 3023 * @param top Resource identifier of the top Drawable. 3024 * @param right Resource identifier of the right Drawable. 3025 * @param bottom Resource identifier of the bottom Drawable. 3026 * 3027 * @attr ref android.R.styleable#TextView_drawableLeft 3028 * @attr ref android.R.styleable#TextView_drawableTop 3029 * @attr ref android.R.styleable#TextView_drawableRight 3030 * @attr ref android.R.styleable#TextView_drawableBottom 3031 */ 3032 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3033 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 3034 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 3035 final Context context = getContext(); 3036 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 3037 top != 0 ? context.getDrawable(top) : null, 3038 right != 0 ? context.getDrawable(right) : null, 3039 bottom != 0 ? context.getDrawable(bottom) : null); 3040 } 3041 3042 /** 3043 * Sets the Drawables (if any) to appear to the left of, above, to the 3044 * right of, and below the text. Use {@code null} if you do not want a 3045 * Drawable there. The Drawables' bounds will be set to their intrinsic 3046 * bounds. 3047 * <p> 3048 * Calling this method will overwrite any Drawables previously set using 3049 * {@link #setCompoundDrawablesRelative} or related methods. 3050 * 3051 * @attr ref android.R.styleable#TextView_drawableLeft 3052 * @attr ref android.R.styleable#TextView_drawableTop 3053 * @attr ref android.R.styleable#TextView_drawableRight 3054 * @attr ref android.R.styleable#TextView_drawableBottom 3055 */ 3056 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3057 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3058 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3059 3060 if (left != null) { 3061 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3062 } 3063 if (right != null) { 3064 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3065 } 3066 if (top != null) { 3067 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3068 } 3069 if (bottom != null) { 3070 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3071 } 3072 setCompoundDrawables(left, top, right, bottom); 3073 } 3074 3075 /** 3076 * Sets the Drawables (if any) to appear to the start of, above, to the end 3077 * of, and below the text. Use {@code null} if you do not want a Drawable 3078 * there. The Drawables must already have had {@link Drawable#setBounds} 3079 * called. 3080 * <p> 3081 * Calling this method will overwrite any Drawables previously set using 3082 * {@link #setCompoundDrawables} or related methods. 3083 * 3084 * @attr ref android.R.styleable#TextView_drawableStart 3085 * @attr ref android.R.styleable#TextView_drawableTop 3086 * @attr ref android.R.styleable#TextView_drawableEnd 3087 * @attr ref android.R.styleable#TextView_drawableBottom 3088 */ 3089 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3090 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3091 @Nullable Drawable end, @Nullable Drawable bottom) { 3092 Drawables dr = mDrawables; 3093 3094 // We're switching to relative, discard absolute. 3095 if (dr != null) { 3096 if (dr.mShowing[Drawables.LEFT] != null) { 3097 dr.mShowing[Drawables.LEFT].setCallback(null); 3098 } 3099 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3100 if (dr.mShowing[Drawables.RIGHT] != null) { 3101 dr.mShowing[Drawables.RIGHT].setCallback(null); 3102 } 3103 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3104 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3105 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3106 } 3107 3108 final boolean drawables = start != null || top != null 3109 || end != null || bottom != null; 3110 3111 if (!drawables) { 3112 // Clearing drawables... can we free the data structure? 3113 if (dr != null) { 3114 if (!dr.hasMetadata()) { 3115 mDrawables = null; 3116 } else { 3117 // We need to retain the last set padding, so just clear 3118 // out all of the fields in the existing structure. 3119 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3120 dr.mDrawableStart = null; 3121 if (dr.mShowing[Drawables.TOP] != null) { 3122 dr.mShowing[Drawables.TOP].setCallback(null); 3123 } 3124 dr.mShowing[Drawables.TOP] = null; 3125 if (dr.mDrawableEnd != null) { 3126 dr.mDrawableEnd.setCallback(null); 3127 } 3128 dr.mDrawableEnd = null; 3129 if (dr.mShowing[Drawables.BOTTOM] != null) { 3130 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3131 } 3132 dr.mShowing[Drawables.BOTTOM] = null; 3133 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3134 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3135 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3136 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3137 } 3138 } 3139 } else { 3140 if (dr == null) { 3141 mDrawables = dr = new Drawables(getContext()); 3142 } 3143 3144 mDrawables.mOverride = true; 3145 3146 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3147 dr.mDrawableStart.setCallback(null); 3148 } 3149 dr.mDrawableStart = start; 3150 3151 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3152 dr.mShowing[Drawables.TOP].setCallback(null); 3153 } 3154 dr.mShowing[Drawables.TOP] = top; 3155 3156 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3157 dr.mDrawableEnd.setCallback(null); 3158 } 3159 dr.mDrawableEnd = end; 3160 3161 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3162 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3163 } 3164 dr.mShowing[Drawables.BOTTOM] = bottom; 3165 3166 final Rect compoundRect = dr.mCompoundRect; 3167 int[] state; 3168 3169 state = getDrawableState(); 3170 3171 if (start != null) { 3172 start.setState(state); 3173 start.copyBounds(compoundRect); 3174 start.setCallback(this); 3175 dr.mDrawableSizeStart = compoundRect.width(); 3176 dr.mDrawableHeightStart = compoundRect.height(); 3177 } else { 3178 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3179 } 3180 3181 if (end != null) { 3182 end.setState(state); 3183 end.copyBounds(compoundRect); 3184 end.setCallback(this); 3185 dr.mDrawableSizeEnd = compoundRect.width(); 3186 dr.mDrawableHeightEnd = compoundRect.height(); 3187 } else { 3188 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3189 } 3190 3191 if (top != null) { 3192 top.setState(state); 3193 top.copyBounds(compoundRect); 3194 top.setCallback(this); 3195 dr.mDrawableSizeTop = compoundRect.height(); 3196 dr.mDrawableWidthTop = compoundRect.width(); 3197 } else { 3198 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3199 } 3200 3201 if (bottom != null) { 3202 bottom.setState(state); 3203 bottom.copyBounds(compoundRect); 3204 bottom.setCallback(this); 3205 dr.mDrawableSizeBottom = compoundRect.height(); 3206 dr.mDrawableWidthBottom = compoundRect.width(); 3207 } else { 3208 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3209 } 3210 } 3211 3212 resetResolvedDrawables(); 3213 resolveDrawables(); 3214 invalidate(); 3215 requestLayout(); 3216 } 3217 3218 /** 3219 * Sets the Drawables (if any) to appear to the start of, above, to the end 3220 * of, and below the text. Use 0 if you do not want a Drawable there. The 3221 * Drawables' bounds will be set to their intrinsic bounds. 3222 * <p> 3223 * Calling this method will overwrite any Drawables previously set using 3224 * {@link #setCompoundDrawables} or related methods. 3225 * 3226 * @param start Resource identifier of the start Drawable. 3227 * @param top Resource identifier of the top Drawable. 3228 * @param end Resource identifier of the end Drawable. 3229 * @param bottom Resource identifier of the bottom Drawable. 3230 * 3231 * @attr ref android.R.styleable#TextView_drawableStart 3232 * @attr ref android.R.styleable#TextView_drawableTop 3233 * @attr ref android.R.styleable#TextView_drawableEnd 3234 * @attr ref android.R.styleable#TextView_drawableBottom 3235 */ 3236 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3237 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3238 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3239 final Context context = getContext(); 3240 setCompoundDrawablesRelativeWithIntrinsicBounds( 3241 start != 0 ? context.getDrawable(start) : null, 3242 top != 0 ? context.getDrawable(top) : null, 3243 end != 0 ? context.getDrawable(end) : null, 3244 bottom != 0 ? context.getDrawable(bottom) : null); 3245 } 3246 3247 /** 3248 * Sets the Drawables (if any) to appear to the start of, above, to the end 3249 * of, and below the text. Use {@code null} if you do not want a Drawable 3250 * there. The Drawables' bounds will be set to their intrinsic bounds. 3251 * <p> 3252 * Calling this method will overwrite any Drawables previously set using 3253 * {@link #setCompoundDrawables} or related methods. 3254 * 3255 * @attr ref android.R.styleable#TextView_drawableStart 3256 * @attr ref android.R.styleable#TextView_drawableTop 3257 * @attr ref android.R.styleable#TextView_drawableEnd 3258 * @attr ref android.R.styleable#TextView_drawableBottom 3259 */ 3260 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3261 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3262 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3263 3264 if (start != null) { 3265 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3266 } 3267 if (end != null) { 3268 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3269 } 3270 if (top != null) { 3271 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3272 } 3273 if (bottom != null) { 3274 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3275 } 3276 setCompoundDrawablesRelative(start, top, end, bottom); 3277 } 3278 3279 /** 3280 * Returns drawables for the left, top, right, and bottom borders. 3281 * 3282 * @attr ref android.R.styleable#TextView_drawableLeft 3283 * @attr ref android.R.styleable#TextView_drawableTop 3284 * @attr ref android.R.styleable#TextView_drawableRight 3285 * @attr ref android.R.styleable#TextView_drawableBottom 3286 */ 3287 @NonNull getCompoundDrawables()3288 public Drawable[] getCompoundDrawables() { 3289 final Drawables dr = mDrawables; 3290 if (dr != null) { 3291 return dr.mShowing.clone(); 3292 } else { 3293 return new Drawable[] { null, null, null, null }; 3294 } 3295 } 3296 3297 /** 3298 * Returns drawables for the start, top, end, and bottom borders. 3299 * 3300 * @attr ref android.R.styleable#TextView_drawableStart 3301 * @attr ref android.R.styleable#TextView_drawableTop 3302 * @attr ref android.R.styleable#TextView_drawableEnd 3303 * @attr ref android.R.styleable#TextView_drawableBottom 3304 */ 3305 @NonNull getCompoundDrawablesRelative()3306 public Drawable[] getCompoundDrawablesRelative() { 3307 final Drawables dr = mDrawables; 3308 if (dr != null) { 3309 return new Drawable[] { 3310 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3311 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3312 }; 3313 } else { 3314 return new Drawable[] { null, null, null, null }; 3315 } 3316 } 3317 3318 /** 3319 * Sets the size of the padding between the compound drawables and 3320 * the text. 3321 * 3322 * @attr ref android.R.styleable#TextView_drawablePadding 3323 */ 3324 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3325 public void setCompoundDrawablePadding(int pad) { 3326 Drawables dr = mDrawables; 3327 if (pad == 0) { 3328 if (dr != null) { 3329 dr.mDrawablePadding = pad; 3330 } 3331 } else { 3332 if (dr == null) { 3333 mDrawables = dr = new Drawables(getContext()); 3334 } 3335 dr.mDrawablePadding = pad; 3336 } 3337 3338 invalidate(); 3339 requestLayout(); 3340 } 3341 3342 /** 3343 * Returns the padding between the compound drawables and the text. 3344 * 3345 * @attr ref android.R.styleable#TextView_drawablePadding 3346 */ 3347 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3348 public int getCompoundDrawablePadding() { 3349 final Drawables dr = mDrawables; 3350 return dr != null ? dr.mDrawablePadding : 0; 3351 } 3352 3353 /** 3354 * Applies a tint to the compound drawables. Does not modify the 3355 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3356 * <p> 3357 * Subsequent calls to 3358 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3359 * and related methods will automatically mutate the drawables and apply 3360 * the specified tint and tint mode using 3361 * {@link Drawable#setTintList(ColorStateList)}. 3362 * 3363 * @param tint the tint to apply, may be {@code null} to clear tint 3364 * 3365 * @attr ref android.R.styleable#TextView_drawableTint 3366 * @see #getCompoundDrawableTintList() 3367 * @see Drawable#setTintList(ColorStateList) 3368 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3369 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3370 if (mDrawables == null) { 3371 mDrawables = new Drawables(getContext()); 3372 } 3373 mDrawables.mTintList = tint; 3374 mDrawables.mHasTint = true; 3375 3376 applyCompoundDrawableTint(); 3377 } 3378 3379 /** 3380 * @return the tint applied to the compound drawables 3381 * @attr ref android.R.styleable#TextView_drawableTint 3382 * @see #setCompoundDrawableTintList(ColorStateList) 3383 */ 3384 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3385 public ColorStateList getCompoundDrawableTintList() { 3386 return mDrawables != null ? mDrawables.mTintList : null; 3387 } 3388 3389 /** 3390 * Specifies the blending mode used to apply the tint specified by 3391 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3392 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3393 * 3394 * @param tintMode the blending mode used to apply the tint, may be 3395 * {@code null} to clear tint 3396 * @attr ref android.R.styleable#TextView_drawableTintMode 3397 * @see #setCompoundDrawableTintList(ColorStateList) 3398 * @see Drawable#setTintMode(PorterDuff.Mode) 3399 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3400 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3401 setCompoundDrawableTintBlendMode(tintMode != null 3402 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3403 } 3404 3405 /** 3406 * Specifies the blending mode used to apply the tint specified by 3407 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3408 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3409 * 3410 * @param blendMode the blending mode used to apply the tint, may be 3411 * {@code null} to clear tint 3412 * @attr ref android.R.styleable#TextView_drawableTintMode 3413 * @see #setCompoundDrawableTintList(ColorStateList) 3414 * @see Drawable#setTintBlendMode(BlendMode) 3415 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3416 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3417 if (mDrawables == null) { 3418 mDrawables = new Drawables(getContext()); 3419 } 3420 mDrawables.mBlendMode = blendMode; 3421 mDrawables.mHasTintMode = true; 3422 3423 applyCompoundDrawableTint(); 3424 } 3425 3426 /** 3427 * Returns the blending mode used to apply the tint to the compound 3428 * drawables, if specified. 3429 * 3430 * @return the blending mode used to apply the tint to the compound 3431 * drawables 3432 * @attr ref android.R.styleable#TextView_drawableTintMode 3433 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3434 * 3435 */ 3436 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3437 public PorterDuff.Mode getCompoundDrawableTintMode() { 3438 BlendMode mode = getCompoundDrawableTintBlendMode(); 3439 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3440 } 3441 3442 /** 3443 * Returns the blending mode used to apply the tint to the compound 3444 * drawables, if specified. 3445 * 3446 * @return the blending mode used to apply the tint to the compound 3447 * drawables 3448 * @attr ref android.R.styleable#TextView_drawableTintMode 3449 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3450 */ 3451 @InspectableProperty(name = "drawableBlendMode", 3452 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3453 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3454 return mDrawables != null ? mDrawables.mBlendMode : null; 3455 } 3456 applyCompoundDrawableTint()3457 private void applyCompoundDrawableTint() { 3458 if (mDrawables == null) { 3459 return; 3460 } 3461 3462 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3463 final ColorStateList tintList = mDrawables.mTintList; 3464 final BlendMode blendMode = mDrawables.mBlendMode; 3465 final boolean hasTint = mDrawables.mHasTint; 3466 final boolean hasTintMode = mDrawables.mHasTintMode; 3467 final int[] state = getDrawableState(); 3468 3469 for (Drawable dr : mDrawables.mShowing) { 3470 if (dr == null) { 3471 continue; 3472 } 3473 3474 if (dr == mDrawables.mDrawableError) { 3475 // From a developer's perspective, the error drawable isn't 3476 // a compound drawable. Don't apply the generic compound 3477 // drawable tint to it. 3478 continue; 3479 } 3480 3481 dr.mutate(); 3482 3483 if (hasTint) { 3484 dr.setTintList(tintList); 3485 } 3486 3487 if (hasTintMode) { 3488 dr.setTintBlendMode(blendMode); 3489 } 3490 3491 // The drawable (or one of its children) may not have been 3492 // stateful before applying the tint, so let's try again. 3493 if (dr.isStateful()) { 3494 dr.setState(state); 3495 } 3496 } 3497 } 3498 } 3499 3500 /** 3501 * @inheritDoc 3502 * 3503 * @see #setFirstBaselineToTopHeight(int) 3504 * @see #setLastBaselineToBottomHeight(int) 3505 */ 3506 @Override setPadding(int left, int top, int right, int bottom)3507 public void setPadding(int left, int top, int right, int bottom) { 3508 if (left != mPaddingLeft 3509 || right != mPaddingRight 3510 || top != mPaddingTop 3511 || bottom != mPaddingBottom) { 3512 nullLayouts(); 3513 } 3514 3515 // the super call will requestLayout() 3516 super.setPadding(left, top, right, bottom); 3517 invalidate(); 3518 } 3519 3520 /** 3521 * @inheritDoc 3522 * 3523 * @see #setFirstBaselineToTopHeight(int) 3524 * @see #setLastBaselineToBottomHeight(int) 3525 */ 3526 @Override setPaddingRelative(int start, int top, int end, int bottom)3527 public void setPaddingRelative(int start, int top, int end, int bottom) { 3528 if (start != getPaddingStart() 3529 || end != getPaddingEnd() 3530 || top != mPaddingTop 3531 || bottom != mPaddingBottom) { 3532 nullLayouts(); 3533 } 3534 3535 // the super call will requestLayout() 3536 super.setPaddingRelative(start, top, end, bottom); 3537 invalidate(); 3538 } 3539 3540 /** 3541 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3542 * the distance between the top of the TextView and first line's baseline. 3543 * <p> 3544 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3545 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3546 * 3547 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3548 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3549 * Moreover since this function sets the top padding, if the height of the TextView is less than 3550 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3551 * down and bottom will be clipped. 3552 * 3553 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3554 * in pixels 3555 * 3556 * @see #getFirstBaselineToTopHeight() 3557 * @see #setLastBaselineToBottomHeight(int) 3558 * @see #setPadding(int, int, int, int) 3559 * @see #setPaddingRelative(int, int, int, int) 3560 * 3561 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3562 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3563 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3564 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3565 3566 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3567 final int fontMetricsTop; 3568 if (getIncludeFontPadding()) { 3569 fontMetricsTop = fontMetrics.top; 3570 } else { 3571 fontMetricsTop = fontMetrics.ascent; 3572 } 3573 3574 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3575 // in settings). At the moment, we don't. 3576 3577 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3578 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3579 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3580 } 3581 } 3582 3583 /** 3584 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3585 * the distance between the bottom of the TextView and the last line's baseline. 3586 * <p> 3587 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3588 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3589 * 3590 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3591 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3592 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3593 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3594 * clipped. 3595 * 3596 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3597 * in pixels 3598 * 3599 * @see #getLastBaselineToBottomHeight() 3600 * @see #setFirstBaselineToTopHeight(int) 3601 * @see #setPadding(int, int, int, int) 3602 * @see #setPaddingRelative(int, int, int, int) 3603 * 3604 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3605 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3606 public void setLastBaselineToBottomHeight( 3607 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3608 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3609 3610 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3611 final int fontMetricsBottom; 3612 if (getIncludeFontPadding()) { 3613 fontMetricsBottom = fontMetrics.bottom; 3614 } else { 3615 fontMetricsBottom = fontMetrics.descent; 3616 } 3617 3618 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3619 // in settings). At the moment, we don't. 3620 3621 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3622 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3623 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3624 } 3625 } 3626 3627 /** 3628 * Returns the distance between the first text baseline and the top of this TextView. 3629 * 3630 * @see #setFirstBaselineToTopHeight(int) 3631 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3632 */ 3633 @InspectableProperty getFirstBaselineToTopHeight()3634 public int getFirstBaselineToTopHeight() { 3635 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3636 } 3637 3638 /** 3639 * Returns the distance between the last text baseline and the bottom of this TextView. 3640 * 3641 * @see #setLastBaselineToBottomHeight(int) 3642 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3643 */ 3644 @InspectableProperty getLastBaselineToBottomHeight()3645 public int getLastBaselineToBottomHeight() { 3646 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3647 } 3648 3649 /** 3650 * Gets the autolink mask of the text. 3651 * 3652 * See {@link Linkify#ALL} and peers for possible values. 3653 * 3654 * @attr ref android.R.styleable#TextView_autoLink 3655 */ 3656 @InspectableProperty(name = "autoLink", flagMapping = { 3657 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3658 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3659 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3660 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3661 }) getAutoLinkMask()3662 public final int getAutoLinkMask() { 3663 return mAutoLinkMask; 3664 } 3665 3666 /** 3667 * Sets the Drawable corresponding to the selection handle used for 3668 * positioning the cursor within text. The Drawable defaults to the value 3669 * of the textSelectHandle attribute. 3670 * Note that any change applied to the handle Drawable will not be visible 3671 * until the handle is hidden and then drawn again. 3672 * 3673 * @see #setTextSelectHandle(int) 3674 * @attr ref android.R.styleable#TextView_textSelectHandle 3675 */ 3676 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3677 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3678 Preconditions.checkNotNull(textSelectHandle, 3679 "The text select handle should not be null."); 3680 mTextSelectHandle = textSelectHandle; 3681 mTextSelectHandleRes = 0; 3682 if (mEditor != null) { 3683 mEditor.loadHandleDrawables(true /* overwrite */); 3684 } 3685 } 3686 3687 /** 3688 * Sets the Drawable corresponding to the selection handle used for 3689 * positioning the cursor within text. The Drawable defaults to the value 3690 * of the textSelectHandle attribute. 3691 * Note that any change applied to the handle Drawable will not be visible 3692 * until the handle is hidden and then drawn again. 3693 * 3694 * @see #setTextSelectHandle(Drawable) 3695 * @attr ref android.R.styleable#TextView_textSelectHandle 3696 */ 3697 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3698 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3699 Preconditions.checkArgument(textSelectHandle != 0, 3700 "The text select handle should be a valid drawable resource id."); 3701 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3702 } 3703 3704 /** 3705 * Returns the Drawable corresponding to the selection handle used 3706 * for positioning the cursor within text. 3707 * Note that any change applied to the handle Drawable will not be visible 3708 * until the handle is hidden and then drawn again. 3709 * 3710 * @return the text select handle drawable 3711 * 3712 * @see #setTextSelectHandle(Drawable) 3713 * @see #setTextSelectHandle(int) 3714 * @attr ref android.R.styleable#TextView_textSelectHandle 3715 */ getTextSelectHandle()3716 @Nullable public Drawable getTextSelectHandle() { 3717 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3718 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3719 } 3720 return mTextSelectHandle; 3721 } 3722 3723 /** 3724 * Sets the Drawable corresponding to the left handle used 3725 * for selecting text. The Drawable defaults to the value of the 3726 * textSelectHandleLeft attribute. 3727 * Note that any change applied to the handle Drawable will not be visible 3728 * until the handle is hidden and then drawn again. 3729 * 3730 * @see #setTextSelectHandleLeft(int) 3731 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3732 */ 3733 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3734 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3735 Preconditions.checkNotNull(textSelectHandleLeft, 3736 "The left text select handle should not be null."); 3737 mTextSelectHandleLeft = textSelectHandleLeft; 3738 mTextSelectHandleLeftRes = 0; 3739 if (mEditor != null) { 3740 mEditor.loadHandleDrawables(true /* overwrite */); 3741 } 3742 } 3743 3744 /** 3745 * Sets the Drawable corresponding to the left handle used 3746 * for selecting text. The Drawable defaults to the value of the 3747 * textSelectHandleLeft attribute. 3748 * Note that any change applied to the handle Drawable will not be visible 3749 * until the handle is hidden and then drawn again. 3750 * 3751 * @see #setTextSelectHandleLeft(Drawable) 3752 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3753 */ 3754 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3755 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3756 Preconditions.checkArgument(textSelectHandleLeft != 0, 3757 "The text select left handle should be a valid drawable resource id."); 3758 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3759 } 3760 3761 /** 3762 * Returns the Drawable corresponding to the left handle used 3763 * for selecting text. 3764 * Note that any change applied to the handle Drawable will not be visible 3765 * until the handle is hidden and then drawn again. 3766 * 3767 * @return the left text selection handle drawable 3768 * 3769 * @see #setTextSelectHandleLeft(Drawable) 3770 * @see #setTextSelectHandleLeft(int) 3771 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3772 */ getTextSelectHandleLeft()3773 @Nullable public Drawable getTextSelectHandleLeft() { 3774 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3775 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3776 } 3777 return mTextSelectHandleLeft; 3778 } 3779 3780 /** 3781 * Sets the Drawable corresponding to the right handle used 3782 * for selecting text. The Drawable defaults to the value of the 3783 * textSelectHandleRight attribute. 3784 * Note that any change applied to the handle Drawable will not be visible 3785 * until the handle is hidden and then drawn again. 3786 * 3787 * @see #setTextSelectHandleRight(int) 3788 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3789 */ 3790 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3791 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3792 Preconditions.checkNotNull(textSelectHandleRight, 3793 "The right text select handle should not be null."); 3794 mTextSelectHandleRight = textSelectHandleRight; 3795 mTextSelectHandleRightRes = 0; 3796 if (mEditor != null) { 3797 mEditor.loadHandleDrawables(true /* overwrite */); 3798 } 3799 } 3800 3801 /** 3802 * Sets the Drawable corresponding to the right handle used 3803 * for selecting text. The Drawable defaults to the value of the 3804 * textSelectHandleRight attribute. 3805 * Note that any change applied to the handle Drawable will not be visible 3806 * until the handle is hidden and then drawn again. 3807 * 3808 * @see #setTextSelectHandleRight(Drawable) 3809 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3810 */ 3811 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3812 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3813 Preconditions.checkArgument(textSelectHandleRight != 0, 3814 "The text select right handle should be a valid drawable resource id."); 3815 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3816 } 3817 3818 /** 3819 * Returns the Drawable corresponding to the right handle used 3820 * for selecting text. 3821 * Note that any change applied to the handle Drawable will not be visible 3822 * until the handle is hidden and then drawn again. 3823 * 3824 * @return the right text selection handle drawable 3825 * 3826 * @see #setTextSelectHandleRight(Drawable) 3827 * @see #setTextSelectHandleRight(int) 3828 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3829 */ getTextSelectHandleRight()3830 @Nullable public Drawable getTextSelectHandleRight() { 3831 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3832 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3833 } 3834 return mTextSelectHandleRight; 3835 } 3836 3837 /** 3838 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3839 * value of the textCursorDrawable attribute. 3840 * Note that any change applied to the cursor Drawable will not be visible 3841 * until the cursor is hidden and then drawn again. 3842 * 3843 * @see #setTextCursorDrawable(int) 3844 * @attr ref android.R.styleable#TextView_textCursorDrawable 3845 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3846 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3847 mCursorDrawable = textCursorDrawable; 3848 mCursorDrawableRes = 0; 3849 if (mEditor != null) { 3850 mEditor.loadCursorDrawable(); 3851 } 3852 } 3853 3854 /** 3855 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3856 * value of the textCursorDrawable attribute. 3857 * Note that any change applied to the cursor Drawable will not be visible 3858 * until the cursor is hidden and then drawn again. 3859 * 3860 * @see #setTextCursorDrawable(Drawable) 3861 * @attr ref android.R.styleable#TextView_textCursorDrawable 3862 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3863 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3864 setTextCursorDrawable( 3865 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3866 } 3867 3868 /** 3869 * Returns the Drawable corresponding to the text cursor. 3870 * Note that any change applied to the cursor Drawable will not be visible 3871 * until the cursor is hidden and then drawn again. 3872 * 3873 * @return the text cursor drawable 3874 * 3875 * @see #setTextCursorDrawable(Drawable) 3876 * @see #setTextCursorDrawable(int) 3877 * @attr ref android.R.styleable#TextView_textCursorDrawable 3878 */ getTextCursorDrawable()3879 @Nullable public Drawable getTextCursorDrawable() { 3880 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3881 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3882 } 3883 return mCursorDrawable; 3884 } 3885 3886 /** 3887 * Sets the text appearance from the specified style resource. 3888 * <p> 3889 * Use a framework-defined {@code TextAppearance} style like 3890 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3891 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3892 * set of attributes that can be used in a custom style. 3893 * 3894 * @param resId the resource identifier of the style to apply 3895 * @attr ref android.R.styleable#TextView_textAppearance 3896 */ 3897 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3898 public void setTextAppearance(@StyleRes int resId) { 3899 setTextAppearance(mContext, resId); 3900 } 3901 3902 /** 3903 * Sets the text color, size, style, hint color, and highlight color 3904 * from the specified TextAppearance resource. 3905 * 3906 * @deprecated Use {@link #setTextAppearance(int)} instead. 3907 */ 3908 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3909 public void setTextAppearance(Context context, @StyleRes int resId) { 3910 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3911 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3912 readTextAppearance(context, ta, attributes, false /* styleArray */); 3913 ta.recycle(); 3914 applyTextAppearance(attributes); 3915 } 3916 3917 /** 3918 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3919 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3920 */ 3921 private static class TextAppearanceAttributes { 3922 int mTextColorHighlight = 0; 3923 ColorStateList mTextColor = null; 3924 ColorStateList mTextColorHint = null; 3925 ColorStateList mTextColorLink = null; 3926 int mTextSize = -1; 3927 int mTextSizeUnit = -1; 3928 LocaleList mTextLocales = null; 3929 String mFontFamily = null; 3930 Typeface mFontTypeface = null; 3931 boolean mFontFamilyExplicit = false; 3932 int mTypefaceIndex = -1; 3933 int mTextStyle = 0; 3934 int mFontWeight = -1; 3935 boolean mAllCaps = false; 3936 int mShadowColor = 0; 3937 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3938 boolean mHasElegant = false; 3939 boolean mElegant = false; 3940 boolean mHasFallbackLineSpacing = false; 3941 boolean mFallbackLineSpacing = false; 3942 boolean mHasLetterSpacing = false; 3943 float mLetterSpacing = 0; 3944 String mFontFeatureSettings = null; 3945 String mFontVariationSettings = null; 3946 3947 @Override toString()3948 public String toString() { 3949 return "TextAppearanceAttributes {\n" 3950 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 3951 + " mTextColor:" + mTextColor + "\n" 3952 + " mTextColorHint:" + mTextColorHint + "\n" 3953 + " mTextColorLink:" + mTextColorLink + "\n" 3954 + " mTextSize:" + mTextSize + "\n" 3955 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 3956 + " mTextLocales:" + mTextLocales + "\n" 3957 + " mFontFamily:" + mFontFamily + "\n" 3958 + " mFontTypeface:" + mFontTypeface + "\n" 3959 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 3960 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 3961 + " mTextStyle:" + mTextStyle + "\n" 3962 + " mFontWeight:" + mFontWeight + "\n" 3963 + " mAllCaps:" + mAllCaps + "\n" 3964 + " mShadowColor:" + mShadowColor + "\n" 3965 + " mShadowDx:" + mShadowDx + "\n" 3966 + " mShadowDy:" + mShadowDy + "\n" 3967 + " mShadowRadius:" + mShadowRadius + "\n" 3968 + " mHasElegant:" + mHasElegant + "\n" 3969 + " mElegant:" + mElegant + "\n" 3970 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 3971 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 3972 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 3973 + " mLetterSpacing:" + mLetterSpacing + "\n" 3974 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 3975 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 3976 + "}"; 3977 } 3978 } 3979 3980 // Maps styleable attributes that exist both in TextView style and TextAppearance. 3981 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 3982 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3983 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 3984 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3985 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 3986 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3987 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 3988 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3989 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 3990 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3991 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 3992 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3993 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 3994 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3995 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 3996 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3997 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 3998 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3999 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4000 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4001 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4002 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4003 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4004 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4005 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4006 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4007 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4008 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4009 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4010 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4011 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4012 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4013 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4014 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4015 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4016 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4017 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4018 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4019 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4020 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4021 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4022 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 4023 } 4024 4025 /** 4026 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 4027 * set. If the TypedArray contains a value that was already set in the given attributes, that 4028 * will be overridden. 4029 * 4030 * @param context The Context to be used 4031 * @param appearance The TypedArray to read properties from 4032 * @param attributes the TextAppearanceAttributes to fill in 4033 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 4034 * what attribute indexes will be used to read the properties. 4035 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4036 private void readTextAppearance(Context context, TypedArray appearance, 4037 TextAppearanceAttributes attributes, boolean styleArray) { 4038 final int n = appearance.getIndexCount(); 4039 for (int i = 0; i < n; i++) { 4040 final int attr = appearance.getIndex(i); 4041 int index = attr; 4042 // Translate style array index ids to TextAppearance ids. 4043 if (styleArray) { 4044 index = sAppearanceValues.get(attr, -1); 4045 if (index == -1) { 4046 // This value is not part of a Text Appearance and should be ignored. 4047 continue; 4048 } 4049 } 4050 switch (index) { 4051 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4052 attributes.mTextColorHighlight = 4053 appearance.getColor(attr, attributes.mTextColorHighlight); 4054 break; 4055 case com.android.internal.R.styleable.TextAppearance_textColor: 4056 attributes.mTextColor = appearance.getColorStateList(attr); 4057 break; 4058 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4059 attributes.mTextColorHint = appearance.getColorStateList(attr); 4060 break; 4061 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4062 attributes.mTextColorLink = appearance.getColorStateList(attr); 4063 break; 4064 case com.android.internal.R.styleable.TextAppearance_textSize: 4065 attributes.mTextSize = 4066 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4067 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4068 break; 4069 case com.android.internal.R.styleable.TextAppearance_textLocale: 4070 final String localeString = appearance.getString(attr); 4071 if (localeString != null) { 4072 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4073 if (!localeList.isEmpty()) { 4074 attributes.mTextLocales = localeList; 4075 } 4076 } 4077 break; 4078 case com.android.internal.R.styleable.TextAppearance_typeface: 4079 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4080 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4081 attributes.mFontFamily = null; 4082 } 4083 break; 4084 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4085 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4086 try { 4087 attributes.mFontTypeface = appearance.getFont(attr); 4088 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4089 // Expected if it is not a font resource. 4090 } 4091 } 4092 if (attributes.mFontTypeface == null) { 4093 attributes.mFontFamily = appearance.getString(attr); 4094 } 4095 attributes.mFontFamilyExplicit = true; 4096 break; 4097 case com.android.internal.R.styleable.TextAppearance_textStyle: 4098 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4099 break; 4100 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4101 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4102 break; 4103 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4104 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4105 break; 4106 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4107 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4108 break; 4109 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4110 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4111 break; 4112 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4113 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4114 break; 4115 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4116 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4117 break; 4118 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4119 attributes.mHasElegant = true; 4120 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4121 break; 4122 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4123 attributes.mHasFallbackLineSpacing = true; 4124 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4125 attributes.mFallbackLineSpacing); 4126 break; 4127 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4128 attributes.mHasLetterSpacing = true; 4129 attributes.mLetterSpacing = 4130 appearance.getFloat(attr, attributes.mLetterSpacing); 4131 break; 4132 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4133 attributes.mFontFeatureSettings = appearance.getString(attr); 4134 break; 4135 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4136 attributes.mFontVariationSettings = appearance.getString(attr); 4137 break; 4138 default: 4139 } 4140 } 4141 } 4142 applyTextAppearance(TextAppearanceAttributes attributes)4143 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4144 if (attributes.mTextColor != null) { 4145 setTextColor(attributes.mTextColor); 4146 } 4147 4148 if (attributes.mTextColorHint != null) { 4149 setHintTextColor(attributes.mTextColorHint); 4150 } 4151 4152 if (attributes.mTextColorLink != null) { 4153 setLinkTextColor(attributes.mTextColorLink); 4154 } 4155 4156 if (attributes.mTextColorHighlight != 0) { 4157 setHighlightColor(attributes.mTextColorHighlight); 4158 } 4159 4160 if (attributes.mTextSize != -1) { 4161 mTextSizeUnit = attributes.mTextSizeUnit; 4162 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4163 } 4164 4165 if (attributes.mTextLocales != null) { 4166 setTextLocales(attributes.mTextLocales); 4167 } 4168 4169 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4170 attributes.mFontFamily = null; 4171 } 4172 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4173 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4174 4175 if (attributes.mShadowColor != 0) { 4176 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4177 attributes.mShadowColor); 4178 } 4179 4180 if (attributes.mAllCaps) { 4181 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4182 } 4183 4184 if (attributes.mHasElegant) { 4185 setElegantTextHeight(attributes.mElegant); 4186 } 4187 4188 if (attributes.mHasFallbackLineSpacing) { 4189 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4190 } 4191 4192 if (attributes.mHasLetterSpacing) { 4193 setLetterSpacing(attributes.mLetterSpacing); 4194 } 4195 4196 if (attributes.mFontFeatureSettings != null) { 4197 setFontFeatureSettings(attributes.mFontFeatureSettings); 4198 } 4199 4200 if (attributes.mFontVariationSettings != null) { 4201 setFontVariationSettings(attributes.mFontVariationSettings); 4202 } 4203 } 4204 4205 /** 4206 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4207 * the first member of {@link #getTextLocales()}. 4208 * @return the default primary {@link Locale} of the text in this TextView. 4209 */ 4210 @NonNull getTextLocale()4211 public Locale getTextLocale() { 4212 return mTextPaint.getTextLocale(); 4213 } 4214 4215 /** 4216 * Get the default {@link LocaleList} of the text in this TextView. 4217 * @return the default {@link LocaleList} of the text in this TextView. 4218 */ 4219 @NonNull @Size(min = 1) getTextLocales()4220 public LocaleList getTextLocales() { 4221 return mTextPaint.getTextLocales(); 4222 } 4223 changeListenerLocaleTo(@ullable Locale locale)4224 private void changeListenerLocaleTo(@Nullable Locale locale) { 4225 if (mListenerChanged) { 4226 // If a listener has been explicitly set, don't change it. We may break something. 4227 return; 4228 } 4229 // The following null check is not absolutely necessary since all calling points of 4230 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4231 // here in case others would want to call this method in the future. 4232 if (mEditor != null) { 4233 KeyListener listener = mEditor.mKeyListener; 4234 if (listener instanceof DigitsKeyListener) { 4235 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4236 } else if (listener instanceof DateKeyListener) { 4237 listener = DateKeyListener.getInstance(locale); 4238 } else if (listener instanceof TimeKeyListener) { 4239 listener = TimeKeyListener.getInstance(locale); 4240 } else if (listener instanceof DateTimeKeyListener) { 4241 listener = DateTimeKeyListener.getInstance(locale); 4242 } else { 4243 return; 4244 } 4245 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4246 setKeyListenerOnly(listener); 4247 setInputTypeFromEditor(); 4248 if (wasPasswordType) { 4249 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4250 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4251 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4252 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4253 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4254 } 4255 } 4256 } 4257 } 4258 4259 /** 4260 * Set the default {@link Locale} of the text in this TextView to a one-member 4261 * {@link LocaleList} containing just the given Locale. 4262 * 4263 * @param locale the {@link Locale} for drawing text, must not be null. 4264 * 4265 * @see #setTextLocales 4266 */ setTextLocale(@onNull Locale locale)4267 public void setTextLocale(@NonNull Locale locale) { 4268 mLocalesChanged = true; 4269 mTextPaint.setTextLocale(locale); 4270 if (mLayout != null) { 4271 nullLayouts(); 4272 requestLayout(); 4273 invalidate(); 4274 } 4275 } 4276 4277 /** 4278 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4279 * 4280 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4281 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4282 * other aspects of text display, including line breaking. 4283 * 4284 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4285 * 4286 * @see Paint#setTextLocales 4287 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4288 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4289 mLocalesChanged = true; 4290 mTextPaint.setTextLocales(locales); 4291 if (mLayout != null) { 4292 nullLayouts(); 4293 requestLayout(); 4294 invalidate(); 4295 } 4296 } 4297 4298 @Override onConfigurationChanged(Configuration newConfig)4299 protected void onConfigurationChanged(Configuration newConfig) { 4300 super.onConfigurationChanged(newConfig); 4301 if (!mLocalesChanged) { 4302 mTextPaint.setTextLocales(LocaleList.getDefault()); 4303 if (mLayout != null) { 4304 nullLayouts(); 4305 requestLayout(); 4306 invalidate(); 4307 } 4308 } 4309 if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { 4310 mFontWeightAdjustment = newConfig.fontWeightAdjustment; 4311 setTypeface(getTypeface()); 4312 } 4313 } 4314 4315 /** 4316 * @return the size (in pixels) of the default text size in this TextView. 4317 */ 4318 @InspectableProperty 4319 @ViewDebug.ExportedProperty(category = "text") getTextSize()4320 public float getTextSize() { 4321 return mTextPaint.getTextSize(); 4322 } 4323 4324 /** 4325 * @return the size (in scaled pixels) of the default text size in this TextView. 4326 * @hide 4327 */ 4328 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4329 public float getScaledTextSize() { 4330 return mTextPaint.getTextSize() / mTextPaint.density; 4331 } 4332 4333 /** @hide */ 4334 @ViewDebug.ExportedProperty(category = "text", mapping = { 4335 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4336 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4337 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4338 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4339 }) getTypefaceStyle()4340 public int getTypefaceStyle() { 4341 Typeface typeface = mTextPaint.getTypeface(); 4342 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4343 } 4344 4345 /** 4346 * Set the default text size to the given value, interpreted as "scaled 4347 * pixel" units. This size is adjusted based on the current density and 4348 * user font size preference. 4349 * 4350 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4351 * 4352 * @param size The scaled pixel size. 4353 * 4354 * @attr ref android.R.styleable#TextView_textSize 4355 */ 4356 @android.view.RemotableViewMethod setTextSize(float size)4357 public void setTextSize(float size) { 4358 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4359 } 4360 4361 /** 4362 * Set the default text size to a given unit and value. See {@link 4363 * TypedValue} for the possible dimension units. 4364 * 4365 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4366 * 4367 * @param unit The desired dimension unit. 4368 * @param size The desired size in the given units. 4369 * 4370 * @attr ref android.R.styleable#TextView_textSize 4371 */ setTextSize(int unit, float size)4372 public void setTextSize(int unit, float size) { 4373 if (!isAutoSizeEnabled()) { 4374 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4375 } 4376 } 4377 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4378 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4379 Context c = getContext(); 4380 Resources r; 4381 4382 if (c == null) { 4383 r = Resources.getSystem(); 4384 } else { 4385 r = c.getResources(); 4386 } 4387 4388 mTextSizeUnit = unit; 4389 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4390 shouldRequestLayout); 4391 } 4392 4393 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setRawTextSize(float size, boolean shouldRequestLayout)4394 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4395 if (size != mTextPaint.getTextSize()) { 4396 mTextPaint.setTextSize(size); 4397 4398 if (shouldRequestLayout && mLayout != null) { 4399 // Do not auto-size right after setting the text size. 4400 mNeedsAutoSizeText = false; 4401 nullLayouts(); 4402 requestLayout(); 4403 invalidate(); 4404 } 4405 } 4406 } 4407 4408 /** 4409 * Gets the text size unit defined by the developer. It may be specified in resources or be 4410 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4411 * 4412 * @return the dimension type of the text size unit originally defined. 4413 * @see TypedValue#TYPE_DIMENSION 4414 */ getTextSizeUnit()4415 public int getTextSizeUnit() { 4416 return mTextSizeUnit; 4417 } 4418 4419 /** 4420 * Gets the extent by which text should be stretched horizontally. 4421 * This will usually be 1.0. 4422 * @return The horizontal scale factor. 4423 */ 4424 @InspectableProperty getTextScaleX()4425 public float getTextScaleX() { 4426 return mTextPaint.getTextScaleX(); 4427 } 4428 4429 /** 4430 * Sets the horizontal scale factor for text. The default value 4431 * is 1.0. Values greater than 1.0 stretch the text wider. 4432 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4433 * @param size The horizontal scale factor. 4434 * @attr ref android.R.styleable#TextView_textScaleX 4435 */ 4436 @android.view.RemotableViewMethod setTextScaleX(float size)4437 public void setTextScaleX(float size) { 4438 if (size != mTextPaint.getTextScaleX()) { 4439 mUserSetTextScaleX = true; 4440 mTextPaint.setTextScaleX(size); 4441 4442 if (mLayout != null) { 4443 nullLayouts(); 4444 requestLayout(); 4445 invalidate(); 4446 } 4447 } 4448 } 4449 4450 /** 4451 * Sets the typeface and style in which the text should be displayed. 4452 * Note that not all Typeface families actually have bold and italic 4453 * variants, so you may need to use 4454 * {@link #setTypeface(Typeface, int)} to get the appearance 4455 * that you actually want. 4456 * 4457 * @see #getTypeface() 4458 * 4459 * @attr ref android.R.styleable#TextView_fontFamily 4460 * @attr ref android.R.styleable#TextView_typeface 4461 * @attr ref android.R.styleable#TextView_textStyle 4462 */ setTypeface(@ullable Typeface tf)4463 public void setTypeface(@Nullable Typeface tf) { 4464 mOriginalTypeface = tf; 4465 if (mFontWeightAdjustment != 0 4466 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 4467 if (tf == null) { 4468 tf = Typeface.DEFAULT; 4469 } else { 4470 int newWeight = Math.min( 4471 Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), 4472 FontStyle.FONT_WEIGHT_MAX); 4473 int typefaceStyle = tf != null ? tf.getStyle() : 0; 4474 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; 4475 tf = Typeface.create(tf, newWeight, italic); 4476 } 4477 } 4478 if (mTextPaint.getTypeface() != tf) { 4479 mTextPaint.setTypeface(tf); 4480 4481 if (mLayout != null) { 4482 nullLayouts(); 4483 requestLayout(); 4484 invalidate(); 4485 } 4486 } 4487 } 4488 4489 /** 4490 * Gets the current {@link Typeface} that is used to style the text. 4491 * @return The current Typeface. 4492 * 4493 * @see #setTypeface(Typeface) 4494 * 4495 * @attr ref android.R.styleable#TextView_fontFamily 4496 * @attr ref android.R.styleable#TextView_typeface 4497 * @attr ref android.R.styleable#TextView_textStyle 4498 */ 4499 @InspectableProperty getTypeface()4500 public Typeface getTypeface() { 4501 return mOriginalTypeface; 4502 } 4503 4504 /** 4505 * Set the TextView's elegant height metrics flag. This setting selects font 4506 * variants that have not been compacted to fit Latin-based vertical 4507 * metrics, and also increases top and bottom bounds to provide more space. 4508 * 4509 * @param elegant set the paint's elegant metrics flag. 4510 * 4511 * @see #isElegantTextHeight() 4512 * @see Paint#isElegantTextHeight() 4513 * 4514 * @attr ref android.R.styleable#TextView_elegantTextHeight 4515 */ setElegantTextHeight(boolean elegant)4516 public void setElegantTextHeight(boolean elegant) { 4517 if (elegant != mTextPaint.isElegantTextHeight()) { 4518 mTextPaint.setElegantTextHeight(elegant); 4519 if (mLayout != null) { 4520 nullLayouts(); 4521 requestLayout(); 4522 invalidate(); 4523 } 4524 } 4525 } 4526 4527 /** 4528 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4529 * displaying the text (which is needed to avoid text from consecutive lines running into 4530 * each other). If set, fallback fonts that end up getting used can increase the ascent 4531 * and descent of the lines that they are used on. 4532 * <p/> 4533 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4534 * is typically much taller or deeper than Latin text. 4535 * 4536 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4537 * 4538 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4539 * 4540 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4541 */ setFallbackLineSpacing(boolean enabled)4542 public void setFallbackLineSpacing(boolean enabled) { 4543 if (mUseFallbackLineSpacing != enabled) { 4544 mUseFallbackLineSpacing = enabled; 4545 if (mLayout != null) { 4546 nullLayouts(); 4547 requestLayout(); 4548 invalidate(); 4549 } 4550 } 4551 } 4552 4553 /** 4554 * @return whether fallback line spacing is enabled, {@code true} by default 4555 * 4556 * @see #setFallbackLineSpacing(boolean) 4557 * 4558 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4559 */ 4560 @InspectableProperty isFallbackLineSpacing()4561 public boolean isFallbackLineSpacing() { 4562 return mUseFallbackLineSpacing; 4563 } 4564 4565 /** 4566 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4567 * variants that have not been compacted to fit Latin-based vertical 4568 * metrics, and also increases top and bottom bounds to provide more space. 4569 * @return {@code true} if the elegant height metrics flag is set. 4570 * 4571 * @see #setElegantTextHeight(boolean) 4572 * @see Paint#setElegantTextHeight(boolean) 4573 */ 4574 @InspectableProperty isElegantTextHeight()4575 public boolean isElegantTextHeight() { 4576 return mTextPaint.isElegantTextHeight(); 4577 } 4578 4579 /** 4580 * Gets the text letter-space value, which determines the spacing between characters. 4581 * The value returned is in ems. Normally, this value is 0.0. 4582 * @return The text letter-space value in ems. 4583 * 4584 * @see #setLetterSpacing(float) 4585 * @see Paint#setLetterSpacing 4586 */ 4587 @InspectableProperty getLetterSpacing()4588 public float getLetterSpacing() { 4589 return mTextPaint.getLetterSpacing(); 4590 } 4591 4592 /** 4593 * Sets text letter-spacing in em units. Typical values 4594 * for slight expansion will be around 0.05. Negative values tighten text. 4595 * 4596 * @see #getLetterSpacing() 4597 * @see Paint#getLetterSpacing 4598 * 4599 * @param letterSpacing A text letter-space value in ems. 4600 * @attr ref android.R.styleable#TextView_letterSpacing 4601 */ 4602 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4603 public void setLetterSpacing(float letterSpacing) { 4604 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4605 mTextPaint.setLetterSpacing(letterSpacing); 4606 4607 if (mLayout != null) { 4608 nullLayouts(); 4609 requestLayout(); 4610 invalidate(); 4611 } 4612 } 4613 } 4614 4615 /** 4616 * Returns the font feature settings. The format is the same as the CSS 4617 * font-feature-settings attribute: 4618 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4619 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4620 * 4621 * @return the currently set font feature settings. Default is null. 4622 * 4623 * @see #setFontFeatureSettings(String) 4624 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4625 */ 4626 @InspectableProperty 4627 @Nullable getFontFeatureSettings()4628 public String getFontFeatureSettings() { 4629 return mTextPaint.getFontFeatureSettings(); 4630 } 4631 4632 /** 4633 * Returns the font variation settings. 4634 * 4635 * @return the currently set font variation settings. Returns null if no variation is 4636 * specified. 4637 * 4638 * @see #setFontVariationSettings(String) 4639 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4640 */ 4641 @Nullable getFontVariationSettings()4642 public String getFontVariationSettings() { 4643 return mTextPaint.getFontVariationSettings(); 4644 } 4645 4646 /** 4647 * Sets the break strategy for breaking paragraphs into lines. The default value for 4648 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4649 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4650 * text "dancing" when being edited. 4651 * <p/> 4652 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4653 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4654 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4655 * improves the structure of text layout however has performance impact and requires more time 4656 * to do the text layout. 4657 * 4658 * @attr ref android.R.styleable#TextView_breakStrategy 4659 * @see #getBreakStrategy() 4660 * @see #setHyphenationFrequency(int) 4661 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4662 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4663 mBreakStrategy = breakStrategy; 4664 if (mLayout != null) { 4665 nullLayouts(); 4666 requestLayout(); 4667 invalidate(); 4668 } 4669 } 4670 4671 /** 4672 * Gets the current strategy for breaking paragraphs into lines. 4673 * @return the current strategy for breaking paragraphs into lines. 4674 * 4675 * @attr ref android.R.styleable#TextView_breakStrategy 4676 * @see #setBreakStrategy(int) 4677 */ 4678 @InspectableProperty(enumMapping = { 4679 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4680 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4681 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4682 }) 4683 @Layout.BreakStrategy getBreakStrategy()4684 public int getBreakStrategy() { 4685 return mBreakStrategy; 4686 } 4687 4688 /** 4689 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4690 * The default value for both TextView and {@link EditText} is 4691 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4692 * is set from the theme. 4693 * <p/> 4694 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4695 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4696 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4697 * improves the structure of text layout however has performance impact and requires more time 4698 * to do the text layout. 4699 * <p/> 4700 * Note: Before Android Q, in the theme hyphenation frequency is set to 4701 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4702 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4703 * 4704 * @param hyphenationFrequency the hyphenation frequency to use, one of 4705 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4706 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4707 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4708 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4709 * @see #getHyphenationFrequency() 4710 * @see #getBreakStrategy() 4711 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4712 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4713 mHyphenationFrequency = hyphenationFrequency; 4714 if (mLayout != null) { 4715 nullLayouts(); 4716 requestLayout(); 4717 invalidate(); 4718 } 4719 } 4720 4721 /** 4722 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4723 * @return the current frequency of automatic hyphenation to be used when determining word 4724 * breaks. 4725 * 4726 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4727 * @see #setHyphenationFrequency(int) 4728 */ 4729 @InspectableProperty(enumMapping = { 4730 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4731 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4732 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4733 }) 4734 @Layout.HyphenationFrequency getHyphenationFrequency()4735 public int getHyphenationFrequency() { 4736 return mHyphenationFrequency; 4737 } 4738 4739 /** 4740 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4741 * 4742 * @return a current {@link PrecomputedText.Params} 4743 * @see PrecomputedText 4744 */ getTextMetricsParams()4745 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4746 return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), 4747 mBreakStrategy, mHyphenationFrequency); 4748 } 4749 4750 /** 4751 * Apply the text layout parameter. 4752 * 4753 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4754 * @see PrecomputedText 4755 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4756 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4757 mTextPaint.set(params.getTextPaint()); 4758 mUserSetTextScaleX = true; 4759 mTextDir = params.getTextDirection(); 4760 mBreakStrategy = params.getBreakStrategy(); 4761 mHyphenationFrequency = params.getHyphenationFrequency(); 4762 if (mLayout != null) { 4763 nullLayouts(); 4764 requestLayout(); 4765 invalidate(); 4766 } 4767 } 4768 4769 /** 4770 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4771 * last line is too short for justification, the last line will be displayed with the 4772 * alignment set by {@link android.view.View#setTextAlignment}. 4773 * 4774 * @see #getJustificationMode() 4775 */ 4776 @Layout.JustificationMode 4777 @android.view.RemotableViewMethod setJustificationMode(@ayout.JustificationMode int justificationMode)4778 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 4779 mJustificationMode = justificationMode; 4780 if (mLayout != null) { 4781 nullLayouts(); 4782 requestLayout(); 4783 invalidate(); 4784 } 4785 } 4786 4787 /** 4788 * @return true if currently paragraph justification mode. 4789 * 4790 * @see #setJustificationMode(int) 4791 */ 4792 @InspectableProperty(enumMapping = { 4793 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 4794 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 4795 }) getJustificationMode()4796 public @Layout.JustificationMode int getJustificationMode() { 4797 return mJustificationMode; 4798 } 4799 4800 /** 4801 * Sets font feature settings. The format is the same as the CSS 4802 * font-feature-settings attribute: 4803 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4804 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4805 * 4806 * @param fontFeatureSettings font feature settings represented as CSS compatible string 4807 * 4808 * @see #getFontFeatureSettings() 4809 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 4810 * 4811 * @attr ref android.R.styleable#TextView_fontFeatureSettings 4812 */ 4813 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)4814 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 4815 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 4816 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 4817 4818 if (mLayout != null) { 4819 nullLayouts(); 4820 requestLayout(); 4821 invalidate(); 4822 } 4823 } 4824 } 4825 4826 4827 /** 4828 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 4829 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 4830 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 4831 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 4832 * are invalid. If a specified axis name is not defined in the font, the settings will be 4833 * ignored. 4834 * 4835 * <p> 4836 * Examples, 4837 * <ul> 4838 * <li>Set font width to 150. 4839 * <pre> 4840 * <code> 4841 * TextView textView = (TextView) findViewById(R.id.textView); 4842 * textView.setFontVariationSettings("'wdth' 150"); 4843 * </code> 4844 * </pre> 4845 * </li> 4846 * 4847 * <li>Set the font slant to 20 degrees and ask for italic style. 4848 * <pre> 4849 * <code> 4850 * TextView textView = (TextView) findViewById(R.id.textView); 4851 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 4852 * </code> 4853 * </pre> 4854 * </p> 4855 * </li> 4856 * </ul> 4857 * 4858 * @param fontVariationSettings font variation settings. You can pass null or empty string as 4859 * no variation settings. 4860 * @return true if the given settings is effective to at least one font file underlying this 4861 * TextView. This function also returns true for empty settings string. Otherwise 4862 * returns false. 4863 * 4864 * @throws IllegalArgumentException If given string is not a valid font variation settings 4865 * format. 4866 * 4867 * @see #getFontVariationSettings() 4868 * @see FontVariationAxis 4869 * 4870 * @attr ref android.R.styleable#TextView_fontVariationSettings 4871 */ setFontVariationSettings(@ullable String fontVariationSettings)4872 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 4873 final String existingSettings = mTextPaint.getFontVariationSettings(); 4874 if (fontVariationSettings == existingSettings 4875 || (fontVariationSettings != null 4876 && fontVariationSettings.equals(existingSettings))) { 4877 return true; 4878 } 4879 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 4880 4881 if (effective && mLayout != null) { 4882 nullLayouts(); 4883 requestLayout(); 4884 invalidate(); 4885 } 4886 return effective; 4887 } 4888 4889 /** 4890 * Sets the text color for all the states (normal, selected, 4891 * focused) to be this color. 4892 * 4893 * @param color A color value in the form 0xAARRGGBB. 4894 * Do not pass a resource ID. To get a color value from a resource ID, call 4895 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 4896 * 4897 * @see #setTextColor(ColorStateList) 4898 * @see #getTextColors() 4899 * 4900 * @attr ref android.R.styleable#TextView_textColor 4901 */ 4902 @android.view.RemotableViewMethod setTextColor(@olorInt int color)4903 public void setTextColor(@ColorInt int color) { 4904 mTextColor = ColorStateList.valueOf(color); 4905 updateTextColors(); 4906 } 4907 4908 /** 4909 * Sets the text color. 4910 * 4911 * @see #setTextColor(int) 4912 * @see #getTextColors() 4913 * @see #setHintTextColor(ColorStateList) 4914 * @see #setLinkTextColor(ColorStateList) 4915 * 4916 * @attr ref android.R.styleable#TextView_textColor 4917 */ 4918 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)4919 public void setTextColor(ColorStateList colors) { 4920 if (colors == null) { 4921 throw new NullPointerException(); 4922 } 4923 4924 mTextColor = colors; 4925 updateTextColors(); 4926 } 4927 4928 /** 4929 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4930 * 4931 * @see #setTextColor(ColorStateList) 4932 * @see #setTextColor(int) 4933 * 4934 * @attr ref android.R.styleable#TextView_textColor 4935 */ 4936 @InspectableProperty(name = "textColor") getTextColors()4937 public final ColorStateList getTextColors() { 4938 return mTextColor; 4939 } 4940 4941 /** 4942 * Return the current color selected for normal text. 4943 * 4944 * @return Returns the current text color. 4945 */ 4946 @ColorInt getCurrentTextColor()4947 public final int getCurrentTextColor() { 4948 return mCurTextColor; 4949 } 4950 4951 /** 4952 * Sets the color used to display the selection highlight. 4953 * 4954 * @attr ref android.R.styleable#TextView_textColorHighlight 4955 */ 4956 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)4957 public void setHighlightColor(@ColorInt int color) { 4958 if (mHighlightColor != color) { 4959 mHighlightColor = color; 4960 invalidate(); 4961 } 4962 } 4963 4964 /** 4965 * @return the color used to display the selection highlight 4966 * 4967 * @see #setHighlightColor(int) 4968 * 4969 * @attr ref android.R.styleable#TextView_textColorHighlight 4970 */ 4971 @InspectableProperty(name = "textColorHighlight") 4972 @ColorInt getHighlightColor()4973 public int getHighlightColor() { 4974 return mHighlightColor; 4975 } 4976 4977 /** 4978 * Sets whether the soft input method will be made visible when this 4979 * TextView gets focused. The default is true. 4980 */ 4981 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)4982 public final void setShowSoftInputOnFocus(boolean show) { 4983 createEditorIfNeeded(); 4984 mEditor.mShowSoftInputOnFocus = show; 4985 } 4986 4987 /** 4988 * Returns whether the soft input method will be made visible when this 4989 * TextView gets focused. The default is true. 4990 */ getShowSoftInputOnFocus()4991 public final boolean getShowSoftInputOnFocus() { 4992 // When there is no Editor, return default true value 4993 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4994 } 4995 4996 /** 4997 * Gives the text a shadow of the specified blur radius and color, the specified 4998 * distance from its drawn position. 4999 * <p> 5000 * The text shadow produced does not interact with the properties on view 5001 * that are responsible for real time shadows, 5002 * {@link View#getElevation() elevation} and 5003 * {@link View#getTranslationZ() translationZ}. 5004 * 5005 * @see Paint#setShadowLayer(float, float, float, int) 5006 * 5007 * @attr ref android.R.styleable#TextView_shadowColor 5008 * @attr ref android.R.styleable#TextView_shadowDx 5009 * @attr ref android.R.styleable#TextView_shadowDy 5010 * @attr ref android.R.styleable#TextView_shadowRadius 5011 */ setShadowLayer(float radius, float dx, float dy, int color)5012 public void setShadowLayer(float radius, float dx, float dy, int color) { 5013 mTextPaint.setShadowLayer(radius, dx, dy, color); 5014 5015 mShadowRadius = radius; 5016 mShadowDx = dx; 5017 mShadowDy = dy; 5018 mShadowColor = color; 5019 5020 // Will change text clip region 5021 if (mEditor != null) { 5022 mEditor.invalidateTextDisplayList(); 5023 mEditor.invalidateHandlesAndActionMode(); 5024 } 5025 invalidate(); 5026 } 5027 5028 /** 5029 * Gets the radius of the shadow layer. 5030 * 5031 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 5032 * 5033 * @see #setShadowLayer(float, float, float, int) 5034 * 5035 * @attr ref android.R.styleable#TextView_shadowRadius 5036 */ 5037 @InspectableProperty getShadowRadius()5038 public float getShadowRadius() { 5039 return mShadowRadius; 5040 } 5041 5042 /** 5043 * @return the horizontal offset of the shadow layer 5044 * 5045 * @see #setShadowLayer(float, float, float, int) 5046 * 5047 * @attr ref android.R.styleable#TextView_shadowDx 5048 */ 5049 @InspectableProperty getShadowDx()5050 public float getShadowDx() { 5051 return mShadowDx; 5052 } 5053 5054 /** 5055 * Gets the vertical offset of the shadow layer. 5056 * @return The vertical offset of the shadow layer. 5057 * 5058 * @see #setShadowLayer(float, float, float, int) 5059 * 5060 * @attr ref android.R.styleable#TextView_shadowDy 5061 */ 5062 @InspectableProperty getShadowDy()5063 public float getShadowDy() { 5064 return mShadowDy; 5065 } 5066 5067 /** 5068 * Gets the color of the shadow layer. 5069 * @return the color of the shadow layer 5070 * 5071 * @see #setShadowLayer(float, float, float, int) 5072 * 5073 * @attr ref android.R.styleable#TextView_shadowColor 5074 */ 5075 @InspectableProperty 5076 @ColorInt getShadowColor()5077 public int getShadowColor() { 5078 return mShadowColor; 5079 } 5080 5081 /** 5082 * Gets the {@link TextPaint} used for the text. 5083 * Use this only to consult the Paint's properties and not to change them. 5084 * @return The base paint used for the text. 5085 */ getPaint()5086 public TextPaint getPaint() { 5087 return mTextPaint; 5088 } 5089 5090 /** 5091 * Sets the autolink mask of the text. See {@link 5092 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5093 * possible values. 5094 * 5095 * <p class="note"><b>Note:</b> 5096 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5097 * is deprecated and should be avoided; see its documentation. 5098 * 5099 * @attr ref android.R.styleable#TextView_autoLink 5100 */ 5101 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5102 public final void setAutoLinkMask(int mask) { 5103 mAutoLinkMask = mask; 5104 } 5105 5106 /** 5107 * Sets whether the movement method will automatically be set to 5108 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5109 * set to nonzero and links are detected in {@link #setText}. 5110 * The default is true. 5111 * 5112 * @attr ref android.R.styleable#TextView_linksClickable 5113 */ 5114 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5115 public final void setLinksClickable(boolean whether) { 5116 mLinksClickable = whether; 5117 } 5118 5119 /** 5120 * Returns whether the movement method will automatically be set to 5121 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5122 * set to nonzero and links are detected in {@link #setText}. 5123 * The default is true. 5124 * 5125 * @attr ref android.R.styleable#TextView_linksClickable 5126 */ 5127 @InspectableProperty getLinksClickable()5128 public final boolean getLinksClickable() { 5129 return mLinksClickable; 5130 } 5131 5132 /** 5133 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5134 * (by {@link Linkify} or otherwise) if any. You can call 5135 * {@link URLSpan#getURL} on them to find where they link to 5136 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5137 * to find the region of the text they are attached to. 5138 */ getUrls()5139 public URLSpan[] getUrls() { 5140 if (mText instanceof Spanned) { 5141 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5142 } else { 5143 return new URLSpan[0]; 5144 } 5145 } 5146 5147 /** 5148 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5149 * TextView. 5150 * 5151 * @see #setHintTextColor(ColorStateList) 5152 * @see #getHintTextColors() 5153 * @see #setTextColor(int) 5154 * 5155 * @attr ref android.R.styleable#TextView_textColorHint 5156 */ 5157 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5158 public final void setHintTextColor(@ColorInt int color) { 5159 mHintTextColor = ColorStateList.valueOf(color); 5160 updateTextColors(); 5161 } 5162 5163 /** 5164 * Sets the color of the hint text. 5165 * 5166 * @see #getHintTextColors() 5167 * @see #setHintTextColor(int) 5168 * @see #setTextColor(ColorStateList) 5169 * @see #setLinkTextColor(ColorStateList) 5170 * 5171 * @attr ref android.R.styleable#TextView_textColorHint 5172 */ setHintTextColor(ColorStateList colors)5173 public final void setHintTextColor(ColorStateList colors) { 5174 mHintTextColor = colors; 5175 updateTextColors(); 5176 } 5177 5178 /** 5179 * @return the color of the hint text, for the different states of this TextView. 5180 * 5181 * @see #setHintTextColor(ColorStateList) 5182 * @see #setHintTextColor(int) 5183 * @see #setTextColor(ColorStateList) 5184 * @see #setLinkTextColor(ColorStateList) 5185 * 5186 * @attr ref android.R.styleable#TextView_textColorHint 5187 */ 5188 @InspectableProperty(name = "textColorHint") getHintTextColors()5189 public final ColorStateList getHintTextColors() { 5190 return mHintTextColor; 5191 } 5192 5193 /** 5194 * <p>Return the current color selected to paint the hint text.</p> 5195 * 5196 * @return Returns the current hint text color. 5197 */ 5198 @ColorInt getCurrentHintTextColor()5199 public final int getCurrentHintTextColor() { 5200 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5201 } 5202 5203 /** 5204 * Sets the color of links in the text. 5205 * 5206 * @see #setLinkTextColor(ColorStateList) 5207 * @see #getLinkTextColors() 5208 * 5209 * @attr ref android.R.styleable#TextView_textColorLink 5210 */ 5211 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5212 public final void setLinkTextColor(@ColorInt int color) { 5213 mLinkTextColor = ColorStateList.valueOf(color); 5214 updateTextColors(); 5215 } 5216 5217 /** 5218 * Sets the color of links in the text. 5219 * 5220 * @see #setLinkTextColor(int) 5221 * @see #getLinkTextColors() 5222 * @see #setTextColor(ColorStateList) 5223 * @see #setHintTextColor(ColorStateList) 5224 * 5225 * @attr ref android.R.styleable#TextView_textColorLink 5226 */ setLinkTextColor(ColorStateList colors)5227 public final void setLinkTextColor(ColorStateList colors) { 5228 mLinkTextColor = colors; 5229 updateTextColors(); 5230 } 5231 5232 /** 5233 * @return the list of colors used to paint the links in the text, for the different states of 5234 * this TextView 5235 * 5236 * @see #setLinkTextColor(ColorStateList) 5237 * @see #setLinkTextColor(int) 5238 * 5239 * @attr ref android.R.styleable#TextView_textColorLink 5240 */ 5241 @InspectableProperty(name = "textColorLink") getLinkTextColors()5242 public final ColorStateList getLinkTextColors() { 5243 return mLinkTextColor; 5244 } 5245 5246 /** 5247 * Sets the horizontal alignment of the text and the 5248 * vertical gravity that will be used when there is extra space 5249 * in the TextView beyond what is required for the text itself. 5250 * 5251 * @see android.view.Gravity 5252 * @attr ref android.R.styleable#TextView_gravity 5253 */ 5254 @android.view.RemotableViewMethod setGravity(int gravity)5255 public void setGravity(int gravity) { 5256 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5257 gravity |= Gravity.START; 5258 } 5259 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5260 gravity |= Gravity.TOP; 5261 } 5262 5263 boolean newLayout = false; 5264 5265 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5266 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5267 newLayout = true; 5268 } 5269 5270 if (gravity != mGravity) { 5271 invalidate(); 5272 } 5273 5274 mGravity = gravity; 5275 5276 if (mLayout != null && newLayout) { 5277 // XXX this is heavy-handed because no actual content changes. 5278 int want = mLayout.getWidth(); 5279 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5280 5281 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5282 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5283 } 5284 } 5285 5286 /** 5287 * Returns the horizontal and vertical alignment of this TextView. 5288 * 5289 * @see android.view.Gravity 5290 * @attr ref android.R.styleable#TextView_gravity 5291 */ 5292 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5293 public int getGravity() { 5294 return mGravity; 5295 } 5296 5297 /** 5298 * Gets the flags on the Paint being used to display the text. 5299 * @return The flags on the Paint being used to display the text. 5300 * @see Paint#getFlags 5301 */ getPaintFlags()5302 public int getPaintFlags() { 5303 return mTextPaint.getFlags(); 5304 } 5305 5306 /** 5307 * Sets flags on the Paint being used to display the text and 5308 * reflows the text if they are different from the old flags. 5309 * @see Paint#setFlags 5310 */ 5311 @android.view.RemotableViewMethod setPaintFlags(int flags)5312 public void setPaintFlags(int flags) { 5313 if (mTextPaint.getFlags() != flags) { 5314 mTextPaint.setFlags(flags); 5315 5316 if (mLayout != null) { 5317 nullLayouts(); 5318 requestLayout(); 5319 invalidate(); 5320 } 5321 } 5322 } 5323 5324 /** 5325 * Sets whether the text should be allowed to be wider than the 5326 * View is. If false, it will be wrapped to the width of the View. 5327 * 5328 * @attr ref android.R.styleable#TextView_scrollHorizontally 5329 */ setHorizontallyScrolling(boolean whether)5330 public void setHorizontallyScrolling(boolean whether) { 5331 if (mHorizontallyScrolling != whether) { 5332 mHorizontallyScrolling = whether; 5333 5334 if (mLayout != null) { 5335 nullLayouts(); 5336 requestLayout(); 5337 invalidate(); 5338 } 5339 } 5340 } 5341 5342 /** 5343 * Returns whether the text is allowed to be wider than the View. 5344 * If false, the text will be wrapped to the width of the View. 5345 * 5346 * @attr ref android.R.styleable#TextView_scrollHorizontally 5347 * @see #setHorizontallyScrolling(boolean) 5348 */ 5349 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5350 public final boolean isHorizontallyScrollable() { 5351 return mHorizontallyScrolling; 5352 } 5353 5354 /** 5355 * Returns whether the text is allowed to be wider than the View. 5356 * If false, the text will be wrapped to the width of the View. 5357 * 5358 * @attr ref android.R.styleable#TextView_scrollHorizontally 5359 * @hide 5360 */ 5361 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5362 public boolean getHorizontallyScrolling() { 5363 return mHorizontallyScrolling; 5364 } 5365 5366 /** 5367 * Sets the height of the TextView to be at least {@code minLines} tall. 5368 * <p> 5369 * This value is used for height calculation if LayoutParams does not force TextView to have an 5370 * exact height. Setting this value overrides other previous minimum height configurations such 5371 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5372 * this value to 1. 5373 * 5374 * @param minLines the minimum height of TextView in terms of number of lines 5375 * 5376 * @see #getMinLines() 5377 * @see #setLines(int) 5378 * 5379 * @attr ref android.R.styleable#TextView_minLines 5380 */ 5381 @android.view.RemotableViewMethod setMinLines(int minLines)5382 public void setMinLines(int minLines) { 5383 mMinimum = minLines; 5384 mMinMode = LINES; 5385 5386 requestLayout(); 5387 invalidate(); 5388 } 5389 5390 /** 5391 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5392 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5393 * 5394 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5395 * height is not defined in lines 5396 * 5397 * @see #setMinLines(int) 5398 * @see #setLines(int) 5399 * 5400 * @attr ref android.R.styleable#TextView_minLines 5401 */ 5402 @InspectableProperty getMinLines()5403 public int getMinLines() { 5404 return mMinMode == LINES ? mMinimum : -1; 5405 } 5406 5407 /** 5408 * Sets the height of the TextView to be at least {@code minPixels} tall. 5409 * <p> 5410 * This value is used for height calculation if LayoutParams does not force TextView to have an 5411 * exact height. Setting this value overrides previous minimum height configurations such as 5412 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5413 * <p> 5414 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5415 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5416 * used to decide the final height. 5417 * 5418 * @param minPixels the minimum height of TextView in terms of pixels 5419 * 5420 * @see #getMinHeight() 5421 * @see #setHeight(int) 5422 * 5423 * @attr ref android.R.styleable#TextView_minHeight 5424 */ 5425 @android.view.RemotableViewMethod setMinHeight(int minPixels)5426 public void setMinHeight(int minPixels) { 5427 mMinimum = minPixels; 5428 mMinMode = PIXELS; 5429 5430 requestLayout(); 5431 invalidate(); 5432 } 5433 5434 /** 5435 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5436 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5437 * 5438 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5439 * defined in pixels 5440 * 5441 * @see #setMinHeight(int) 5442 * @see #setHeight(int) 5443 * 5444 * @attr ref android.R.styleable#TextView_minHeight 5445 */ getMinHeight()5446 public int getMinHeight() { 5447 return mMinMode == PIXELS ? mMinimum : -1; 5448 } 5449 5450 /** 5451 * Sets the height of the TextView to be at most {@code maxLines} tall. 5452 * <p> 5453 * This value is used for height calculation if LayoutParams does not force TextView to have an 5454 * exact height. Setting this value overrides previous maximum height configurations such as 5455 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5456 * 5457 * @param maxLines the maximum height of TextView in terms of number of lines 5458 * 5459 * @see #getMaxLines() 5460 * @see #setLines(int) 5461 * 5462 * @attr ref android.R.styleable#TextView_maxLines 5463 */ 5464 @android.view.RemotableViewMethod setMaxLines(int maxLines)5465 public void setMaxLines(int maxLines) { 5466 mMaximum = maxLines; 5467 mMaxMode = LINES; 5468 5469 requestLayout(); 5470 invalidate(); 5471 } 5472 5473 /** 5474 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5475 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5476 * 5477 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5478 * is not defined in lines. 5479 * 5480 * @see #setMaxLines(int) 5481 * @see #setLines(int) 5482 * 5483 * @attr ref android.R.styleable#TextView_maxLines 5484 */ 5485 @InspectableProperty getMaxLines()5486 public int getMaxLines() { 5487 return mMaxMode == LINES ? mMaximum : -1; 5488 } 5489 5490 /** 5491 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5492 * <p> 5493 * This value is used for height calculation if LayoutParams does not force TextView to have an 5494 * exact height. Setting this value overrides previous maximum height configurations such as 5495 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5496 * 5497 * @param maxPixels the maximum height of TextView in terms of pixels 5498 * 5499 * @see #getMaxHeight() 5500 * @see #setHeight(int) 5501 * 5502 * @attr ref android.R.styleable#TextView_maxHeight 5503 */ 5504 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5505 public void setMaxHeight(int maxPixels) { 5506 mMaximum = maxPixels; 5507 mMaxMode = PIXELS; 5508 5509 requestLayout(); 5510 invalidate(); 5511 } 5512 5513 /** 5514 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5515 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5516 * 5517 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5518 * is not defined in pixels 5519 * 5520 * @see #setMaxHeight(int) 5521 * @see #setHeight(int) 5522 * 5523 * @attr ref android.R.styleable#TextView_maxHeight 5524 */ 5525 @InspectableProperty getMaxHeight()5526 public int getMaxHeight() { 5527 return mMaxMode == PIXELS ? mMaximum : -1; 5528 } 5529 5530 /** 5531 * Sets the height of the TextView to be exactly {@code lines} tall. 5532 * <p> 5533 * This value is used for height calculation if LayoutParams does not force TextView to have an 5534 * exact height. Setting this value overrides previous minimum/maximum height configurations 5535 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5536 * set this value to 1. 5537 * 5538 * @param lines the exact height of the TextView in terms of lines 5539 * 5540 * @see #setHeight(int) 5541 * 5542 * @attr ref android.R.styleable#TextView_lines 5543 */ 5544 @android.view.RemotableViewMethod setLines(int lines)5545 public void setLines(int lines) { 5546 mMaximum = mMinimum = lines; 5547 mMaxMode = mMinMode = LINES; 5548 5549 requestLayout(); 5550 invalidate(); 5551 } 5552 5553 /** 5554 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5555 * <p> 5556 * This value is used for height calculation if LayoutParams does not force TextView to have an 5557 * exact height. Setting this value overrides previous minimum/maximum height configurations 5558 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5559 * 5560 * @param pixels the exact height of the TextView in terms of pixels 5561 * 5562 * @see #setLines(int) 5563 * 5564 * @attr ref android.R.styleable#TextView_height 5565 */ 5566 @android.view.RemotableViewMethod setHeight(int pixels)5567 public void setHeight(int pixels) { 5568 mMaximum = mMinimum = pixels; 5569 mMaxMode = mMinMode = PIXELS; 5570 5571 requestLayout(); 5572 invalidate(); 5573 } 5574 5575 /** 5576 * Sets the width of the TextView to be at least {@code minEms} wide. 5577 * <p> 5578 * This value is used for width calculation if LayoutParams does not force TextView to have an 5579 * exact width. Setting this value overrides previous minimum width configurations such as 5580 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5581 * 5582 * @param minEms the minimum width of TextView in terms of ems 5583 * 5584 * @see #getMinEms() 5585 * @see #setEms(int) 5586 * 5587 * @attr ref android.R.styleable#TextView_minEms 5588 */ 5589 @android.view.RemotableViewMethod setMinEms(int minEms)5590 public void setMinEms(int minEms) { 5591 mMinWidth = minEms; 5592 mMinWidthMode = EMS; 5593 5594 requestLayout(); 5595 invalidate(); 5596 } 5597 5598 /** 5599 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5600 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5601 * 5602 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5603 * defined in ems 5604 * 5605 * @see #setMinEms(int) 5606 * @see #setEms(int) 5607 * 5608 * @attr ref android.R.styleable#TextView_minEms 5609 */ 5610 @InspectableProperty getMinEms()5611 public int getMinEms() { 5612 return mMinWidthMode == EMS ? mMinWidth : -1; 5613 } 5614 5615 /** 5616 * Sets the width of the TextView to be at least {@code minPixels} wide. 5617 * <p> 5618 * This value is used for width calculation if LayoutParams does not force TextView to have an 5619 * exact width. Setting this value overrides previous minimum width configurations such as 5620 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5621 * <p> 5622 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5623 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5624 * to decide the final width. 5625 * 5626 * @param minPixels the minimum width of TextView in terms of pixels 5627 * 5628 * @see #getMinWidth() 5629 * @see #setWidth(int) 5630 * 5631 * @attr ref android.R.styleable#TextView_minWidth 5632 */ 5633 @android.view.RemotableViewMethod setMinWidth(int minPixels)5634 public void setMinWidth(int minPixels) { 5635 mMinWidth = minPixels; 5636 mMinWidthMode = PIXELS; 5637 5638 requestLayout(); 5639 invalidate(); 5640 } 5641 5642 /** 5643 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5644 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5645 * 5646 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5647 * defined in pixels 5648 * 5649 * @see #setMinWidth(int) 5650 * @see #setWidth(int) 5651 * 5652 * @attr ref android.R.styleable#TextView_minWidth 5653 */ 5654 @InspectableProperty getMinWidth()5655 public int getMinWidth() { 5656 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5657 } 5658 5659 /** 5660 * Sets the width of the TextView to be at most {@code maxEms} wide. 5661 * <p> 5662 * This value is used for width calculation if LayoutParams does not force TextView to have an 5663 * exact width. Setting this value overrides previous maximum width configurations such as 5664 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5665 * 5666 * @param maxEms the maximum width of TextView in terms of ems 5667 * 5668 * @see #getMaxEms() 5669 * @see #setEms(int) 5670 * 5671 * @attr ref android.R.styleable#TextView_maxEms 5672 */ 5673 @android.view.RemotableViewMethod setMaxEms(int maxEms)5674 public void setMaxEms(int maxEms) { 5675 mMaxWidth = maxEms; 5676 mMaxWidthMode = EMS; 5677 5678 requestLayout(); 5679 invalidate(); 5680 } 5681 5682 /** 5683 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5684 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5685 * 5686 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5687 * defined in ems 5688 * 5689 * @see #setMaxEms(int) 5690 * @see #setEms(int) 5691 * 5692 * @attr ref android.R.styleable#TextView_maxEms 5693 */ 5694 @InspectableProperty getMaxEms()5695 public int getMaxEms() { 5696 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5697 } 5698 5699 /** 5700 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5701 * <p> 5702 * This value is used for width calculation if LayoutParams does not force TextView to have an 5703 * exact width. Setting this value overrides previous maximum width configurations such as 5704 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5705 * 5706 * @param maxPixels the maximum width of TextView in terms of pixels 5707 * 5708 * @see #getMaxWidth() 5709 * @see #setWidth(int) 5710 * 5711 * @attr ref android.R.styleable#TextView_maxWidth 5712 */ 5713 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5714 public void setMaxWidth(int maxPixels) { 5715 mMaxWidth = maxPixels; 5716 mMaxWidthMode = PIXELS; 5717 5718 requestLayout(); 5719 invalidate(); 5720 } 5721 5722 /** 5723 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5724 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5725 * 5726 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5727 * defined in pixels 5728 * 5729 * @see #setMaxWidth(int) 5730 * @see #setWidth(int) 5731 * 5732 * @attr ref android.R.styleable#TextView_maxWidth 5733 */ 5734 @InspectableProperty getMaxWidth()5735 public int getMaxWidth() { 5736 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5737 } 5738 5739 /** 5740 * Sets the width of the TextView to be exactly {@code ems} wide. 5741 * 5742 * This value is used for width calculation if LayoutParams does not force TextView to have an 5743 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5744 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5745 * 5746 * @param ems the exact width of the TextView in terms of ems 5747 * 5748 * @see #setWidth(int) 5749 * 5750 * @attr ref android.R.styleable#TextView_ems 5751 */ 5752 @android.view.RemotableViewMethod setEms(int ems)5753 public void setEms(int ems) { 5754 mMaxWidth = mMinWidth = ems; 5755 mMaxWidthMode = mMinWidthMode = EMS; 5756 5757 requestLayout(); 5758 invalidate(); 5759 } 5760 5761 /** 5762 * Sets the width of the TextView to be exactly {@code pixels} wide. 5763 * <p> 5764 * This value is used for width calculation if LayoutParams does not force TextView to have an 5765 * exact width. Setting this value overrides previous minimum/maximum width configurations 5766 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5767 * 5768 * @param pixels the exact width of the TextView in terms of pixels 5769 * 5770 * @see #setEms(int) 5771 * 5772 * @attr ref android.R.styleable#TextView_width 5773 */ 5774 @android.view.RemotableViewMethod setWidth(int pixels)5775 public void setWidth(int pixels) { 5776 mMaxWidth = mMinWidth = pixels; 5777 mMaxWidthMode = mMinWidthMode = PIXELS; 5778 5779 requestLayout(); 5780 invalidate(); 5781 } 5782 5783 /** 5784 * Sets line spacing for this TextView. Each line other than the last line will have its height 5785 * multiplied by {@code mult} and have {@code add} added to it. 5786 * 5787 * @param add The value in pixels that should be added to each line other than the last line. 5788 * This will be applied after the multiplier 5789 * @param mult The value by which each line height other than the last line will be multiplied 5790 * by 5791 * 5792 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5793 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5794 */ setLineSpacing(float add, float mult)5795 public void setLineSpacing(float add, float mult) { 5796 if (mSpacingAdd != add || mSpacingMult != mult) { 5797 mSpacingAdd = add; 5798 mSpacingMult = mult; 5799 5800 if (mLayout != null) { 5801 nullLayouts(); 5802 requestLayout(); 5803 invalidate(); 5804 } 5805 } 5806 } 5807 5808 /** 5809 * Gets the line spacing multiplier 5810 * 5811 * @return the value by which each line's height is multiplied to get its actual height. 5812 * 5813 * @see #setLineSpacing(float, float) 5814 * @see #getLineSpacingExtra() 5815 * 5816 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5817 */ 5818 @InspectableProperty getLineSpacingMultiplier()5819 public float getLineSpacingMultiplier() { 5820 return mSpacingMult; 5821 } 5822 5823 /** 5824 * Gets the line spacing extra space 5825 * 5826 * @return the extra space that is added to the height of each lines of this TextView. 5827 * 5828 * @see #setLineSpacing(float, float) 5829 * @see #getLineSpacingMultiplier() 5830 * 5831 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5832 */ 5833 @InspectableProperty getLineSpacingExtra()5834 public float getLineSpacingExtra() { 5835 return mSpacingAdd; 5836 } 5837 5838 /** 5839 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 5840 * between subsequent baselines in the TextView. 5841 * 5842 * @param lineHeight the line height in pixels 5843 * 5844 * @see #setLineSpacing(float, float) 5845 * @see #getLineSpacingExtra() 5846 * 5847 * @attr ref android.R.styleable#TextView_lineHeight 5848 */ 5849 @android.view.RemotableViewMethod setLineHeight(@x @ntRangefrom = 0) int lineHeight)5850 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 5851 Preconditions.checkArgumentNonnegative(lineHeight); 5852 5853 final int fontHeight = getPaint().getFontMetricsInt(null); 5854 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 5855 if (lineHeight != fontHeight) { 5856 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 5857 setLineSpacing(lineHeight - fontHeight, 1f); 5858 } 5859 } 5860 5861 /** 5862 * Convenience method to append the specified text to the TextView's 5863 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5864 * if it was not already editable. 5865 * 5866 * @param text text to be appended to the already displayed text 5867 */ append(CharSequence text)5868 public final void append(CharSequence text) { 5869 append(text, 0, text.length()); 5870 } 5871 5872 /** 5873 * Convenience method to append the specified text slice to the TextView's 5874 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5875 * if it was not already editable. 5876 * 5877 * @param text text to be appended to the already displayed text 5878 * @param start the index of the first character in the {@code text} 5879 * @param end the index of the character following the last character in the {@code text} 5880 * 5881 * @see Appendable#append(CharSequence, int, int) 5882 */ append(CharSequence text, int start, int end)5883 public void append(CharSequence text, int start, int end) { 5884 if (!(mText instanceof Editable)) { 5885 setText(mText, BufferType.EDITABLE); 5886 } 5887 5888 ((Editable) mText).append(text, start, end); 5889 5890 if (mAutoLinkMask != 0) { 5891 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 5892 // Do not change the movement method for text that support text selection as it 5893 // would prevent an arbitrary cursor displacement. 5894 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 5895 setMovementMethod(LinkMovementMethod.getInstance()); 5896 } 5897 } 5898 } 5899 updateTextColors()5900 private void updateTextColors() { 5901 boolean inval = false; 5902 final int[] drawableState = getDrawableState(); 5903 int color = mTextColor.getColorForState(drawableState, 0); 5904 if (color != mCurTextColor) { 5905 mCurTextColor = color; 5906 inval = true; 5907 } 5908 if (mLinkTextColor != null) { 5909 color = mLinkTextColor.getColorForState(drawableState, 0); 5910 if (color != mTextPaint.linkColor) { 5911 mTextPaint.linkColor = color; 5912 inval = true; 5913 } 5914 } 5915 if (mHintTextColor != null) { 5916 color = mHintTextColor.getColorForState(drawableState, 0); 5917 if (color != mCurHintTextColor) { 5918 mCurHintTextColor = color; 5919 if (mText.length() == 0) { 5920 inval = true; 5921 } 5922 } 5923 } 5924 if (inval) { 5925 // Text needs to be redrawn with the new color 5926 if (mEditor != null) mEditor.invalidateTextDisplayList(); 5927 invalidate(); 5928 } 5929 } 5930 5931 @Override drawableStateChanged()5932 protected void drawableStateChanged() { 5933 super.drawableStateChanged(); 5934 5935 if (mTextColor != null && mTextColor.isStateful() 5936 || (mHintTextColor != null && mHintTextColor.isStateful()) 5937 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 5938 updateTextColors(); 5939 } 5940 5941 if (mDrawables != null) { 5942 final int[] state = getDrawableState(); 5943 for (Drawable dr : mDrawables.mShowing) { 5944 if (dr != null && dr.isStateful() && dr.setState(state)) { 5945 invalidateDrawable(dr); 5946 } 5947 } 5948 } 5949 } 5950 5951 @Override drawableHotspotChanged(float x, float y)5952 public void drawableHotspotChanged(float x, float y) { 5953 super.drawableHotspotChanged(x, y); 5954 5955 if (mDrawables != null) { 5956 for (Drawable dr : mDrawables.mShowing) { 5957 if (dr != null) { 5958 dr.setHotspot(x, y); 5959 } 5960 } 5961 } 5962 } 5963 5964 @Override onSaveInstanceState()5965 public Parcelable onSaveInstanceState() { 5966 Parcelable superState = super.onSaveInstanceState(); 5967 5968 // Save state if we are forced to 5969 final boolean freezesText = getFreezesText(); 5970 boolean hasSelection = false; 5971 int start = -1; 5972 int end = -1; 5973 5974 if (mText != null) { 5975 start = getSelectionStart(); 5976 end = getSelectionEnd(); 5977 if (start >= 0 || end >= 0) { 5978 // Or save state if there is a selection 5979 hasSelection = true; 5980 } 5981 } 5982 5983 if (freezesText || hasSelection) { 5984 SavedState ss = new SavedState(superState); 5985 5986 if (freezesText) { 5987 if (mText instanceof Spanned) { 5988 final Spannable sp = new SpannableStringBuilder(mText); 5989 5990 if (mEditor != null) { 5991 removeMisspelledSpans(sp); 5992 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5993 } 5994 5995 ss.text = sp; 5996 } else { 5997 ss.text = mText.toString(); 5998 } 5999 } 6000 6001 if (hasSelection) { 6002 // XXX Should also save the current scroll position! 6003 ss.selStart = start; 6004 ss.selEnd = end; 6005 } 6006 6007 if (isFocused() && start >= 0 && end >= 0) { 6008 ss.frozenWithFocus = true; 6009 } 6010 6011 ss.error = getError(); 6012 6013 if (mEditor != null) { 6014 ss.editorState = mEditor.saveInstanceState(); 6015 } 6016 return ss; 6017 } 6018 6019 return superState; 6020 } 6021 removeMisspelledSpans(Spannable spannable)6022 void removeMisspelledSpans(Spannable spannable) { 6023 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 6024 SuggestionSpan.class); 6025 for (int i = 0; i < suggestionSpans.length; i++) { 6026 int flags = suggestionSpans[i].getFlags(); 6027 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 6028 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 6029 spannable.removeSpan(suggestionSpans[i]); 6030 } 6031 } 6032 } 6033 6034 @Override onRestoreInstanceState(Parcelable state)6035 public void onRestoreInstanceState(Parcelable state) { 6036 if (!(state instanceof SavedState)) { 6037 super.onRestoreInstanceState(state); 6038 return; 6039 } 6040 6041 SavedState ss = (SavedState) state; 6042 super.onRestoreInstanceState(ss.getSuperState()); 6043 6044 // XXX restore buffer type too, as well as lots of other stuff 6045 if (ss.text != null) { 6046 setText(ss.text); 6047 } 6048 6049 if (ss.selStart >= 0 && ss.selEnd >= 0) { 6050 if (mSpannable != null) { 6051 int len = mText.length(); 6052 6053 if (ss.selStart > len || ss.selEnd > len) { 6054 String restored = ""; 6055 6056 if (ss.text != null) { 6057 restored = "(restored) "; 6058 } 6059 6060 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 6061 + " out of range for " + restored + "text " + mText); 6062 } else { 6063 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 6064 6065 if (ss.frozenWithFocus) { 6066 createEditorIfNeeded(); 6067 mEditor.mFrozenWithFocus = true; 6068 } 6069 } 6070 } 6071 } 6072 6073 if (ss.error != null) { 6074 final CharSequence error = ss.error; 6075 // Display the error later, after the first layout pass 6076 post(new Runnable() { 6077 public void run() { 6078 if (mEditor == null || !mEditor.mErrorWasChanged) { 6079 setError(error); 6080 } 6081 } 6082 }); 6083 } 6084 6085 if (ss.editorState != null) { 6086 createEditorIfNeeded(); 6087 mEditor.restoreInstanceState(ss.editorState); 6088 } 6089 } 6090 6091 /** 6092 * Control whether this text view saves its entire text contents when 6093 * freezing to an icicle, in addition to dynamic state such as cursor 6094 * position. By default this is false, not saving the text. Set to true 6095 * if the text in the text view is not being saved somewhere else in 6096 * persistent storage (such as in a content provider) so that if the 6097 * view is later thawed the user will not lose their data. For 6098 * {@link android.widget.EditText} it is always enabled, regardless of 6099 * the value of the attribute. 6100 * 6101 * @param freezesText Controls whether a frozen icicle should include the 6102 * entire text data: true to include it, false to not. 6103 * 6104 * @attr ref android.R.styleable#TextView_freezesText 6105 */ 6106 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)6107 public void setFreezesText(boolean freezesText) { 6108 mFreezesText = freezesText; 6109 } 6110 6111 /** 6112 * Return whether this text view is including its entire text contents 6113 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 6114 * 6115 * @return Returns true if text is included, false if it isn't. 6116 * 6117 * @see #setFreezesText 6118 */ 6119 @InspectableProperty getFreezesText()6120 public boolean getFreezesText() { 6121 return mFreezesText; 6122 } 6123 6124 /////////////////////////////////////////////////////////////////////////// 6125 6126 /** 6127 * Sets the Factory used to create new {@link Editable Editables}. 6128 * 6129 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6130 * 6131 * @see android.text.Editable.Factory 6132 * @see android.widget.TextView.BufferType#EDITABLE 6133 */ setEditableFactory(Editable.Factory factory)6134 public final void setEditableFactory(Editable.Factory factory) { 6135 mEditableFactory = factory; 6136 setText(mText); 6137 } 6138 6139 /** 6140 * Sets the Factory used to create new {@link Spannable Spannables}. 6141 * 6142 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6143 * 6144 * @see android.text.Spannable.Factory 6145 * @see android.widget.TextView.BufferType#SPANNABLE 6146 */ setSpannableFactory(Spannable.Factory factory)6147 public final void setSpannableFactory(Spannable.Factory factory) { 6148 mSpannableFactory = factory; 6149 setText(mText); 6150 } 6151 6152 /** 6153 * Sets the text to be displayed. TextView <em>does not</em> accept 6154 * HTML-like formatting, which you can do with text strings in XML resource files. 6155 * To style your strings, attach android.text.style.* objects to a 6156 * {@link android.text.SpannableString}, or see the 6157 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6158 * Available Resource Types</a> documentation for an example of setting 6159 * formatted text in the XML resource file. 6160 * <p/> 6161 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6162 * intermediate {@link Spannable Spannables}. Likewise it will use 6163 * {@link android.text.Editable.Factory} to create final or intermediate 6164 * {@link Editable Editables}. 6165 * 6166 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6167 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6168 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6169 * 6170 * @param text text to be displayed 6171 * 6172 * @attr ref android.R.styleable#TextView_text 6173 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6174 * parameters used to create the PrecomputedText mismatches 6175 * with this TextView. 6176 */ 6177 @android.view.RemotableViewMethod setText(CharSequence text)6178 public final void setText(CharSequence text) { 6179 setText(text, mBufferType); 6180 } 6181 6182 /** 6183 * Sets the text to be displayed but retains the cursor position. Same as 6184 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6185 * new text. 6186 * <p/> 6187 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6188 * intermediate {@link Spannable Spannables}. Likewise it will use 6189 * {@link android.text.Editable.Factory} to create final or intermediate 6190 * {@link Editable Editables}. 6191 * 6192 * @param text text to be displayed 6193 * 6194 * @see #setText(CharSequence) 6195 */ 6196 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6197 public final void setTextKeepState(CharSequence text) { 6198 setTextKeepState(text, mBufferType); 6199 } 6200 6201 /** 6202 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6203 * <p/> 6204 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6205 * intermediate {@link Spannable Spannables}. Likewise it will use 6206 * {@link android.text.Editable.Factory} to create final or intermediate 6207 * {@link Editable Editables}. 6208 * 6209 * Subclasses overriding this method should ensure that the following post condition holds, 6210 * in order to guarantee the safety of the view's measurement and layout operations: 6211 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6212 * will be different from {@code null}. 6213 * 6214 * @param text text to be displayed 6215 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6216 * stored as a static text, styleable/spannable text, or editable text 6217 * 6218 * @see #setText(CharSequence) 6219 * @see android.widget.TextView.BufferType 6220 * @see #setSpannableFactory(Spannable.Factory) 6221 * @see #setEditableFactory(Editable.Factory) 6222 * 6223 * @attr ref android.R.styleable#TextView_text 6224 * @attr ref android.R.styleable#TextView_bufferType 6225 */ setText(CharSequence text, BufferType type)6226 public void setText(CharSequence text, BufferType type) { 6227 setText(text, type, true, 0); 6228 6229 if (mCharWrapper != null) { 6230 mCharWrapper.mChars = null; 6231 } 6232 } 6233 6234 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6235 private void setText(CharSequence text, BufferType type, 6236 boolean notifyBefore, int oldlen) { 6237 mTextSetFromXmlOrResourceId = false; 6238 if (text == null) { 6239 text = ""; 6240 } 6241 6242 // If suggestions are not enabled, remove the suggestion spans from the text 6243 if (!isSuggestionsEnabled()) { 6244 text = removeSuggestionSpans(text); 6245 } 6246 6247 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6248 6249 if (text instanceof Spanned 6250 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6251 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6252 setHorizontalFadingEdgeEnabled(true); 6253 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6254 } else { 6255 setHorizontalFadingEdgeEnabled(false); 6256 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6257 } 6258 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6259 } 6260 6261 int n = mFilters.length; 6262 for (int i = 0; i < n; i++) { 6263 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6264 if (out != null) { 6265 text = out; 6266 } 6267 } 6268 6269 if (notifyBefore) { 6270 if (mText != null) { 6271 oldlen = mText.length(); 6272 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6273 } else { 6274 sendBeforeTextChanged("", 0, 0, text.length()); 6275 } 6276 } 6277 6278 boolean needEditableForNotification = false; 6279 6280 if (mListeners != null && mListeners.size() != 0) { 6281 needEditableForNotification = true; 6282 } 6283 6284 PrecomputedText precomputed = 6285 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6286 if (type == BufferType.EDITABLE || getKeyListener() != null 6287 || needEditableForNotification) { 6288 createEditorIfNeeded(); 6289 mEditor.forgetUndoRedo(); 6290 mEditor.scheduleRestartInputForSetText(); 6291 Editable t = mEditableFactory.newEditable(text); 6292 text = t; 6293 setFilters(t, mFilters); 6294 } else if (precomputed != null) { 6295 if (mTextDir == null) { 6296 mTextDir = getTextDirectionHeuristic(); 6297 } 6298 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6299 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6300 mHyphenationFrequency); 6301 switch (checkResult) { 6302 case PrecomputedText.Params.UNUSABLE: 6303 throw new IllegalArgumentException( 6304 "PrecomputedText's Parameters don't match the parameters of this TextView." 6305 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6306 + "to override the settings of this TextView: " 6307 + "PrecomputedText: " + precomputed.getParams() 6308 + "TextView: " + getTextMetricsParams()); 6309 case PrecomputedText.Params.NEED_RECOMPUTE: 6310 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6311 break; 6312 case PrecomputedText.Params.USABLE: 6313 // pass through 6314 } 6315 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6316 text = mSpannableFactory.newSpannable(text); 6317 } else if (!(text instanceof CharWrapper)) { 6318 text = TextUtils.stringOrSpannedString(text); 6319 } 6320 6321 if (mAutoLinkMask != 0) { 6322 Spannable s2; 6323 6324 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6325 s2 = (Spannable) text; 6326 } else { 6327 s2 = mSpannableFactory.newSpannable(text); 6328 } 6329 6330 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6331 text = s2; 6332 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6333 6334 /* 6335 * We must go ahead and set the text before changing the 6336 * movement method, because setMovementMethod() may call 6337 * setText() again to try to upgrade the buffer type. 6338 */ 6339 setTextInternal(text); 6340 6341 // Do not change the movement method for text that support text selection as it 6342 // would prevent an arbitrary cursor displacement. 6343 if (mLinksClickable && !textCanBeSelected()) { 6344 setMovementMethod(LinkMovementMethod.getInstance()); 6345 } 6346 } 6347 } 6348 6349 mBufferType = type; 6350 setTextInternal(text); 6351 6352 if (mTransformation == null) { 6353 mTransformed = text; 6354 } else { 6355 mTransformed = mTransformation.getTransformation(text, this); 6356 } 6357 if (mTransformed == null) { 6358 // Should not happen if the transformation method follows the non-null postcondition. 6359 mTransformed = ""; 6360 } 6361 6362 final int textLength = text.length(); 6363 6364 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6365 Spannable sp = (Spannable) text; 6366 6367 // Remove any ChangeWatchers that might have come from other TextViews. 6368 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6369 final int count = watchers.length; 6370 for (int i = 0; i < count; i++) { 6371 sp.removeSpan(watchers[i]); 6372 } 6373 6374 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6375 6376 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6377 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6378 6379 if (mEditor != null) mEditor.addSpanWatchers(sp); 6380 6381 if (mTransformation != null) { 6382 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6383 } 6384 6385 if (mMovement != null) { 6386 mMovement.initialize(this, (Spannable) text); 6387 6388 /* 6389 * Initializing the movement method will have set the 6390 * selection, so reset mSelectionMoved to keep that from 6391 * interfering with the normal on-focus selection-setting. 6392 */ 6393 if (mEditor != null) mEditor.mSelectionMoved = false; 6394 } 6395 } 6396 6397 if (mLayout != null) { 6398 checkForRelayout(); 6399 } 6400 6401 sendOnTextChanged(text, 0, oldlen, textLength); 6402 onTextChanged(text, 0, oldlen, textLength); 6403 6404 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6405 6406 if (needEditableForNotification) { 6407 sendAfterTextChanged((Editable) text); 6408 } else { 6409 notifyListeningManagersAfterTextChanged(); 6410 } 6411 6412 if (mEditor != null) { 6413 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6414 mEditor.prepareCursorControllers(); 6415 6416 mEditor.maybeFireScheduledRestartInputForSetText(); 6417 } 6418 } 6419 6420 /** 6421 * Sets the TextView to display the specified slice of the specified 6422 * char array. You must promise that you will not change the contents 6423 * of the array except for right before another call to setText(), 6424 * since the TextView has no way to know that the text 6425 * has changed and that it needs to invalidate and re-layout. 6426 * 6427 * @param text char array to be displayed 6428 * @param start start index in the char array 6429 * @param len length of char count after {@code start} 6430 */ setText(char[] text, int start, int len)6431 public final void setText(char[] text, int start, int len) { 6432 int oldlen = 0; 6433 6434 if (start < 0 || len < 0 || start + len > text.length) { 6435 throw new IndexOutOfBoundsException(start + ", " + len); 6436 } 6437 6438 /* 6439 * We must do the before-notification here ourselves because if 6440 * the old text is a CharWrapper we destroy it before calling 6441 * into the normal path. 6442 */ 6443 if (mText != null) { 6444 oldlen = mText.length(); 6445 sendBeforeTextChanged(mText, 0, oldlen, len); 6446 } else { 6447 sendBeforeTextChanged("", 0, 0, len); 6448 } 6449 6450 if (mCharWrapper == null) { 6451 mCharWrapper = new CharWrapper(text, start, len); 6452 } else { 6453 mCharWrapper.set(text, start, len); 6454 } 6455 6456 setText(mCharWrapper, mBufferType, false, oldlen); 6457 } 6458 6459 /** 6460 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6461 * the cursor position. Same as 6462 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6463 * position (if any) is retained in the new text. 6464 * <p/> 6465 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6466 * intermediate {@link Spannable Spannables}. Likewise it will use 6467 * {@link android.text.Editable.Factory} to create final or intermediate 6468 * {@link Editable Editables}. 6469 * 6470 * @param text text to be displayed 6471 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6472 * stored as a static text, styleable/spannable text, or editable text 6473 * 6474 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6475 */ setTextKeepState(CharSequence text, BufferType type)6476 public final void setTextKeepState(CharSequence text, BufferType type) { 6477 int start = getSelectionStart(); 6478 int end = getSelectionEnd(); 6479 int len = text.length(); 6480 6481 setText(text, type); 6482 6483 if (start >= 0 || end >= 0) { 6484 if (mSpannable != null) { 6485 Selection.setSelection(mSpannable, 6486 Math.max(0, Math.min(start, len)), 6487 Math.max(0, Math.min(end, len))); 6488 } 6489 } 6490 } 6491 6492 /** 6493 * Sets the text to be displayed using a string resource identifier. 6494 * 6495 * @param resid the resource identifier of the string resource to be displayed 6496 * 6497 * @see #setText(CharSequence) 6498 * 6499 * @attr ref android.R.styleable#TextView_text 6500 */ 6501 @android.view.RemotableViewMethod setText(@tringRes int resid)6502 public final void setText(@StringRes int resid) { 6503 setText(getContext().getResources().getText(resid)); 6504 mTextSetFromXmlOrResourceId = true; 6505 mTextId = resid; 6506 } 6507 6508 /** 6509 * Sets the text to be displayed using a string resource identifier and the 6510 * {@link android.widget.TextView.BufferType}. 6511 * <p/> 6512 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6513 * intermediate {@link Spannable Spannables}. Likewise it will use 6514 * {@link android.text.Editable.Factory} to create final or intermediate 6515 * {@link Editable Editables}. 6516 * 6517 * @param resid the resource identifier of the string resource to be displayed 6518 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6519 * stored as a static text, styleable/spannable text, or editable text 6520 * 6521 * @see #setText(int) 6522 * @see #setText(CharSequence) 6523 * @see android.widget.TextView.BufferType 6524 * @see #setSpannableFactory(Spannable.Factory) 6525 * @see #setEditableFactory(Editable.Factory) 6526 * 6527 * @attr ref android.R.styleable#TextView_text 6528 * @attr ref android.R.styleable#TextView_bufferType 6529 */ setText(@tringRes int resid, BufferType type)6530 public final void setText(@StringRes int resid, BufferType type) { 6531 setText(getContext().getResources().getText(resid), type); 6532 mTextSetFromXmlOrResourceId = true; 6533 mTextId = resid; 6534 } 6535 6536 /** 6537 * Sets the text to be displayed when the text of the TextView is empty. 6538 * Null means to use the normal empty text. The hint does not currently 6539 * participate in determining the size of the view. 6540 * 6541 * @attr ref android.R.styleable#TextView_hint 6542 */ 6543 @android.view.RemotableViewMethod setHint(CharSequence hint)6544 public final void setHint(CharSequence hint) { 6545 setHintInternal(hint); 6546 6547 if (mEditor != null && isInputMethodTarget()) { 6548 mEditor.reportExtractedText(); 6549 } 6550 } 6551 setHintInternal(CharSequence hint)6552 private void setHintInternal(CharSequence hint) { 6553 mHint = TextUtils.stringOrSpannedString(hint); 6554 6555 if (mLayout != null) { 6556 checkForRelayout(); 6557 } 6558 6559 if (mText.length() == 0) { 6560 invalidate(); 6561 } 6562 6563 // Invalidate display list if hint is currently used 6564 if (mEditor != null && mText.length() == 0 && mHint != null) { 6565 mEditor.invalidateTextDisplayList(); 6566 } 6567 } 6568 6569 /** 6570 * Sets the text to be displayed when the text of the TextView is empty, 6571 * from a resource. 6572 * 6573 * @attr ref android.R.styleable#TextView_hint 6574 */ 6575 @android.view.RemotableViewMethod setHint(@tringRes int resid)6576 public final void setHint(@StringRes int resid) { 6577 mHintId = resid; 6578 setHint(getContext().getResources().getText(resid)); 6579 } 6580 6581 /** 6582 * Returns the hint that is displayed when the text of the TextView 6583 * is empty. 6584 * 6585 * @attr ref android.R.styleable#TextView_hint 6586 */ 6587 @InspectableProperty 6588 @ViewDebug.CapturedViewProperty getHint()6589 public CharSequence getHint() { 6590 return mHint; 6591 } 6592 6593 /** 6594 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6595 * line characters instead of letting it wrap onto multiple lines. 6596 * 6597 * @attr ref android.R.styleable#TextView_singleLine 6598 */ 6599 @InspectableProperty isSingleLine()6600 public boolean isSingleLine() { 6601 return mSingleLine; 6602 } 6603 isMultilineInputType(int type)6604 private static boolean isMultilineInputType(int type) { 6605 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6606 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6607 } 6608 6609 /** 6610 * Removes the suggestion spans. 6611 */ removeSuggestionSpans(CharSequence text)6612 CharSequence removeSuggestionSpans(CharSequence text) { 6613 if (text instanceof Spanned) { 6614 Spannable spannable; 6615 if (text instanceof Spannable) { 6616 spannable = (Spannable) text; 6617 } else { 6618 spannable = mSpannableFactory.newSpannable(text); 6619 } 6620 6621 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6622 if (spans.length == 0) { 6623 return text; 6624 } else { 6625 text = spannable; 6626 } 6627 6628 for (int i = 0; i < spans.length; i++) { 6629 spannable.removeSpan(spans[i]); 6630 } 6631 } 6632 return text; 6633 } 6634 6635 /** 6636 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6637 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6638 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6639 * then a soft keyboard will not be displayed for this text view. 6640 * 6641 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6642 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6643 * type. 6644 * 6645 * @see #getInputType() 6646 * @see #setRawInputType(int) 6647 * @see android.text.InputType 6648 * @attr ref android.R.styleable#TextView_inputType 6649 */ setInputType(int type)6650 public void setInputType(int type) { 6651 final boolean wasPassword = isPasswordInputType(getInputType()); 6652 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6653 setInputType(type, false); 6654 final boolean isPassword = isPasswordInputType(type); 6655 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6656 boolean forceUpdate = false; 6657 if (isPassword) { 6658 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6659 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6660 Typeface.NORMAL, -1 /* weight, not specifeid */); 6661 } else if (isVisiblePassword) { 6662 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6663 forceUpdate = true; 6664 } 6665 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6666 Typeface.NORMAL, -1 /* weight, not specified */); 6667 } else if (wasPassword || wasVisiblePassword) { 6668 // not in password mode, clean up typeface and transformation 6669 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6670 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6671 -1 /* weight, not specified */); 6672 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6673 forceUpdate = true; 6674 } 6675 } 6676 6677 boolean singleLine = !isMultilineInputType(type); 6678 6679 // We need to update the single line mode if it has changed or we 6680 // were previously in password mode. 6681 if (mSingleLine != singleLine || forceUpdate) { 6682 // Change single line mode, but only change the transformation if 6683 // we are not in password mode. 6684 applySingleLine(singleLine, !isPassword, true, true); 6685 } 6686 6687 if (!isSuggestionsEnabled()) { 6688 setTextInternal(removeSuggestionSpans(mText)); 6689 } 6690 6691 InputMethodManager imm = getInputMethodManager(); 6692 if (imm != null) imm.restartInput(this); 6693 } 6694 6695 /** 6696 * It would be better to rely on the input type for everything. A password inputType should have 6697 * a password transformation. We should hence use isPasswordInputType instead of this method. 6698 * 6699 * We should: 6700 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6701 * would install the correct transformation). 6702 * - Refuse the installation of a non-password transformation in setTransformation if the input 6703 * type is password. 6704 * 6705 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6706 * is useful since it matches what the user can see (obfuscated text or not). 6707 * 6708 * @return true if the current transformation method is of the password type. 6709 */ hasPasswordTransformationMethod()6710 boolean hasPasswordTransformationMethod() { 6711 return mTransformation instanceof PasswordTransformationMethod; 6712 } 6713 6714 /** 6715 * Returns true if the current inputType is any type of password. 6716 * 6717 * @hide 6718 */ isAnyPasswordInputType()6719 public boolean isAnyPasswordInputType() { 6720 final int inputType = getInputType(); 6721 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6722 } 6723 isPasswordInputType(int inputType)6724 static boolean isPasswordInputType(int inputType) { 6725 final int variation = 6726 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6727 return variation 6728 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6729 || variation 6730 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6731 || variation 6732 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6733 } 6734 isVisiblePasswordInputType(int inputType)6735 private static boolean isVisiblePasswordInputType(int inputType) { 6736 final int variation = 6737 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6738 return variation 6739 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6740 } 6741 6742 /** 6743 * Directly change the content type integer of the text view, without 6744 * modifying any other state. 6745 * @see #setInputType(int) 6746 * @see android.text.InputType 6747 * @attr ref android.R.styleable#TextView_inputType 6748 */ setRawInputType(int type)6749 public void setRawInputType(int type) { 6750 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6751 createEditorIfNeeded(); 6752 mEditor.mInputType = type; 6753 } 6754 6755 /** 6756 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6757 * a {@code Locale} object that can be used to customize key various listeners. 6758 * @see DateKeyListener#getInstance(Locale) 6759 * @see DateTimeKeyListener#getInstance(Locale) 6760 * @see DigitsKeyListener#getInstance(Locale) 6761 * @see TimeKeyListener#getInstance(Locale) 6762 */ 6763 @Nullable getCustomLocaleForKeyListenerOrNull()6764 private Locale getCustomLocaleForKeyListenerOrNull() { 6765 if (!mUseInternationalizedInput) { 6766 // If the application does not target O, stick to the previous behavior. 6767 return null; 6768 } 6769 final LocaleList locales = getImeHintLocales(); 6770 if (locales == null) { 6771 // If the application does not explicitly specify IME hint locale, also stick to the 6772 // previous behavior. 6773 return null; 6774 } 6775 return locales.get(0); 6776 } 6777 6778 @UnsupportedAppUsage setInputType(int type, boolean direct)6779 private void setInputType(int type, boolean direct) { 6780 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6781 KeyListener input; 6782 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6783 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6784 TextKeyListener.Capitalize cap; 6785 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6786 cap = TextKeyListener.Capitalize.CHARACTERS; 6787 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6788 cap = TextKeyListener.Capitalize.WORDS; 6789 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6790 cap = TextKeyListener.Capitalize.SENTENCES; 6791 } else { 6792 cap = TextKeyListener.Capitalize.NONE; 6793 } 6794 input = TextKeyListener.getInstance(autotext, cap); 6795 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6796 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6797 input = DigitsKeyListener.getInstance( 6798 locale, 6799 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6800 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6801 if (locale != null) { 6802 // Override type, if necessary for i18n. 6803 int newType = input.getInputType(); 6804 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6805 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6806 // The class is different from the original class. So we need to override 6807 // 'type'. But we want to keep the password flag if it's there. 6808 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6809 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6810 } 6811 type = newType; 6812 } 6813 } 6814 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6815 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6816 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6817 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6818 input = DateKeyListener.getInstance(locale); 6819 break; 6820 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6821 input = TimeKeyListener.getInstance(locale); 6822 break; 6823 default: 6824 input = DateTimeKeyListener.getInstance(locale); 6825 break; 6826 } 6827 if (mUseInternationalizedInput) { 6828 type = input.getInputType(); // Override type, if necessary for i18n. 6829 } 6830 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6831 input = DialerKeyListener.getInstance(); 6832 } else { 6833 input = TextKeyListener.getInstance(); 6834 } 6835 setRawInputType(type); 6836 mListenerChanged = false; 6837 if (direct) { 6838 createEditorIfNeeded(); 6839 mEditor.mKeyListener = input; 6840 } else { 6841 setKeyListenerOnly(input); 6842 } 6843 } 6844 6845 /** 6846 * Get the type of the editable content. 6847 * 6848 * @see #setInputType(int) 6849 * @see android.text.InputType 6850 */ 6851 @InspectableProperty(flagMapping = { 6852 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6853 @FlagEntry( 6854 name = "text", 6855 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6856 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6857 @FlagEntry( 6858 name = "textUri", 6859 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6860 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6861 @FlagEntry( 6862 name = "textEmailAddress", 6863 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6864 target = InputType.TYPE_CLASS_TEXT 6865 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6866 @FlagEntry( 6867 name = "textEmailSubject", 6868 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6869 target = InputType.TYPE_CLASS_TEXT 6870 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6871 @FlagEntry( 6872 name = "textShortMessage", 6873 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6874 target = InputType.TYPE_CLASS_TEXT 6875 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6876 @FlagEntry( 6877 name = "textLongMessage", 6878 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6879 target = InputType.TYPE_CLASS_TEXT 6880 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6881 @FlagEntry( 6882 name = "textPersonName", 6883 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6884 target = InputType.TYPE_CLASS_TEXT 6885 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6886 @FlagEntry( 6887 name = "textPostalAddress", 6888 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6889 target = InputType.TYPE_CLASS_TEXT 6890 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6891 @FlagEntry( 6892 name = "textPassword", 6893 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6894 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6895 @FlagEntry( 6896 name = "textVisiblePassword", 6897 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6898 target = InputType.TYPE_CLASS_TEXT 6899 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6900 @FlagEntry( 6901 name = "textWebEditText", 6902 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6903 target = InputType.TYPE_CLASS_TEXT 6904 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6905 @FlagEntry( 6906 name = "textFilter", 6907 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6908 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6909 @FlagEntry( 6910 name = "textPhonetic", 6911 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6912 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6913 @FlagEntry( 6914 name = "textWebEmailAddress", 6915 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6916 target = InputType.TYPE_CLASS_TEXT 6917 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6918 @FlagEntry( 6919 name = "textWebPassword", 6920 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6921 target = InputType.TYPE_CLASS_TEXT 6922 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6923 @FlagEntry( 6924 name = "number", 6925 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6926 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6927 @FlagEntry( 6928 name = "numberPassword", 6929 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6930 target = InputType.TYPE_CLASS_NUMBER 6931 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6932 @FlagEntry( 6933 name = "phone", 6934 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6935 target = InputType.TYPE_CLASS_PHONE), 6936 @FlagEntry( 6937 name = "datetime", 6938 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6939 target = InputType.TYPE_CLASS_DATETIME 6940 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6941 @FlagEntry( 6942 name = "date", 6943 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6944 target = InputType.TYPE_CLASS_DATETIME 6945 | InputType.TYPE_DATETIME_VARIATION_DATE), 6946 @FlagEntry( 6947 name = "time", 6948 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6949 target = InputType.TYPE_CLASS_DATETIME 6950 | InputType.TYPE_DATETIME_VARIATION_TIME), 6951 @FlagEntry( 6952 name = "textCapCharacters", 6953 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6954 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6955 @FlagEntry( 6956 name = "textCapWords", 6957 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6958 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6959 @FlagEntry( 6960 name = "textCapSentences", 6961 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6962 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6963 @FlagEntry( 6964 name = "textAutoCorrect", 6965 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6966 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6967 @FlagEntry( 6968 name = "textAutoComplete", 6969 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6970 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6971 @FlagEntry( 6972 name = "textMultiLine", 6973 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6974 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6975 @FlagEntry( 6976 name = "textImeMultiLine", 6977 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6978 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6979 @FlagEntry( 6980 name = "textNoSuggestions", 6981 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6982 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6983 @FlagEntry( 6984 name = "numberSigned", 6985 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6986 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6987 @FlagEntry( 6988 name = "numberDecimal", 6989 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6990 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6991 }) getInputType()6992 public int getInputType() { 6993 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6994 } 6995 6996 /** 6997 * Change the editor type integer associated with the text view, which 6998 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6999 * when it has focus. 7000 * @see #getImeOptions 7001 * @see android.view.inputmethod.EditorInfo 7002 * @attr ref android.R.styleable#TextView_imeOptions 7003 */ setImeOptions(int imeOptions)7004 public void setImeOptions(int imeOptions) { 7005 createEditorIfNeeded(); 7006 mEditor.createInputContentTypeIfNeeded(); 7007 mEditor.mInputContentType.imeOptions = imeOptions; 7008 } 7009 7010 /** 7011 * Get the type of the Input Method Editor (IME). 7012 * @return the type of the IME 7013 * @see #setImeOptions(int) 7014 * @see EditorInfo 7015 */ 7016 @InspectableProperty(flagMapping = { 7017 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 7018 @FlagEntry( 7019 name = "actionUnspecified", 7020 mask = EditorInfo.IME_MASK_ACTION, 7021 target = EditorInfo.IME_ACTION_UNSPECIFIED), 7022 @FlagEntry( 7023 name = "actionNone", 7024 mask = EditorInfo.IME_MASK_ACTION, 7025 target = EditorInfo.IME_ACTION_NONE), 7026 @FlagEntry( 7027 name = "actionGo", 7028 mask = EditorInfo.IME_MASK_ACTION, 7029 target = EditorInfo.IME_ACTION_GO), 7030 @FlagEntry( 7031 name = "actionSearch", 7032 mask = EditorInfo.IME_MASK_ACTION, 7033 target = EditorInfo.IME_ACTION_SEARCH), 7034 @FlagEntry( 7035 name = "actionSend", 7036 mask = EditorInfo.IME_MASK_ACTION, 7037 target = EditorInfo.IME_ACTION_SEND), 7038 @FlagEntry( 7039 name = "actionNext", 7040 mask = EditorInfo.IME_MASK_ACTION, 7041 target = EditorInfo.IME_ACTION_NEXT), 7042 @FlagEntry( 7043 name = "actionDone", 7044 mask = EditorInfo.IME_MASK_ACTION, 7045 target = EditorInfo.IME_ACTION_DONE), 7046 @FlagEntry( 7047 name = "actionPrevious", 7048 mask = EditorInfo.IME_MASK_ACTION, 7049 target = EditorInfo.IME_ACTION_PREVIOUS), 7050 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 7051 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 7052 @FlagEntry( 7053 name = "flagNavigatePrevious", 7054 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 7055 @FlagEntry( 7056 name = "flagNoAccessoryAction", 7057 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 7058 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 7059 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 7060 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 7061 @FlagEntry( 7062 name = "flagNoPersonalizedLearning", 7063 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 7064 }) getImeOptions()7065 public int getImeOptions() { 7066 return mEditor != null && mEditor.mInputContentType != null 7067 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 7068 } 7069 7070 /** 7071 * Change the custom IME action associated with the text view, which 7072 * will be reported to an IME with {@link EditorInfo#actionLabel} 7073 * and {@link EditorInfo#actionId} when it has focus. 7074 * @see #getImeActionLabel 7075 * @see #getImeActionId 7076 * @see android.view.inputmethod.EditorInfo 7077 * @attr ref android.R.styleable#TextView_imeActionLabel 7078 * @attr ref android.R.styleable#TextView_imeActionId 7079 */ setImeActionLabel(CharSequence label, int actionId)7080 public void setImeActionLabel(CharSequence label, int actionId) { 7081 createEditorIfNeeded(); 7082 mEditor.createInputContentTypeIfNeeded(); 7083 mEditor.mInputContentType.imeActionLabel = label; 7084 mEditor.mInputContentType.imeActionId = actionId; 7085 } 7086 7087 /** 7088 * Get the IME action label previous set with {@link #setImeActionLabel}. 7089 * 7090 * @see #setImeActionLabel 7091 * @see android.view.inputmethod.EditorInfo 7092 */ 7093 @InspectableProperty getImeActionLabel()7094 public CharSequence getImeActionLabel() { 7095 return mEditor != null && mEditor.mInputContentType != null 7096 ? mEditor.mInputContentType.imeActionLabel : null; 7097 } 7098 7099 /** 7100 * Get the IME action ID previous set with {@link #setImeActionLabel}. 7101 * 7102 * @see #setImeActionLabel 7103 * @see android.view.inputmethod.EditorInfo 7104 */ 7105 @InspectableProperty getImeActionId()7106 public int getImeActionId() { 7107 return mEditor != null && mEditor.mInputContentType != null 7108 ? mEditor.mInputContentType.imeActionId : 0; 7109 } 7110 7111 /** 7112 * Set a special listener to be called when an action is performed 7113 * on the text view. This will be called when the enter key is pressed, 7114 * or when an action supplied to the IME is selected by the user. Setting 7115 * this means that the normal hard key event will not insert a newline 7116 * into the text view, even if it is multi-line; holding down the ALT 7117 * modifier will, however, allow the user to insert a newline character. 7118 */ setOnEditorActionListener(OnEditorActionListener l)7119 public void setOnEditorActionListener(OnEditorActionListener l) { 7120 createEditorIfNeeded(); 7121 mEditor.createInputContentTypeIfNeeded(); 7122 mEditor.mInputContentType.onEditorActionListener = l; 7123 } 7124 7125 /** 7126 * Called when an attached input method calls 7127 * {@link InputConnection#performEditorAction(int) 7128 * InputConnection.performEditorAction()} 7129 * for this text view. The default implementation will call your action 7130 * listener supplied to {@link #setOnEditorActionListener}, or perform 7131 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7132 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7133 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7134 * EditorInfo.IME_ACTION_DONE}. 7135 * 7136 * <p>For backwards compatibility, if no IME options have been set and the 7137 * text view would not normally advance focus on enter, then 7138 * the NEXT and DONE actions received here will be turned into an enter 7139 * key down/up pair to go through the normal key handling. 7140 * 7141 * @param actionCode The code of the action being performed. 7142 * 7143 * @see #setOnEditorActionListener 7144 */ onEditorAction(int actionCode)7145 public void onEditorAction(int actionCode) { 7146 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7147 if (ict != null) { 7148 if (ict.onEditorActionListener != null) { 7149 if (ict.onEditorActionListener.onEditorAction(this, 7150 actionCode, null)) { 7151 return; 7152 } 7153 } 7154 7155 // This is the handling for some default action. 7156 // Note that for backwards compatibility we don't do this 7157 // default handling if explicit ime options have not been given, 7158 // instead turning this into the normal enter key codes that an 7159 // app may be expecting. 7160 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7161 View v = focusSearch(FOCUS_FORWARD); 7162 if (v != null) { 7163 if (!v.requestFocus(FOCUS_FORWARD)) { 7164 throw new IllegalStateException("focus search returned a view " 7165 + "that wasn't able to take focus!"); 7166 } 7167 } 7168 return; 7169 7170 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7171 View v = focusSearch(FOCUS_BACKWARD); 7172 if (v != null) { 7173 if (!v.requestFocus(FOCUS_BACKWARD)) { 7174 throw new IllegalStateException("focus search returned a view " 7175 + "that wasn't able to take focus!"); 7176 } 7177 } 7178 return; 7179 7180 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7181 InputMethodManager imm = getInputMethodManager(); 7182 if (imm != null && imm.isActive(this)) { 7183 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7184 } 7185 return; 7186 } 7187 } 7188 7189 ViewRootImpl viewRootImpl = getViewRootImpl(); 7190 if (viewRootImpl != null) { 7191 long eventTime = SystemClock.uptimeMillis(); 7192 viewRootImpl.dispatchKeyFromIme( 7193 new KeyEvent(eventTime, eventTime, 7194 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7195 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7196 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7197 | KeyEvent.FLAG_EDITOR_ACTION)); 7198 viewRootImpl.dispatchKeyFromIme( 7199 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7200 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7201 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7202 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7203 | KeyEvent.FLAG_EDITOR_ACTION)); 7204 } 7205 } 7206 7207 /** 7208 * Set the private content type of the text, which is the 7209 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7210 * field that will be filled in when creating an input connection. 7211 * 7212 * @see #getPrivateImeOptions() 7213 * @see EditorInfo#privateImeOptions 7214 * @attr ref android.R.styleable#TextView_privateImeOptions 7215 */ setPrivateImeOptions(String type)7216 public void setPrivateImeOptions(String type) { 7217 createEditorIfNeeded(); 7218 mEditor.createInputContentTypeIfNeeded(); 7219 mEditor.mInputContentType.privateImeOptions = type; 7220 } 7221 7222 /** 7223 * Get the private type of the content. 7224 * 7225 * @see #setPrivateImeOptions(String) 7226 * @see EditorInfo#privateImeOptions 7227 */ 7228 @InspectableProperty getPrivateImeOptions()7229 public String getPrivateImeOptions() { 7230 return mEditor != null && mEditor.mInputContentType != null 7231 ? mEditor.mInputContentType.privateImeOptions : null; 7232 } 7233 7234 /** 7235 * Set the extra input data of the text, which is the 7236 * {@link EditorInfo#extras TextBoxAttribute.extras} 7237 * Bundle that will be filled in when creating an input connection. The 7238 * given integer is the resource identifier of an XML resource holding an 7239 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7240 * 7241 * @see #getInputExtras(boolean) 7242 * @see EditorInfo#extras 7243 * @attr ref android.R.styleable#TextView_editorExtras 7244 */ setInputExtras(@mlRes int xmlResId)7245 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7246 createEditorIfNeeded(); 7247 XmlResourceParser parser = getResources().getXml(xmlResId); 7248 mEditor.createInputContentTypeIfNeeded(); 7249 mEditor.mInputContentType.extras = new Bundle(); 7250 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7251 } 7252 7253 /** 7254 * Retrieve the input extras currently associated with the text view, which 7255 * can be viewed as well as modified. 7256 * 7257 * @param create If true, the extras will be created if they don't already 7258 * exist. Otherwise, null will be returned if none have been created. 7259 * @see #setInputExtras(int) 7260 * @see EditorInfo#extras 7261 * @attr ref android.R.styleable#TextView_editorExtras 7262 */ getInputExtras(boolean create)7263 public Bundle getInputExtras(boolean create) { 7264 if (mEditor == null && !create) return null; 7265 createEditorIfNeeded(); 7266 if (mEditor.mInputContentType == null) { 7267 if (!create) return null; 7268 mEditor.createInputContentTypeIfNeeded(); 7269 } 7270 if (mEditor.mInputContentType.extras == null) { 7271 if (!create) return null; 7272 mEditor.mInputContentType.extras = new Bundle(); 7273 } 7274 return mEditor.mInputContentType.extras; 7275 } 7276 7277 /** 7278 * Change "hint" locales associated with the text view, which will be reported to an IME with 7279 * {@link EditorInfo#hintLocales} when it has focus. 7280 * 7281 * Starting with Android O, this also causes internationalized listeners to be created (or 7282 * change locale) based on the first locale in the input locale list. 7283 * 7284 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7285 * call {@link InputMethodManager#restartInput(View)}.</p> 7286 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7287 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7288 * @see #getImeHintLocales() 7289 * @see android.view.inputmethod.EditorInfo#hintLocales 7290 */ setImeHintLocales(@ullable LocaleList hintLocales)7291 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7292 createEditorIfNeeded(); 7293 mEditor.createInputContentTypeIfNeeded(); 7294 mEditor.mInputContentType.imeHintLocales = hintLocales; 7295 if (mUseInternationalizedInput) { 7296 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7297 } 7298 } 7299 7300 /** 7301 * @return The current languages list "hint". {@code null} when no "hint" is available. 7302 * @see #setImeHintLocales(LocaleList) 7303 * @see android.view.inputmethod.EditorInfo#hintLocales 7304 */ 7305 @Nullable getImeHintLocales()7306 public LocaleList getImeHintLocales() { 7307 if (mEditor == null) { 7308 return null; 7309 } 7310 if (mEditor.mInputContentType == null) { 7311 return null; 7312 } 7313 return mEditor.mInputContentType.imeHintLocales; 7314 } 7315 7316 /** 7317 * Returns the error message that was set to be displayed with 7318 * {@link #setError}, or <code>null</code> if no error was set 7319 * or if it the error was cleared by the widget after user input. 7320 */ getError()7321 public CharSequence getError() { 7322 return mEditor == null ? null : mEditor.mError; 7323 } 7324 7325 /** 7326 * Sets the right-hand compound drawable of the TextView to the "error" 7327 * icon and sets an error message that will be displayed in a popup when 7328 * the TextView has focus. The icon and error message will be reset to 7329 * null when any key events cause changes to the TextView's text. If the 7330 * <code>error</code> is <code>null</code>, the error message and icon 7331 * will be cleared. 7332 */ 7333 @android.view.RemotableViewMethod setError(CharSequence error)7334 public void setError(CharSequence error) { 7335 if (error == null) { 7336 setError(null, null); 7337 } else { 7338 Drawable dr = getContext().getDrawable( 7339 com.android.internal.R.drawable.indicator_input_error); 7340 7341 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7342 setError(error, dr); 7343 } 7344 } 7345 7346 /** 7347 * Sets the right-hand compound drawable of the TextView to the specified 7348 * icon and sets an error message that will be displayed in a popup when 7349 * the TextView has focus. The icon and error message will be reset to 7350 * null when any key events cause changes to the TextView's text. The 7351 * drawable must already have had {@link Drawable#setBounds} set on it. 7352 * If the <code>error</code> is <code>null</code>, the error message will 7353 * be cleared (and you should provide a <code>null</code> icon as well). 7354 */ setError(CharSequence error, Drawable icon)7355 public void setError(CharSequence error, Drawable icon) { 7356 createEditorIfNeeded(); 7357 mEditor.setError(error, icon); 7358 notifyViewAccessibilityStateChangedIfNeeded( 7359 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7360 } 7361 7362 @Override setFrame(int l, int t, int r, int b)7363 protected boolean setFrame(int l, int t, int r, int b) { 7364 boolean result = super.setFrame(l, t, r, b); 7365 7366 if (mEditor != null) mEditor.setFrame(); 7367 7368 restartMarqueeIfNeeded(); 7369 7370 return result; 7371 } 7372 restartMarqueeIfNeeded()7373 private void restartMarqueeIfNeeded() { 7374 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7375 mRestartMarquee = false; 7376 startMarquee(); 7377 } 7378 } 7379 7380 /** 7381 * Sets the list of input filters that will be used if the buffer is 7382 * Editable. Has no effect otherwise. 7383 * 7384 * @attr ref android.R.styleable#TextView_maxLength 7385 */ setFilters(InputFilter[] filters)7386 public void setFilters(InputFilter[] filters) { 7387 if (filters == null) { 7388 throw new IllegalArgumentException(); 7389 } 7390 7391 mFilters = filters; 7392 7393 if (mText instanceof Editable) { 7394 setFilters((Editable) mText, filters); 7395 } 7396 } 7397 7398 /** 7399 * Sets the list of input filters on the specified Editable, 7400 * and includes mInput in the list if it is an InputFilter. 7401 */ setFilters(Editable e, InputFilter[] filters)7402 private void setFilters(Editable e, InputFilter[] filters) { 7403 if (mEditor != null) { 7404 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7405 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7406 int num = 0; 7407 if (undoFilter) num++; 7408 if (keyFilter) num++; 7409 if (num > 0) { 7410 InputFilter[] nf = new InputFilter[filters.length + num]; 7411 7412 System.arraycopy(filters, 0, nf, 0, filters.length); 7413 num = 0; 7414 if (undoFilter) { 7415 nf[filters.length] = mEditor.mUndoInputFilter; 7416 num++; 7417 } 7418 if (keyFilter) { 7419 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7420 } 7421 7422 e.setFilters(nf); 7423 return; 7424 } 7425 } 7426 e.setFilters(filters); 7427 } 7428 7429 /** 7430 * Returns the current list of input filters. 7431 * 7432 * @attr ref android.R.styleable#TextView_maxLength 7433 */ getFilters()7434 public InputFilter[] getFilters() { 7435 return mFilters; 7436 } 7437 7438 ///////////////////////////////////////////////////////////////////////// 7439 getBoxHeight(Layout l)7440 private int getBoxHeight(Layout l) { 7441 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7442 int padding = (l == mHintLayout) 7443 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7444 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7445 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7446 } 7447 7448 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7449 int getVerticalOffset(boolean forceNormal) { 7450 int voffset = 0; 7451 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7452 7453 Layout l = mLayout; 7454 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7455 l = mHintLayout; 7456 } 7457 7458 if (gravity != Gravity.TOP) { 7459 int boxht = getBoxHeight(l); 7460 int textht = l.getHeight(); 7461 7462 if (textht < boxht) { 7463 if (gravity == Gravity.BOTTOM) { 7464 voffset = boxht - textht; 7465 } else { // (gravity == Gravity.CENTER_VERTICAL) 7466 voffset = (boxht - textht) >> 1; 7467 } 7468 } 7469 } 7470 return voffset; 7471 } 7472 getBottomVerticalOffset(boolean forceNormal)7473 private int getBottomVerticalOffset(boolean forceNormal) { 7474 int voffset = 0; 7475 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7476 7477 Layout l = mLayout; 7478 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7479 l = mHintLayout; 7480 } 7481 7482 if (gravity != Gravity.BOTTOM) { 7483 int boxht = getBoxHeight(l); 7484 int textht = l.getHeight(); 7485 7486 if (textht < boxht) { 7487 if (gravity == Gravity.TOP) { 7488 voffset = boxht - textht; 7489 } else { // (gravity == Gravity.CENTER_VERTICAL) 7490 voffset = (boxht - textht) >> 1; 7491 } 7492 } 7493 } 7494 return voffset; 7495 } 7496 invalidateCursorPath()7497 void invalidateCursorPath() { 7498 if (mHighlightPathBogus) { 7499 invalidateCursor(); 7500 } else { 7501 final int horizontalPadding = getCompoundPaddingLeft(); 7502 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7503 7504 if (mEditor.mDrawableForCursor == null) { 7505 synchronized (TEMP_RECTF) { 7506 /* 7507 * The reason for this concern about the thickness of the 7508 * cursor and doing the floor/ceil on the coordinates is that 7509 * some EditTexts (notably textfields in the Browser) have 7510 * anti-aliased text where not all the characters are 7511 * necessarily at integer-multiple locations. This should 7512 * make sure the entire cursor gets invalidated instead of 7513 * sometimes missing half a pixel. 7514 */ 7515 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7516 if (thick < 1.0f) { 7517 thick = 1.0f; 7518 } 7519 7520 thick /= 2.0f; 7521 7522 // mHighlightPath is guaranteed to be non null at that point. 7523 mHighlightPath.computeBounds(TEMP_RECTF, false); 7524 7525 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7526 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7527 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7528 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7529 } 7530 } else { 7531 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7532 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7533 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7534 } 7535 } 7536 } 7537 invalidateCursor()7538 void invalidateCursor() { 7539 int where = getSelectionEnd(); 7540 7541 invalidateCursor(where, where, where); 7542 } 7543 invalidateCursor(int a, int b, int c)7544 private void invalidateCursor(int a, int b, int c) { 7545 if (a >= 0 || b >= 0 || c >= 0) { 7546 int start = Math.min(Math.min(a, b), c); 7547 int end = Math.max(Math.max(a, b), c); 7548 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7549 } 7550 } 7551 7552 /** 7553 * Invalidates the region of text enclosed between the start and end text offsets. 7554 */ invalidateRegion(int start, int end, boolean invalidateCursor)7555 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7556 if (mLayout == null) { 7557 invalidate(); 7558 } else { 7559 int lineStart = mLayout.getLineForOffset(start); 7560 int top = mLayout.getLineTop(lineStart); 7561 7562 // This is ridiculous, but the descent from the line above 7563 // can hang down into the line we really want to redraw, 7564 // so we have to invalidate part of the line above to make 7565 // sure everything that needs to be redrawn really is. 7566 // (But not the whole line above, because that would cause 7567 // the same problem with the descenders on the line above it!) 7568 if (lineStart > 0) { 7569 top -= mLayout.getLineDescent(lineStart - 1); 7570 } 7571 7572 int lineEnd; 7573 7574 if (start == end) { 7575 lineEnd = lineStart; 7576 } else { 7577 lineEnd = mLayout.getLineForOffset(end); 7578 } 7579 7580 int bottom = mLayout.getLineBottom(lineEnd); 7581 7582 // mEditor can be null in case selection is set programmatically. 7583 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7584 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7585 top = Math.min(top, bounds.top); 7586 bottom = Math.max(bottom, bounds.bottom); 7587 } 7588 7589 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7590 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7591 7592 int left, right; 7593 if (lineStart == lineEnd && !invalidateCursor) { 7594 left = (int) mLayout.getPrimaryHorizontal(start); 7595 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7596 left += compoundPaddingLeft; 7597 right += compoundPaddingLeft; 7598 } else { 7599 // Rectangle bounding box when the region spans several lines 7600 left = compoundPaddingLeft; 7601 right = getWidth() - getCompoundPaddingRight(); 7602 } 7603 7604 invalidate(mScrollX + left, verticalPadding + top, 7605 mScrollX + right, verticalPadding + bottom); 7606 } 7607 } 7608 registerForPreDraw()7609 private void registerForPreDraw() { 7610 if (!mPreDrawRegistered) { 7611 getViewTreeObserver().addOnPreDrawListener(this); 7612 mPreDrawRegistered = true; 7613 } 7614 } 7615 unregisterForPreDraw()7616 private void unregisterForPreDraw() { 7617 getViewTreeObserver().removeOnPreDrawListener(this); 7618 mPreDrawRegistered = false; 7619 mPreDrawListenerDetached = false; 7620 } 7621 7622 /** 7623 * {@inheritDoc} 7624 */ 7625 @Override onPreDraw()7626 public boolean onPreDraw() { 7627 if (mLayout == null) { 7628 assumeLayout(); 7629 } 7630 7631 if (mMovement != null) { 7632 /* This code also provides auto-scrolling when a cursor is moved using a 7633 * CursorController (insertion point or selection limits). 7634 * For selection, ensure start or end is visible depending on controller's state. 7635 */ 7636 int curs = getSelectionEnd(); 7637 // Do not create the controller if it is not already created. 7638 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7639 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7640 curs = getSelectionStart(); 7641 } 7642 7643 /* 7644 * TODO: This should really only keep the end in view if 7645 * it already was before the text changed. I'm not sure 7646 * of a good way to tell from here if it was. 7647 */ 7648 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7649 curs = mText.length(); 7650 } 7651 7652 if (curs >= 0) { 7653 bringPointIntoView(curs); 7654 } 7655 } else { 7656 bringTextIntoView(); 7657 } 7658 7659 // This has to be checked here since: 7660 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7661 // a screen rotation) since layout is not yet initialized at that point. 7662 if (mEditor != null && mEditor.mCreatedWithASelection) { 7663 mEditor.refreshTextActionMode(); 7664 mEditor.mCreatedWithASelection = false; 7665 } 7666 7667 unregisterForPreDraw(); 7668 7669 return true; 7670 } 7671 7672 @Override onAttachedToWindow()7673 protected void onAttachedToWindow() { 7674 super.onAttachedToWindow(); 7675 7676 if (mEditor != null) mEditor.onAttachedToWindow(); 7677 7678 if (mPreDrawListenerDetached) { 7679 getViewTreeObserver().addOnPreDrawListener(this); 7680 mPreDrawListenerDetached = false; 7681 } 7682 } 7683 7684 /** @hide */ 7685 @Override onDetachedFromWindowInternal()7686 protected void onDetachedFromWindowInternal() { 7687 if (mPreDrawRegistered) { 7688 getViewTreeObserver().removeOnPreDrawListener(this); 7689 mPreDrawListenerDetached = true; 7690 } 7691 7692 resetResolvedDrawables(); 7693 7694 if (mEditor != null) mEditor.onDetachedFromWindow(); 7695 7696 super.onDetachedFromWindowInternal(); 7697 } 7698 7699 @Override onScreenStateChanged(int screenState)7700 public void onScreenStateChanged(int screenState) { 7701 super.onScreenStateChanged(screenState); 7702 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7703 } 7704 7705 @Override isPaddingOffsetRequired()7706 protected boolean isPaddingOffsetRequired() { 7707 return mShadowRadius != 0 || mDrawables != null; 7708 } 7709 7710 @Override getLeftPaddingOffset()7711 protected int getLeftPaddingOffset() { 7712 return getCompoundPaddingLeft() - mPaddingLeft 7713 + (int) Math.min(0, mShadowDx - mShadowRadius); 7714 } 7715 7716 @Override getTopPaddingOffset()7717 protected int getTopPaddingOffset() { 7718 return (int) Math.min(0, mShadowDy - mShadowRadius); 7719 } 7720 7721 @Override getBottomPaddingOffset()7722 protected int getBottomPaddingOffset() { 7723 return (int) Math.max(0, mShadowDy + mShadowRadius); 7724 } 7725 7726 @Override getRightPaddingOffset()7727 protected int getRightPaddingOffset() { 7728 return -(getCompoundPaddingRight() - mPaddingRight) 7729 + (int) Math.max(0, mShadowDx + mShadowRadius); 7730 } 7731 7732 @Override verifyDrawable(@onNull Drawable who)7733 protected boolean verifyDrawable(@NonNull Drawable who) { 7734 final boolean verified = super.verifyDrawable(who); 7735 if (!verified && mDrawables != null) { 7736 for (Drawable dr : mDrawables.mShowing) { 7737 if (who == dr) { 7738 return true; 7739 } 7740 } 7741 } 7742 return verified; 7743 } 7744 7745 @Override jumpDrawablesToCurrentState()7746 public void jumpDrawablesToCurrentState() { 7747 super.jumpDrawablesToCurrentState(); 7748 if (mDrawables != null) { 7749 for (Drawable dr : mDrawables.mShowing) { 7750 if (dr != null) { 7751 dr.jumpToCurrentState(); 7752 } 7753 } 7754 } 7755 } 7756 7757 @Override invalidateDrawable(@onNull Drawable drawable)7758 public void invalidateDrawable(@NonNull Drawable drawable) { 7759 boolean handled = false; 7760 7761 if (verifyDrawable(drawable)) { 7762 final Rect dirty = drawable.getBounds(); 7763 int scrollX = mScrollX; 7764 int scrollY = mScrollY; 7765 7766 // IMPORTANT: The coordinates below are based on the coordinates computed 7767 // for each compound drawable in onDraw(). Make sure to update each section 7768 // accordingly. 7769 final TextView.Drawables drawables = mDrawables; 7770 if (drawables != null) { 7771 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7772 final int compoundPaddingTop = getCompoundPaddingTop(); 7773 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7774 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7775 7776 scrollX += mPaddingLeft; 7777 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7778 handled = true; 7779 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7780 final int compoundPaddingTop = getCompoundPaddingTop(); 7781 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7782 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7783 7784 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7785 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7786 handled = true; 7787 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7788 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7789 final int compoundPaddingRight = getCompoundPaddingRight(); 7790 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7791 7792 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7793 scrollY += mPaddingTop; 7794 handled = true; 7795 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7796 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7797 final int compoundPaddingRight = getCompoundPaddingRight(); 7798 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7799 7800 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7801 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7802 handled = true; 7803 } 7804 } 7805 7806 if (handled) { 7807 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7808 dirty.right + scrollX, dirty.bottom + scrollY); 7809 } 7810 } 7811 7812 if (!handled) { 7813 super.invalidateDrawable(drawable); 7814 } 7815 } 7816 7817 @Override hasOverlappingRendering()7818 public boolean hasOverlappingRendering() { 7819 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7820 return ((getBackground() != null && getBackground().getCurrent() != null) 7821 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7822 || mShadowColor != 0); 7823 } 7824 7825 /** 7826 * 7827 * Returns the state of the {@code textIsSelectable} flag (See 7828 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7829 * to allow users to select and copy text in a non-editable TextView, the content of an 7830 * {@link EditText} can always be selected, independently of the value of this flag. 7831 * <p> 7832 * 7833 * @return True if the text displayed in this TextView can be selected by the user. 7834 * 7835 * @attr ref android.R.styleable#TextView_textIsSelectable 7836 */ 7837 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7838 public boolean isTextSelectable() { 7839 return mEditor == null ? false : mEditor.mTextIsSelectable; 7840 } 7841 7842 /** 7843 * Sets whether the content of this view is selectable by the user. The default is 7844 * {@code false}, meaning that the content is not selectable. 7845 * <p> 7846 * When you use a TextView to display a useful piece of information to the user (such as a 7847 * contact's address), make it selectable, so that the user can select and copy its 7848 * content. You can also use set the XML attribute 7849 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7850 * <p> 7851 * When you call this method to set the value of {@code textIsSelectable}, it sets 7852 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7853 * and {@code longClickable} to the same value. These flags correspond to the attributes 7854 * {@link android.R.styleable#View_focusable android:focusable}, 7855 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7856 * {@link android.R.styleable#View_clickable android:clickable}, and 7857 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7858 * flags to a state you had set previously, call one or more of the following methods: 7859 * {@link #setFocusable(boolean) setFocusable()}, 7860 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7861 * {@link #setClickable(boolean) setClickable()} or 7862 * {@link #setLongClickable(boolean) setLongClickable()}. 7863 * 7864 * @param selectable Whether the content of this TextView should be selectable. 7865 */ setTextIsSelectable(boolean selectable)7866 public void setTextIsSelectable(boolean selectable) { 7867 if (!selectable && mEditor == null) return; // false is default value with no edit data 7868 7869 createEditorIfNeeded(); 7870 if (mEditor.mTextIsSelectable == selectable) return; 7871 7872 mEditor.mTextIsSelectable = selectable; 7873 setFocusableInTouchMode(selectable); 7874 setFocusable(FOCUSABLE_AUTO); 7875 setClickable(selectable); 7876 setLongClickable(selectable); 7877 7878 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7879 7880 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7881 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7882 7883 // Called by setText above, but safer in case of future code changes 7884 mEditor.prepareCursorControllers(); 7885 } 7886 7887 @Override onCreateDrawableState(int extraSpace)7888 protected int[] onCreateDrawableState(int extraSpace) { 7889 final int[] drawableState; 7890 7891 if (mSingleLine) { 7892 drawableState = super.onCreateDrawableState(extraSpace); 7893 } else { 7894 drawableState = super.onCreateDrawableState(extraSpace + 1); 7895 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7896 } 7897 7898 if (isTextSelectable()) { 7899 // Disable pressed state, which was introduced when TextView was made clickable. 7900 // Prevents text color change. 7901 // setClickable(false) would have a similar effect, but it also disables focus changes 7902 // and long press actions, which are both needed by text selection. 7903 final int length = drawableState.length; 7904 for (int i = 0; i < length; i++) { 7905 if (drawableState[i] == R.attr.state_pressed) { 7906 final int[] nonPressedState = new int[length - 1]; 7907 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7908 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7909 return nonPressedState; 7910 } 7911 } 7912 } 7913 7914 return drawableState; 7915 } 7916 7917 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getUpdatedHighlightPath()7918 private Path getUpdatedHighlightPath() { 7919 Path highlight = null; 7920 Paint highlightPaint = mHighlightPaint; 7921 7922 final int selStart = getSelectionStart(); 7923 final int selEnd = getSelectionEnd(); 7924 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7925 if (selStart == selEnd) { 7926 if (mEditor != null && mEditor.shouldRenderCursor()) { 7927 if (mHighlightPathBogus) { 7928 if (mHighlightPath == null) mHighlightPath = new Path(); 7929 mHighlightPath.reset(); 7930 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7931 mEditor.updateCursorPosition(); 7932 mHighlightPathBogus = false; 7933 } 7934 7935 // XXX should pass to skin instead of drawing directly 7936 highlightPaint.setColor(mCurTextColor); 7937 highlightPaint.setStyle(Paint.Style.STROKE); 7938 highlight = mHighlightPath; 7939 } 7940 } else { 7941 if (mHighlightPathBogus) { 7942 if (mHighlightPath == null) mHighlightPath = new Path(); 7943 mHighlightPath.reset(); 7944 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7945 mHighlightPathBogus = false; 7946 } 7947 7948 // XXX should pass to skin instead of drawing directly 7949 highlightPaint.setColor(mHighlightColor); 7950 highlightPaint.setStyle(Paint.Style.FILL); 7951 7952 highlight = mHighlightPath; 7953 } 7954 } 7955 return highlight; 7956 } 7957 7958 /** 7959 * @hide 7960 */ getHorizontalOffsetForDrawables()7961 public int getHorizontalOffsetForDrawables() { 7962 return 0; 7963 } 7964 7965 @Override onDraw(Canvas canvas)7966 protected void onDraw(Canvas canvas) { 7967 restartMarqueeIfNeeded(); 7968 7969 // Draw the background for this view 7970 super.onDraw(canvas); 7971 7972 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7973 final int compoundPaddingTop = getCompoundPaddingTop(); 7974 final int compoundPaddingRight = getCompoundPaddingRight(); 7975 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7976 final int scrollX = mScrollX; 7977 final int scrollY = mScrollY; 7978 final int right = mRight; 7979 final int left = mLeft; 7980 final int bottom = mBottom; 7981 final int top = mTop; 7982 final boolean isLayoutRtl = isLayoutRtl(); 7983 final int offset = getHorizontalOffsetForDrawables(); 7984 final int leftOffset = isLayoutRtl ? 0 : offset; 7985 final int rightOffset = isLayoutRtl ? offset : 0; 7986 7987 final Drawables dr = mDrawables; 7988 if (dr != null) { 7989 /* 7990 * Compound, not extended, because the icon is not clipped 7991 * if the text height is smaller. 7992 */ 7993 7994 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7995 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7996 7997 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7998 // Make sure to update invalidateDrawable() when changing this code. 7999 if (dr.mShowing[Drawables.LEFT] != null) { 8000 canvas.save(); 8001 canvas.translate(scrollX + mPaddingLeft + leftOffset, 8002 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 8003 dr.mShowing[Drawables.LEFT].draw(canvas); 8004 canvas.restore(); 8005 } 8006 8007 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8008 // Make sure to update invalidateDrawable() when changing this code. 8009 if (dr.mShowing[Drawables.RIGHT] != null) { 8010 canvas.save(); 8011 canvas.translate(scrollX + right - left - mPaddingRight 8012 - dr.mDrawableSizeRight - rightOffset, 8013 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 8014 dr.mShowing[Drawables.RIGHT].draw(canvas); 8015 canvas.restore(); 8016 } 8017 8018 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8019 // Make sure to update invalidateDrawable() when changing this code. 8020 if (dr.mShowing[Drawables.TOP] != null) { 8021 canvas.save(); 8022 canvas.translate(scrollX + compoundPaddingLeft 8023 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 8024 dr.mShowing[Drawables.TOP].draw(canvas); 8025 canvas.restore(); 8026 } 8027 8028 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8029 // Make sure to update invalidateDrawable() when changing this code. 8030 if (dr.mShowing[Drawables.BOTTOM] != null) { 8031 canvas.save(); 8032 canvas.translate(scrollX + compoundPaddingLeft 8033 + (hspace - dr.mDrawableWidthBottom) / 2, 8034 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 8035 dr.mShowing[Drawables.BOTTOM].draw(canvas); 8036 canvas.restore(); 8037 } 8038 } 8039 8040 int color = mCurTextColor; 8041 8042 if (mLayout == null) { 8043 assumeLayout(); 8044 } 8045 8046 Layout layout = mLayout; 8047 8048 if (mHint != null && mText.length() == 0) { 8049 if (mHintTextColor != null) { 8050 color = mCurHintTextColor; 8051 } 8052 8053 layout = mHintLayout; 8054 } 8055 8056 mTextPaint.setColor(color); 8057 mTextPaint.drawableState = getDrawableState(); 8058 8059 canvas.save(); 8060 /* Would be faster if we didn't have to do this. Can we chop the 8061 (displayable) text so that we don't need to do this ever? 8062 */ 8063 8064 int extendedPaddingTop = getExtendedPaddingTop(); 8065 int extendedPaddingBottom = getExtendedPaddingBottom(); 8066 8067 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8068 final int maxScrollY = mLayout.getHeight() - vspace; 8069 8070 float clipLeft = compoundPaddingLeft + scrollX; 8071 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 8072 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 8073 float clipBottom = bottom - top + scrollY 8074 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 8075 8076 if (mShadowRadius != 0) { 8077 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 8078 clipRight += Math.max(0, mShadowDx + mShadowRadius); 8079 8080 clipTop += Math.min(0, mShadowDy - mShadowRadius); 8081 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 8082 } 8083 8084 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 8085 8086 int voffsetText = 0; 8087 int voffsetCursor = 0; 8088 8089 // translate in by our padding 8090 /* shortcircuit calling getVerticaOffset() */ 8091 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8092 voffsetText = getVerticalOffset(false); 8093 voffsetCursor = getVerticalOffset(true); 8094 } 8095 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 8096 8097 final int layoutDirection = getLayoutDirection(); 8098 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8099 if (isMarqueeFadeEnabled()) { 8100 if (!mSingleLine && getLineCount() == 1 && canMarquee() 8101 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 8102 final int width = mRight - mLeft; 8103 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 8104 final float dx = mLayout.getLineRight(0) - (width - padding); 8105 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8106 } 8107 8108 if (mMarquee != null && mMarquee.isRunning()) { 8109 final float dx = -mMarquee.getScroll(); 8110 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8111 } 8112 } 8113 8114 final int cursorOffsetVertical = voffsetCursor - voffsetText; 8115 8116 Path highlight = getUpdatedHighlightPath(); 8117 if (mEditor != null) { 8118 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 8119 } else { 8120 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8121 } 8122 8123 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 8124 final float dx = mMarquee.getGhostOffset(); 8125 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8126 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8127 } 8128 8129 canvas.restore(); 8130 } 8131 8132 @Override getFocusedRect(Rect r)8133 public void getFocusedRect(Rect r) { 8134 if (mLayout == null) { 8135 super.getFocusedRect(r); 8136 return; 8137 } 8138 8139 int selEnd = getSelectionEnd(); 8140 if (selEnd < 0) { 8141 super.getFocusedRect(r); 8142 return; 8143 } 8144 8145 int selStart = getSelectionStart(); 8146 if (selStart < 0 || selStart >= selEnd) { 8147 int line = mLayout.getLineForOffset(selEnd); 8148 r.top = mLayout.getLineTop(line); 8149 r.bottom = mLayout.getLineBottom(line); 8150 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8151 r.right = r.left + 4; 8152 } else { 8153 int lineStart = mLayout.getLineForOffset(selStart); 8154 int lineEnd = mLayout.getLineForOffset(selEnd); 8155 r.top = mLayout.getLineTop(lineStart); 8156 r.bottom = mLayout.getLineBottom(lineEnd); 8157 if (lineStart == lineEnd) { 8158 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8159 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8160 } else { 8161 // Selection extends across multiple lines -- make the focused 8162 // rect cover the entire width. 8163 if (mHighlightPathBogus) { 8164 if (mHighlightPath == null) mHighlightPath = new Path(); 8165 mHighlightPath.reset(); 8166 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8167 mHighlightPathBogus = false; 8168 } 8169 synchronized (TEMP_RECTF) { 8170 mHighlightPath.computeBounds(TEMP_RECTF, true); 8171 r.left = (int) TEMP_RECTF.left - 1; 8172 r.right = (int) TEMP_RECTF.right + 1; 8173 } 8174 } 8175 } 8176 8177 // Adjust for padding and gravity. 8178 int paddingLeft = getCompoundPaddingLeft(); 8179 int paddingTop = getExtendedPaddingTop(); 8180 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8181 paddingTop += getVerticalOffset(false); 8182 } 8183 r.offset(paddingLeft, paddingTop); 8184 int paddingBottom = getExtendedPaddingBottom(); 8185 r.bottom += paddingBottom; 8186 } 8187 8188 /** 8189 * Return the number of lines of text, or 0 if the internal Layout has not 8190 * been built. 8191 */ getLineCount()8192 public int getLineCount() { 8193 return mLayout != null ? mLayout.getLineCount() : 0; 8194 } 8195 8196 /** 8197 * Return the baseline for the specified line (0...getLineCount() - 1) 8198 * If bounds is not null, return the top, left, right, bottom extents 8199 * of the specified line in it. If the internal Layout has not been built, 8200 * return 0 and set bounds to (0, 0, 0, 0) 8201 * @param line which line to examine (0..getLineCount() - 1) 8202 * @param bounds Optional. If not null, it returns the extent of the line 8203 * @return the Y-coordinate of the baseline 8204 */ getLineBounds(int line, Rect bounds)8205 public int getLineBounds(int line, Rect bounds) { 8206 if (mLayout == null) { 8207 if (bounds != null) { 8208 bounds.set(0, 0, 0, 0); 8209 } 8210 return 0; 8211 } else { 8212 int baseline = mLayout.getLineBounds(line, bounds); 8213 8214 int voffset = getExtendedPaddingTop(); 8215 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8216 voffset += getVerticalOffset(true); 8217 } 8218 if (bounds != null) { 8219 bounds.offset(getCompoundPaddingLeft(), voffset); 8220 } 8221 return baseline + voffset; 8222 } 8223 } 8224 8225 @Override getBaseline()8226 public int getBaseline() { 8227 if (mLayout == null) { 8228 return super.getBaseline(); 8229 } 8230 8231 return getBaselineOffset() + mLayout.getLineBaseline(0); 8232 } 8233 getBaselineOffset()8234 int getBaselineOffset() { 8235 int voffset = 0; 8236 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8237 voffset = getVerticalOffset(true); 8238 } 8239 8240 if (isLayoutModeOptical(mParent)) { 8241 voffset -= getOpticalInsets().top; 8242 } 8243 8244 return getExtendedPaddingTop() + voffset; 8245 } 8246 8247 /** 8248 * @hide 8249 */ 8250 @Override getFadeTop(boolean offsetRequired)8251 protected int getFadeTop(boolean offsetRequired) { 8252 if (mLayout == null) return 0; 8253 8254 int voffset = 0; 8255 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8256 voffset = getVerticalOffset(true); 8257 } 8258 8259 if (offsetRequired) voffset += getTopPaddingOffset(); 8260 8261 return getExtendedPaddingTop() + voffset; 8262 } 8263 8264 /** 8265 * @hide 8266 */ 8267 @Override getFadeHeight(boolean offsetRequired)8268 protected int getFadeHeight(boolean offsetRequired) { 8269 return mLayout != null ? mLayout.getHeight() : 0; 8270 } 8271 8272 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8273 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8274 if (mSpannable != null && mLinksClickable) { 8275 final float x = event.getX(pointerIndex); 8276 final float y = event.getY(pointerIndex); 8277 final int offset = getOffsetForPosition(x, y); 8278 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8279 ClickableSpan.class); 8280 if (clickables.length > 0) { 8281 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8282 } 8283 } 8284 if (isTextSelectable() || isTextEditable()) { 8285 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8286 } 8287 return super.onResolvePointerIcon(event, pointerIndex); 8288 } 8289 8290 @Override onKeyPreIme(int keyCode, KeyEvent event)8291 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8292 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8293 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8294 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8295 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8296 return true; 8297 } 8298 return super.onKeyPreIme(keyCode, event); 8299 } 8300 8301 /** 8302 * @hide 8303 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8304 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8305 // Do nothing unless mEditor is in text action mode. 8306 if (mEditor == null || mEditor.getTextActionMode() == null) { 8307 return false; 8308 } 8309 8310 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8311 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8312 if (state != null) { 8313 state.startTracking(event, this); 8314 } 8315 return true; 8316 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8317 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8318 if (state != null) { 8319 state.handleUpEvent(event); 8320 } 8321 if (event.isTracking() && !event.isCanceled()) { 8322 stopTextActionMode(); 8323 return true; 8324 } 8325 } 8326 return false; 8327 } 8328 8329 @Override onKeyDown(int keyCode, KeyEvent event)8330 public boolean onKeyDown(int keyCode, KeyEvent event) { 8331 final int which = doKeyDown(keyCode, event, null); 8332 if (which == KEY_EVENT_NOT_HANDLED) { 8333 return super.onKeyDown(keyCode, event); 8334 } 8335 8336 return true; 8337 } 8338 8339 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8340 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8341 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8342 final int which = doKeyDown(keyCode, down, event); 8343 if (which == KEY_EVENT_NOT_HANDLED) { 8344 // Go through default dispatching. 8345 return super.onKeyMultiple(keyCode, repeatCount, event); 8346 } 8347 if (which == KEY_EVENT_HANDLED) { 8348 // Consumed the whole thing. 8349 return true; 8350 } 8351 8352 repeatCount--; 8353 8354 // We are going to dispatch the remaining events to either the input 8355 // or movement method. To do this, we will just send a repeated stream 8356 // of down and up events until we have done the complete repeatCount. 8357 // It would be nice if those interfaces had an onKeyMultiple() method, 8358 // but adding that is a more complicated change. 8359 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8360 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8361 // mEditor and mEditor.mInput are not null from doKeyDown 8362 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8363 while (--repeatCount > 0) { 8364 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8365 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8366 } 8367 hideErrorIfUnchanged(); 8368 8369 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8370 // mMovement is not null from doKeyDown 8371 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8372 while (--repeatCount > 0) { 8373 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8374 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8375 } 8376 } 8377 8378 return true; 8379 } 8380 8381 /** 8382 * Returns true if pressing ENTER in this field advances focus instead 8383 * of inserting the character. This is true mostly in single-line fields, 8384 * but also in mail addresses and subjects which will display on multiple 8385 * lines but where it doesn't make sense to insert newlines. 8386 */ shouldAdvanceFocusOnEnter()8387 private boolean shouldAdvanceFocusOnEnter() { 8388 if (getKeyListener() == null) { 8389 return false; 8390 } 8391 8392 if (mSingleLine) { 8393 return true; 8394 } 8395 8396 if (mEditor != null 8397 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8398 == EditorInfo.TYPE_CLASS_TEXT) { 8399 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8400 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8401 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8402 return true; 8403 } 8404 } 8405 8406 return false; 8407 } 8408 isDirectionalNavigationKey(int keyCode)8409 private boolean isDirectionalNavigationKey(int keyCode) { 8410 switch(keyCode) { 8411 case KeyEvent.KEYCODE_DPAD_UP: 8412 case KeyEvent.KEYCODE_DPAD_DOWN: 8413 case KeyEvent.KEYCODE_DPAD_LEFT: 8414 case KeyEvent.KEYCODE_DPAD_RIGHT: 8415 return true; 8416 } 8417 return false; 8418 } 8419 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8420 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8421 if (!isEnabled()) { 8422 return KEY_EVENT_NOT_HANDLED; 8423 } 8424 8425 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8426 // While this shouldn't be necessary because any time we're preventing default movement we 8427 // should be restricting the focus to remain within this view, thus we'll also receive 8428 // the key up event, occasionally key up events will get dropped and we don't want to 8429 // prevent the user from traversing out of this on the next key down. 8430 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8431 mPreventDefaultMovement = false; 8432 } 8433 8434 switch (keyCode) { 8435 case KeyEvent.KEYCODE_ENTER: 8436 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8437 if (event.hasNoModifiers()) { 8438 // When mInputContentType is set, we know that we are 8439 // running in a "modern" cupcake environment, so don't need 8440 // to worry about the application trying to capture 8441 // enter key events. 8442 if (mEditor != null && mEditor.mInputContentType != null) { 8443 // If there is an action listener, given them a 8444 // chance to consume the event. 8445 if (mEditor.mInputContentType.onEditorActionListener != null 8446 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8447 this, EditorInfo.IME_NULL, event)) { 8448 mEditor.mInputContentType.enterDown = true; 8449 // We are consuming the enter key for them. 8450 return KEY_EVENT_HANDLED; 8451 } 8452 } 8453 8454 // If our editor should move focus when enter is pressed, or 8455 // this is a generated event from an IME action button, then 8456 // don't let it be inserted into the text. 8457 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8458 || shouldAdvanceFocusOnEnter()) { 8459 if (hasOnClickListeners()) { 8460 return KEY_EVENT_NOT_HANDLED; 8461 } 8462 return KEY_EVENT_HANDLED; 8463 } 8464 } 8465 break; 8466 8467 case KeyEvent.KEYCODE_DPAD_CENTER: 8468 if (event.hasNoModifiers()) { 8469 if (shouldAdvanceFocusOnEnter()) { 8470 return KEY_EVENT_NOT_HANDLED; 8471 } 8472 } 8473 break; 8474 8475 case KeyEvent.KEYCODE_TAB: 8476 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8477 // Tab is used to move focus. 8478 return KEY_EVENT_NOT_HANDLED; 8479 } 8480 break; 8481 8482 // Has to be done on key down (and not on key up) to correctly be intercepted. 8483 case KeyEvent.KEYCODE_BACK: 8484 if (mEditor != null && mEditor.getTextActionMode() != null) { 8485 stopTextActionMode(); 8486 return KEY_EVENT_HANDLED; 8487 } 8488 break; 8489 8490 case KeyEvent.KEYCODE_CUT: 8491 if (event.hasNoModifiers() && canCut()) { 8492 if (onTextContextMenuItem(ID_CUT)) { 8493 return KEY_EVENT_HANDLED; 8494 } 8495 } 8496 break; 8497 8498 case KeyEvent.KEYCODE_COPY: 8499 if (event.hasNoModifiers() && canCopy()) { 8500 if (onTextContextMenuItem(ID_COPY)) { 8501 return KEY_EVENT_HANDLED; 8502 } 8503 } 8504 break; 8505 8506 case KeyEvent.KEYCODE_PASTE: 8507 if (event.hasNoModifiers() && canPaste()) { 8508 if (onTextContextMenuItem(ID_PASTE)) { 8509 return KEY_EVENT_HANDLED; 8510 } 8511 } 8512 break; 8513 8514 case KeyEvent.KEYCODE_FORWARD_DEL: 8515 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8516 if (onTextContextMenuItem(ID_CUT)) { 8517 return KEY_EVENT_HANDLED; 8518 } 8519 } 8520 break; 8521 8522 case KeyEvent.KEYCODE_INSERT: 8523 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8524 if (onTextContextMenuItem(ID_COPY)) { 8525 return KEY_EVENT_HANDLED; 8526 } 8527 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8528 if (onTextContextMenuItem(ID_PASTE)) { 8529 return KEY_EVENT_HANDLED; 8530 } 8531 } 8532 break; 8533 } 8534 8535 if (mEditor != null && mEditor.mKeyListener != null) { 8536 boolean doDown = true; 8537 if (otherEvent != null) { 8538 try { 8539 beginBatchEdit(); 8540 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8541 otherEvent); 8542 hideErrorIfUnchanged(); 8543 doDown = false; 8544 if (handled) { 8545 return KEY_EVENT_HANDLED; 8546 } 8547 } catch (AbstractMethodError e) { 8548 // onKeyOther was added after 1.0, so if it isn't 8549 // implemented we need to try to dispatch as a regular down. 8550 } finally { 8551 endBatchEdit(); 8552 } 8553 } 8554 8555 if (doDown) { 8556 beginBatchEdit(); 8557 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8558 keyCode, event); 8559 endBatchEdit(); 8560 hideErrorIfUnchanged(); 8561 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8562 } 8563 } 8564 8565 // bug 650865: sometimes we get a key event before a layout. 8566 // don't try to move around if we don't know the layout. 8567 8568 if (mMovement != null && mLayout != null) { 8569 boolean doDown = true; 8570 if (otherEvent != null) { 8571 try { 8572 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8573 doDown = false; 8574 if (handled) { 8575 return KEY_EVENT_HANDLED; 8576 } 8577 } catch (AbstractMethodError e) { 8578 // onKeyOther was added after 1.0, so if it isn't 8579 // implemented we need to try to dispatch as a regular down. 8580 } 8581 } 8582 if (doDown) { 8583 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8584 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8585 mPreventDefaultMovement = true; 8586 } 8587 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8588 } 8589 } 8590 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8591 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8592 // to move focus with arrows. 8593 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8594 && isDirectionalNavigationKey(keyCode)) { 8595 return KEY_EVENT_HANDLED; 8596 } 8597 } 8598 8599 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8600 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8601 } 8602 8603 /** 8604 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8605 * can be recorded. 8606 * @hide 8607 */ resetErrorChangedFlag()8608 public void resetErrorChangedFlag() { 8609 /* 8610 * Keep track of what the error was before doing the input 8611 * so that if an input filter changed the error, we leave 8612 * that error showing. Otherwise, we take down whatever 8613 * error was showing when the user types something. 8614 */ 8615 if (mEditor != null) mEditor.mErrorWasChanged = false; 8616 } 8617 8618 /** 8619 * @hide 8620 */ hideErrorIfUnchanged()8621 public void hideErrorIfUnchanged() { 8622 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8623 setError(null, null); 8624 } 8625 } 8626 8627 @Override onKeyUp(int keyCode, KeyEvent event)8628 public boolean onKeyUp(int keyCode, KeyEvent event) { 8629 if (!isEnabled()) { 8630 return super.onKeyUp(keyCode, event); 8631 } 8632 8633 if (!KeyEvent.isModifierKey(keyCode)) { 8634 mPreventDefaultMovement = false; 8635 } 8636 8637 switch (keyCode) { 8638 case KeyEvent.KEYCODE_DPAD_CENTER: 8639 if (event.hasNoModifiers()) { 8640 /* 8641 * If there is a click listener, just call through to 8642 * super, which will invoke it. 8643 * 8644 * If there isn't a click listener, try to show the soft 8645 * input method. (It will also 8646 * call performClick(), but that won't do anything in 8647 * this case.) 8648 */ 8649 if (!hasOnClickListeners()) { 8650 if (mMovement != null && mText instanceof Editable 8651 && mLayout != null && onCheckIsTextEditor()) { 8652 InputMethodManager imm = getInputMethodManager(); 8653 viewClicked(imm); 8654 if (imm != null && getShowSoftInputOnFocus()) { 8655 imm.showSoftInput(this, 0); 8656 } 8657 } 8658 } 8659 } 8660 return super.onKeyUp(keyCode, event); 8661 8662 case KeyEvent.KEYCODE_ENTER: 8663 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8664 if (event.hasNoModifiers()) { 8665 if (mEditor != null && mEditor.mInputContentType != null 8666 && mEditor.mInputContentType.onEditorActionListener != null 8667 && mEditor.mInputContentType.enterDown) { 8668 mEditor.mInputContentType.enterDown = false; 8669 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8670 this, EditorInfo.IME_NULL, event)) { 8671 return true; 8672 } 8673 } 8674 8675 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8676 || shouldAdvanceFocusOnEnter()) { 8677 /* 8678 * If there is a click listener, just call through to 8679 * super, which will invoke it. 8680 * 8681 * If there isn't a click listener, try to advance focus, 8682 * but still call through to super, which will reset the 8683 * pressed state and longpress state. (It will also 8684 * call performClick(), but that won't do anything in 8685 * this case.) 8686 */ 8687 if (!hasOnClickListeners()) { 8688 View v = focusSearch(FOCUS_DOWN); 8689 8690 if (v != null) { 8691 if (!v.requestFocus(FOCUS_DOWN)) { 8692 throw new IllegalStateException("focus search returned a view " 8693 + "that wasn't able to take focus!"); 8694 } 8695 8696 /* 8697 * Return true because we handled the key; super 8698 * will return false because there was no click 8699 * listener. 8700 */ 8701 super.onKeyUp(keyCode, event); 8702 return true; 8703 } else if ((event.getFlags() 8704 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8705 // No target for next focus, but make sure the IME 8706 // if this came from it. 8707 InputMethodManager imm = getInputMethodManager(); 8708 if (imm != null && imm.isActive(this)) { 8709 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8710 } 8711 } 8712 } 8713 } 8714 return super.onKeyUp(keyCode, event); 8715 } 8716 break; 8717 } 8718 8719 if (mEditor != null && mEditor.mKeyListener != null) { 8720 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8721 return true; 8722 } 8723 } 8724 8725 if (mMovement != null && mLayout != null) { 8726 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8727 return true; 8728 } 8729 } 8730 8731 return super.onKeyUp(keyCode, event); 8732 } 8733 8734 @Override onCheckIsTextEditor()8735 public boolean onCheckIsTextEditor() { 8736 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8737 } 8738 8739 @Override onCreateInputConnection(EditorInfo outAttrs)8740 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8741 if (onCheckIsTextEditor() && isEnabled()) { 8742 mEditor.createInputMethodStateIfNeeded(); 8743 outAttrs.inputType = getInputType(); 8744 if (mEditor.mInputContentType != null) { 8745 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8746 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8747 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8748 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8749 outAttrs.extras = mEditor.mInputContentType.extras; 8750 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8751 } else { 8752 outAttrs.imeOptions = EditorInfo.IME_NULL; 8753 outAttrs.hintLocales = null; 8754 } 8755 if (focusSearch(FOCUS_DOWN) != null) { 8756 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8757 } 8758 if (focusSearch(FOCUS_UP) != null) { 8759 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8760 } 8761 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8762 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8763 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8764 // An action has not been set, but the enter key will move to 8765 // the next focus, so set the action to that. 8766 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8767 } else { 8768 // An action has not been set, and there is no focus to move 8769 // to, so let's just supply a "done" action. 8770 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8771 } 8772 if (!shouldAdvanceFocusOnEnter()) { 8773 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8774 } 8775 } 8776 if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 8777 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; 8778 } 8779 if (isMultilineInputType(outAttrs.inputType)) { 8780 // Multi-line text editors should always show an enter key. 8781 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8782 } 8783 outAttrs.hintText = mHint; 8784 outAttrs.targetInputMethodUser = mTextOperationUser; 8785 if (mText instanceof Editable) { 8786 InputConnection ic = new EditableInputConnection(this); 8787 outAttrs.initialSelStart = getSelectionStart(); 8788 outAttrs.initialSelEnd = getSelectionEnd(); 8789 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8790 outAttrs.setInitialSurroundingText(mText); 8791 outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); 8792 return ic; 8793 } 8794 } 8795 return null; 8796 } 8797 8798 /** 8799 * If this TextView contains editable content, extract a portion of it 8800 * based on the information in <var>request</var> in to <var>outText</var>. 8801 * @return Returns true if the text was successfully extracted, else false. 8802 */ extractText(ExtractedTextRequest request, ExtractedText outText)8803 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8804 createEditorIfNeeded(); 8805 return mEditor.extractText(request, outText); 8806 } 8807 8808 /** 8809 * This is used to remove all style-impacting spans from text before new 8810 * extracted text is being replaced into it, so that we don't have any 8811 * lingering spans applied during the replace. 8812 */ removeParcelableSpans(Spannable spannable, int start, int end)8813 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8814 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8815 int i = spans.length; 8816 while (i > 0) { 8817 i--; 8818 spannable.removeSpan(spans[i]); 8819 } 8820 } 8821 8822 /** 8823 * Apply to this text view the given extracted text, as previously 8824 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8825 */ setExtractedText(ExtractedText text)8826 public void setExtractedText(ExtractedText text) { 8827 Editable content = getEditableText(); 8828 if (text.text != null) { 8829 if (content == null) { 8830 setText(text.text, TextView.BufferType.EDITABLE); 8831 } else { 8832 int start = 0; 8833 int end = content.length(); 8834 8835 if (text.partialStartOffset >= 0) { 8836 final int N = content.length(); 8837 start = text.partialStartOffset; 8838 if (start > N) start = N; 8839 end = text.partialEndOffset; 8840 if (end > N) end = N; 8841 } 8842 8843 removeParcelableSpans(content, start, end); 8844 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8845 if (text.text instanceof Spanned) { 8846 // OK to copy spans only. 8847 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8848 Object.class, content, start); 8849 } 8850 } else { 8851 content.replace(start, end, text.text); 8852 } 8853 } 8854 } 8855 8856 // Now set the selection position... make sure it is in range, to 8857 // avoid crashes. If this is a partial update, it is possible that 8858 // the underlying text may have changed, causing us problems here. 8859 // Also we just don't want to trust clients to do the right thing. 8860 Spannable sp = (Spannable) getText(); 8861 final int N = sp.length(); 8862 int start = text.selectionStart; 8863 if (start < 0) { 8864 start = 0; 8865 } else if (start > N) { 8866 start = N; 8867 } 8868 int end = text.selectionEnd; 8869 if (end < 0) { 8870 end = 0; 8871 } else if (end > N) { 8872 end = N; 8873 } 8874 Selection.setSelection(sp, start, end); 8875 8876 // Finally, update the selection mode. 8877 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8878 MetaKeyKeyListener.startSelecting(this, sp); 8879 } else { 8880 MetaKeyKeyListener.stopSelecting(this, sp); 8881 } 8882 8883 setHintInternal(text.hint); 8884 } 8885 8886 /** 8887 * @hide 8888 */ setExtracting(ExtractedTextRequest req)8889 public void setExtracting(ExtractedTextRequest req) { 8890 if (mEditor.mInputMethodState != null) { 8891 mEditor.mInputMethodState.mExtractedTextRequest = req; 8892 } 8893 // This would stop a possible selection mode, but no such mode is started in case 8894 // extracted mode will start. Some text is selected though, and will trigger an action mode 8895 // in the extracted view. 8896 mEditor.hideCursorAndSpanControllers(); 8897 stopTextActionMode(); 8898 if (mEditor.mSelectionModifierCursorController != null) { 8899 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8900 } 8901 } 8902 8903 /** 8904 * Called by the framework in response to a text completion from 8905 * the current input method, provided by it calling 8906 * {@link InputConnection#commitCompletion 8907 * InputConnection.commitCompletion()}. The default implementation does 8908 * nothing; text views that are supporting auto-completion should override 8909 * this to do their desired behavior. 8910 * 8911 * @param text The auto complete text the user has selected. 8912 */ onCommitCompletion(CompletionInfo text)8913 public void onCommitCompletion(CompletionInfo text) { 8914 // intentionally empty 8915 } 8916 8917 /** 8918 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8919 * dictionary) from the current input method, provided by it calling 8920 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8921 * The default implementation flashes the background of the corrected word to provide 8922 * feedback to the user. 8923 * 8924 * @param info The auto correct info about the text that was corrected. 8925 */ onCommitCorrection(CorrectionInfo info)8926 public void onCommitCorrection(CorrectionInfo info) { 8927 if (mEditor != null) mEditor.onCommitCorrection(info); 8928 } 8929 beginBatchEdit()8930 public void beginBatchEdit() { 8931 if (mEditor != null) mEditor.beginBatchEdit(); 8932 } 8933 endBatchEdit()8934 public void endBatchEdit() { 8935 if (mEditor != null) mEditor.endBatchEdit(); 8936 } 8937 8938 /** 8939 * Called by the framework in response to a request to begin a batch 8940 * of edit operations through a call to link {@link #beginBatchEdit()}. 8941 */ onBeginBatchEdit()8942 public void onBeginBatchEdit() { 8943 // intentionally empty 8944 } 8945 8946 /** 8947 * Called by the framework in response to a request to end a batch 8948 * of edit operations through a call to link {@link #endBatchEdit}. 8949 */ onEndBatchEdit()8950 public void onEndBatchEdit() { 8951 // intentionally empty 8952 } 8953 8954 /** @hide */ onPerformSpellCheck()8955 public void onPerformSpellCheck() { 8956 if (mEditor != null && mEditor.mSpellChecker != null) { 8957 mEditor.mSpellChecker.onPerformSpellCheck(); 8958 } 8959 } 8960 8961 /** 8962 * Called by the framework in response to a private command from the 8963 * current method, provided by it calling 8964 * {@link InputConnection#performPrivateCommand 8965 * InputConnection.performPrivateCommand()}. 8966 * 8967 * @param action The action name of the command. 8968 * @param data Any additional data for the command. This may be null. 8969 * @return Return true if you handled the command, else false. 8970 */ onPrivateIMECommand(String action, Bundle data)8971 public boolean onPrivateIMECommand(String action, Bundle data) { 8972 return false; 8973 } 8974 8975 /** @hide */ 8976 @VisibleForTesting 8977 @UnsupportedAppUsage nullLayouts()8978 public void nullLayouts() { 8979 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8980 mSavedLayout = (BoringLayout) mLayout; 8981 } 8982 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8983 mSavedHintLayout = (BoringLayout) mHintLayout; 8984 } 8985 8986 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8987 8988 mBoring = mHintBoring = null; 8989 8990 // Since it depends on the value of mLayout 8991 if (mEditor != null) mEditor.prepareCursorControllers(); 8992 } 8993 8994 /** 8995 * Make a new Layout based on the already-measured size of the view, 8996 * on the assumption that it was measured correctly at some point. 8997 */ 8998 @UnsupportedAppUsage assumeLayout()8999 private void assumeLayout() { 9000 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9001 9002 if (width < 1) { 9003 width = 0; 9004 } 9005 9006 int physicalWidth = width; 9007 9008 if (mHorizontallyScrolling) { 9009 width = VERY_WIDE; 9010 } 9011 9012 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 9013 physicalWidth, false); 9014 } 9015 9016 @UnsupportedAppUsage getLayoutAlignment()9017 private Layout.Alignment getLayoutAlignment() { 9018 Layout.Alignment alignment; 9019 switch (getTextAlignment()) { 9020 case TEXT_ALIGNMENT_GRAVITY: 9021 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 9022 case Gravity.START: 9023 alignment = Layout.Alignment.ALIGN_NORMAL; 9024 break; 9025 case Gravity.END: 9026 alignment = Layout.Alignment.ALIGN_OPPOSITE; 9027 break; 9028 case Gravity.LEFT: 9029 alignment = Layout.Alignment.ALIGN_LEFT; 9030 break; 9031 case Gravity.RIGHT: 9032 alignment = Layout.Alignment.ALIGN_RIGHT; 9033 break; 9034 case Gravity.CENTER_HORIZONTAL: 9035 alignment = Layout.Alignment.ALIGN_CENTER; 9036 break; 9037 default: 9038 alignment = Layout.Alignment.ALIGN_NORMAL; 9039 break; 9040 } 9041 break; 9042 case TEXT_ALIGNMENT_TEXT_START: 9043 alignment = Layout.Alignment.ALIGN_NORMAL; 9044 break; 9045 case TEXT_ALIGNMENT_TEXT_END: 9046 alignment = Layout.Alignment.ALIGN_OPPOSITE; 9047 break; 9048 case TEXT_ALIGNMENT_CENTER: 9049 alignment = Layout.Alignment.ALIGN_CENTER; 9050 break; 9051 case TEXT_ALIGNMENT_VIEW_START: 9052 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 9053 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9054 break; 9055 case TEXT_ALIGNMENT_VIEW_END: 9056 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 9057 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9058 break; 9059 case TEXT_ALIGNMENT_INHERIT: 9060 // This should never happen as we have already resolved the text alignment 9061 // but better safe than sorry so we just fall through 9062 default: 9063 alignment = Layout.Alignment.ALIGN_NORMAL; 9064 break; 9065 } 9066 return alignment; 9067 } 9068 9069 /** 9070 * The width passed in is now the desired layout width, 9071 * not the full view width with padding. 9072 * {@hide} 9073 */ 9074 @VisibleForTesting 9075 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)9076 public void makeNewLayout(int wantWidth, int hintWidth, 9077 BoringLayout.Metrics boring, 9078 BoringLayout.Metrics hintBoring, 9079 int ellipsisWidth, boolean bringIntoView) { 9080 stopMarquee(); 9081 9082 // Update "old" cached values 9083 mOldMaximum = mMaximum; 9084 mOldMaxMode = mMaxMode; 9085 9086 mHighlightPathBogus = true; 9087 9088 if (wantWidth < 0) { 9089 wantWidth = 0; 9090 } 9091 if (hintWidth < 0) { 9092 hintWidth = 0; 9093 } 9094 9095 Layout.Alignment alignment = getLayoutAlignment(); 9096 final boolean testDirChange = mSingleLine && mLayout != null 9097 && (alignment == Layout.Alignment.ALIGN_NORMAL 9098 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 9099 int oldDir = 0; 9100 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 9101 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 9102 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 9103 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 9104 TruncateAt effectiveEllipsize = mEllipsize; 9105 if (mEllipsize == TruncateAt.MARQUEE 9106 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 9107 effectiveEllipsize = TruncateAt.END_SMALL; 9108 } 9109 9110 if (mTextDir == null) { 9111 mTextDir = getTextDirectionHeuristic(); 9112 } 9113 9114 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 9115 effectiveEllipsize, effectiveEllipsize == mEllipsize); 9116 if (switchEllipsize) { 9117 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 9118 ? TruncateAt.END : TruncateAt.MARQUEE; 9119 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 9120 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 9121 } 9122 9123 shouldEllipsize = mEllipsize != null; 9124 mHintLayout = null; 9125 9126 if (mHint != null) { 9127 if (shouldEllipsize) hintWidth = wantWidth; 9128 9129 if (hintBoring == UNKNOWN_BORING) { 9130 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9131 mHintBoring); 9132 if (hintBoring != null) { 9133 mHintBoring = hintBoring; 9134 } 9135 } 9136 9137 if (hintBoring != null) { 9138 if (hintBoring.width <= hintWidth 9139 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9140 if (mSavedHintLayout != null) { 9141 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9142 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9143 hintBoring, mIncludePad); 9144 } else { 9145 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9146 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9147 hintBoring, mIncludePad); 9148 } 9149 9150 mSavedHintLayout = (BoringLayout) mHintLayout; 9151 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9152 if (mSavedHintLayout != null) { 9153 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9154 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9155 hintBoring, mIncludePad, mEllipsize, 9156 ellipsisWidth); 9157 } else { 9158 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9159 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9160 hintBoring, mIncludePad, mEllipsize, 9161 ellipsisWidth); 9162 } 9163 } 9164 } 9165 // TODO: code duplication with makeSingleLayout() 9166 if (mHintLayout == null) { 9167 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9168 mHint.length(), mTextPaint, hintWidth) 9169 .setAlignment(alignment) 9170 .setTextDirection(mTextDir) 9171 .setLineSpacing(mSpacingAdd, mSpacingMult) 9172 .setIncludePad(mIncludePad) 9173 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9174 .setBreakStrategy(mBreakStrategy) 9175 .setHyphenationFrequency(mHyphenationFrequency) 9176 .setJustificationMode(mJustificationMode) 9177 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9178 if (shouldEllipsize) { 9179 builder.setEllipsize(mEllipsize) 9180 .setEllipsizedWidth(ellipsisWidth); 9181 } 9182 mHintLayout = builder.build(); 9183 } 9184 } 9185 9186 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9187 registerForPreDraw(); 9188 } 9189 9190 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9191 if (!compressText(ellipsisWidth)) { 9192 final int height = mLayoutParams.height; 9193 // If the size of the view does not depend on the size of the text, try to 9194 // start the marquee immediately 9195 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9196 startMarquee(); 9197 } else { 9198 // Defer the start of the marquee until we know our width (see setFrame()) 9199 mRestartMarquee = true; 9200 } 9201 } 9202 } 9203 9204 // CursorControllers need a non-null mLayout 9205 if (mEditor != null) mEditor.prepareCursorControllers(); 9206 } 9207 9208 /** 9209 * Returns true if DynamicLayout is required 9210 * 9211 * @hide 9212 */ 9213 @VisibleForTesting useDynamicLayout()9214 public boolean useDynamicLayout() { 9215 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9216 } 9217 9218 /** 9219 * @hide 9220 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9221 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9222 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9223 boolean useSaved) { 9224 Layout result = null; 9225 if (useDynamicLayout()) { 9226 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9227 wantWidth) 9228 .setDisplayText(mTransformed) 9229 .setAlignment(alignment) 9230 .setTextDirection(mTextDir) 9231 .setLineSpacing(mSpacingAdd, mSpacingMult) 9232 .setIncludePad(mIncludePad) 9233 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9234 .setBreakStrategy(mBreakStrategy) 9235 .setHyphenationFrequency(mHyphenationFrequency) 9236 .setJustificationMode(mJustificationMode) 9237 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9238 .setEllipsizedWidth(ellipsisWidth); 9239 result = builder.build(); 9240 } else { 9241 if (boring == UNKNOWN_BORING) { 9242 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9243 if (boring != null) { 9244 mBoring = boring; 9245 } 9246 } 9247 9248 if (boring != null) { 9249 if (boring.width <= wantWidth 9250 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9251 if (useSaved && mSavedLayout != null) { 9252 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9253 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9254 boring, mIncludePad); 9255 } else { 9256 result = BoringLayout.make(mTransformed, mTextPaint, 9257 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9258 boring, mIncludePad); 9259 } 9260 9261 if (useSaved) { 9262 mSavedLayout = (BoringLayout) result; 9263 } 9264 } else if (shouldEllipsize && boring.width <= wantWidth) { 9265 if (useSaved && mSavedLayout != null) { 9266 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9267 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9268 boring, mIncludePad, effectiveEllipsize, 9269 ellipsisWidth); 9270 } else { 9271 result = BoringLayout.make(mTransformed, mTextPaint, 9272 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9273 boring, mIncludePad, effectiveEllipsize, 9274 ellipsisWidth); 9275 } 9276 } 9277 } 9278 } 9279 if (result == null) { 9280 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9281 0, mTransformed.length(), mTextPaint, wantWidth) 9282 .setAlignment(alignment) 9283 .setTextDirection(mTextDir) 9284 .setLineSpacing(mSpacingAdd, mSpacingMult) 9285 .setIncludePad(mIncludePad) 9286 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9287 .setBreakStrategy(mBreakStrategy) 9288 .setHyphenationFrequency(mHyphenationFrequency) 9289 .setJustificationMode(mJustificationMode) 9290 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9291 if (shouldEllipsize) { 9292 builder.setEllipsize(effectiveEllipsize) 9293 .setEllipsizedWidth(ellipsisWidth); 9294 } 9295 result = builder.build(); 9296 } 9297 return result; 9298 } 9299 9300 @UnsupportedAppUsage compressText(float width)9301 private boolean compressText(float width) { 9302 if (isHardwareAccelerated()) return false; 9303 9304 // Only compress the text if it hasn't been compressed by the previous pass 9305 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9306 && mTextPaint.getTextScaleX() == 1.0f) { 9307 final float textWidth = mLayout.getLineWidth(0); 9308 final float overflow = (textWidth + 1.0f - width) / width; 9309 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9310 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9311 post(new Runnable() { 9312 public void run() { 9313 requestLayout(); 9314 } 9315 }); 9316 return true; 9317 } 9318 } 9319 9320 return false; 9321 } 9322 desired(Layout layout)9323 private static int desired(Layout layout) { 9324 int n = layout.getLineCount(); 9325 CharSequence text = layout.getText(); 9326 float max = 0; 9327 9328 // if any line was wrapped, we can't use it. 9329 // but it's ok for the last line not to have a newline 9330 9331 for (int i = 0; i < n - 1; i++) { 9332 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9333 return -1; 9334 } 9335 } 9336 9337 for (int i = 0; i < n; i++) { 9338 max = Math.max(max, layout.getLineMax(i)); 9339 } 9340 9341 return (int) Math.ceil(max); 9342 } 9343 9344 /** 9345 * Set whether the TextView includes extra top and bottom padding to make 9346 * room for accents that go above the normal ascent and descent. 9347 * The default is true. 9348 * 9349 * @see #getIncludeFontPadding() 9350 * 9351 * @attr ref android.R.styleable#TextView_includeFontPadding 9352 */ setIncludeFontPadding(boolean includepad)9353 public void setIncludeFontPadding(boolean includepad) { 9354 if (mIncludePad != includepad) { 9355 mIncludePad = includepad; 9356 9357 if (mLayout != null) { 9358 nullLayouts(); 9359 requestLayout(); 9360 invalidate(); 9361 } 9362 } 9363 } 9364 9365 /** 9366 * Gets whether the TextView includes extra top and bottom padding to make 9367 * room for accents that go above the normal ascent and descent. 9368 * 9369 * @see #setIncludeFontPadding(boolean) 9370 * 9371 * @attr ref android.R.styleable#TextView_includeFontPadding 9372 */ 9373 @InspectableProperty getIncludeFontPadding()9374 public boolean getIncludeFontPadding() { 9375 return mIncludePad; 9376 } 9377 9378 /** @hide */ 9379 @VisibleForTesting 9380 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9381 9382 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9383 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9384 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9385 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9386 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9387 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9388 9389 int width; 9390 int height; 9391 9392 BoringLayout.Metrics boring = UNKNOWN_BORING; 9393 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9394 9395 if (mTextDir == null) { 9396 mTextDir = getTextDirectionHeuristic(); 9397 } 9398 9399 int des = -1; 9400 boolean fromexisting = false; 9401 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9402 ? (float) widthSize : Float.MAX_VALUE; 9403 9404 if (widthMode == MeasureSpec.EXACTLY) { 9405 // Parent has told us how big to be. So be it. 9406 width = widthSize; 9407 } else { 9408 if (mLayout != null && mEllipsize == null) { 9409 des = desired(mLayout); 9410 } 9411 9412 if (des < 0) { 9413 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9414 if (boring != null) { 9415 mBoring = boring; 9416 } 9417 } else { 9418 fromexisting = true; 9419 } 9420 9421 if (boring == null || boring == UNKNOWN_BORING) { 9422 if (des < 0) { 9423 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9424 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9425 } 9426 width = des; 9427 } else { 9428 width = boring.width; 9429 } 9430 9431 final Drawables dr = mDrawables; 9432 if (dr != null) { 9433 width = Math.max(width, dr.mDrawableWidthTop); 9434 width = Math.max(width, dr.mDrawableWidthBottom); 9435 } 9436 9437 if (mHint != null) { 9438 int hintDes = -1; 9439 int hintWidth; 9440 9441 if (mHintLayout != null && mEllipsize == null) { 9442 hintDes = desired(mHintLayout); 9443 } 9444 9445 if (hintDes < 0) { 9446 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9447 if (hintBoring != null) { 9448 mHintBoring = hintBoring; 9449 } 9450 } 9451 9452 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9453 if (hintDes < 0) { 9454 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9455 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9456 } 9457 hintWidth = hintDes; 9458 } else { 9459 hintWidth = hintBoring.width; 9460 } 9461 9462 if (hintWidth > width) { 9463 width = hintWidth; 9464 } 9465 } 9466 9467 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9468 9469 if (mMaxWidthMode == EMS) { 9470 width = Math.min(width, mMaxWidth * getLineHeight()); 9471 } else { 9472 width = Math.min(width, mMaxWidth); 9473 } 9474 9475 if (mMinWidthMode == EMS) { 9476 width = Math.max(width, mMinWidth * getLineHeight()); 9477 } else { 9478 width = Math.max(width, mMinWidth); 9479 } 9480 9481 // Check against our minimum width 9482 width = Math.max(width, getSuggestedMinimumWidth()); 9483 9484 if (widthMode == MeasureSpec.AT_MOST) { 9485 width = Math.min(widthSize, width); 9486 } 9487 } 9488 9489 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9490 int unpaddedWidth = want; 9491 9492 if (mHorizontallyScrolling) want = VERY_WIDE; 9493 9494 int hintWant = want; 9495 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9496 9497 if (mLayout == null) { 9498 makeNewLayout(want, hintWant, boring, hintBoring, 9499 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9500 } else { 9501 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9502 || (mLayout.getEllipsizedWidth() 9503 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9504 9505 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9506 && (want > mLayout.getWidth()) 9507 && (mLayout instanceof BoringLayout 9508 || (fromexisting && des >= 0 && des <= want)); 9509 9510 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9511 9512 if (layoutChanged || maximumChanged) { 9513 if (!maximumChanged && widthChanged) { 9514 mLayout.increaseWidthTo(want); 9515 } else { 9516 makeNewLayout(want, hintWant, boring, hintBoring, 9517 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9518 } 9519 } else { 9520 // Nothing has changed 9521 } 9522 } 9523 9524 if (heightMode == MeasureSpec.EXACTLY) { 9525 // Parent has told us how big to be. So be it. 9526 height = heightSize; 9527 mDesiredHeightAtMeasure = -1; 9528 } else { 9529 int desired = getDesiredHeight(); 9530 9531 height = desired; 9532 mDesiredHeightAtMeasure = desired; 9533 9534 if (heightMode == MeasureSpec.AT_MOST) { 9535 height = Math.min(desired, heightSize); 9536 } 9537 } 9538 9539 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9540 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9541 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9542 } 9543 9544 /* 9545 * We didn't let makeNewLayout() register to bring the cursor into view, 9546 * so do it here if there is any possibility that it is needed. 9547 */ 9548 if (mMovement != null 9549 || mLayout.getWidth() > unpaddedWidth 9550 || mLayout.getHeight() > unpaddedHeight) { 9551 registerForPreDraw(); 9552 } else { 9553 scrollTo(0, 0); 9554 } 9555 9556 setMeasuredDimension(width, height); 9557 } 9558 9559 /** 9560 * Automatically computes and sets the text size. 9561 */ autoSizeText()9562 private void autoSizeText() { 9563 if (!isAutoSizeEnabled()) { 9564 return; 9565 } 9566 9567 if (mNeedsAutoSizeText) { 9568 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9569 return; 9570 } 9571 9572 final int availableWidth = mHorizontallyScrolling 9573 ? VERY_WIDE 9574 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9575 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9576 - getExtendedPaddingTop(); 9577 9578 if (availableWidth <= 0 || availableHeight <= 0) { 9579 return; 9580 } 9581 9582 synchronized (TEMP_RECTF) { 9583 TEMP_RECTF.setEmpty(); 9584 TEMP_RECTF.right = availableWidth; 9585 TEMP_RECTF.bottom = availableHeight; 9586 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9587 9588 if (optimalTextSize != getTextSize()) { 9589 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9590 false /* shouldRequestLayout */); 9591 9592 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9593 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9594 false /* bringIntoView */); 9595 } 9596 } 9597 } 9598 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9599 // after the next layout pass should set this to false. 9600 mNeedsAutoSizeText = true; 9601 } 9602 9603 /** 9604 * Performs a binary search to find the largest text size that will still fit within the size 9605 * available to this view. 9606 */ findLargestTextSizeWhichFits(RectF availableSpace)9607 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9608 final int sizesCount = mAutoSizeTextSizesInPx.length; 9609 if (sizesCount == 0) { 9610 throw new IllegalStateException("No available text sizes to choose from."); 9611 } 9612 9613 int bestSizeIndex = 0; 9614 int lowIndex = bestSizeIndex + 1; 9615 int highIndex = sizesCount - 1; 9616 int sizeToTryIndex; 9617 while (lowIndex <= highIndex) { 9618 sizeToTryIndex = (lowIndex + highIndex) / 2; 9619 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9620 bestSizeIndex = lowIndex; 9621 lowIndex = sizeToTryIndex + 1; 9622 } else { 9623 highIndex = sizeToTryIndex - 1; 9624 bestSizeIndex = highIndex; 9625 } 9626 } 9627 9628 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9629 } 9630 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9631 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9632 final CharSequence text = mTransformed != null 9633 ? mTransformed 9634 : getText(); 9635 final int maxLines = getMaxLines(); 9636 if (mTempTextPaint == null) { 9637 mTempTextPaint = new TextPaint(); 9638 } else { 9639 mTempTextPaint.reset(); 9640 } 9641 mTempTextPaint.set(getPaint()); 9642 mTempTextPaint.setTextSize(suggestedSizeInPx); 9643 9644 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9645 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9646 9647 layoutBuilder.setAlignment(getLayoutAlignment()) 9648 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9649 .setIncludePad(getIncludeFontPadding()) 9650 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9651 .setBreakStrategy(getBreakStrategy()) 9652 .setHyphenationFrequency(getHyphenationFrequency()) 9653 .setJustificationMode(getJustificationMode()) 9654 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9655 .setTextDirection(getTextDirectionHeuristic()); 9656 9657 final StaticLayout layout = layoutBuilder.build(); 9658 9659 // Lines overflow. 9660 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9661 return false; 9662 } 9663 9664 // Height overflow. 9665 if (layout.getHeight() > availableSpace.bottom) { 9666 return false; 9667 } 9668 9669 return true; 9670 } 9671 getDesiredHeight()9672 private int getDesiredHeight() { 9673 return Math.max( 9674 getDesiredHeight(mLayout, true), 9675 getDesiredHeight(mHintLayout, mEllipsize != null)); 9676 } 9677 getDesiredHeight(Layout layout, boolean cap)9678 private int getDesiredHeight(Layout layout, boolean cap) { 9679 if (layout == null) { 9680 return 0; 9681 } 9682 9683 /* 9684 * Don't cap the hint to a certain number of lines. 9685 * (Do cap it, though, if we have a maximum pixel height.) 9686 */ 9687 int desired = layout.getHeight(cap); 9688 9689 final Drawables dr = mDrawables; 9690 if (dr != null) { 9691 desired = Math.max(desired, dr.mDrawableHeightLeft); 9692 desired = Math.max(desired, dr.mDrawableHeightRight); 9693 } 9694 9695 int linecount = layout.getLineCount(); 9696 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9697 desired += padding; 9698 9699 if (mMaxMode != LINES) { 9700 desired = Math.min(desired, mMaximum); 9701 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9702 || layout instanceof BoringLayout)) { 9703 desired = layout.getLineTop(mMaximum); 9704 9705 if (dr != null) { 9706 desired = Math.max(desired, dr.mDrawableHeightLeft); 9707 desired = Math.max(desired, dr.mDrawableHeightRight); 9708 } 9709 9710 desired += padding; 9711 linecount = mMaximum; 9712 } 9713 9714 if (mMinMode == LINES) { 9715 if (linecount < mMinimum) { 9716 desired += getLineHeight() * (mMinimum - linecount); 9717 } 9718 } else { 9719 desired = Math.max(desired, mMinimum); 9720 } 9721 9722 // Check against our minimum height 9723 desired = Math.max(desired, getSuggestedMinimumHeight()); 9724 9725 return desired; 9726 } 9727 9728 /** 9729 * Check whether a change to the existing text layout requires a 9730 * new view layout. 9731 */ checkForResize()9732 private void checkForResize() { 9733 boolean sizeChanged = false; 9734 9735 if (mLayout != null) { 9736 // Check if our width changed 9737 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9738 sizeChanged = true; 9739 invalidate(); 9740 } 9741 9742 // Check if our height changed 9743 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9744 int desiredHeight = getDesiredHeight(); 9745 9746 if (desiredHeight != this.getHeight()) { 9747 sizeChanged = true; 9748 } 9749 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9750 if (mDesiredHeightAtMeasure >= 0) { 9751 int desiredHeight = getDesiredHeight(); 9752 9753 if (desiredHeight != mDesiredHeightAtMeasure) { 9754 sizeChanged = true; 9755 } 9756 } 9757 } 9758 } 9759 9760 if (sizeChanged) { 9761 requestLayout(); 9762 // caller will have already invalidated 9763 } 9764 } 9765 9766 /** 9767 * Check whether entirely new text requires a new view layout 9768 * or merely a new text layout. 9769 */ 9770 @UnsupportedAppUsage checkForRelayout()9771 private void checkForRelayout() { 9772 // If we have a fixed width, we can just swap in a new text layout 9773 // if the text height stays the same or if the view height is fixed. 9774 9775 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9776 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9777 && (mHint == null || mHintLayout != null) 9778 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9779 // Static width, so try making a new text layout. 9780 9781 int oldht = mLayout.getHeight(); 9782 int want = mLayout.getWidth(); 9783 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9784 9785 /* 9786 * No need to bring the text into view, since the size is not 9787 * changing (unless we do the requestLayout(), in which case it 9788 * will happen at measure). 9789 */ 9790 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9791 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9792 false); 9793 9794 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9795 // In a fixed-height view, so use our new text layout. 9796 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9797 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9798 autoSizeText(); 9799 invalidate(); 9800 return; 9801 } 9802 9803 // Dynamic height, but height has stayed the same, 9804 // so use our new text layout. 9805 if (mLayout.getHeight() == oldht 9806 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9807 autoSizeText(); 9808 invalidate(); 9809 return; 9810 } 9811 } 9812 9813 // We lose: the height has changed and we have a dynamic height. 9814 // Request a new view layout using our new text layout. 9815 requestLayout(); 9816 invalidate(); 9817 } else { 9818 // Dynamic width, so we have no choice but to request a new 9819 // view layout with a new text layout. 9820 nullLayouts(); 9821 requestLayout(); 9822 invalidate(); 9823 } 9824 } 9825 9826 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9827 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9828 super.onLayout(changed, left, top, right, bottom); 9829 if (mDeferScroll >= 0) { 9830 int curs = mDeferScroll; 9831 mDeferScroll = -1; 9832 bringPointIntoView(Math.min(curs, mText.length())); 9833 } 9834 // Call auto-size after the width and height have been calculated. 9835 autoSizeText(); 9836 } 9837 isShowingHint()9838 private boolean isShowingHint() { 9839 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9840 } 9841 9842 /** 9843 * Returns true if anything changed. 9844 */ 9845 @UnsupportedAppUsage bringTextIntoView()9846 private boolean bringTextIntoView() { 9847 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9848 int line = 0; 9849 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9850 line = layout.getLineCount() - 1; 9851 } 9852 9853 Layout.Alignment a = layout.getParagraphAlignment(line); 9854 int dir = layout.getParagraphDirection(line); 9855 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9856 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9857 int ht = layout.getHeight(); 9858 9859 int scrollx, scrolly; 9860 9861 // Convert to left, center, or right alignment. 9862 if (a == Layout.Alignment.ALIGN_NORMAL) { 9863 a = dir == Layout.DIR_LEFT_TO_RIGHT 9864 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9865 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9866 a = dir == Layout.DIR_LEFT_TO_RIGHT 9867 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9868 } 9869 9870 if (a == Layout.Alignment.ALIGN_CENTER) { 9871 /* 9872 * Keep centered if possible, or, if it is too wide to fit, 9873 * keep leading edge in view. 9874 */ 9875 9876 int left = (int) Math.floor(layout.getLineLeft(line)); 9877 int right = (int) Math.ceil(layout.getLineRight(line)); 9878 9879 if (right - left < hspace) { 9880 scrollx = (right + left) / 2 - hspace / 2; 9881 } else { 9882 if (dir < 0) { 9883 scrollx = right - hspace; 9884 } else { 9885 scrollx = left; 9886 } 9887 } 9888 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9889 int right = (int) Math.ceil(layout.getLineRight(line)); 9890 scrollx = right - hspace; 9891 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9892 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9893 } 9894 9895 if (ht < vspace) { 9896 scrolly = 0; 9897 } else { 9898 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9899 scrolly = ht - vspace; 9900 } else { 9901 scrolly = 0; 9902 } 9903 } 9904 9905 if (scrollx != mScrollX || scrolly != mScrollY) { 9906 scrollTo(scrollx, scrolly); 9907 return true; 9908 } else { 9909 return false; 9910 } 9911 } 9912 9913 /** 9914 * Move the point, specified by the offset, into the view if it is needed. 9915 * This has to be called after layout. Returns true if anything changed. 9916 */ bringPointIntoView(int offset)9917 public boolean bringPointIntoView(int offset) { 9918 if (isLayoutRequested()) { 9919 mDeferScroll = offset; 9920 return false; 9921 } 9922 boolean changed = false; 9923 9924 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9925 9926 if (layout == null) return changed; 9927 9928 int line = layout.getLineForOffset(offset); 9929 9930 int grav; 9931 9932 switch (layout.getParagraphAlignment(line)) { 9933 case ALIGN_LEFT: 9934 grav = 1; 9935 break; 9936 case ALIGN_RIGHT: 9937 grav = -1; 9938 break; 9939 case ALIGN_NORMAL: 9940 grav = layout.getParagraphDirection(line); 9941 break; 9942 case ALIGN_OPPOSITE: 9943 grav = -layout.getParagraphDirection(line); 9944 break; 9945 case ALIGN_CENTER: 9946 default: 9947 grav = 0; 9948 break; 9949 } 9950 9951 // We only want to clamp the cursor to fit within the layout width 9952 // in left-to-right modes, because in a right to left alignment, 9953 // we want to scroll to keep the line-right on the screen, as other 9954 // lines are likely to have text flush with the right margin, which 9955 // we want to keep visible. 9956 // A better long-term solution would probably be to measure both 9957 // the full line and a blank-trimmed version, and, for example, use 9958 // the latter measurement for centering and right alignment, but for 9959 // the time being we only implement the cursor clamping in left to 9960 // right where it is most likely to be annoying. 9961 final boolean clamped = grav > 0; 9962 // FIXME: Is it okay to truncate this, or should we round? 9963 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9964 final int top = layout.getLineTop(line); 9965 final int bottom = layout.getLineTop(line + 1); 9966 9967 int left = (int) Math.floor(layout.getLineLeft(line)); 9968 int right = (int) Math.ceil(layout.getLineRight(line)); 9969 int ht = layout.getHeight(); 9970 9971 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9972 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9973 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9974 // If cursor has been clamped, make sure we don't scroll. 9975 right = Math.max(x, left + hspace); 9976 } 9977 9978 int hslack = (bottom - top) / 2; 9979 int vslack = hslack; 9980 9981 if (vslack > vspace / 4) { 9982 vslack = vspace / 4; 9983 } 9984 if (hslack > hspace / 4) { 9985 hslack = hspace / 4; 9986 } 9987 9988 int hs = mScrollX; 9989 int vs = mScrollY; 9990 9991 if (top - vs < vslack) { 9992 vs = top - vslack; 9993 } 9994 if (bottom - vs > vspace - vslack) { 9995 vs = bottom - (vspace - vslack); 9996 } 9997 if (ht - vs < vspace) { 9998 vs = ht - vspace; 9999 } 10000 if (0 - vs > 0) { 10001 vs = 0; 10002 } 10003 10004 if (grav != 0) { 10005 if (x - hs < hslack) { 10006 hs = x - hslack; 10007 } 10008 if (x - hs > hspace - hslack) { 10009 hs = x - (hspace - hslack); 10010 } 10011 } 10012 10013 if (grav < 0) { 10014 if (left - hs > 0) { 10015 hs = left; 10016 } 10017 if (right - hs < hspace) { 10018 hs = right - hspace; 10019 } 10020 } else if (grav > 0) { 10021 if (right - hs < hspace) { 10022 hs = right - hspace; 10023 } 10024 if (left - hs > 0) { 10025 hs = left; 10026 } 10027 } else /* grav == 0 */ { 10028 if (right - left <= hspace) { 10029 /* 10030 * If the entire text fits, center it exactly. 10031 */ 10032 hs = left - (hspace - (right - left)) / 2; 10033 } else if (x > right - hslack) { 10034 /* 10035 * If we are near the right edge, keep the right edge 10036 * at the edge of the view. 10037 */ 10038 hs = right - hspace; 10039 } else if (x < left + hslack) { 10040 /* 10041 * If we are near the left edge, keep the left edge 10042 * at the edge of the view. 10043 */ 10044 hs = left; 10045 } else if (left > hs) { 10046 /* 10047 * Is there whitespace visible at the left? Fix it if so. 10048 */ 10049 hs = left; 10050 } else if (right < hs + hspace) { 10051 /* 10052 * Is there whitespace visible at the right? Fix it if so. 10053 */ 10054 hs = right - hspace; 10055 } else { 10056 /* 10057 * Otherwise, float as needed. 10058 */ 10059 if (x - hs < hslack) { 10060 hs = x - hslack; 10061 } 10062 if (x - hs > hspace - hslack) { 10063 hs = x - (hspace - hslack); 10064 } 10065 } 10066 } 10067 10068 if (hs != mScrollX || vs != mScrollY) { 10069 if (mScroller == null) { 10070 scrollTo(hs, vs); 10071 } else { 10072 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 10073 int dx = hs - mScrollX; 10074 int dy = vs - mScrollY; 10075 10076 if (duration > ANIMATED_SCROLL_GAP) { 10077 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 10078 awakenScrollBars(mScroller.getDuration()); 10079 invalidate(); 10080 } else { 10081 if (!mScroller.isFinished()) { 10082 mScroller.abortAnimation(); 10083 } 10084 10085 scrollBy(dx, dy); 10086 } 10087 10088 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 10089 } 10090 10091 changed = true; 10092 } 10093 10094 if (isFocused()) { 10095 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 10096 // requestRectangleOnScreen() is in terms of content coordinates. 10097 10098 // The offsets here are to ensure the rectangle we are using is 10099 // within our view bounds, in case the cursor is on the far left 10100 // or right. If it isn't withing the bounds, then this request 10101 // will be ignored. 10102 if (mTempRect == null) mTempRect = new Rect(); 10103 mTempRect.set(x - 2, top, x + 2, bottom); 10104 getInterestingRect(mTempRect, line); 10105 mTempRect.offset(mScrollX, mScrollY); 10106 10107 if (requestRectangleOnScreen(mTempRect)) { 10108 changed = true; 10109 } 10110 } 10111 10112 return changed; 10113 } 10114 10115 /** 10116 * Move the cursor, if needed, so that it is at an offset that is visible 10117 * to the user. This will not move the cursor if it represents more than 10118 * one character (a selection range). This will only work if the 10119 * TextView contains spannable text; otherwise it will do nothing. 10120 * 10121 * @return True if the cursor was actually moved, false otherwise. 10122 */ moveCursorToVisibleOffset()10123 public boolean moveCursorToVisibleOffset() { 10124 if (!(mText instanceof Spannable)) { 10125 return false; 10126 } 10127 int start = getSelectionStart(); 10128 int end = getSelectionEnd(); 10129 if (start != end) { 10130 return false; 10131 } 10132 10133 // First: make sure the line is visible on screen: 10134 10135 int line = mLayout.getLineForOffset(start); 10136 10137 final int top = mLayout.getLineTop(line); 10138 final int bottom = mLayout.getLineTop(line + 1); 10139 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10140 int vslack = (bottom - top) / 2; 10141 if (vslack > vspace / 4) { 10142 vslack = vspace / 4; 10143 } 10144 final int vs = mScrollY; 10145 10146 if (top < (vs + vslack)) { 10147 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10148 } else if (bottom > (vspace + vs - vslack)) { 10149 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10150 } 10151 10152 // Next: make sure the character is visible on screen: 10153 10154 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10155 final int hs = mScrollX; 10156 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10157 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10158 10159 // line might contain bidirectional text 10160 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10161 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10162 10163 int newStart = start; 10164 if (newStart < lowChar) { 10165 newStart = lowChar; 10166 } else if (newStart > highChar) { 10167 newStart = highChar; 10168 } 10169 10170 if (newStart != start) { 10171 Selection.setSelection(mSpannable, newStart); 10172 return true; 10173 } 10174 10175 return false; 10176 } 10177 10178 @Override computeScroll()10179 public void computeScroll() { 10180 if (mScroller != null) { 10181 if (mScroller.computeScrollOffset()) { 10182 mScrollX = mScroller.getCurrX(); 10183 mScrollY = mScroller.getCurrY(); 10184 invalidateParentCaches(); 10185 postInvalidate(); // So we draw again 10186 } 10187 } 10188 } 10189 getInterestingRect(Rect r, int line)10190 private void getInterestingRect(Rect r, int line) { 10191 convertFromViewportToContentCoordinates(r); 10192 10193 // Rectangle can can be expanded on first and last line to take 10194 // padding into account. 10195 // TODO Take left/right padding into account too? 10196 if (line == 0) r.top -= getExtendedPaddingTop(); 10197 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10198 } 10199 convertFromViewportToContentCoordinates(Rect r)10200 private void convertFromViewportToContentCoordinates(Rect r) { 10201 final int horizontalOffset = viewportToContentHorizontalOffset(); 10202 r.left += horizontalOffset; 10203 r.right += horizontalOffset; 10204 10205 final int verticalOffset = viewportToContentVerticalOffset(); 10206 r.top += verticalOffset; 10207 r.bottom += verticalOffset; 10208 } 10209 viewportToContentHorizontalOffset()10210 int viewportToContentHorizontalOffset() { 10211 return getCompoundPaddingLeft() - mScrollX; 10212 } 10213 10214 @UnsupportedAppUsage viewportToContentVerticalOffset()10215 int viewportToContentVerticalOffset() { 10216 int offset = getExtendedPaddingTop() - mScrollY; 10217 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10218 offset += getVerticalOffset(false); 10219 } 10220 return offset; 10221 } 10222 10223 @Override debug(int depth)10224 public void debug(int depth) { 10225 super.debug(depth); 10226 10227 String output = debugIndent(depth); 10228 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10229 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10230 + "} "; 10231 10232 if (mText != null) { 10233 10234 output += "mText=\"" + mText + "\" "; 10235 if (mLayout != null) { 10236 output += "mLayout width=" + mLayout.getWidth() 10237 + " height=" + mLayout.getHeight(); 10238 } 10239 } else { 10240 output += "mText=NULL"; 10241 } 10242 Log.d(VIEW_LOG_TAG, output); 10243 } 10244 10245 /** 10246 * Convenience for {@link Selection#getSelectionStart}. 10247 */ 10248 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10249 public int getSelectionStart() { 10250 return Selection.getSelectionStart(getText()); 10251 } 10252 10253 /** 10254 * Convenience for {@link Selection#getSelectionEnd}. 10255 */ 10256 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10257 public int getSelectionEnd() { 10258 return Selection.getSelectionEnd(getText()); 10259 } 10260 10261 /** 10262 * Return true iff there is a selection of nonzero length inside this text view. 10263 */ hasSelection()10264 public boolean hasSelection() { 10265 final int selectionStart = getSelectionStart(); 10266 final int selectionEnd = getSelectionEnd(); 10267 10268 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10269 } 10270 getSelectedText()10271 String getSelectedText() { 10272 if (!hasSelection()) { 10273 return null; 10274 } 10275 10276 final int start = getSelectionStart(); 10277 final int end = getSelectionEnd(); 10278 return String.valueOf( 10279 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10280 } 10281 10282 /** 10283 * Sets the properties of this field (lines, horizontally scrolling, 10284 * transformation method) to be for a single-line input. 10285 * 10286 * @attr ref android.R.styleable#TextView_singleLine 10287 */ setSingleLine()10288 public void setSingleLine() { 10289 setSingleLine(true); 10290 } 10291 10292 /** 10293 * Sets the properties of this field to transform input to ALL CAPS 10294 * display. This may use a "small caps" formatting if available. 10295 * This setting will be ignored if this field is editable or selectable. 10296 * 10297 * This call replaces the current transformation method. Disabling this 10298 * will not necessarily restore the previous behavior from before this 10299 * was enabled. 10300 * 10301 * @see #setTransformationMethod(TransformationMethod) 10302 * @attr ref android.R.styleable#TextView_textAllCaps 10303 */ 10304 @android.view.RemotableViewMethod setAllCaps(boolean allCaps)10305 public void setAllCaps(boolean allCaps) { 10306 if (allCaps) { 10307 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10308 } else { 10309 setTransformationMethod(null); 10310 } 10311 } 10312 10313 /** 10314 * 10315 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10316 * @return Whether the current transformation method is for ALL CAPS. 10317 * 10318 * @see #setAllCaps(boolean) 10319 * @see #setTransformationMethod(TransformationMethod) 10320 */ 10321 @InspectableProperty(name = "textAllCaps") isAllCaps()10322 public boolean isAllCaps() { 10323 final TransformationMethod method = getTransformationMethod(); 10324 return method != null && method instanceof AllCapsTransformationMethod; 10325 } 10326 10327 /** 10328 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10329 * transformation method) to be for a single-line input; if false, restores these to the default 10330 * conditions. 10331 * 10332 * Note that the default conditions are not necessarily those that were in effect prior this 10333 * method, and you may want to reset these properties to your custom values. 10334 * 10335 * Note that due to performance reasons, by setting single line for the EditText, the maximum 10336 * text length is set to 5000 if no other character limitation are applied. 10337 * 10338 * @attr ref android.R.styleable#TextView_singleLine 10339 */ 10340 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10341 public void setSingleLine(boolean singleLine) { 10342 // Could be used, but may break backward compatibility. 10343 // if (mSingleLine == singleLine) return; 10344 setInputTypeSingleLine(singleLine); 10345 applySingleLine(singleLine, true, true, true); 10346 } 10347 10348 /** 10349 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10350 * @param singleLine 10351 */ setInputTypeSingleLine(boolean singleLine)10352 private void setInputTypeSingleLine(boolean singleLine) { 10353 if (mEditor != null 10354 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10355 == EditorInfo.TYPE_CLASS_TEXT) { 10356 if (singleLine) { 10357 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10358 } else { 10359 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10360 } 10361 } 10362 } 10363 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10364 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10365 boolean changeMaxLines, boolean changeMaxLength) { 10366 mSingleLine = singleLine; 10367 10368 if (singleLine) { 10369 setLines(1); 10370 setHorizontallyScrolling(true); 10371 if (applyTransformation) { 10372 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10373 } 10374 10375 if (!changeMaxLength) return; 10376 10377 // Single line length filter is only applicable editable text. 10378 if (mBufferType != BufferType.EDITABLE) return; 10379 10380 final InputFilter[] prevFilters = getFilters(); 10381 for (InputFilter filter: getFilters()) { 10382 // We don't add LengthFilter if already there. 10383 if (filter instanceof InputFilter.LengthFilter) return; 10384 } 10385 10386 if (mSingleLineLengthFilter == null) { 10387 mSingleLineLengthFilter = new InputFilter.LengthFilter( 10388 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 10389 } 10390 10391 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 10392 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 10393 newFilters[prevFilters.length] = mSingleLineLengthFilter; 10394 10395 setFilters(newFilters); 10396 10397 // Since filter doesn't apply to existing text, trigger filter by setting text. 10398 setText(getText()); 10399 } else { 10400 if (changeMaxLines) { 10401 setMaxLines(Integer.MAX_VALUE); 10402 } 10403 setHorizontallyScrolling(false); 10404 if (applyTransformation) { 10405 setTransformationMethod(null); 10406 } 10407 10408 if (!changeMaxLength) return; 10409 10410 // Single line length filter is only applicable editable text. 10411 if (mBufferType != BufferType.EDITABLE) return; 10412 10413 final InputFilter[] prevFilters = getFilters(); 10414 if (prevFilters.length == 0) return; 10415 10416 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 10417 // single line char limit filter. 10418 if (mSingleLineLengthFilter == null) return; 10419 10420 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 10421 // Since filter list is expected to be small and want to avoid unnecessary array 10422 // allocation, check if there is mSingleLengthFilter first. 10423 int targetIndex = -1; 10424 for (int i = 0; i < prevFilters.length; ++i) { 10425 if (prevFilters[i] == mSingleLineLengthFilter) { 10426 targetIndex = i; 10427 break; 10428 } 10429 } 10430 if (targetIndex == -1) return; // not found. Do nothing. 10431 10432 if (prevFilters.length == 1) { 10433 setFilters(NO_FILTERS); 10434 return; 10435 } 10436 10437 // Create new array which doesn't include mSingleLengthFilter. 10438 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 10439 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 10440 System.arraycopy( 10441 prevFilters, 10442 targetIndex + 1, 10443 newFilters, 10444 targetIndex, 10445 prevFilters.length - targetIndex - 1); 10446 setFilters(newFilters); 10447 mSingleLineLengthFilter = null; 10448 } 10449 } 10450 10451 /** 10452 * Causes words in the text that are longer than the view's width 10453 * to be ellipsized instead of broken in the middle. You may also 10454 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10455 * to constrain the text to a single line. Use <code>null</code> 10456 * to turn off ellipsizing. 10457 * 10458 * If {@link #setMaxLines} has been used to set two or more lines, 10459 * only {@link android.text.TextUtils.TruncateAt#END} and 10460 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10461 * (other ellipsizing types will not do anything). 10462 * 10463 * @attr ref android.R.styleable#TextView_ellipsize 10464 */ setEllipsize(TextUtils.TruncateAt where)10465 public void setEllipsize(TextUtils.TruncateAt where) { 10466 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10467 if (mEllipsize != where) { 10468 mEllipsize = where; 10469 10470 if (mLayout != null) { 10471 nullLayouts(); 10472 requestLayout(); 10473 invalidate(); 10474 } 10475 } 10476 } 10477 10478 /** 10479 * Sets how many times to repeat the marquee animation. Only applied if the 10480 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10481 * 10482 * @see #getMarqueeRepeatLimit() 10483 * 10484 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10485 */ setMarqueeRepeatLimit(int marqueeLimit)10486 public void setMarqueeRepeatLimit(int marqueeLimit) { 10487 mMarqueeRepeatLimit = marqueeLimit; 10488 } 10489 10490 /** 10491 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10492 * TextView has marquee enabled. 10493 * 10494 * @return the number of times the marquee animation is repeated. -1 if the animation 10495 * repeats indefinitely 10496 * 10497 * @see #setMarqueeRepeatLimit(int) 10498 * 10499 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10500 */ 10501 @InspectableProperty getMarqueeRepeatLimit()10502 public int getMarqueeRepeatLimit() { 10503 return mMarqueeRepeatLimit; 10504 } 10505 10506 /** 10507 * Returns where, if anywhere, words that are longer than the view 10508 * is wide should be ellipsized. 10509 */ 10510 @InspectableProperty 10511 @ViewDebug.ExportedProperty getEllipsize()10512 public TextUtils.TruncateAt getEllipsize() { 10513 return mEllipsize; 10514 } 10515 10516 /** 10517 * Set the TextView so that when it takes focus, all the text is 10518 * selected. 10519 * 10520 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10521 */ 10522 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10523 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10524 createEditorIfNeeded(); 10525 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10526 10527 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10528 setText(mText, BufferType.SPANNABLE); 10529 } 10530 } 10531 10532 /** 10533 * Set whether the cursor is visible. The default is true. Note that this property only 10534 * makes sense for editable TextView. If IME is consuming the input, the cursor will always be 10535 * invisible, visibility will be updated as the last state when IME does not consume 10536 * the input anymore. 10537 * 10538 * @see #isCursorVisible() 10539 * 10540 * @attr ref android.R.styleable#TextView_cursorVisible 10541 */ 10542 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10543 public void setCursorVisible(boolean visible) { 10544 mCursorVisibleFromAttr = visible; 10545 updateCursorVisibleInternal(); 10546 } 10547 10548 /** 10549 * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} 10550 * is {@code true}. Otherwise, make the cursor visible. 10551 * 10552 * @param imeConsumesInput {@code true} if IME is consuming the input 10553 * 10554 * @hide 10555 */ setImeConsumesInput(boolean imeConsumesInput)10556 public void setImeConsumesInput(boolean imeConsumesInput) { 10557 mImeIsConsumingInput = imeConsumesInput; 10558 updateCursorVisibleInternal(); 10559 } 10560 updateCursorVisibleInternal()10561 private void updateCursorVisibleInternal() { 10562 boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; 10563 if (visible && mEditor == null) return; // visible is the default value with no edit data 10564 createEditorIfNeeded(); 10565 if (mEditor.mCursorVisible != visible) { 10566 mEditor.mCursorVisible = visible; 10567 invalidate(); 10568 10569 mEditor.makeBlink(); 10570 10571 // InsertionPointCursorController depends on mCursorVisible 10572 mEditor.prepareCursorControllers(); 10573 } 10574 } 10575 10576 /** 10577 * @return whether or not the cursor is visible (assuming this TextView is editable). This 10578 * method may return {@code false} when the IME is consuming the input even if the 10579 * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} 10580 * is called. 10581 * 10582 * @see #setCursorVisible(boolean) 10583 * 10584 * @attr ref android.R.styleable#TextView_cursorVisible 10585 */ 10586 @InspectableProperty isCursorVisible()10587 public boolean isCursorVisible() { 10588 // true is the default value 10589 return mEditor == null ? true : mEditor.mCursorVisible; 10590 } 10591 10592 /** 10593 * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. 10594 * {@code true} is the default value. 10595 * 10596 * @see #setCursorVisible(boolean) 10597 * @hide 10598 */ isCursorVisibleFromAttr()10599 public boolean isCursorVisibleFromAttr() { 10600 return mCursorVisibleFromAttr; 10601 } 10602 canMarquee()10603 private boolean canMarquee() { 10604 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10605 return width > 0 && (mLayout.getLineWidth(0) > width 10606 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10607 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10608 } 10609 10610 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10611 private void startMarquee() { 10612 // Do not ellipsize EditText 10613 if (getKeyListener() != null) return; 10614 10615 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10616 return; 10617 } 10618 10619 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10620 && getLineCount() == 1 && canMarquee()) { 10621 10622 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10623 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10624 final Layout tmp = mLayout; 10625 mLayout = mSavedMarqueeModeLayout; 10626 mSavedMarqueeModeLayout = tmp; 10627 setHorizontalFadingEdgeEnabled(true); 10628 requestLayout(); 10629 invalidate(); 10630 } 10631 10632 if (mMarquee == null) mMarquee = new Marquee(this); 10633 mMarquee.start(mMarqueeRepeatLimit); 10634 } 10635 } 10636 stopMarquee()10637 private void stopMarquee() { 10638 if (mMarquee != null && !mMarquee.isStopped()) { 10639 mMarquee.stop(); 10640 } 10641 10642 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10643 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10644 final Layout tmp = mSavedMarqueeModeLayout; 10645 mSavedMarqueeModeLayout = mLayout; 10646 mLayout = tmp; 10647 setHorizontalFadingEdgeEnabled(false); 10648 requestLayout(); 10649 invalidate(); 10650 } 10651 } 10652 10653 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10654 private void startStopMarquee(boolean start) { 10655 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10656 if (start) { 10657 startMarquee(); 10658 } else { 10659 stopMarquee(); 10660 } 10661 } 10662 } 10663 10664 /** 10665 * This method is called when the text is changed, in case any subclasses 10666 * would like to know. 10667 * 10668 * Within <code>text</code>, the <code>lengthAfter</code> characters 10669 * beginning at <code>start</code> have just replaced old text that had 10670 * length <code>lengthBefore</code>. It is an error to attempt to make 10671 * changes to <code>text</code> from this callback. 10672 * 10673 * @param text The text the TextView is displaying 10674 * @param start The offset of the start of the range of the text that was 10675 * modified 10676 * @param lengthBefore The length of the former text that has been replaced 10677 * @param lengthAfter The length of the replacement modified text 10678 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10679 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10680 // intentionally empty, template pattern method can be overridden by subclasses 10681 } 10682 10683 /** 10684 * This method is called when the selection has changed, in case any 10685 * subclasses would like to know. 10686 * </p> 10687 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 10688 * the accessibility subsystem about the selection change. 10689 * </p> 10690 * 10691 * @param selStart The new selection start location. 10692 * @param selEnd The new selection end location. 10693 */ 10694 @CallSuper onSelectionChanged(int selStart, int selEnd)10695 protected void onSelectionChanged(int selStart, int selEnd) { 10696 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10697 } 10698 10699 /** 10700 * Adds a TextWatcher to the list of those whose methods are called 10701 * whenever this TextView's text changes. 10702 * <p> 10703 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10704 * not called after {@link #setText} calls. Now, doing {@link #setText} 10705 * if there are any text changed listeners forces the buffer type to 10706 * Editable if it would not otherwise be and does call this method. 10707 */ addTextChangedListener(TextWatcher watcher)10708 public void addTextChangedListener(TextWatcher watcher) { 10709 if (mListeners == null) { 10710 mListeners = new ArrayList<TextWatcher>(); 10711 } 10712 10713 mListeners.add(watcher); 10714 } 10715 10716 /** 10717 * Removes the specified TextWatcher from the list of those whose 10718 * methods are called 10719 * whenever this TextView's text changes. 10720 */ removeTextChangedListener(TextWatcher watcher)10721 public void removeTextChangedListener(TextWatcher watcher) { 10722 if (mListeners != null) { 10723 int i = mListeners.indexOf(watcher); 10724 10725 if (i >= 0) { 10726 mListeners.remove(i); 10727 } 10728 } 10729 } 10730 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10731 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10732 if (mListeners != null) { 10733 final ArrayList<TextWatcher> list = mListeners; 10734 final int count = list.size(); 10735 for (int i = 0; i < count; i++) { 10736 list.get(i).beforeTextChanged(text, start, before, after); 10737 } 10738 } 10739 10740 // The spans that are inside or intersect the modified region no longer make sense 10741 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10742 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10743 } 10744 10745 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10746 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10747 if (!(mText instanceof Editable)) return; 10748 Editable text = (Editable) mText; 10749 10750 T[] spans = text.getSpans(start, end, type); 10751 ArrayList<T> spansToRemove = new ArrayList<>(); 10752 for (T span : spans) { 10753 final int spanStart = text.getSpanStart(span); 10754 final int spanEnd = text.getSpanEnd(span); 10755 if (spanEnd == start || spanStart == end) continue; 10756 spansToRemove.add(span); 10757 } 10758 for (T span : spansToRemove) { 10759 text.removeSpan(span); 10760 } 10761 } 10762 removeAdjacentSuggestionSpans(final int pos)10763 void removeAdjacentSuggestionSpans(final int pos) { 10764 if (!(mText instanceof Editable)) return; 10765 final Editable text = (Editable) mText; 10766 10767 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10768 final int length = spans.length; 10769 for (int i = 0; i < length; i++) { 10770 final int spanStart = text.getSpanStart(spans[i]); 10771 final int spanEnd = text.getSpanEnd(spans[i]); 10772 if (spanEnd == pos || spanStart == pos) { 10773 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10774 text.removeSpan(spans[i]); 10775 } 10776 } 10777 } 10778 } 10779 10780 /** 10781 * Not private so it can be called from an inner class without going 10782 * through a thunk. 10783 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10784 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10785 if (mListeners != null) { 10786 final ArrayList<TextWatcher> list = mListeners; 10787 final int count = list.size(); 10788 for (int i = 0; i < count; i++) { 10789 list.get(i).onTextChanged(text, start, before, after); 10790 } 10791 } 10792 10793 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10794 } 10795 10796 /** 10797 * Not private so it can be called from an inner class without going 10798 * through a thunk. 10799 */ sendAfterTextChanged(Editable text)10800 void sendAfterTextChanged(Editable text) { 10801 if (mListeners != null) { 10802 final ArrayList<TextWatcher> list = mListeners; 10803 final int count = list.size(); 10804 for (int i = 0; i < count; i++) { 10805 list.get(i).afterTextChanged(text); 10806 } 10807 } 10808 10809 notifyListeningManagersAfterTextChanged(); 10810 10811 hideErrorIfUnchanged(); 10812 } 10813 10814 /** 10815 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 10816 * interested on text changes. 10817 */ notifyListeningManagersAfterTextChanged()10818 private void notifyListeningManagersAfterTextChanged() { 10819 10820 // Autofill 10821 if (isAutofillable()) { 10822 // It is important to not check whether the view is important for autofill 10823 // since the user can trigger autofill manually on not important views. 10824 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10825 if (afm != null) { 10826 if (android.view.autofill.Helper.sVerbose) { 10827 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10828 } 10829 afm.notifyValueChanged(TextView.this); 10830 } 10831 } 10832 10833 notifyContentCaptureTextChanged(); 10834 } 10835 10836 /** 10837 * Notifies the ContentCapture service that the text of the view has changed (only if 10838 * ContentCapture has been notified of this view's existence already). 10839 * 10840 * @hide 10841 */ notifyContentCaptureTextChanged()10842 public void notifyContentCaptureTextChanged() { 10843 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 10844 // of using isLaidout(), so it's not called in cases where it's laid out but a 10845 // notifyAppeared was not sent. 10846 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 10847 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 10848 if (cm != null && cm.isContentCaptureEnabled()) { 10849 final ContentCaptureSession session = getContentCaptureSession(); 10850 if (session != null) { 10851 // TODO(b/111276913): pass flags when edited by user / add CTS test 10852 session.notifyViewTextChanged(getAutofillId(), getText()); 10853 } 10854 } 10855 } 10856 } 10857 isAutofillable()10858 private boolean isAutofillable() { 10859 // It is important to not check whether the view is important for autofill 10860 // since the user can trigger autofill manually on not important views. 10861 return getAutofillType() != AUTOFILL_TYPE_NONE; 10862 } 10863 updateAfterEdit()10864 void updateAfterEdit() { 10865 invalidate(); 10866 int curs = getSelectionStart(); 10867 10868 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10869 registerForPreDraw(); 10870 } 10871 10872 checkForResize(); 10873 10874 if (curs >= 0) { 10875 mHighlightPathBogus = true; 10876 if (mEditor != null) mEditor.makeBlink(); 10877 bringPointIntoView(curs); 10878 } 10879 } 10880 10881 /** 10882 * Not private so it can be called from an inner class without going 10883 * through a thunk. 10884 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10885 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10886 sLastCutCopyOrTextChangedTime = 0; 10887 10888 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10889 if (ims == null || ims.mBatchEditNesting == 0) { 10890 updateAfterEdit(); 10891 } 10892 if (ims != null) { 10893 ims.mContentChanged = true; 10894 if (ims.mChangedStart < 0) { 10895 ims.mChangedStart = start; 10896 ims.mChangedEnd = start + before; 10897 } else { 10898 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10899 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10900 } 10901 ims.mChangedDelta += after - before; 10902 } 10903 resetErrorChangedFlag(); 10904 sendOnTextChanged(buffer, start, before, after); 10905 onTextChanged(buffer, start, before, after); 10906 } 10907 10908 /** 10909 * Not private so it can be called from an inner class without going 10910 * through a thunk. 10911 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10912 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10913 // XXX Make the start and end move together if this ends up 10914 // spending too much time invalidating. 10915 10916 boolean selChanged = false; 10917 int newSelStart = -1, newSelEnd = -1; 10918 10919 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10920 10921 if (what == Selection.SELECTION_END) { 10922 selChanged = true; 10923 newSelEnd = newStart; 10924 10925 if (oldStart >= 0 || newStart >= 0) { 10926 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10927 checkForResize(); 10928 registerForPreDraw(); 10929 if (mEditor != null) mEditor.makeBlink(); 10930 } 10931 } 10932 10933 if (what == Selection.SELECTION_START) { 10934 selChanged = true; 10935 newSelStart = newStart; 10936 10937 if (oldStart >= 0 || newStart >= 0) { 10938 int end = Selection.getSelectionEnd(buf); 10939 invalidateCursor(end, oldStart, newStart); 10940 } 10941 } 10942 10943 if (selChanged) { 10944 mHighlightPathBogus = true; 10945 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10946 10947 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10948 if (newSelStart < 0) { 10949 newSelStart = Selection.getSelectionStart(buf); 10950 } 10951 if (newSelEnd < 0) { 10952 newSelEnd = Selection.getSelectionEnd(buf); 10953 } 10954 10955 if (mEditor != null) { 10956 mEditor.refreshTextActionMode(); 10957 if (!hasSelection() 10958 && mEditor.getTextActionMode() == null && hasTransientState()) { 10959 // User generated selection has been removed. 10960 setHasTransientState(false); 10961 } 10962 } 10963 onSelectionChanged(newSelStart, newSelEnd); 10964 } 10965 } 10966 10967 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10968 || what instanceof CharacterStyle) { 10969 if (ims == null || ims.mBatchEditNesting == 0) { 10970 invalidate(); 10971 mHighlightPathBogus = true; 10972 checkForResize(); 10973 } else { 10974 ims.mContentChanged = true; 10975 } 10976 if (mEditor != null) { 10977 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10978 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10979 mEditor.invalidateHandlesAndActionMode(); 10980 } 10981 } 10982 10983 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10984 mHighlightPathBogus = true; 10985 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10986 ims.mSelectionModeChanged = true; 10987 } 10988 10989 if (Selection.getSelectionStart(buf) >= 0) { 10990 if (ims == null || ims.mBatchEditNesting == 0) { 10991 invalidateCursor(); 10992 } else { 10993 ims.mCursorChanged = true; 10994 } 10995 } 10996 } 10997 10998 if (what instanceof ParcelableSpan) { 10999 // If this is a span that can be sent to a remote process, 11000 // the current extract editor would be interested in it. 11001 if (ims != null && ims.mExtractedTextRequest != null) { 11002 if (ims.mBatchEditNesting != 0) { 11003 if (oldStart >= 0) { 11004 if (ims.mChangedStart > oldStart) { 11005 ims.mChangedStart = oldStart; 11006 } 11007 if (ims.mChangedStart > oldEnd) { 11008 ims.mChangedStart = oldEnd; 11009 } 11010 } 11011 if (newStart >= 0) { 11012 if (ims.mChangedStart > newStart) { 11013 ims.mChangedStart = newStart; 11014 } 11015 if (ims.mChangedStart > newEnd) { 11016 ims.mChangedStart = newEnd; 11017 } 11018 } 11019 } else { 11020 if (DEBUG_EXTRACT) { 11021 Log.v(LOG_TAG, "Span change outside of batch: " 11022 + oldStart + "-" + oldEnd + "," 11023 + newStart + "-" + newEnd + " " + what); 11024 } 11025 ims.mContentChanged = true; 11026 } 11027 } 11028 } 11029 11030 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 11031 && what instanceof SpellCheckSpan) { 11032 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 11033 } 11034 } 11035 11036 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)11037 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 11038 if (isTemporarilyDetached()) { 11039 // If we are temporarily in the detach state, then do nothing. 11040 super.onFocusChanged(focused, direction, previouslyFocusedRect); 11041 return; 11042 } 11043 11044 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 11045 11046 if (focused) { 11047 if (mSpannable != null) { 11048 MetaKeyKeyListener.resetMetaState(mSpannable); 11049 } 11050 } 11051 11052 startStopMarquee(focused); 11053 11054 if (mTransformation != null) { 11055 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 11056 } 11057 11058 super.onFocusChanged(focused, direction, previouslyFocusedRect); 11059 } 11060 11061 @Override onWindowFocusChanged(boolean hasWindowFocus)11062 public void onWindowFocusChanged(boolean hasWindowFocus) { 11063 super.onWindowFocusChanged(hasWindowFocus); 11064 11065 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 11066 11067 startStopMarquee(hasWindowFocus); 11068 } 11069 11070 @Override onVisibilityChanged(View changedView, int visibility)11071 protected void onVisibilityChanged(View changedView, int visibility) { 11072 super.onVisibilityChanged(changedView, visibility); 11073 if (mEditor != null && visibility != VISIBLE) { 11074 mEditor.hideCursorAndSpanControllers(); 11075 stopTextActionMode(); 11076 } 11077 } 11078 11079 /** 11080 * Use {@link BaseInputConnection#removeComposingSpans 11081 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 11082 * state from this text view. 11083 */ clearComposingText()11084 public void clearComposingText() { 11085 if (mText instanceof Spannable) { 11086 BaseInputConnection.removeComposingSpans(mSpannable); 11087 } 11088 } 11089 11090 @Override setSelected(boolean selected)11091 public void setSelected(boolean selected) { 11092 boolean wasSelected = isSelected(); 11093 11094 super.setSelected(selected); 11095 11096 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 11097 if (selected) { 11098 startMarquee(); 11099 } else { 11100 stopMarquee(); 11101 } 11102 } 11103 } 11104 11105 /** 11106 * Called from onTouchEvent() to prevent the touches by secondary fingers. 11107 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 11108 * This method is a lock to avoid processing multiple fingers on both text view and handles. 11109 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 11110 * 11111 * @param event The motion event that is being handled and carries the pointer info. 11112 * @param fromHandleView true if the event is delivered to selection handle or insertion 11113 * handle; false if this event is delivered to TextView. 11114 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 11115 * event, otherwise false. 11116 * - Always returns true for the first finger. 11117 * - For secondary fingers, if the first or current finger is from TextView, returns false. 11118 * This is to make touch mutually exclusive between the TextView and the handles, but 11119 * not among the handles. 11120 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)11121 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 11122 boolean res = true; 11123 if (mPrimePointerId == NO_POINTER_ID) { 11124 mPrimePointerId = event.getPointerId(0); 11125 mIsPrimePointerFromHandleView = fromHandleView; 11126 } else if (mPrimePointerId != event.getPointerId(0)) { 11127 res = mIsPrimePointerFromHandleView && fromHandleView; 11128 } 11129 if (event.getActionMasked() == MotionEvent.ACTION_UP 11130 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 11131 mPrimePointerId = -1; 11132 } 11133 return res; 11134 } 11135 11136 @Override onTouchEvent(MotionEvent event)11137 public boolean onTouchEvent(MotionEvent event) { 11138 if (DEBUG_CURSOR) { 11139 logCursor("onTouchEvent", "%d: %s (%f,%f)", 11140 event.getSequenceNumber(), 11141 MotionEvent.actionToString(event.getActionMasked()), 11142 event.getX(), event.getY()); 11143 } 11144 final int action = event.getActionMasked(); 11145 if (mEditor != null) { 11146 if (!isFromPrimePointer(event, false)) { 11147 return true; 11148 } 11149 11150 mEditor.onTouchEvent(event); 11151 11152 if (mEditor.mInsertionPointCursorController != null 11153 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 11154 return true; 11155 } 11156 if (mEditor.mSelectionModifierCursorController != null 11157 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 11158 return true; 11159 } 11160 } 11161 11162 final boolean superResult = super.onTouchEvent(event); 11163 if (DEBUG_CURSOR) { 11164 logCursor("onTouchEvent", "superResult=%s", superResult); 11165 } 11166 11167 /* 11168 * Don't handle the release after a long press, because it will move the selection away from 11169 * whatever the menu action was trying to affect. If the long press should have triggered an 11170 * insertion action mode, we can now actually show it. 11171 */ 11172 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 11173 mEditor.mDiscardNextActionUp = false; 11174 if (DEBUG_CURSOR) { 11175 logCursor("onTouchEvent", "release after long press detected"); 11176 } 11177 if (mEditor.mIsInsertionActionModeStartPending) { 11178 mEditor.startInsertionActionMode(); 11179 mEditor.mIsInsertionActionModeStartPending = false; 11180 } 11181 return superResult; 11182 } 11183 11184 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 11185 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 11186 11187 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 11188 && mText instanceof Spannable && mLayout != null) { 11189 boolean handled = false; 11190 11191 if (mMovement != null) { 11192 handled |= mMovement.onTouchEvent(this, mSpannable, event); 11193 } 11194 11195 final boolean textIsSelectable = isTextSelectable(); 11196 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 11197 // The LinkMovementMethod which should handle taps on links has not been installed 11198 // on non editable text that support text selection. 11199 // We reproduce its behavior here to open links for these. 11200 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 11201 getSelectionEnd(), ClickableSpan.class); 11202 11203 if (links.length > 0) { 11204 links[0].onClick(this); 11205 handled = true; 11206 } 11207 } 11208 11209 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 11210 // Show the IME, except when selecting in read-only text. 11211 final InputMethodManager imm = getInputMethodManager(); 11212 viewClicked(imm); 11213 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 11214 imm.showSoftInput(this, 0); 11215 } 11216 11217 // The above condition ensures that the mEditor is not null 11218 mEditor.onTouchUpEvent(event); 11219 11220 handled = true; 11221 } 11222 11223 if (handled) { 11224 return true; 11225 } 11226 } 11227 11228 return superResult; 11229 } 11230 11231 @Override onGenericMotionEvent(MotionEvent event)11232 public boolean onGenericMotionEvent(MotionEvent event) { 11233 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 11234 try { 11235 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 11236 return true; 11237 } 11238 } catch (AbstractMethodError ex) { 11239 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 11240 // Ignore its absence in case third party applications implemented the 11241 // interface directly. 11242 } 11243 } 11244 return super.onGenericMotionEvent(event); 11245 } 11246 11247 @Override onCreateContextMenu(ContextMenu menu)11248 protected void onCreateContextMenu(ContextMenu menu) { 11249 if (mEditor != null) { 11250 mEditor.onCreateContextMenu(menu); 11251 } 11252 } 11253 11254 @Override showContextMenu()11255 public boolean showContextMenu() { 11256 if (mEditor != null) { 11257 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 11258 } 11259 return super.showContextMenu(); 11260 } 11261 11262 @Override showContextMenu(float x, float y)11263 public boolean showContextMenu(float x, float y) { 11264 if (mEditor != null) { 11265 mEditor.setContextMenuAnchor(x, y); 11266 } 11267 return super.showContextMenu(x, y); 11268 } 11269 11270 /** 11271 * @return True iff this TextView contains a text that can be edited, or if this is 11272 * a selectable TextView. 11273 */ 11274 @UnsupportedAppUsage isTextEditable()11275 boolean isTextEditable() { 11276 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 11277 } 11278 11279 /** 11280 * Returns true, only while processing a touch gesture, if the initial 11281 * touch down event caused focus to move to the text view and as a result 11282 * its selection changed. Only valid while processing the touch gesture 11283 * of interest, in an editable text view. 11284 */ didTouchFocusSelect()11285 public boolean didTouchFocusSelect() { 11286 return mEditor != null && mEditor.mTouchFocusSelected; 11287 } 11288 11289 @Override cancelLongPress()11290 public void cancelLongPress() { 11291 super.cancelLongPress(); 11292 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 11293 } 11294 11295 @Override onTrackballEvent(MotionEvent event)11296 public boolean onTrackballEvent(MotionEvent event) { 11297 if (mMovement != null && mSpannable != null && mLayout != null) { 11298 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 11299 return true; 11300 } 11301 } 11302 11303 return super.onTrackballEvent(event); 11304 } 11305 11306 /** 11307 * Sets the Scroller used for producing a scrolling animation 11308 * 11309 * @param s A Scroller instance 11310 */ setScroller(Scroller s)11311 public void setScroller(Scroller s) { 11312 mScroller = s; 11313 } 11314 11315 @Override getLeftFadingEdgeStrength()11316 protected float getLeftFadingEdgeStrength() { 11317 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11318 final Marquee marquee = mMarquee; 11319 if (marquee.shouldDrawLeftFade()) { 11320 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11321 } else { 11322 return 0.0f; 11323 } 11324 } else if (getLineCount() == 1) { 11325 final float lineLeft = getLayout().getLineLeft(0); 11326 if (lineLeft > mScrollX) return 0.0f; 11327 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11328 } 11329 return super.getLeftFadingEdgeStrength(); 11330 } 11331 11332 @Override getRightFadingEdgeStrength()11333 protected float getRightFadingEdgeStrength() { 11334 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11335 final Marquee marquee = mMarquee; 11336 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11337 } else if (getLineCount() == 1) { 11338 final float rightEdge = mScrollX + 11339 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11340 final float lineRight = getLayout().getLineRight(0); 11341 if (lineRight < rightEdge) return 0.0f; 11342 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11343 } 11344 return super.getRightFadingEdgeStrength(); 11345 } 11346 11347 /** 11348 * Calculates the fading edge strength as the ratio of the distance between two 11349 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11350 * value for the distance calculation. 11351 * 11352 * @param position1 A horizontal position. 11353 * @param position2 A horizontal position. 11354 * @return Fading edge strength between [0.0f, 1.0f]. 11355 */ 11356 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11357 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11358 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11359 if (horizontalFadingEdgeLength == 0) return 0.0f; 11360 final float diff = Math.abs(position1 - position2); 11361 if (diff > horizontalFadingEdgeLength) return 1.0f; 11362 return diff / horizontalFadingEdgeLength; 11363 } 11364 isMarqueeFadeEnabled()11365 private boolean isMarqueeFadeEnabled() { 11366 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11367 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11368 } 11369 11370 @Override computeHorizontalScrollRange()11371 protected int computeHorizontalScrollRange() { 11372 if (mLayout != null) { 11373 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11374 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11375 } 11376 11377 return super.computeHorizontalScrollRange(); 11378 } 11379 11380 @Override computeVerticalScrollRange()11381 protected int computeVerticalScrollRange() { 11382 if (mLayout != null) { 11383 return mLayout.getHeight(); 11384 } 11385 return super.computeVerticalScrollRange(); 11386 } 11387 11388 @Override computeVerticalScrollExtent()11389 protected int computeVerticalScrollExtent() { 11390 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11391 } 11392 11393 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11394 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11395 super.findViewsWithText(outViews, searched, flags); 11396 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11397 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11398 String searchedLowerCase = searched.toString().toLowerCase(); 11399 String textLowerCase = mText.toString().toLowerCase(); 11400 if (textLowerCase.contains(searchedLowerCase)) { 11401 outViews.add(this); 11402 } 11403 } 11404 } 11405 11406 /** 11407 * Type of the text buffer that defines the characteristics of the text such as static, 11408 * styleable, or editable. 11409 */ 11410 public enum BufferType { 11411 NORMAL, SPANNABLE, EDITABLE 11412 } 11413 11414 /** 11415 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11416 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11417 * if TextView_textColor was not set directly. 11418 * 11419 * @removed 11420 */ getTextColors(Context context, TypedArray attrs)11421 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11422 if (attrs == null) { 11423 // Preserve behavior prior to removal of this API. 11424 throw new NullPointerException(); 11425 } 11426 11427 // It's not safe to use this method from apps. The parameter 'attrs' 11428 // must have been obtained using the TextView filter array which is not 11429 // available to the SDK. As such, we grab a default TypedArray with the 11430 // right filter instead here. 11431 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11432 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11433 if (colors == null) { 11434 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11435 if (ap != 0) { 11436 final TypedArray appearance = context.obtainStyledAttributes( 11437 ap, R.styleable.TextAppearance); 11438 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11439 appearance.recycle(); 11440 } 11441 } 11442 a.recycle(); 11443 11444 return colors; 11445 } 11446 11447 /** 11448 * Returns the default color from the TextView_textColor attribute from the 11449 * AttributeSet, if set, or the default color from the 11450 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11451 * TextView_textColor was not set directly. 11452 * 11453 * @removed 11454 */ getTextColor(Context context, TypedArray attrs, int def)11455 public static int getTextColor(Context context, TypedArray attrs, int def) { 11456 final ColorStateList colors = getTextColors(context, attrs); 11457 if (colors == null) { 11458 return def; 11459 } else { 11460 return colors.getDefaultColor(); 11461 } 11462 } 11463 11464 @Override onKeyShortcut(int keyCode, KeyEvent event)11465 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11466 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11467 // Handle Ctrl-only shortcuts. 11468 switch (keyCode) { 11469 case KeyEvent.KEYCODE_A: 11470 if (canSelectText()) { 11471 return onTextContextMenuItem(ID_SELECT_ALL); 11472 } 11473 break; 11474 case KeyEvent.KEYCODE_Z: 11475 if (canUndo()) { 11476 return onTextContextMenuItem(ID_UNDO); 11477 } 11478 break; 11479 case KeyEvent.KEYCODE_X: 11480 if (canCut()) { 11481 return onTextContextMenuItem(ID_CUT); 11482 } 11483 break; 11484 case KeyEvent.KEYCODE_C: 11485 if (canCopy()) { 11486 return onTextContextMenuItem(ID_COPY); 11487 } 11488 break; 11489 case KeyEvent.KEYCODE_V: 11490 if (canPaste()) { 11491 return onTextContextMenuItem(ID_PASTE); 11492 } 11493 break; 11494 } 11495 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11496 // Handle Ctrl-Shift shortcuts. 11497 switch (keyCode) { 11498 case KeyEvent.KEYCODE_Z: 11499 if (canRedo()) { 11500 return onTextContextMenuItem(ID_REDO); 11501 } 11502 break; 11503 case KeyEvent.KEYCODE_V: 11504 if (canPaste()) { 11505 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11506 } 11507 } 11508 } 11509 return super.onKeyShortcut(keyCode, event); 11510 } 11511 11512 /** 11513 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11514 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11515 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11516 * sufficient. 11517 */ canSelectText()11518 boolean canSelectText() { 11519 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11520 } 11521 11522 /** 11523 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11524 * The text must be spannable and the movement method must allow for arbitary selection. 11525 * 11526 * See also {@link #canSelectText()}. 11527 */ textCanBeSelected()11528 boolean textCanBeSelected() { 11529 // prepareCursorController() relies on this method. 11530 // If you change this condition, make sure prepareCursorController is called anywhere 11531 // the value of this condition might be changed. 11532 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11533 return isTextEditable() 11534 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11535 } 11536 11537 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11538 private Locale getTextServicesLocale(boolean allowNullLocale) { 11539 // Start fetching the text services locale asynchronously. 11540 updateTextServicesLocaleAsync(); 11541 // If !allowNullLocale and there is no cached text services locale, just return the default 11542 // locale. 11543 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11544 : mCurrentSpellCheckerLocaleCache; 11545 } 11546 11547 /** 11548 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11549 * this {@link TextView}. 11550 * 11551 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11552 * other apps may need to set this so that the system can user right user's resources and 11553 * services such as input methods and spell checkers.</p> 11554 * 11555 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11556 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11557 * @hide 11558 */ 11559 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11560 public final void setTextOperationUser(@Nullable UserHandle user) { 11561 if (Objects.equals(mTextOperationUser, user)) { 11562 return; 11563 } 11564 if (user != null && !Process.myUserHandle().equals(user)) { 11565 // Just for preventing people from accidentally using this hidden API without 11566 // the required permission. The same permission is also checked in the system server. 11567 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11568 != PackageManager.PERMISSION_GRANTED) { 11569 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11570 + " userId=" + user.getIdentifier() 11571 + " callingUserId" + UserHandle.myUserId()); 11572 } 11573 } 11574 mTextOperationUser = user; 11575 // Invalidate some resources 11576 mCurrentSpellCheckerLocaleCache = null; 11577 if (mEditor != null) { 11578 mEditor.onTextOperationUserChanged(); 11579 } 11580 } 11581 11582 @Nullable getTextServicesManagerForUser()11583 final TextServicesManager getTextServicesManagerForUser() { 11584 return getServiceManagerForUser("android", TextServicesManager.class); 11585 } 11586 11587 @Nullable getClipboardManagerForUser()11588 final ClipboardManager getClipboardManagerForUser() { 11589 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11590 } 11591 11592 @Nullable getTextClassificationManagerForUser()11593 final TextClassificationManager getTextClassificationManagerForUser() { 11594 return getServiceManagerForUser( 11595 getContext().getPackageName(), TextClassificationManager.class); 11596 } 11597 11598 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11599 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11600 if (mTextOperationUser == null) { 11601 return getContext().getSystemService(managerClazz); 11602 } 11603 try { 11604 Context context = getContext().createPackageContextAsUser( 11605 packageName, 0 /* flags */, mTextOperationUser); 11606 return context.getSystemService(managerClazz); 11607 } catch (PackageManager.NameNotFoundException e) { 11608 return null; 11609 } 11610 } 11611 11612 /** 11613 * Starts {@link Activity} as a text-operation user if it is specified with 11614 * {@link #setTextOperationUser(UserHandle)}. 11615 * 11616 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11617 * 11618 * @param intent The description of the activity to start. 11619 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11620 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11621 if (mTextOperationUser != null) { 11622 getContext().startActivityAsUser(intent, mTextOperationUser); 11623 } else { 11624 getContext().startActivity(intent); 11625 } 11626 } 11627 11628 /** 11629 * This is a temporary method. Future versions may support multi-locale text. 11630 * Caveat: This method may not return the latest text services locale, but this should be 11631 * acceptable and it's more important to make this method asynchronous. 11632 * 11633 * @return The locale that should be used for a word iterator 11634 * in this TextView, based on the current spell checker settings, 11635 * the current IME's locale, or the system default locale. 11636 * Please note that a word iterator in this TextView is different from another word iterator 11637 * used by SpellChecker.java of TextView. This method should be used for the former. 11638 * @hide 11639 */ 11640 // TODO: Support multi-locale 11641 // TODO: Update the text services locale immediately after the keyboard locale is switched 11642 // by catching intent of keyboard switch event getTextServicesLocale()11643 public Locale getTextServicesLocale() { 11644 return getTextServicesLocale(false /* allowNullLocale */); 11645 } 11646 11647 /** 11648 * @return {@code true} if this TextView is specialized for showing and interacting with the 11649 * extracted text in a full-screen input method. 11650 * @hide 11651 */ isInExtractedMode()11652 public boolean isInExtractedMode() { 11653 return false; 11654 } 11655 11656 /** 11657 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11658 * auto-size. 11659 */ isAutoSizeEnabled()11660 private boolean isAutoSizeEnabled() { 11661 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11662 } 11663 11664 /** 11665 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11666 * @hide 11667 */ supportsAutoSizeText()11668 protected boolean supportsAutoSizeText() { 11669 return true; 11670 } 11671 11672 /** 11673 * This is a temporary method. Future versions may support multi-locale text. 11674 * Caveat: This method may not return the latest spell checker locale, but this should be 11675 * acceptable and it's more important to make this method asynchronous. 11676 * 11677 * @return The locale that should be used for a spell checker in this TextView, 11678 * based on the current spell checker settings, the current IME's locale, or the system default 11679 * locale. 11680 * @hide 11681 */ getSpellCheckerLocale()11682 public Locale getSpellCheckerLocale() { 11683 return getTextServicesLocale(true /* allowNullLocale */); 11684 } 11685 updateTextServicesLocaleAsync()11686 private void updateTextServicesLocaleAsync() { 11687 // AsyncTask.execute() uses a serial executor which means we don't have 11688 // to lock around updateTextServicesLocaleLocked() to prevent it from 11689 // being executed n times in parallel. 11690 AsyncTask.execute(new Runnable() { 11691 @Override 11692 public void run() { 11693 updateTextServicesLocaleLocked(); 11694 } 11695 }); 11696 } 11697 11698 @UnsupportedAppUsage updateTextServicesLocaleLocked()11699 private void updateTextServicesLocaleLocked() { 11700 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11701 if (textServicesManager == null) { 11702 return; 11703 } 11704 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11705 final Locale locale; 11706 if (subtype != null) { 11707 locale = subtype.getLocaleObject(); 11708 } else { 11709 locale = null; 11710 } 11711 mCurrentSpellCheckerLocaleCache = locale; 11712 } 11713 onLocaleChanged()11714 void onLocaleChanged() { 11715 mEditor.onLocaleChanged(); 11716 } 11717 11718 /** 11719 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11720 * Made available to achieve a consistent behavior. 11721 * @hide 11722 */ getWordIterator()11723 public WordIterator getWordIterator() { 11724 if (mEditor != null) { 11725 return mEditor.getWordIterator(); 11726 } else { 11727 return null; 11728 } 11729 } 11730 11731 /** @hide */ 11732 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11733 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11734 super.onPopulateAccessibilityEventInternal(event); 11735 11736 final CharSequence text = getTextForAccessibility(); 11737 if (!TextUtils.isEmpty(text)) { 11738 event.getText().add(text); 11739 } 11740 } 11741 11742 @Override getAccessibilityClassName()11743 public CharSequence getAccessibilityClassName() { 11744 return TextView.class.getName(); 11745 } 11746 11747 /** @hide */ 11748 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11749 protected void onProvideStructure(@NonNull ViewStructure structure, 11750 @ViewStructureType int viewFor, int flags) { 11751 super.onProvideStructure(structure, viewFor, flags); 11752 11753 final boolean isPassword = hasPasswordTransformationMethod() 11754 || isPasswordInputType(getInputType()); 11755 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11756 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11757 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11758 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11759 } 11760 if (mTextId != Resources.ID_NULL) { 11761 try { 11762 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11763 } catch (Resources.NotFoundException e) { 11764 if (android.view.autofill.Helper.sVerbose) { 11765 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11766 + mTextId + ": " + e.getMessage()); 11767 } 11768 } 11769 } 11770 String[] mimeTypes = getReceiveContentMimeTypes(); 11771 if (mimeTypes == null && mEditor != null) { 11772 // If the app hasn't set a listener for receiving content on this view (ie, 11773 // getReceiveContentMimeTypes() returns null), check if it implements the 11774 // keyboard image API and, if possible, use those MIME types as fallback. 11775 // This fallback is only in place for autofill, not other mechanisms for 11776 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER 11777 // in TextViewOnReceiveContentListener for more info. 11778 mimeTypes = mEditor.getDefaultOnReceiveContentListener() 11779 .getFallbackMimeTypesForAutofill(this); 11780 } 11781 structure.setReceiveContentMimeTypes(mimeTypes); 11782 } 11783 11784 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11785 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11786 if (mLayout == null) { 11787 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11788 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 11789 } 11790 assumeLayout(); 11791 } 11792 Layout layout = mLayout; 11793 final int lineCount = layout.getLineCount(); 11794 if (lineCount <= 1) { 11795 // Simple case: this is a single line. 11796 final CharSequence text = getText(); 11797 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11798 structure.setText(text); 11799 } else { 11800 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11801 } 11802 } else { 11803 // Complex case: multi-line, could be scrolled or within a scroll container 11804 // so some lines are not visible. 11805 final int[] tmpCords = new int[2]; 11806 getLocationInWindow(tmpCords); 11807 final int topWindowLocation = tmpCords[1]; 11808 View root = this; 11809 ViewParent viewParent = getParent(); 11810 while (viewParent instanceof View) { 11811 root = (View) viewParent; 11812 viewParent = root.getParent(); 11813 } 11814 final int windowHeight = root.getHeight(); 11815 final int topLine; 11816 final int bottomLine; 11817 if (topWindowLocation >= 0) { 11818 // The top of the view is fully within its window; start text at line 0. 11819 topLine = getLineAtCoordinateUnclamped(0); 11820 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11821 } else { 11822 // The top of hte window has scrolled off the top of the window; figure out 11823 // the starting line for this. 11824 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11825 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11826 } 11827 // We want to return some contextual lines above/below the lines that are 11828 // actually visible. 11829 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11830 if (expandedTopLine < 0) { 11831 expandedTopLine = 0; 11832 } 11833 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11834 if (expandedBottomLine >= lineCount) { 11835 expandedBottomLine = lineCount - 1; 11836 } 11837 11838 // Convert lines into character offsets. 11839 int expandedTopChar = layout.getLineStart(expandedTopLine); 11840 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11841 11842 // Take into account selection -- if there is a selection, we need to expand 11843 // the text we are returning to include that selection. 11844 final int selStart = getSelectionStart(); 11845 final int selEnd = getSelectionEnd(); 11846 if (selStart < selEnd) { 11847 if (selStart < expandedTopChar) { 11848 expandedTopChar = selStart; 11849 } 11850 if (selEnd > expandedBottomChar) { 11851 expandedBottomChar = selEnd; 11852 } 11853 } 11854 11855 // Get the text and trim it to the range we are reporting. 11856 CharSequence text = getText(); 11857 11858 if (text != null) { 11859 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11860 // Cap the offsets to avoid an OOB exception. That can happen if the 11861 // displayed/layout text, on which these offsets are calculated, is longer 11862 // than the original text (such as when the view is translated by the 11863 // platform intelligence). 11864 // TODO(b/196433694): Figure out how to better handle the offset 11865 // calculations for this case (so we don't unnecessarily cutoff the original 11866 // text, for example). 11867 expandedTopChar = Math.min(expandedTopChar, text.length()); 11868 expandedBottomChar = Math.min(expandedBottomChar, text.length()); 11869 text = text.subSequence(expandedTopChar, expandedBottomChar); 11870 } 11871 11872 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11873 structure.setText(text); 11874 } else { 11875 structure.setText(text, 11876 selStart - expandedTopChar, 11877 selEnd - expandedTopChar); 11878 11879 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11880 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11881 final int baselineOffset = getBaselineOffset(); 11882 for (int i = topLine; i <= bottomLine; i++) { 11883 lineOffsets[i - topLine] = layout.getLineStart(i); 11884 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11885 } 11886 structure.setTextLines(lineOffsets, lineBaselines); 11887 } 11888 } 11889 } 11890 11891 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 11892 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11893 // Extract style information that applies to the TextView as a whole. 11894 int style = 0; 11895 int typefaceStyle = getTypefaceStyle(); 11896 if ((typefaceStyle & Typeface.BOLD) != 0) { 11897 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11898 } 11899 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11900 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11901 } 11902 11903 // Global styles can also be set via TextView.setPaintFlags(). 11904 int paintFlags = mTextPaint.getFlags(); 11905 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11906 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11907 } 11908 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11909 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11910 } 11911 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11912 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11913 } 11914 11915 // TextView does not have its own text background color. A background is either part 11916 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11917 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11918 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11919 } 11920 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11921 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11922 structure.setMinTextEms(getMinEms()); 11923 structure.setMaxTextEms(getMaxEms()); 11924 int maxLength = -1; 11925 for (InputFilter filter: getFilters()) { 11926 if (filter instanceof InputFilter.LengthFilter) { 11927 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11928 break; 11929 } 11930 } 11931 structure.setMaxTextLength(maxLength); 11932 } 11933 } 11934 if (mHintId != Resources.ID_NULL) { 11935 try { 11936 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 11937 } catch (Resources.NotFoundException e) { 11938 if (android.view.autofill.Helper.sVerbose) { 11939 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 11940 + mHintId + ": " + e.getMessage()); 11941 } 11942 } 11943 } 11944 structure.setHint(getHint()); 11945 structure.setInputType(getInputType()); 11946 } 11947 canRequestAutofill()11948 boolean canRequestAutofill() { 11949 if (!isAutofillable()) { 11950 return false; 11951 } 11952 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11953 if (afm != null) { 11954 return afm.isEnabled(); 11955 } 11956 return false; 11957 } 11958 requestAutofill()11959 private void requestAutofill() { 11960 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11961 if (afm != null) { 11962 afm.requestAutofill(this); 11963 } 11964 } 11965 11966 @Override autofill(AutofillValue value)11967 public void autofill(AutofillValue value) { 11968 if (!isTextEditable()) { 11969 Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); 11970 return; 11971 } 11972 if (!value.isText()) { 11973 Log.w(LOG_TAG, "value of type " + value.describeContents() 11974 + " cannot be autofilled into " + this); 11975 return; 11976 } 11977 final ClipData clip = ClipData.newPlainText("", value.getTextValue()); 11978 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 11979 performReceiveContent(payload); 11980 } 11981 11982 @Override getAutofillType()11983 public @AutofillType int getAutofillType() { 11984 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11985 } 11986 11987 /** 11988 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11989 * {@code char}s if longer. 11990 * 11991 * @return current text, {@code null} if the text is not editable 11992 * 11993 * @see View#getAutofillValue() 11994 */ 11995 @Override 11996 @Nullable getAutofillValue()11997 public AutofillValue getAutofillValue() { 11998 if (isTextEditable()) { 11999 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 12000 return AutofillValue.forText(text); 12001 } 12002 return null; 12003 } 12004 12005 /** @hide */ 12006 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)12007 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 12008 super.onInitializeAccessibilityEventInternal(event); 12009 12010 final boolean isPassword = hasPasswordTransformationMethod(); 12011 event.setPassword(isPassword); 12012 12013 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 12014 event.setFromIndex(Selection.getSelectionStart(mText)); 12015 event.setToIndex(Selection.getSelectionEnd(mText)); 12016 event.setItemCount(mText.length()); 12017 } 12018 } 12019 12020 /** @hide */ 12021 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)12022 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 12023 super.onInitializeAccessibilityNodeInfoInternal(info); 12024 12025 final boolean isPassword = hasPasswordTransformationMethod(); 12026 info.setPassword(isPassword); 12027 info.setText(getTextForAccessibility()); 12028 info.setHintText(mHint); 12029 info.setShowingHintText(isShowingHint()); 12030 12031 if (mBufferType == BufferType.EDITABLE) { 12032 info.setEditable(true); 12033 if (isEnabled()) { 12034 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 12035 } 12036 } 12037 12038 if (mEditor != null) { 12039 info.setInputType(mEditor.mInputType); 12040 12041 if (mEditor.mError != null) { 12042 info.setContentInvalid(true); 12043 info.setError(mEditor.mError); 12044 } 12045 // TextView will expose this action if it is editable and has focus. 12046 if (isTextEditable() && isFocused()) { 12047 CharSequence imeActionLabel = mContext.getResources().getString( 12048 com.android.internal.R.string.keyboardview_keycode_enter); 12049 if (getImeActionLabel() != null) { 12050 imeActionLabel = getImeActionLabel(); 12051 } 12052 AccessibilityNodeInfo.AccessibilityAction action = 12053 new AccessibilityNodeInfo.AccessibilityAction( 12054 R.id.accessibilityActionImeEnter, imeActionLabel); 12055 info.addAction(action); 12056 } 12057 } 12058 12059 if (!TextUtils.isEmpty(mText)) { 12060 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 12061 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 12062 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 12063 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 12064 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 12065 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 12066 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 12067 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 12068 info.setAvailableExtraData(Arrays.asList( 12069 EXTRA_DATA_RENDERING_INFO_KEY, 12070 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 12071 )); 12072 } else { 12073 info.setAvailableExtraData(Arrays.asList( 12074 EXTRA_DATA_RENDERING_INFO_KEY 12075 )); 12076 } 12077 12078 if (isFocused()) { 12079 if (canCopy()) { 12080 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 12081 } 12082 if (canPaste()) { 12083 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 12084 } 12085 if (canCut()) { 12086 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 12087 } 12088 if (canShare()) { 12089 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 12090 ACCESSIBILITY_ACTION_SHARE, 12091 getResources().getString(com.android.internal.R.string.share))); 12092 } 12093 if (canProcessText()) { // also implies mEditor is not null. 12094 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 12095 } 12096 } 12097 12098 // Check for known input filter types. 12099 final int numFilters = mFilters.length; 12100 for (int i = 0; i < numFilters; i++) { 12101 final InputFilter filter = mFilters[i]; 12102 if (filter instanceof InputFilter.LengthFilter) { 12103 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 12104 } 12105 } 12106 12107 if (!isSingleLine()) { 12108 info.setMultiLine(true); 12109 } 12110 12111 // A view should not be exposed as clickable/long-clickable to a service because of a 12112 // LinkMovementMethod. 12113 if ((info.isClickable() || info.isLongClickable()) 12114 && mMovement instanceof LinkMovementMethod) { 12115 if (!hasOnClickListeners()) { 12116 info.setClickable(false); 12117 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 12118 } 12119 if (!hasOnLongClickListeners()) { 12120 info.setLongClickable(false); 12121 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 12122 } 12123 } 12124 } 12125 12126 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)12127 public void addExtraDataToAccessibilityNodeInfo( 12128 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 12129 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 12130 int positionInfoStartIndex = arguments.getInt( 12131 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 12132 int positionInfoLength = arguments.getInt( 12133 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 12134 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 12135 || (positionInfoStartIndex >= mText.length())) { 12136 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 12137 return; 12138 } 12139 RectF[] boundingRects = new RectF[positionInfoLength]; 12140 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 12141 populateCharacterBounds(builder, positionInfoStartIndex, 12142 positionInfoStartIndex + positionInfoLength, 12143 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 12144 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 12145 for (int i = 0; i < positionInfoLength; i++) { 12146 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 12147 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 12148 RectF bounds = cursorAnchorInfo 12149 .getCharacterBounds(positionInfoStartIndex + i); 12150 if (bounds != null) { 12151 mapRectFromViewToScreenCoords(bounds, true); 12152 boundingRects[i] = bounds; 12153 } 12154 } 12155 } 12156 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 12157 return; 12158 } 12159 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 12160 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 12161 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 12162 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 12163 extraRenderingInfo.setTextSizeInPx(getTextSize()); 12164 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 12165 info.setExtraRenderingInfo(extraRenderingInfo); 12166 } 12167 } 12168 12169 /** 12170 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 12171 * 12172 * @param builder The builder to populate 12173 * @param startIndex The starting character index to populate 12174 * @param endIndex The ending character index to populate 12175 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 12176 * content 12177 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 12178 * @hide 12179 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12180 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 12181 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 12182 float viewportToContentVerticalOffset) { 12183 final int minLine = mLayout.getLineForOffset(startIndex); 12184 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 12185 for (int line = minLine; line <= maxLine; ++line) { 12186 final int lineStart = mLayout.getLineStart(line); 12187 final int lineEnd = mLayout.getLineEnd(line); 12188 final int offsetStart = Math.max(lineStart, startIndex); 12189 final int offsetEnd = Math.min(lineEnd, endIndex); 12190 final boolean ltrLine = 12191 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 12192 final float[] widths = new float[offsetEnd - offsetStart]; 12193 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 12194 final float top = mLayout.getLineTop(line); 12195 final float bottom = mLayout.getLineBottom(line); 12196 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 12197 final float charWidth = widths[offset - offsetStart]; 12198 final boolean isRtl = mLayout.isRtlCharAt(offset); 12199 final float primary = mLayout.getPrimaryHorizontal(offset); 12200 final float secondary = mLayout.getSecondaryHorizontal(offset); 12201 // TODO: This doesn't work perfectly for text with custom styles and 12202 // TAB chars. 12203 final float left; 12204 final float right; 12205 if (ltrLine) { 12206 if (isRtl) { 12207 left = secondary - charWidth; 12208 right = secondary; 12209 } else { 12210 left = primary; 12211 right = primary + charWidth; 12212 } 12213 } else { 12214 if (!isRtl) { 12215 left = secondary; 12216 right = secondary + charWidth; 12217 } else { 12218 left = primary - charWidth; 12219 right = primary; 12220 } 12221 } 12222 // TODO: Check top-right and bottom-left as well. 12223 final float localLeft = left + viewportToContentHorizontalOffset; 12224 final float localRight = right + viewportToContentHorizontalOffset; 12225 final float localTop = top + viewportToContentVerticalOffset; 12226 final float localBottom = bottom + viewportToContentVerticalOffset; 12227 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 12228 final boolean isBottomRightVisible = 12229 isPositionVisible(localRight, localBottom); 12230 int characterBoundsFlags = 0; 12231 if (isTopLeftVisible || isBottomRightVisible) { 12232 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 12233 } 12234 if (!isTopLeftVisible || !isBottomRightVisible) { 12235 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 12236 } 12237 if (isRtl) { 12238 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 12239 } 12240 // Here offset is the index in Java chars. 12241 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 12242 localBottom, characterBoundsFlags); 12243 } 12244 } 12245 } 12246 12247 /** 12248 * @hide 12249 */ isPositionVisible(final float positionX, final float positionY)12250 public boolean isPositionVisible(final float positionX, final float positionY) { 12251 synchronized (TEMP_POSITION) { 12252 final float[] position = TEMP_POSITION; 12253 position[0] = positionX; 12254 position[1] = positionY; 12255 View view = this; 12256 12257 while (view != null) { 12258 if (view != this) { 12259 // Local scroll is already taken into account in positionX/Y 12260 position[0] -= view.getScrollX(); 12261 position[1] -= view.getScrollY(); 12262 } 12263 12264 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 12265 || position[1] > view.getHeight()) { 12266 return false; 12267 } 12268 12269 if (!view.getMatrix().isIdentity()) { 12270 view.getMatrix().mapPoints(position); 12271 } 12272 12273 position[0] += view.getLeft(); 12274 position[1] += view.getTop(); 12275 12276 final ViewParent parent = view.getParent(); 12277 if (parent instanceof View) { 12278 view = (View) parent; 12279 } else { 12280 // We've reached the ViewRoot, stop iterating 12281 view = null; 12282 } 12283 } 12284 } 12285 12286 // We've been able to walk up the view hierarchy and the position was never clipped 12287 return true; 12288 } 12289 12290 /** 12291 * Performs an accessibility action after it has been offered to the 12292 * delegate. 12293 * 12294 * @hide 12295 */ 12296 @Override performAccessibilityActionInternal(int action, Bundle arguments)12297 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 12298 if (mEditor != null 12299 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 12300 return true; 12301 } 12302 switch (action) { 12303 case AccessibilityNodeInfo.ACTION_CLICK: { 12304 return performAccessibilityActionClick(arguments); 12305 } 12306 case AccessibilityNodeInfo.ACTION_COPY: { 12307 if (isFocused() && canCopy()) { 12308 if (onTextContextMenuItem(ID_COPY)) { 12309 return true; 12310 } 12311 } 12312 } return false; 12313 case AccessibilityNodeInfo.ACTION_PASTE: { 12314 if (isFocused() && canPaste()) { 12315 if (onTextContextMenuItem(ID_PASTE)) { 12316 return true; 12317 } 12318 } 12319 } return false; 12320 case AccessibilityNodeInfo.ACTION_CUT: { 12321 if (isFocused() && canCut()) { 12322 if (onTextContextMenuItem(ID_CUT)) { 12323 return true; 12324 } 12325 } 12326 } return false; 12327 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 12328 ensureIterableTextForAccessibilitySelectable(); 12329 CharSequence text = getIterableTextForAccessibility(); 12330 if (text == null) { 12331 return false; 12332 } 12333 final int start = (arguments != null) ? arguments.getInt( 12334 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 12335 final int end = (arguments != null) ? arguments.getInt( 12336 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 12337 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 12338 // No arguments clears the selection. 12339 if (start == end && end == -1) { 12340 Selection.removeSelection((Spannable) text); 12341 return true; 12342 } 12343 if (start >= 0 && start <= end && end <= text.length()) { 12344 Selection.setSelection((Spannable) text, start, end); 12345 // Make sure selection mode is engaged. 12346 if (mEditor != null) { 12347 mEditor.startSelectionActionModeAsync(false); 12348 } 12349 return true; 12350 } 12351 } 12352 } return false; 12353 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 12354 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 12355 ensureIterableTextForAccessibilitySelectable(); 12356 return super.performAccessibilityActionInternal(action, arguments); 12357 } 12358 case ACCESSIBILITY_ACTION_SHARE: { 12359 if (isFocused() && canShare()) { 12360 if (onTextContextMenuItem(ID_SHARE)) { 12361 return true; 12362 } 12363 } 12364 } return false; 12365 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 12366 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 12367 return false; 12368 } 12369 CharSequence text = (arguments != null) ? arguments.getCharSequence( 12370 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 12371 setText(text); 12372 if (mText != null) { 12373 int updatedTextLength = mText.length(); 12374 if (updatedTextLength > 0) { 12375 Selection.setSelection(mSpannable, updatedTextLength); 12376 } 12377 } 12378 } return true; 12379 case R.id.accessibilityActionImeEnter: { 12380 if (isFocused() && isTextEditable()) { 12381 onEditorAction(getImeActionId()); 12382 } 12383 } return true; 12384 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 12385 if (isLongClickable()) { 12386 boolean handled; 12387 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 12388 mEditor.mIsBeingLongClickedByAccessibility = true; 12389 try { 12390 handled = performLongClick(); 12391 } finally { 12392 mEditor.mIsBeingLongClickedByAccessibility = false; 12393 } 12394 } else { 12395 handled = performLongClick(); 12396 } 12397 return handled; 12398 } 12399 } 12400 return false; 12401 default: { 12402 return super.performAccessibilityActionInternal(action, arguments); 12403 } 12404 } 12405 } 12406 performAccessibilityActionClick(Bundle arguments)12407 private boolean performAccessibilityActionClick(Bundle arguments) { 12408 boolean handled = false; 12409 12410 if (!isEnabled()) { 12411 return false; 12412 } 12413 12414 if (isClickable() || isLongClickable()) { 12415 // Simulate View.onTouchEvent for an ACTION_UP event 12416 if (isFocusable() && !isFocused()) { 12417 requestFocus(); 12418 } 12419 12420 performClick(); 12421 handled = true; 12422 } 12423 12424 // Show the IME, except when selecting in read-only text. 12425 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12426 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12427 final InputMethodManager imm = getInputMethodManager(); 12428 viewClicked(imm); 12429 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12430 handled |= imm.showSoftInput(this, 0); 12431 } 12432 } 12433 12434 return handled; 12435 } 12436 hasSpannableText()12437 private boolean hasSpannableText() { 12438 return mText != null && mText instanceof Spannable; 12439 } 12440 12441 /** @hide */ 12442 @Override sendAccessibilityEventInternal(int eventType)12443 public void sendAccessibilityEventInternal(int eventType) { 12444 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12445 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12446 } 12447 12448 super.sendAccessibilityEventInternal(eventType); 12449 } 12450 12451 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12452 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12453 // Do not send scroll events since first they are not interesting for 12454 // accessibility and second such events a generated too frequently. 12455 // For details see the implementation of bringTextIntoView(). 12456 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12457 return; 12458 } 12459 super.sendAccessibilityEventUnchecked(event); 12460 } 12461 12462 /** 12463 * Returns the text that should be exposed to accessibility services. 12464 * <p> 12465 * This approximates what is displayed visually. If the user has specified 12466 * that accessibility services should speak passwords, this method will 12467 * bypass any password transformation method and return unobscured text. 12468 * 12469 * @return the text that should be exposed to accessibility services, may 12470 * be {@code null} if no text is set 12471 */ 12472 @Nullable 12473 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTextForAccessibility()12474 private CharSequence getTextForAccessibility() { 12475 // If the text is empty, we must be showing the hint text. 12476 if (TextUtils.isEmpty(mText)) { 12477 return mHint; 12478 } 12479 12480 // Otherwise, return whatever text is being displayed. 12481 return TextUtils.trimToParcelableSize(mTransformed); 12482 } 12483 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12484 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12485 int fromIndex, int removedCount, int addedCount) { 12486 AccessibilityEvent event = 12487 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12488 event.setFromIndex(fromIndex); 12489 event.setRemovedCount(removedCount); 12490 event.setAddedCount(addedCount); 12491 event.setBeforeText(beforeText); 12492 sendAccessibilityEventUnchecked(event); 12493 } 12494 getInputMethodManager()12495 private InputMethodManager getInputMethodManager() { 12496 return getContext().getSystemService(InputMethodManager.class); 12497 } 12498 12499 /** 12500 * Returns whether this text view is a current input method target. The 12501 * default implementation just checks with {@link InputMethodManager}. 12502 * @return True if the TextView is a current input method target; false otherwise. 12503 */ isInputMethodTarget()12504 public boolean isInputMethodTarget() { 12505 InputMethodManager imm = getInputMethodManager(); 12506 return imm != null && imm.isActive(this); 12507 } 12508 12509 static final int ID_SELECT_ALL = android.R.id.selectAll; 12510 static final int ID_UNDO = android.R.id.undo; 12511 static final int ID_REDO = android.R.id.redo; 12512 static final int ID_CUT = android.R.id.cut; 12513 static final int ID_COPY = android.R.id.copy; 12514 static final int ID_PASTE = android.R.id.paste; 12515 static final int ID_SHARE = android.R.id.shareText; 12516 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12517 static final int ID_REPLACE = android.R.id.replaceText; 12518 static final int ID_ASSIST = android.R.id.textAssist; 12519 static final int ID_AUTOFILL = android.R.id.autofill; 12520 12521 /** 12522 * Called when a context menu option for the text view is selected. Currently 12523 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12524 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12525 * 12526 * @return true if the context menu item action was performed. 12527 */ onTextContextMenuItem(int id)12528 public boolean onTextContextMenuItem(int id) { 12529 int min = 0; 12530 int max = mText.length(); 12531 12532 if (isFocused()) { 12533 final int selStart = getSelectionStart(); 12534 final int selEnd = getSelectionEnd(); 12535 12536 min = Math.max(0, Math.min(selStart, selEnd)); 12537 max = Math.max(0, Math.max(selStart, selEnd)); 12538 } 12539 12540 switch (id) { 12541 case ID_SELECT_ALL: 12542 final boolean hadSelection = hasSelection(); 12543 selectAllText(); 12544 if (mEditor != null && hadSelection) { 12545 mEditor.invalidateActionModeAsync(); 12546 } 12547 return true; 12548 12549 case ID_UNDO: 12550 if (mEditor != null) { 12551 mEditor.undo(); 12552 } 12553 return true; // Returns true even if nothing was undone. 12554 12555 case ID_REDO: 12556 if (mEditor != null) { 12557 mEditor.redo(); 12558 } 12559 return true; // Returns true even if nothing was undone. 12560 12561 case ID_PASTE: 12562 paste(true /* withFormatting */); 12563 return true; 12564 12565 case ID_PASTE_AS_PLAIN_TEXT: 12566 paste(false /* withFormatting */); 12567 return true; 12568 12569 case ID_CUT: 12570 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12571 if (setPrimaryClip(cutData)) { 12572 deleteText_internal(min, max); 12573 } else { 12574 Toast.makeText(getContext(), 12575 com.android.internal.R.string.failed_to_copy_to_clipboard, 12576 Toast.LENGTH_SHORT).show(); 12577 } 12578 return true; 12579 12580 case ID_COPY: 12581 // For link action mode in a non-selectable/non-focusable TextView, 12582 // make sure that we set the appropriate min/max. 12583 final int selStart = getSelectionStart(); 12584 final int selEnd = getSelectionEnd(); 12585 min = Math.max(0, Math.min(selStart, selEnd)); 12586 max = Math.max(0, Math.max(selStart, selEnd)); 12587 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12588 if (setPrimaryClip(copyData)) { 12589 stopTextActionMode(); 12590 } else { 12591 Toast.makeText(getContext(), 12592 com.android.internal.R.string.failed_to_copy_to_clipboard, 12593 Toast.LENGTH_SHORT).show(); 12594 } 12595 return true; 12596 12597 case ID_REPLACE: 12598 if (mEditor != null) { 12599 mEditor.replace(); 12600 } 12601 return true; 12602 12603 case ID_SHARE: 12604 shareSelectedText(); 12605 return true; 12606 12607 case ID_AUTOFILL: 12608 requestAutofill(); 12609 stopTextActionMode(); 12610 return true; 12611 } 12612 return false; 12613 } 12614 12615 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTransformedText(int start, int end)12616 CharSequence getTransformedText(int start, int end) { 12617 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12618 } 12619 12620 @Override performLongClick()12621 public boolean performLongClick() { 12622 if (DEBUG_CURSOR) { 12623 logCursor("performLongClick", null); 12624 } 12625 12626 boolean handled = false; 12627 boolean performedHapticFeedback = false; 12628 12629 if (mEditor != null) { 12630 mEditor.mIsBeingLongClicked = true; 12631 } 12632 12633 if (super.performLongClick()) { 12634 handled = true; 12635 performedHapticFeedback = true; 12636 } 12637 12638 if (mEditor != null) { 12639 handled |= mEditor.performLongClick(handled); 12640 mEditor.mIsBeingLongClicked = false; 12641 } 12642 12643 if (handled) { 12644 if (!performedHapticFeedback) { 12645 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12646 } 12647 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12648 } else { 12649 MetricsLogger.action( 12650 mContext, 12651 MetricsEvent.TEXT_LONGPRESS, 12652 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12653 } 12654 12655 return handled; 12656 } 12657 12658 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12659 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12660 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12661 if (mEditor != null) { 12662 mEditor.onScrollChanged(); 12663 } 12664 } 12665 12666 /** 12667 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12668 * by the IME or by the spell checker as the user types. This is done by adding 12669 * {@link SuggestionSpan}s to the text. 12670 * 12671 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12672 * user asks for them on these parts of the text. This value depends on the inputType of this 12673 * TextView. 12674 * 12675 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12676 * 12677 * In addition, the type variation must be one of 12678 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12679 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12680 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12681 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12682 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12683 * 12684 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12685 * 12686 * @return true if the suggestions popup window is enabled, based on the inputType. 12687 */ isSuggestionsEnabled()12688 public boolean isSuggestionsEnabled() { 12689 if (mEditor == null) return false; 12690 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12691 return false; 12692 } 12693 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12694 12695 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12696 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12697 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12698 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12699 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12700 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12701 } 12702 12703 /** 12704 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12705 * selection is initiated in this View. 12706 * 12707 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12708 * Paste, Replace and Share actions, depending on what this View supports. 12709 * 12710 * <p>A custom implementation can add new entries in the default menu in its 12711 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12712 * method. The default actions can also be removed from the menu using 12713 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12714 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12715 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12716 * 12717 * <p>Returning false from 12718 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12719 * will prevent the action mode from being started. 12720 * 12721 * <p>Action click events should be handled by the custom implementation of 12722 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12723 * android.view.MenuItem)}. 12724 * 12725 * <p>Note that text selection mode is not started when a TextView receives focus and the 12726 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12727 * that case, to allow for quick replacement. 12728 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12729 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12730 createEditorIfNeeded(); 12731 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12732 } 12733 12734 /** 12735 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12736 * 12737 * @return The current custom selection callback. 12738 */ getCustomSelectionActionModeCallback()12739 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12740 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12741 } 12742 12743 /** 12744 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12745 * insertion is initiated in this View. 12746 * The standard implementation populates the menu with a subset of Select All, 12747 * Paste and Replace actions, depending on what this View supports. 12748 * 12749 * <p>A custom implementation can add new entries in the default menu in its 12750 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12751 * android.view.Menu)} method. The default actions can also be removed from the menu using 12752 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12753 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12754 * 12755 * <p>Returning false from 12756 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12757 * android.view.Menu)} will prevent the action mode from being started.</p> 12758 * 12759 * <p>Action click events should be handled by the custom implementation of 12760 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12761 * android.view.MenuItem)}.</p> 12762 * 12763 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12764 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12765 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12766 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12767 createEditorIfNeeded(); 12768 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12769 } 12770 12771 /** 12772 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12773 * 12774 * @return The current custom insertion callback. 12775 */ getCustomInsertionActionModeCallback()12776 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12777 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12778 } 12779 12780 /** 12781 * Sets the {@link TextClassifier} for this TextView. 12782 */ setTextClassifier(@ullable TextClassifier textClassifier)12783 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12784 mTextClassifier = textClassifier; 12785 } 12786 12787 /** 12788 * Returns the {@link TextClassifier} used by this TextView. 12789 * If no TextClassifier has been set, this TextView uses the default set by the 12790 * {@link TextClassificationManager}. 12791 */ 12792 @NonNull getTextClassifier()12793 public TextClassifier getTextClassifier() { 12794 if (mTextClassifier == null) { 12795 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12796 if (tcm != null) { 12797 return tcm.getTextClassifier(); 12798 } 12799 return TextClassifier.NO_OP; 12800 } 12801 return mTextClassifier; 12802 } 12803 12804 /** 12805 * Returns a session-aware text classifier. 12806 * This method creates one if none already exists or the current one is destroyed. 12807 */ 12808 @NonNull getTextClassificationSession()12809 TextClassifier getTextClassificationSession() { 12810 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12811 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12812 if (tcm != null) { 12813 final String widgetType; 12814 if (isTextEditable()) { 12815 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12816 } else if (isTextSelectable()) { 12817 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12818 } else { 12819 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12820 } 12821 mTextClassificationContext = new TextClassificationContext.Builder( 12822 mContext.getPackageName(), widgetType) 12823 .build(); 12824 if (mTextClassifier != null) { 12825 mTextClassificationSession = tcm.createTextClassificationSession( 12826 mTextClassificationContext, mTextClassifier); 12827 } else { 12828 mTextClassificationSession = tcm.createTextClassificationSession( 12829 mTextClassificationContext); 12830 } 12831 } else { 12832 mTextClassificationSession = TextClassifier.NO_OP; 12833 } 12834 } 12835 return mTextClassificationSession; 12836 } 12837 12838 /** 12839 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12840 * @see #getTextClassificationSession() 12841 */ 12842 @Nullable getTextClassificationContext()12843 TextClassificationContext getTextClassificationContext() { 12844 return mTextClassificationContext; 12845 } 12846 12847 /** 12848 * Returns true if this TextView uses a no-op TextClassifier. 12849 */ usesNoOpTextClassifier()12850 boolean usesNoOpTextClassifier() { 12851 return getTextClassifier() == TextClassifier.NO_OP; 12852 } 12853 12854 /** 12855 * Starts an ActionMode for the specified TextLinkSpan. 12856 * 12857 * @return Whether or not we're attempting to start the action mode. 12858 * @hide 12859 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12860 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12861 Preconditions.checkNotNull(clickedSpan); 12862 12863 if (!(mText instanceof Spanned)) { 12864 return false; 12865 } 12866 12867 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12868 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12869 12870 if (start < 0 || end > mText.length() || start >= end) { 12871 return false; 12872 } 12873 12874 createEditorIfNeeded(); 12875 mEditor.startLinkActionModeAsync(start, end); 12876 return true; 12877 } 12878 12879 /** 12880 * Handles a click on the specified TextLinkSpan. 12881 * 12882 * @return Whether or not the click is being handled. 12883 * @hide 12884 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12885 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12886 Preconditions.checkNotNull(clickedSpan); 12887 if (mText instanceof Spanned) { 12888 final Spanned spanned = (Spanned) mText; 12889 final int start = spanned.getSpanStart(clickedSpan); 12890 final int end = spanned.getSpanEnd(clickedSpan); 12891 if (start >= 0 && end <= mText.length() && start < end) { 12892 final TextClassification.Request request = new TextClassification.Request.Builder( 12893 mText, start, end) 12894 .setDefaultLocales(getTextLocales()) 12895 .build(); 12896 final Supplier<TextClassification> supplier = () -> 12897 getTextClassificationSession().classifyText(request); 12898 final Consumer<TextClassification> consumer = classification -> { 12899 if (classification != null) { 12900 if (!classification.getActions().isEmpty()) { 12901 try { 12902 classification.getActions().get(0).getActionIntent().send(); 12903 } catch (PendingIntent.CanceledException e) { 12904 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12905 } 12906 } else { 12907 Log.d(LOG_TAG, "No link action to perform"); 12908 } 12909 } else { 12910 // classification == null 12911 Log.d(LOG_TAG, "Timeout while classifying text"); 12912 } 12913 }; 12914 CompletableFuture.supplyAsync(supplier) 12915 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12916 .thenAccept(consumer); 12917 return true; 12918 } 12919 } 12920 return false; 12921 } 12922 12923 /** 12924 * @hide 12925 */ 12926 @UnsupportedAppUsage stopTextActionMode()12927 protected void stopTextActionMode() { 12928 if (mEditor != null) { 12929 mEditor.stopTextActionMode(); 12930 } 12931 } 12932 12933 /** @hide */ hideFloatingToolbar(int durationMs)12934 public void hideFloatingToolbar(int durationMs) { 12935 if (mEditor != null) { 12936 mEditor.hideFloatingToolbar(durationMs); 12937 } 12938 } 12939 canUndo()12940 boolean canUndo() { 12941 return mEditor != null && mEditor.canUndo(); 12942 } 12943 canRedo()12944 boolean canRedo() { 12945 return mEditor != null && mEditor.canRedo(); 12946 } 12947 canCut()12948 boolean canCut() { 12949 if (hasPasswordTransformationMethod()) { 12950 return false; 12951 } 12952 12953 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12954 && mEditor.mKeyListener != null) { 12955 return true; 12956 } 12957 12958 return false; 12959 } 12960 canCopy()12961 boolean canCopy() { 12962 if (hasPasswordTransformationMethod()) { 12963 return false; 12964 } 12965 12966 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12967 return true; 12968 } 12969 12970 return false; 12971 } 12972 canShare()12973 boolean canShare() { 12974 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12975 return false; 12976 } 12977 return canCopy(); 12978 } 12979 isDeviceProvisioned()12980 boolean isDeviceProvisioned() { 12981 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12982 mDeviceProvisionedState = Settings.Global.getInt( 12983 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12984 ? DEVICE_PROVISIONED_YES 12985 : DEVICE_PROVISIONED_NO; 12986 } 12987 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12988 } 12989 12990 @UnsupportedAppUsage canPaste()12991 boolean canPaste() { 12992 return (mText instanceof Editable 12993 && mEditor != null && mEditor.mKeyListener != null 12994 && getSelectionStart() >= 0 12995 && getSelectionEnd() >= 0 12996 && getClipboardManagerForUser().hasPrimaryClip()); 12997 } 12998 canPasteAsPlainText()12999 boolean canPasteAsPlainText() { 13000 if (!canPaste()) { 13001 return false; 13002 } 13003 13004 final ClipDescription description = 13005 getClipboardManagerForUser().getPrimaryClipDescription(); 13006 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 13007 return (isPlainType && description.isStyledText()) 13008 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 13009 } 13010 canProcessText()13011 boolean canProcessText() { 13012 if (getId() == View.NO_ID) { 13013 return false; 13014 } 13015 return canShare(); 13016 } 13017 canSelectAllText()13018 boolean canSelectAllText() { 13019 return canSelectText() && !hasPasswordTransformationMethod() 13020 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 13021 } 13022 selectAllText()13023 boolean selectAllText() { 13024 if (mEditor != null) { 13025 // Hide the toolbar before changing the selection to avoid flickering. 13026 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 13027 } 13028 final int length = mText.length(); 13029 Selection.setSelection(mSpannable, 0, length); 13030 return length > 0; 13031 } 13032 paste(boolean withFormatting)13033 private void paste(boolean withFormatting) { 13034 ClipboardManager clipboard = getClipboardManagerForUser(); 13035 ClipData clip = clipboard.getPrimaryClip(); 13036 if (clip == null) { 13037 return; 13038 } 13039 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) 13040 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) 13041 .build(); 13042 performReceiveContent(payload); 13043 sLastCutCopyOrTextChangedTime = 0; 13044 } 13045 shareSelectedText()13046 private void shareSelectedText() { 13047 String selectedText = getSelectedText(); 13048 if (selectedText != null && !selectedText.isEmpty()) { 13049 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 13050 sharingIntent.setType("text/plain"); 13051 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 13052 selectedText = TextUtils.trimToParcelableSize(selectedText); 13053 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 13054 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 13055 Selection.setSelection(mSpannable, getSelectionEnd()); 13056 } 13057 } 13058 13059 @CheckResult setPrimaryClip(ClipData clip)13060 private boolean setPrimaryClip(ClipData clip) { 13061 ClipboardManager clipboard = getClipboardManagerForUser(); 13062 try { 13063 clipboard.setPrimaryClip(clip); 13064 } catch (Throwable t) { 13065 return false; 13066 } 13067 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 13068 return true; 13069 } 13070 13071 /** 13072 * Get the character offset closest to the specified absolute position. A typical use case is to 13073 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 13074 * 13075 * @param x The horizontal absolute position of a point on screen 13076 * @param y The vertical absolute position of a point on screen 13077 * @return the character offset for the character whose position is closest to the specified 13078 * position. Returns -1 if there is no layout. 13079 */ getOffsetForPosition(float x, float y)13080 public int getOffsetForPosition(float x, float y) { 13081 if (getLayout() == null) return -1; 13082 final int line = getLineAtCoordinate(y); 13083 final int offset = getOffsetAtCoordinate(line, x); 13084 return offset; 13085 } 13086 convertToLocalHorizontalCoordinate(float x)13087 float convertToLocalHorizontalCoordinate(float x) { 13088 x -= getTotalPaddingLeft(); 13089 // Clamp the position to inside of the view. 13090 x = Math.max(0.0f, x); 13091 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 13092 x += getScrollX(); 13093 return x; 13094 } 13095 13096 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getLineAtCoordinate(float y)13097 int getLineAtCoordinate(float y) { 13098 y -= getTotalPaddingTop(); 13099 // Clamp the position to inside of the view. 13100 y = Math.max(0.0f, y); 13101 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 13102 y += getScrollY(); 13103 return getLayout().getLineForVertical((int) y); 13104 } 13105 getLineAtCoordinateUnclamped(float y)13106 int getLineAtCoordinateUnclamped(float y) { 13107 y -= getTotalPaddingTop(); 13108 y += getScrollY(); 13109 return getLayout().getLineForVertical((int) y); 13110 } 13111 getOffsetAtCoordinate(int line, float x)13112 int getOffsetAtCoordinate(int line, float x) { 13113 x = convertToLocalHorizontalCoordinate(x); 13114 return getLayout().getOffsetForHorizontal(line, x); 13115 } 13116 13117 /** 13118 * Handles drag events sent by the system following a call to 13119 * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) 13120 * startDragAndDrop()}. 13121 * 13122 * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent} 13123 * implementation. 13124 * 13125 * <p>If this text view is editable, accepts all drag actions (returns true for an 13126 * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all 13127 * subsequent drag events). While the drag is in progress, updates the cursor position 13128 * to follow the touch location. Once a drop event is received, handles content insertion 13129 * via {@link #performReceiveContent}. 13130 * 13131 * @param event The {@link android.view.DragEvent} sent by the system. 13132 * The {@link android.view.DragEvent#getAction()} method returns an action type constant 13133 * defined in DragEvent, indicating the type of drag event represented by this object. 13134 * @return Returns true if this text view is editable and delegates to super otherwise. 13135 * See {@link View#onDragEvent}. 13136 */ 13137 @Override onDragEvent(DragEvent event)13138 public boolean onDragEvent(DragEvent event) { 13139 if (mEditor == null || !mEditor.hasInsertionController()) { 13140 // If this TextView is not editable, defer to the default View implementation. This 13141 // will check for the presence of an OnReceiveContentListener and accept/reject 13142 // drag events depending on whether the listener is/isn't set. 13143 return super.onDragEvent(event); 13144 } 13145 switch (event.getAction()) { 13146 case DragEvent.ACTION_DRAG_STARTED: 13147 return true; 13148 13149 case DragEvent.ACTION_DRAG_ENTERED: 13150 TextView.this.requestFocus(); 13151 return true; 13152 13153 case DragEvent.ACTION_DRAG_LOCATION: 13154 if (mText instanceof Spannable) { 13155 final int offset = getOffsetForPosition(event.getX(), event.getY()); 13156 Selection.setSelection(mSpannable, offset); 13157 } 13158 return true; 13159 13160 case DragEvent.ACTION_DROP: 13161 if (mEditor != null) mEditor.onDrop(event); 13162 return true; 13163 13164 case DragEvent.ACTION_DRAG_ENDED: 13165 case DragEvent.ACTION_DRAG_EXITED: 13166 default: 13167 return true; 13168 } 13169 } 13170 isInBatchEditMode()13171 boolean isInBatchEditMode() { 13172 if (mEditor == null) return false; 13173 final Editor.InputMethodState ims = mEditor.mInputMethodState; 13174 if (ims != null) { 13175 return ims.mBatchEditNesting > 0; 13176 } 13177 return mEditor.mInBatchEditControllers; 13178 } 13179 13180 @Override onRtlPropertiesChanged(int layoutDirection)13181 public void onRtlPropertiesChanged(int layoutDirection) { 13182 super.onRtlPropertiesChanged(layoutDirection); 13183 13184 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 13185 if (mTextDir != newTextDir) { 13186 mTextDir = newTextDir; 13187 if (mLayout != null) { 13188 checkForRelayout(); 13189 } 13190 } 13191 } 13192 13193 /** 13194 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 13195 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 13196 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 13197 * return value may not be the same as the one TextView uses if the View's layout direction is 13198 * not resolved or detached from parent root view. 13199 */ getTextDirectionHeuristic()13200 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 13201 if (hasPasswordTransformationMethod()) { 13202 // passwords fields should be LTR 13203 return TextDirectionHeuristics.LTR; 13204 } 13205 13206 if (mEditor != null 13207 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 13208 == EditorInfo.TYPE_CLASS_PHONE) { 13209 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 13210 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 13211 // RTL digits. 13212 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 13213 final String zero = symbols.getDigitStrings()[0]; 13214 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 13215 // direction. 13216 final int firstCodepoint = zero.codePointAt(0); 13217 final byte digitDirection = Character.getDirectionality(firstCodepoint); 13218 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 13219 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 13220 return TextDirectionHeuristics.RTL; 13221 } else { 13222 return TextDirectionHeuristics.LTR; 13223 } 13224 } 13225 13226 // Always need to resolve layout direction first 13227 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 13228 13229 // Now, we can select the heuristic 13230 switch (getTextDirection()) { 13231 default: 13232 case TEXT_DIRECTION_FIRST_STRONG: 13233 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 13234 TextDirectionHeuristics.FIRSTSTRONG_LTR); 13235 case TEXT_DIRECTION_ANY_RTL: 13236 return TextDirectionHeuristics.ANYRTL_LTR; 13237 case TEXT_DIRECTION_LTR: 13238 return TextDirectionHeuristics.LTR; 13239 case TEXT_DIRECTION_RTL: 13240 return TextDirectionHeuristics.RTL; 13241 case TEXT_DIRECTION_LOCALE: 13242 return TextDirectionHeuristics.LOCALE; 13243 case TEXT_DIRECTION_FIRST_STRONG_LTR: 13244 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 13245 case TEXT_DIRECTION_FIRST_STRONG_RTL: 13246 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 13247 } 13248 } 13249 13250 /** 13251 * @hide 13252 */ 13253 @Override onResolveDrawables(int layoutDirection)13254 public void onResolveDrawables(int layoutDirection) { 13255 // No need to resolve twice 13256 if (mLastLayoutDirection == layoutDirection) { 13257 return; 13258 } 13259 mLastLayoutDirection = layoutDirection; 13260 13261 // Resolve drawables 13262 if (mDrawables != null) { 13263 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 13264 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 13265 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 13266 applyCompoundDrawableTint(); 13267 } 13268 } 13269 } 13270 13271 /** 13272 * Prepares a drawable for display by propagating layout direction and 13273 * drawable state. 13274 * 13275 * @param dr the drawable to prepare 13276 */ prepareDrawableForDisplay(@ullable Drawable dr)13277 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 13278 if (dr == null) { 13279 return; 13280 } 13281 13282 dr.setLayoutDirection(getLayoutDirection()); 13283 13284 if (dr.isStateful()) { 13285 dr.setState(getDrawableState()); 13286 dr.jumpToCurrentState(); 13287 } 13288 } 13289 13290 /** 13291 * @hide 13292 */ resetResolvedDrawables()13293 protected void resetResolvedDrawables() { 13294 super.resetResolvedDrawables(); 13295 mLastLayoutDirection = -1; 13296 } 13297 13298 /** 13299 * @hide 13300 */ viewClicked(InputMethodManager imm)13301 protected void viewClicked(InputMethodManager imm) { 13302 if (imm != null) { 13303 imm.viewClicked(this); 13304 } 13305 } 13306 13307 /** 13308 * Deletes the range of text [start, end[. 13309 * @hide 13310 */ 13311 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) deleteText_internal(int start, int end)13312 protected void deleteText_internal(int start, int end) { 13313 ((Editable) mText).delete(start, end); 13314 } 13315 13316 /** 13317 * Replaces the range of text [start, end[ by replacement text 13318 * @hide 13319 */ replaceText_internal(int start, int end, CharSequence text)13320 protected void replaceText_internal(int start, int end, CharSequence text) { 13321 ((Editable) mText).replace(start, end, text); 13322 } 13323 13324 /** 13325 * Sets a span on the specified range of text 13326 * @hide 13327 */ setSpan_internal(Object span, int start, int end, int flags)13328 protected void setSpan_internal(Object span, int start, int end, int flags) { 13329 ((Editable) mText).setSpan(span, start, end, flags); 13330 } 13331 13332 /** 13333 * Moves the cursor to the specified offset position in text 13334 * @hide 13335 */ setCursorPosition_internal(int start, int end)13336 protected void setCursorPosition_internal(int start, int end) { 13337 Selection.setSelection(((Editable) mText), start, end); 13338 } 13339 13340 /** 13341 * An Editor should be created as soon as any of the editable-specific fields (grouped 13342 * inside the Editor object) is assigned to a non-default value. 13343 * This method will create the Editor if needed. 13344 * 13345 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 13346 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 13347 * Editor for backward compatibility, as soon as one of these fields is assigned. 13348 * 13349 * Also note that for performance reasons, the mEditor is created when needed, but not 13350 * reset when no more edit-specific fields are needed. 13351 */ 13352 @UnsupportedAppUsage createEditorIfNeeded()13353 private void createEditorIfNeeded() { 13354 if (mEditor == null) { 13355 mEditor = new Editor(this); 13356 } 13357 } 13358 13359 /** 13360 * @hide 13361 */ 13362 @Override 13363 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getIterableTextForAccessibility()13364 public CharSequence getIterableTextForAccessibility() { 13365 return mText; 13366 } 13367 ensureIterableTextForAccessibilitySelectable()13368 private void ensureIterableTextForAccessibilitySelectable() { 13369 if (!(mText instanceof Spannable)) { 13370 setText(mText, BufferType.SPANNABLE); 13371 } 13372 } 13373 13374 /** 13375 * @hide 13376 */ 13377 @Override getIteratorForGranularity(int granularity)13378 public TextSegmentIterator getIteratorForGranularity(int granularity) { 13379 switch (granularity) { 13380 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 13381 Spannable text = (Spannable) getIterableTextForAccessibility(); 13382 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13383 AccessibilityIterators.LineTextSegmentIterator iterator = 13384 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 13385 iterator.initialize(text, getLayout()); 13386 return iterator; 13387 } 13388 } break; 13389 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 13390 Spannable text = (Spannable) getIterableTextForAccessibility(); 13391 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13392 AccessibilityIterators.PageTextSegmentIterator iterator = 13393 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 13394 iterator.initialize(this); 13395 return iterator; 13396 } 13397 } break; 13398 } 13399 return super.getIteratorForGranularity(granularity); 13400 } 13401 13402 /** 13403 * @hide 13404 */ 13405 @Override getAccessibilitySelectionStart()13406 public int getAccessibilitySelectionStart() { 13407 return getSelectionStart(); 13408 } 13409 13410 /** 13411 * @hide 13412 */ isAccessibilitySelectionExtendable()13413 public boolean isAccessibilitySelectionExtendable() { 13414 return true; 13415 } 13416 13417 /** 13418 * @hide 13419 */ 13420 @Override getAccessibilitySelectionEnd()13421 public int getAccessibilitySelectionEnd() { 13422 return getSelectionEnd(); 13423 } 13424 13425 /** 13426 * @hide 13427 */ 13428 @Override setAccessibilitySelection(int start, int end)13429 public void setAccessibilitySelection(int start, int end) { 13430 if (getAccessibilitySelectionStart() == start 13431 && getAccessibilitySelectionEnd() == end) { 13432 return; 13433 } 13434 CharSequence text = getIterableTextForAccessibility(); 13435 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13436 Selection.setSelection((Spannable) text, start, end); 13437 } else { 13438 Selection.removeSelection((Spannable) text); 13439 } 13440 // Hide all selection controllers used for adjusting selection 13441 // since we are doing so explicitlty by other means and these 13442 // controllers interact with how selection behaves. 13443 if (mEditor != null) { 13444 mEditor.hideCursorAndSpanControllers(); 13445 mEditor.stopTextActionMode(); 13446 } 13447 } 13448 13449 /** @hide */ 13450 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13451 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13452 super.encodeProperties(stream); 13453 13454 TruncateAt ellipsize = getEllipsize(); 13455 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13456 stream.addProperty("text:textSize", getTextSize()); 13457 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13458 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13459 stream.addProperty("text:selectionStart", getSelectionStart()); 13460 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13461 stream.addProperty("text:curTextColor", mCurTextColor); 13462 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 13463 stream.addProperty("text:gravity", mGravity); 13464 } 13465 13466 /** 13467 * User interface state that is stored by TextView for implementing 13468 * {@link View#onSaveInstanceState}. 13469 */ 13470 public static class SavedState extends BaseSavedState { 13471 int selStart = -1; 13472 int selEnd = -1; 13473 @UnsupportedAppUsage 13474 CharSequence text; 13475 boolean frozenWithFocus; 13476 CharSequence error; 13477 ParcelableParcel editorState; // Optional state from Editor. 13478 SavedState(Parcelable superState)13479 SavedState(Parcelable superState) { 13480 super(superState); 13481 } 13482 13483 @Override writeToParcel(Parcel out, int flags)13484 public void writeToParcel(Parcel out, int flags) { 13485 super.writeToParcel(out, flags); 13486 out.writeInt(selStart); 13487 out.writeInt(selEnd); 13488 out.writeInt(frozenWithFocus ? 1 : 0); 13489 TextUtils.writeToParcel(text, out, flags); 13490 13491 if (error == null) { 13492 out.writeInt(0); 13493 } else { 13494 out.writeInt(1); 13495 TextUtils.writeToParcel(error, out, flags); 13496 } 13497 13498 if (editorState == null) { 13499 out.writeInt(0); 13500 } else { 13501 out.writeInt(1); 13502 editorState.writeToParcel(out, flags); 13503 } 13504 } 13505 13506 @Override toString()13507 public String toString() { 13508 String str = "TextView.SavedState{" 13509 + Integer.toHexString(System.identityHashCode(this)) 13510 + " start=" + selStart + " end=" + selEnd; 13511 if (text != null) { 13512 str += " text=" + text; 13513 } 13514 return str + "}"; 13515 } 13516 13517 @SuppressWarnings("hiding") 13518 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13519 new Parcelable.Creator<SavedState>() { 13520 public SavedState createFromParcel(Parcel in) { 13521 return new SavedState(in); 13522 } 13523 13524 public SavedState[] newArray(int size) { 13525 return new SavedState[size]; 13526 } 13527 }; 13528 SavedState(Parcel in)13529 private SavedState(Parcel in) { 13530 super(in); 13531 selStart = in.readInt(); 13532 selEnd = in.readInt(); 13533 frozenWithFocus = (in.readInt() != 0); 13534 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13535 13536 if (in.readInt() != 0) { 13537 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13538 } 13539 13540 if (in.readInt() != 0) { 13541 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13542 } 13543 } 13544 } 13545 13546 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13547 private char[] mChars; 13548 private int mStart, mLength; 13549 CharWrapper(char[] chars, int start, int len)13550 public CharWrapper(char[] chars, int start, int len) { 13551 mChars = chars; 13552 mStart = start; 13553 mLength = len; 13554 } 13555 set(char[] chars, int start, int len)13556 /* package */ void set(char[] chars, int start, int len) { 13557 mChars = chars; 13558 mStart = start; 13559 mLength = len; 13560 } 13561 length()13562 public int length() { 13563 return mLength; 13564 } 13565 charAt(int off)13566 public char charAt(int off) { 13567 return mChars[off + mStart]; 13568 } 13569 13570 @Override toString()13571 public String toString() { 13572 return new String(mChars, mStart, mLength); 13573 } 13574 subSequence(int start, int end)13575 public CharSequence subSequence(int start, int end) { 13576 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13577 throw new IndexOutOfBoundsException(start + ", " + end); 13578 } 13579 13580 return new String(mChars, start + mStart, end - start); 13581 } 13582 getChars(int start, int end, char[] buf, int off)13583 public void getChars(int start, int end, char[] buf, int off) { 13584 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13585 throw new IndexOutOfBoundsException(start + ", " + end); 13586 } 13587 13588 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13589 } 13590 13591 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13592 public void drawText(BaseCanvas c, int start, int end, 13593 float x, float y, Paint p) { 13594 c.drawText(mChars, start + mStart, end - start, x, y, p); 13595 } 13596 13597 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13598 public void drawTextRun(BaseCanvas c, int start, int end, 13599 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13600 int count = end - start; 13601 int contextCount = contextEnd - contextStart; 13602 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13603 contextCount, x, y, isRtl, p); 13604 } 13605 measureText(int start, int end, Paint p)13606 public float measureText(int start, int end, Paint p) { 13607 return p.measureText(mChars, start + mStart, end - start); 13608 } 13609 getTextWidths(int start, int end, float[] widths, Paint p)13610 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13611 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13612 } 13613 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13614 public float getTextRunAdvances(int start, int end, int contextStart, 13615 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13616 Paint p) { 13617 int count = end - start; 13618 int contextCount = contextEnd - contextStart; 13619 return p.getTextRunAdvances(mChars, start + mStart, count, 13620 contextStart + mStart, contextCount, isRtl, advances, 13621 advancesIndex); 13622 } 13623 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13624 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13625 int offset, int cursorOpt, Paint p) { 13626 int contextCount = contextEnd - contextStart; 13627 return p.getTextRunCursor(mChars, contextStart + mStart, 13628 contextCount, isRtl, offset + mStart, cursorOpt); 13629 } 13630 } 13631 13632 private static final class Marquee { 13633 // TODO: Add an option to configure this 13634 private static final float MARQUEE_DELTA_MAX = 0.07f; 13635 private static final int MARQUEE_DELAY = 1200; 13636 private static final int MARQUEE_DP_PER_SECOND = 30; 13637 13638 private static final byte MARQUEE_STOPPED = 0x0; 13639 private static final byte MARQUEE_STARTING = 0x1; 13640 private static final byte MARQUEE_RUNNING = 0x2; 13641 13642 private final WeakReference<TextView> mView; 13643 private final Choreographer mChoreographer; 13644 13645 private byte mStatus = MARQUEE_STOPPED; 13646 private final float mPixelsPerMs; 13647 private float mMaxScroll; 13648 private float mMaxFadeScroll; 13649 private float mGhostStart; 13650 private float mGhostOffset; 13651 private float mFadeStop; 13652 private int mRepeatLimit; 13653 13654 private float mScroll; 13655 private long mLastAnimationMs; 13656 Marquee(TextView v)13657 Marquee(TextView v) { 13658 final float density = v.getContext().getResources().getDisplayMetrics().density; 13659 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13660 mView = new WeakReference<TextView>(v); 13661 mChoreographer = Choreographer.getInstance(); 13662 } 13663 13664 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13665 @Override 13666 public void doFrame(long frameTimeNanos) { 13667 tick(); 13668 } 13669 }; 13670 13671 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13672 @Override 13673 public void doFrame(long frameTimeNanos) { 13674 mStatus = MARQUEE_RUNNING; 13675 mLastAnimationMs = mChoreographer.getFrameTime(); 13676 tick(); 13677 } 13678 }; 13679 13680 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13681 @Override 13682 public void doFrame(long frameTimeNanos) { 13683 if (mStatus == MARQUEE_RUNNING) { 13684 if (mRepeatLimit >= 0) { 13685 mRepeatLimit--; 13686 } 13687 start(mRepeatLimit); 13688 } 13689 } 13690 }; 13691 tick()13692 void tick() { 13693 if (mStatus != MARQUEE_RUNNING) { 13694 return; 13695 } 13696 13697 mChoreographer.removeFrameCallback(mTickCallback); 13698 13699 final TextView textView = mView.get(); 13700 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13701 long currentMs = mChoreographer.getFrameTime(); 13702 long deltaMs = currentMs - mLastAnimationMs; 13703 mLastAnimationMs = currentMs; 13704 float deltaPx = deltaMs * mPixelsPerMs; 13705 mScroll += deltaPx; 13706 if (mScroll > mMaxScroll) { 13707 mScroll = mMaxScroll; 13708 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13709 } else { 13710 mChoreographer.postFrameCallback(mTickCallback); 13711 } 13712 textView.invalidate(); 13713 } 13714 } 13715 stop()13716 void stop() { 13717 mStatus = MARQUEE_STOPPED; 13718 mChoreographer.removeFrameCallback(mStartCallback); 13719 mChoreographer.removeFrameCallback(mRestartCallback); 13720 mChoreographer.removeFrameCallback(mTickCallback); 13721 resetScroll(); 13722 } 13723 resetScroll()13724 private void resetScroll() { 13725 mScroll = 0.0f; 13726 final TextView textView = mView.get(); 13727 if (textView != null) textView.invalidate(); 13728 } 13729 start(int repeatLimit)13730 void start(int repeatLimit) { 13731 if (repeatLimit == 0) { 13732 stop(); 13733 return; 13734 } 13735 mRepeatLimit = repeatLimit; 13736 final TextView textView = mView.get(); 13737 if (textView != null && textView.mLayout != null) { 13738 mStatus = MARQUEE_STARTING; 13739 mScroll = 0.0f; 13740 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13741 - textView.getCompoundPaddingRight(); 13742 final float lineWidth = textView.mLayout.getLineWidth(0); 13743 final float gap = textWidth / 3.0f; 13744 mGhostStart = lineWidth - textWidth + gap; 13745 mMaxScroll = mGhostStart + textWidth; 13746 mGhostOffset = lineWidth + gap; 13747 mFadeStop = lineWidth + textWidth / 6.0f; 13748 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13749 13750 textView.invalidate(); 13751 mChoreographer.postFrameCallback(mStartCallback); 13752 } 13753 } 13754 getGhostOffset()13755 float getGhostOffset() { 13756 return mGhostOffset; 13757 } 13758 getScroll()13759 float getScroll() { 13760 return mScroll; 13761 } 13762 getMaxFadeScroll()13763 float getMaxFadeScroll() { 13764 return mMaxFadeScroll; 13765 } 13766 shouldDrawLeftFade()13767 boolean shouldDrawLeftFade() { 13768 return mScroll <= mFadeStop; 13769 } 13770 shouldDrawGhost()13771 boolean shouldDrawGhost() { 13772 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13773 } 13774 isRunning()13775 boolean isRunning() { 13776 return mStatus == MARQUEE_RUNNING; 13777 } 13778 isStopped()13779 boolean isStopped() { 13780 return mStatus == MARQUEE_STOPPED; 13781 } 13782 } 13783 13784 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13785 13786 private CharSequence mBeforeText; 13787 beforeTextChanged(CharSequence buffer, int start, int before, int after)13788 public void beforeTextChanged(CharSequence buffer, int start, 13789 int before, int after) { 13790 if (DEBUG_EXTRACT) { 13791 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13792 + " before=" + before + " after=" + after + ": " + buffer); 13793 } 13794 13795 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13796 mBeforeText = mTransformed.toString(); 13797 } 13798 13799 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13800 } 13801 onTextChanged(CharSequence buffer, int start, int before, int after)13802 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13803 if (DEBUG_EXTRACT) { 13804 Log.v(LOG_TAG, "onTextChanged start=" + start 13805 + " before=" + before + " after=" + after + ": " + buffer); 13806 } 13807 TextView.this.handleTextChanged(buffer, start, before, after); 13808 13809 if (AccessibilityManager.getInstance(mContext).isEnabled() 13810 && (isFocused() || isSelected() && isShown())) { 13811 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13812 mBeforeText = null; 13813 } 13814 } 13815 afterTextChanged(Editable buffer)13816 public void afterTextChanged(Editable buffer) { 13817 if (DEBUG_EXTRACT) { 13818 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13819 } 13820 TextView.this.sendAfterTextChanged(buffer); 13821 13822 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13823 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13824 } 13825 } 13826 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13827 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13828 if (DEBUG_EXTRACT) { 13829 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13830 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13831 } 13832 TextView.this.spanChange(buf, what, s, st, e, en); 13833 } 13834 onSpanAdded(Spannable buf, Object what, int s, int e)13835 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13836 if (DEBUG_EXTRACT) { 13837 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13838 } 13839 TextView.this.spanChange(buf, what, -1, s, -1, e); 13840 } 13841 onSpanRemoved(Spannable buf, Object what, int s, int e)13842 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13843 if (DEBUG_EXTRACT) { 13844 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13845 } 13846 TextView.this.spanChange(buf, what, s, -1, e, -1); 13847 } 13848 } 13849 13850 /** @hide */ 13851 @Override onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)13852 public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, 13853 @NonNull EditorInfo editorInfo, @Nullable Handler handler) { 13854 if (mEditor != null) { 13855 mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, 13856 editorInfo); 13857 } 13858 } 13859 13860 /** @hide */ 13861 @Override onInputConnectionClosedInternal()13862 public void onInputConnectionClosedInternal() { 13863 if (mEditor != null) { 13864 mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); 13865 } 13866 } 13867 13868 /** 13869 * Default {@link TextView} implementation for receiving content. Apps wishing to provide 13870 * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. 13871 * 13872 * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in 13873 * content without acting on it). 13874 * 13875 * <p>For editable TextViews the default behavior is to insert text into the view, coercing 13876 * non-text content to text as needed. The MIME types "text/plain" and "text/html" have 13877 * well-defined behavior for this, while other MIME types have reasonable fallback behavior 13878 * (see {@link ClipData.Item#coerceToStyledText}). 13879 * 13880 * @param payload The content to insert and related metadata. 13881 * 13882 * @return The portion of the passed-in content that was not handled (may be all, some, or none 13883 * of the passed-in content). 13884 */ 13885 @Nullable 13886 @Override onReceiveContent(@onNull ContentInfo payload)13887 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 13888 if (mEditor != null) { 13889 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 13890 } 13891 return payload; 13892 } 13893 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13894 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 13895 if (msgFormat == null) { 13896 Log.d(LOG_TAG, location); 13897 } else { 13898 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 13899 } 13900 } 13901 13902 /** 13903 * Collects a {@link ViewTranslationRequest} which represents the content to be translated in 13904 * the view. 13905 * 13906 * <p>NOTE: When overriding the method, it should not translate the password. If the subclass 13907 * uses {@link TransformationMethod} to display the translated result, it's also not recommend 13908 * to translate text is selectable or editable. 13909 * 13910 * @param supportedFormats the supported translation format. The value could be {@link 13911 * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. 13912 * @return the {@link ViewTranslationRequest} which contains the information to be translated. 13913 */ 13914 @Override onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)13915 public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, 13916 @NonNull Consumer<ViewTranslationRequest> requestsCollector) { 13917 if (supportedFormats == null || supportedFormats.length == 0) { 13918 if (UiTranslationController.DEBUG) { 13919 Log.w(LOG_TAG, "Do not provide the support translation formats."); 13920 } 13921 return; 13922 } 13923 ViewTranslationRequest.Builder requestBuilder = 13924 new ViewTranslationRequest.Builder(getAutofillId()); 13925 // Support Text translation 13926 if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { 13927 if (mText == null || mText.length() == 0) { 13928 if (UiTranslationController.DEBUG) { 13929 Log.w(LOG_TAG, "Cannot create translation request for the empty text."); 13930 } 13931 return; 13932 } 13933 boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); 13934 // TODO(b/177214256): support selectable text translation. 13935 // We use the TransformationMethod to implement showing the translated text. The 13936 // TextView does not support the text length change for TransformationMethod. If the 13937 // text is selectable or editable, it will crash while selecting the text. To support 13938 // it, it needs broader changes to text APIs, we only allow to translate non selectable 13939 // and editable text in S. 13940 if (isTextEditable() || isPassword || isTextSelectable()) { 13941 if (UiTranslationController.DEBUG) { 13942 Log.w(LOG_TAG, "Cannot create translation request. editable = " 13943 + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " 13944 + isTextSelectable()); 13945 } 13946 return; 13947 } 13948 // TODO(b/176488462): apply the view's important for translation 13949 requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, 13950 TranslationRequestValue.forText(mText)); 13951 if (!TextUtils.isEmpty(getContentDescription())) { 13952 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, 13953 TranslationRequestValue.forText(getContentDescription())); 13954 } 13955 } 13956 requestsCollector.accept(requestBuilder.build()); 13957 } 13958 } 13959