1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; 21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 23 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 24 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 25 26 import android.R; 27 import android.annotation.CallSuper; 28 import android.annotation.CheckResult; 29 import android.annotation.ColorInt; 30 import android.annotation.DrawableRes; 31 import android.annotation.FloatRange; 32 import android.annotation.IntDef; 33 import android.annotation.IntRange; 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.Px; 37 import android.annotation.RequiresPermission; 38 import android.annotation.Size; 39 import android.annotation.StringRes; 40 import android.annotation.StyleRes; 41 import android.annotation.XmlRes; 42 import android.app.Activity; 43 import android.app.PendingIntent; 44 import android.app.assist.AssistStructure; 45 import android.compat.annotation.UnsupportedAppUsage; 46 import android.content.ClipData; 47 import android.content.ClipDescription; 48 import android.content.ClipboardManager; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.UndoManager; 52 import android.content.pm.PackageManager; 53 import android.content.res.ColorStateList; 54 import android.content.res.CompatibilityInfo; 55 import android.content.res.Configuration; 56 import android.content.res.Resources; 57 import android.content.res.TypedArray; 58 import android.content.res.XmlResourceParser; 59 import android.graphics.BaseCanvas; 60 import android.graphics.BlendMode; 61 import android.graphics.Canvas; 62 import android.graphics.Insets; 63 import android.graphics.Paint; 64 import android.graphics.Paint.FontMetricsInt; 65 import android.graphics.Path; 66 import android.graphics.PorterDuff; 67 import android.graphics.Rect; 68 import android.graphics.RectF; 69 import android.graphics.Typeface; 70 import android.graphics.drawable.Drawable; 71 import android.graphics.fonts.FontStyle; 72 import android.graphics.fonts.FontVariationAxis; 73 import android.icu.text.DecimalFormatSymbols; 74 import android.os.AsyncTask; 75 import android.os.Build; 76 import android.os.Build.VERSION_CODES; 77 import android.os.Bundle; 78 import android.os.LocaleList; 79 import android.os.Parcel; 80 import android.os.Parcelable; 81 import android.os.ParcelableParcel; 82 import android.os.Process; 83 import android.os.SystemClock; 84 import android.os.UserHandle; 85 import android.provider.Settings; 86 import android.text.BoringLayout; 87 import android.text.DynamicLayout; 88 import android.text.Editable; 89 import android.text.GetChars; 90 import android.text.GraphicsOperations; 91 import android.text.InputFilter; 92 import android.text.InputType; 93 import android.text.Layout; 94 import android.text.ParcelableSpan; 95 import android.text.PrecomputedText; 96 import android.text.Selection; 97 import android.text.SpanWatcher; 98 import android.text.Spannable; 99 import android.text.SpannableStringBuilder; 100 import android.text.Spanned; 101 import android.text.SpannedString; 102 import android.text.StaticLayout; 103 import android.text.TextDirectionHeuristic; 104 import android.text.TextDirectionHeuristics; 105 import android.text.TextPaint; 106 import android.text.TextUtils; 107 import android.text.TextUtils.TruncateAt; 108 import android.text.TextWatcher; 109 import android.text.method.AllCapsTransformationMethod; 110 import android.text.method.ArrowKeyMovementMethod; 111 import android.text.method.DateKeyListener; 112 import android.text.method.DateTimeKeyListener; 113 import android.text.method.DialerKeyListener; 114 import android.text.method.DigitsKeyListener; 115 import android.text.method.KeyListener; 116 import android.text.method.LinkMovementMethod; 117 import android.text.method.MetaKeyKeyListener; 118 import android.text.method.MovementMethod; 119 import android.text.method.PasswordTransformationMethod; 120 import android.text.method.SingleLineTransformationMethod; 121 import android.text.method.TextKeyListener; 122 import android.text.method.TimeKeyListener; 123 import android.text.method.TransformationMethod; 124 import android.text.method.TransformationMethod2; 125 import android.text.method.WordIterator; 126 import android.text.style.CharacterStyle; 127 import android.text.style.ClickableSpan; 128 import android.text.style.ParagraphStyle; 129 import android.text.style.SpellCheckSpan; 130 import android.text.style.SuggestionSpan; 131 import android.text.style.URLSpan; 132 import android.text.style.UpdateAppearance; 133 import android.text.util.Linkify; 134 import android.util.AttributeSet; 135 import android.util.DisplayMetrics; 136 import android.util.IntArray; 137 import android.util.Log; 138 import android.util.SparseIntArray; 139 import android.util.TypedValue; 140 import android.view.AccessibilityIterators.TextSegmentIterator; 141 import android.view.ActionMode; 142 import android.view.Choreographer; 143 import android.view.ContextMenu; 144 import android.view.DragEvent; 145 import android.view.Gravity; 146 import android.view.HapticFeedbackConstants; 147 import android.view.InputDevice; 148 import android.view.KeyCharacterMap; 149 import android.view.KeyEvent; 150 import android.view.MotionEvent; 151 import android.view.PointerIcon; 152 import android.view.View; 153 import android.view.ViewConfiguration; 154 import android.view.ViewDebug; 155 import android.view.ViewGroup.LayoutParams; 156 import android.view.ViewHierarchyEncoder; 157 import android.view.ViewParent; 158 import android.view.ViewRootImpl; 159 import android.view.ViewStructure; 160 import android.view.ViewTreeObserver; 161 import android.view.accessibility.AccessibilityEvent; 162 import android.view.accessibility.AccessibilityManager; 163 import android.view.accessibility.AccessibilityNodeInfo; 164 import android.view.animation.AnimationUtils; 165 import android.view.autofill.AutofillManager; 166 import android.view.autofill.AutofillValue; 167 import android.view.contentcapture.ContentCaptureManager; 168 import android.view.contentcapture.ContentCaptureSession; 169 import android.view.inputmethod.BaseInputConnection; 170 import android.view.inputmethod.CompletionInfo; 171 import android.view.inputmethod.CorrectionInfo; 172 import android.view.inputmethod.CursorAnchorInfo; 173 import android.view.inputmethod.EditorInfo; 174 import android.view.inputmethod.ExtractedText; 175 import android.view.inputmethod.ExtractedTextRequest; 176 import android.view.inputmethod.InputConnection; 177 import android.view.inputmethod.InputMethodManager; 178 import android.view.inspector.InspectableProperty; 179 import android.view.inspector.InspectableProperty.EnumEntry; 180 import android.view.inspector.InspectableProperty.FlagEntry; 181 import android.view.textclassifier.TextClassification; 182 import android.view.textclassifier.TextClassificationContext; 183 import android.view.textclassifier.TextClassificationManager; 184 import android.view.textclassifier.TextClassifier; 185 import android.view.textclassifier.TextLinks; 186 import android.view.textservice.SpellCheckerSubtype; 187 import android.view.textservice.TextServicesManager; 188 import android.widget.RemoteViews.RemoteView; 189 190 import com.android.internal.annotations.VisibleForTesting; 191 import com.android.internal.logging.MetricsLogger; 192 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 193 import com.android.internal.util.FastMath; 194 import com.android.internal.util.Preconditions; 195 import com.android.internal.widget.EditableInputConnection; 196 197 import libcore.util.EmptyArray; 198 199 import org.xmlpull.v1.XmlPullParserException; 200 201 import java.io.IOException; 202 import java.lang.annotation.Retention; 203 import java.lang.annotation.RetentionPolicy; 204 import java.lang.ref.WeakReference; 205 import java.util.ArrayList; 206 import java.util.Arrays; 207 import java.util.Locale; 208 import java.util.Objects; 209 import java.util.concurrent.CompletableFuture; 210 import java.util.concurrent.TimeUnit; 211 import java.util.function.Consumer; 212 import java.util.function.Supplier; 213 214 /** 215 * A user interface element that displays text to the user. 216 * To provide user-editable text, see {@link EditText}. 217 * <p> 218 * The following code sample shows a typical use, with an XML layout 219 * and code to modify the contents of the text view: 220 * </p> 221 222 * <pre> 223 * <LinearLayout 224 xmlns:android="http://schemas.android.com/apk/res/android" 225 android:layout_width="match_parent" 226 android:layout_height="match_parent"> 227 * <TextView 228 * android:id="@+id/text_view_id" 229 * android:layout_height="wrap_content" 230 * android:layout_width="wrap_content" 231 * android:text="@string/hello" /> 232 * </LinearLayout> 233 * </pre> 234 * <p> 235 * This code sample demonstrates how to modify the contents of the text view 236 * defined in the previous XML layout: 237 * </p> 238 * <pre> 239 * public class MainActivity extends Activity { 240 * 241 * protected void onCreate(Bundle savedInstanceState) { 242 * super.onCreate(savedInstanceState); 243 * setContentView(R.layout.activity_main); 244 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 245 * helloTextView.setText(R.string.user_greeting); 246 * } 247 * } 248 * </pre> 249 * <p> 250 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 251 * </p> 252 * <p> 253 * <b>XML attributes</b> 254 * <p> 255 * See {@link android.R.styleable#TextView TextView Attributes}, 256 * {@link android.R.styleable#View View Attributes} 257 * 258 * @attr ref android.R.styleable#TextView_text 259 * @attr ref android.R.styleable#TextView_bufferType 260 * @attr ref android.R.styleable#TextView_hint 261 * @attr ref android.R.styleable#TextView_textColor 262 * @attr ref android.R.styleable#TextView_textColorHighlight 263 * @attr ref android.R.styleable#TextView_textColorHint 264 * @attr ref android.R.styleable#TextView_textAppearance 265 * @attr ref android.R.styleable#TextView_textColorLink 266 * @attr ref android.R.styleable#TextView_textFontWeight 267 * @attr ref android.R.styleable#TextView_textSize 268 * @attr ref android.R.styleable#TextView_textScaleX 269 * @attr ref android.R.styleable#TextView_fontFamily 270 * @attr ref android.R.styleable#TextView_typeface 271 * @attr ref android.R.styleable#TextView_textStyle 272 * @attr ref android.R.styleable#TextView_cursorVisible 273 * @attr ref android.R.styleable#TextView_maxLines 274 * @attr ref android.R.styleable#TextView_maxHeight 275 * @attr ref android.R.styleable#TextView_lines 276 * @attr ref android.R.styleable#TextView_height 277 * @attr ref android.R.styleable#TextView_minLines 278 * @attr ref android.R.styleable#TextView_minHeight 279 * @attr ref android.R.styleable#TextView_maxEms 280 * @attr ref android.R.styleable#TextView_maxWidth 281 * @attr ref android.R.styleable#TextView_ems 282 * @attr ref android.R.styleable#TextView_width 283 * @attr ref android.R.styleable#TextView_minEms 284 * @attr ref android.R.styleable#TextView_minWidth 285 * @attr ref android.R.styleable#TextView_gravity 286 * @attr ref android.R.styleable#TextView_scrollHorizontally 287 * @attr ref android.R.styleable#TextView_password 288 * @attr ref android.R.styleable#TextView_singleLine 289 * @attr ref android.R.styleable#TextView_selectAllOnFocus 290 * @attr ref android.R.styleable#TextView_includeFontPadding 291 * @attr ref android.R.styleable#TextView_maxLength 292 * @attr ref android.R.styleable#TextView_shadowColor 293 * @attr ref android.R.styleable#TextView_shadowDx 294 * @attr ref android.R.styleable#TextView_shadowDy 295 * @attr ref android.R.styleable#TextView_shadowRadius 296 * @attr ref android.R.styleable#TextView_autoLink 297 * @attr ref android.R.styleable#TextView_linksClickable 298 * @attr ref android.R.styleable#TextView_numeric 299 * @attr ref android.R.styleable#TextView_digits 300 * @attr ref android.R.styleable#TextView_phoneNumber 301 * @attr ref android.R.styleable#TextView_inputMethod 302 * @attr ref android.R.styleable#TextView_capitalize 303 * @attr ref android.R.styleable#TextView_autoText 304 * @attr ref android.R.styleable#TextView_editable 305 * @attr ref android.R.styleable#TextView_freezesText 306 * @attr ref android.R.styleable#TextView_ellipsize 307 * @attr ref android.R.styleable#TextView_drawableTop 308 * @attr ref android.R.styleable#TextView_drawableBottom 309 * @attr ref android.R.styleable#TextView_drawableRight 310 * @attr ref android.R.styleable#TextView_drawableLeft 311 * @attr ref android.R.styleable#TextView_drawableStart 312 * @attr ref android.R.styleable#TextView_drawableEnd 313 * @attr ref android.R.styleable#TextView_drawablePadding 314 * @attr ref android.R.styleable#TextView_drawableTint 315 * @attr ref android.R.styleable#TextView_drawableTintMode 316 * @attr ref android.R.styleable#TextView_lineSpacingExtra 317 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 318 * @attr ref android.R.styleable#TextView_justificationMode 319 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 320 * @attr ref android.R.styleable#TextView_inputType 321 * @attr ref android.R.styleable#TextView_imeOptions 322 * @attr ref android.R.styleable#TextView_privateImeOptions 323 * @attr ref android.R.styleable#TextView_imeActionLabel 324 * @attr ref android.R.styleable#TextView_imeActionId 325 * @attr ref android.R.styleable#TextView_editorExtras 326 * @attr ref android.R.styleable#TextView_elegantTextHeight 327 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 328 * @attr ref android.R.styleable#TextView_letterSpacing 329 * @attr ref android.R.styleable#TextView_fontFeatureSettings 330 * @attr ref android.R.styleable#TextView_fontVariationSettings 331 * @attr ref android.R.styleable#TextView_breakStrategy 332 * @attr ref android.R.styleable#TextView_hyphenationFrequency 333 * @attr ref android.R.styleable#TextView_autoSizeTextType 334 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 335 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 336 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 337 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 338 * @attr ref android.R.styleable#TextView_textCursorDrawable 339 * @attr ref android.R.styleable#TextView_textSelectHandle 340 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 341 * @attr ref android.R.styleable#TextView_textSelectHandleRight 342 * @attr ref android.R.styleable#TextView_allowUndo 343 * @attr ref android.R.styleable#TextView_enabled 344 */ 345 @RemoteView 346 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 347 static final String LOG_TAG = "TextView"; 348 static final boolean DEBUG_EXTRACT = false; 349 static final boolean DEBUG_CURSOR = false; 350 351 private static final float[] TEMP_POSITION = new float[2]; 352 353 // Enum for the "typeface" XML parameter. 354 // TODO: How can we get this from the XML instead of hardcoding it here? 355 /** @hide */ 356 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 357 @Retention(RetentionPolicy.SOURCE) 358 public @interface XMLTypefaceAttr{} 359 private static final int DEFAULT_TYPEFACE = -1; 360 private static final int SANS = 1; 361 private static final int SERIF = 2; 362 private static final int MONOSPACE = 3; 363 364 // Enum for the "ellipsize" XML parameter. 365 private static final int ELLIPSIZE_NOT_SET = -1; 366 private static final int ELLIPSIZE_NONE = 0; 367 private static final int ELLIPSIZE_START = 1; 368 private static final int ELLIPSIZE_MIDDLE = 2; 369 private static final int ELLIPSIZE_END = 3; 370 private static final int ELLIPSIZE_MARQUEE = 4; 371 372 // Bitfield for the "numeric" XML parameter. 373 // TODO: How can we get this from the XML instead of hardcoding it here? 374 private static final int SIGNED = 2; 375 private static final int DECIMAL = 4; 376 377 /** 378 * Draw marquee text with fading edges as usual 379 */ 380 private static final int MARQUEE_FADE_NORMAL = 0; 381 382 /** 383 * Draw marquee text as ellipsize end while inactive instead of with the fade. 384 * (Useful for devices where the fade can be expensive if overdone) 385 */ 386 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 387 388 /** 389 * Draw marquee text with fading edges because it is currently active/animating. 390 */ 391 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 392 393 @UnsupportedAppUsage 394 private static final int LINES = 1; 395 private static final int EMS = LINES; 396 private static final int PIXELS = 2; 397 398 // Maximum text length for single line input. 399 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 400 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 401 402 private static final RectF TEMP_RECTF = new RectF(); 403 404 /** @hide */ 405 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 406 private static final int ANIMATED_SCROLL_GAP = 250; 407 408 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 409 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 410 411 private static final int CHANGE_WATCHER_PRIORITY = 100; 412 413 // New state used to change background based on whether this TextView is multiline. 414 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 415 416 // Accessibility action to share selected text. 417 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 418 419 /** 420 * @hide 421 */ 422 // Accessibility action start id for "process text" actions. 423 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 424 425 /** 426 * @hide 427 */ 428 static final int PROCESS_TEXT_REQUEST_CODE = 100; 429 430 /** 431 * Return code of {@link #doKeyDown}. 432 */ 433 private static final int KEY_EVENT_NOT_HANDLED = 0; 434 private static final int KEY_EVENT_HANDLED = -1; 435 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 436 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 437 438 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 439 440 // System wide time for last cut, copy or text changed action. 441 static long sLastCutCopyOrTextChangedTime; 442 443 private ColorStateList mTextColor; 444 private ColorStateList mHintTextColor; 445 private ColorStateList mLinkTextColor; 446 @ViewDebug.ExportedProperty(category = "text") 447 448 /** 449 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 450 */ 451 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 452 private int mCurTextColor; 453 454 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 455 private int mCurHintTextColor; 456 private boolean mFreezesText; 457 458 @UnsupportedAppUsage 459 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 460 @UnsupportedAppUsage 461 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 462 463 @UnsupportedAppUsage 464 private float mShadowRadius; 465 @UnsupportedAppUsage 466 private float mShadowDx; 467 @UnsupportedAppUsage 468 private float mShadowDy; 469 private int mShadowColor; 470 471 private boolean mPreDrawRegistered; 472 private boolean mPreDrawListenerDetached; 473 474 private TextClassifier mTextClassifier; 475 private TextClassifier mTextClassificationSession; 476 private TextClassificationContext mTextClassificationContext; 477 478 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 479 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 480 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 481 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 482 // into by the user holding the movement key down) then we shouldn't prevent the focus from 483 // changing. 484 private boolean mPreventDefaultMovement; 485 486 private TextUtils.TruncateAt mEllipsize; 487 488 static class Drawables { 489 static final int LEFT = 0; 490 static final int TOP = 1; 491 static final int RIGHT = 2; 492 static final int BOTTOM = 3; 493 494 static final int DRAWABLE_NONE = -1; 495 static final int DRAWABLE_RIGHT = 0; 496 static final int DRAWABLE_LEFT = 1; 497 498 final Rect mCompoundRect = new Rect(); 499 500 final Drawable[] mShowing = new Drawable[4]; 501 502 ColorStateList mTintList; 503 BlendMode mBlendMode; 504 boolean mHasTint; 505 boolean mHasTintMode; 506 507 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 508 Drawable mDrawableLeftInitial, mDrawableRightInitial; 509 510 boolean mIsRtlCompatibilityMode; 511 boolean mOverride; 512 513 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 514 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 515 516 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 517 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 518 519 int mDrawablePadding; 520 521 int mDrawableSaved = DRAWABLE_NONE; 522 Drawables(Context context)523 public Drawables(Context context) { 524 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 525 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 526 || !context.getApplicationInfo().hasRtlSupport(); 527 mOverride = false; 528 } 529 530 /** 531 * @return {@code true} if this object contains metadata that needs to 532 * be retained, {@code false} otherwise 533 */ 534 public boolean hasMetadata() { 535 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 536 } 537 538 /** 539 * Updates the list of displayed drawables to account for the current 540 * layout direction. 541 * 542 * @param layoutDirection the current layout direction 543 * @return {@code true} if the displayed drawables changed 544 */ 545 public boolean resolveWithLayoutDirection(int layoutDirection) { 546 final Drawable previousLeft = mShowing[Drawables.LEFT]; 547 final Drawable previousRight = mShowing[Drawables.RIGHT]; 548 549 // First reset "left" and "right" drawables to their initial values 550 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 551 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 552 553 if (mIsRtlCompatibilityMode) { 554 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 555 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 556 mShowing[Drawables.LEFT] = mDrawableStart; 557 mDrawableSizeLeft = mDrawableSizeStart; 558 mDrawableHeightLeft = mDrawableHeightStart; 559 } 560 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 561 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 562 mShowing[Drawables.RIGHT] = mDrawableEnd; 563 mDrawableSizeRight = mDrawableSizeEnd; 564 mDrawableHeightRight = mDrawableHeightEnd; 565 } 566 } else { 567 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 568 // drawable if and only if they have been defined 569 switch(layoutDirection) { 570 case LAYOUT_DIRECTION_RTL: 571 if (mOverride) { 572 mShowing[Drawables.RIGHT] = mDrawableStart; 573 mDrawableSizeRight = mDrawableSizeStart; 574 mDrawableHeightRight = mDrawableHeightStart; 575 576 mShowing[Drawables.LEFT] = mDrawableEnd; 577 mDrawableSizeLeft = mDrawableSizeEnd; 578 mDrawableHeightLeft = mDrawableHeightEnd; 579 } 580 break; 581 582 case LAYOUT_DIRECTION_LTR: 583 default: 584 if (mOverride) { 585 mShowing[Drawables.LEFT] = mDrawableStart; 586 mDrawableSizeLeft = mDrawableSizeStart; 587 mDrawableHeightLeft = mDrawableHeightStart; 588 589 mShowing[Drawables.RIGHT] = mDrawableEnd; 590 mDrawableSizeRight = mDrawableSizeEnd; 591 mDrawableHeightRight = mDrawableHeightEnd; 592 } 593 break; 594 } 595 } 596 597 applyErrorDrawableIfNeeded(layoutDirection); 598 599 return mShowing[Drawables.LEFT] != previousLeft 600 || mShowing[Drawables.RIGHT] != previousRight; 601 } 602 603 public void setErrorDrawable(Drawable dr, TextView tv) { 604 if (mDrawableError != dr && mDrawableError != null) { 605 mDrawableError.setCallback(null); 606 } 607 mDrawableError = dr; 608 609 if (mDrawableError != null) { 610 final Rect compoundRect = mCompoundRect; 611 final int[] state = tv.getDrawableState(); 612 613 mDrawableError.setState(state); 614 mDrawableError.copyBounds(compoundRect); 615 mDrawableError.setCallback(tv); 616 mDrawableSizeError = compoundRect.width(); 617 mDrawableHeightError = compoundRect.height(); 618 } else { 619 mDrawableSizeError = mDrawableHeightError = 0; 620 } 621 } 622 623 private void applyErrorDrawableIfNeeded(int layoutDirection) { 624 // first restore the initial state if needed 625 switch (mDrawableSaved) { 626 case DRAWABLE_LEFT: 627 mShowing[Drawables.LEFT] = mDrawableTemp; 628 mDrawableSizeLeft = mDrawableSizeTemp; 629 mDrawableHeightLeft = mDrawableHeightTemp; 630 break; 631 case DRAWABLE_RIGHT: 632 mShowing[Drawables.RIGHT] = mDrawableTemp; 633 mDrawableSizeRight = mDrawableSizeTemp; 634 mDrawableHeightRight = mDrawableHeightTemp; 635 break; 636 case DRAWABLE_NONE: 637 default: 638 } 639 // then, if needed, assign the Error drawable to the correct location 640 if (mDrawableError != null) { 641 switch(layoutDirection) { 642 case LAYOUT_DIRECTION_RTL: 643 mDrawableSaved = DRAWABLE_LEFT; 644 645 mDrawableTemp = mShowing[Drawables.LEFT]; 646 mDrawableSizeTemp = mDrawableSizeLeft; 647 mDrawableHeightTemp = mDrawableHeightLeft; 648 649 mShowing[Drawables.LEFT] = mDrawableError; 650 mDrawableSizeLeft = mDrawableSizeError; 651 mDrawableHeightLeft = mDrawableHeightError; 652 break; 653 case LAYOUT_DIRECTION_LTR: 654 default: 655 mDrawableSaved = DRAWABLE_RIGHT; 656 657 mDrawableTemp = mShowing[Drawables.RIGHT]; 658 mDrawableSizeTemp = mDrawableSizeRight; 659 mDrawableHeightTemp = mDrawableHeightRight; 660 661 mShowing[Drawables.RIGHT] = mDrawableError; 662 mDrawableSizeRight = mDrawableSizeError; 663 mDrawableHeightRight = mDrawableHeightError; 664 break; 665 } 666 } 667 } 668 } 669 670 @UnsupportedAppUsage 671 Drawables mDrawables; 672 673 @UnsupportedAppUsage 674 private CharWrapper mCharWrapper; 675 676 @UnsupportedAppUsage(trackingBug = 124050217) 677 private Marquee mMarquee; 678 @UnsupportedAppUsage 679 private boolean mRestartMarquee; 680 681 private int mMarqueeRepeatLimit = 3; 682 683 private int mLastLayoutDirection = -1; 684 685 /** 686 * On some devices the fading edges add a performance penalty if used 687 * extensively in the same layout. This mode indicates how the marquee 688 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 689 */ 690 @UnsupportedAppUsage 691 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 692 693 /** 694 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 695 * the layout that should be used when the mode switches. 696 */ 697 @UnsupportedAppUsage 698 private Layout mSavedMarqueeModeLayout; 699 700 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 701 @ViewDebug.ExportedProperty(category = "text") 702 @UnsupportedAppUsage 703 private @Nullable CharSequence mText; 704 private @Nullable Spannable mSpannable; 705 private @Nullable PrecomputedText mPrecomputed; 706 707 @UnsupportedAppUsage 708 private CharSequence mTransformed; 709 @UnsupportedAppUsage 710 private BufferType mBufferType = BufferType.NORMAL; 711 712 private CharSequence mHint; 713 @UnsupportedAppUsage 714 private Layout mHintLayout; 715 716 private MovementMethod mMovement; 717 718 private TransformationMethod mTransformation; 719 @UnsupportedAppUsage 720 private boolean mAllowTransformationLengthChange; 721 @UnsupportedAppUsage 722 private ChangeWatcher mChangeWatcher; 723 724 @UnsupportedAppUsage(trackingBug = 123769451) 725 private ArrayList<TextWatcher> mListeners; 726 727 // display attributes 728 @UnsupportedAppUsage 729 private final TextPaint mTextPaint; 730 @UnsupportedAppUsage 731 private boolean mUserSetTextScaleX; 732 @UnsupportedAppUsage 733 private Layout mLayout; 734 private boolean mLocalesChanged = false; 735 private int mTextSizeUnit = -1; 736 737 // True if setKeyListener() has been explicitly called 738 private boolean mListenerChanged = false; 739 // True if internationalized input should be used for numbers and date and time. 740 private final boolean mUseInternationalizedInput; 741 // True if fallback fonts that end up getting used should be allowed to affect line spacing. 742 /* package */ boolean mUseFallbackLineSpacing; 743 744 @ViewDebug.ExportedProperty(category = "text") 745 @UnsupportedAppUsage 746 private int mGravity = Gravity.TOP | Gravity.START; 747 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 748 private boolean mHorizontallyScrolling; 749 750 private int mAutoLinkMask; 751 private boolean mLinksClickable = true; 752 753 @UnsupportedAppUsage 754 private float mSpacingMult = 1.0f; 755 @UnsupportedAppUsage 756 private float mSpacingAdd = 0.0f; 757 758 private int mBreakStrategy; 759 private int mHyphenationFrequency; 760 private int mJustificationMode; 761 762 @UnsupportedAppUsage 763 private int mMaximum = Integer.MAX_VALUE; 764 @UnsupportedAppUsage 765 private int mMaxMode = LINES; 766 @UnsupportedAppUsage 767 private int mMinimum = 0; 768 @UnsupportedAppUsage 769 private int mMinMode = LINES; 770 771 @UnsupportedAppUsage 772 private int mOldMaximum = mMaximum; 773 @UnsupportedAppUsage 774 private int mOldMaxMode = mMaxMode; 775 776 @UnsupportedAppUsage 777 private int mMaxWidth = Integer.MAX_VALUE; 778 @UnsupportedAppUsage 779 private int mMaxWidthMode = PIXELS; 780 @UnsupportedAppUsage 781 private int mMinWidth = 0; 782 @UnsupportedAppUsage 783 private int mMinWidthMode = PIXELS; 784 785 @UnsupportedAppUsage 786 private boolean mSingleLine; 787 @UnsupportedAppUsage 788 private int mDesiredHeightAtMeasure = -1; 789 @UnsupportedAppUsage 790 private boolean mIncludePad = true; 791 private int mDeferScroll = -1; 792 793 // tmp primitives, so we don't alloc them on each draw 794 private Rect mTempRect; 795 private long mLastScroll; 796 private Scroller mScroller; 797 private TextPaint mTempTextPaint; 798 799 @UnsupportedAppUsage 800 private BoringLayout.Metrics mBoring; 801 @UnsupportedAppUsage 802 private BoringLayout.Metrics mHintBoring; 803 @UnsupportedAppUsage 804 private BoringLayout mSavedLayout; 805 @UnsupportedAppUsage 806 private BoringLayout mSavedHintLayout; 807 808 @UnsupportedAppUsage 809 private TextDirectionHeuristic mTextDir; 810 811 private InputFilter[] mFilters = NO_FILTERS; 812 813 /** 814 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 815 * the same as {@link Process#myUserHandle()}. 816 * 817 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 818 * other apps may need to set this so that the system can use right user's resources and 819 * services such as input methods and spell checkers.</p> 820 * 821 * @see #setTextOperationUser(UserHandle) 822 */ 823 @Nullable 824 private UserHandle mTextOperationUser; 825 826 private volatile Locale mCurrentSpellCheckerLocaleCache; 827 828 // It is possible to have a selection even when mEditor is null (programmatically set, like when 829 // a link is pressed). These highlight-related fields do not go in mEditor. 830 @UnsupportedAppUsage 831 int mHighlightColor = 0x6633B5E5; 832 private Path mHighlightPath; 833 @UnsupportedAppUsage 834 private final Paint mHighlightPaint; 835 @UnsupportedAppUsage 836 private boolean mHighlightPathBogus = true; 837 838 // Although these fields are specific to editable text, they are not added to Editor because 839 // they are defined by the TextView's style and are theme-dependent. 840 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 841 int mCursorDrawableRes; 842 private Drawable mCursorDrawable; 843 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 844 // by removing it, but we would break apps targeting <= P that use it by reflection. 845 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 846 int mTextSelectHandleLeftRes; 847 private Drawable mTextSelectHandleLeft; 848 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 849 // by removing it, but we would break apps targeting <= P that use it by reflection. 850 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 851 int mTextSelectHandleRightRes; 852 private Drawable mTextSelectHandleRight; 853 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 854 // by removing it, but we would break apps targeting <= P that use it by reflection. 855 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 856 int mTextSelectHandleRes; 857 private Drawable mTextSelectHandle; 858 int mTextEditSuggestionItemLayout; 859 int mTextEditSuggestionContainerLayout; 860 int mTextEditSuggestionHighlightStyle; 861 862 private static final int NO_POINTER_ID = -1; 863 /** 864 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 865 * TextView and the handle views which are rendered on popup windows. 866 */ 867 private int mPrimePointerId = NO_POINTER_ID; 868 869 /** 870 * Whether the prime pointer is from the event delivered to selection handle or insertion 871 * handle. 872 */ 873 private boolean mIsPrimePointerFromHandleView; 874 875 /** 876 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 877 * See {@link #createEditorIfNeeded()}. 878 */ 879 @UnsupportedAppUsage 880 private Editor mEditor; 881 882 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 883 private static final int DEVICE_PROVISIONED_NO = 1; 884 private static final int DEVICE_PROVISIONED_YES = 2; 885 886 /** 887 * Some special options such as sharing selected text should only be shown if the device 888 * is provisioned. Only check the provisioned state once for a given view instance. 889 */ 890 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 891 892 /** 893 * The TextView does not auto-size text (default). 894 */ 895 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 896 897 /** 898 * The TextView scales text size both horizontally and vertically to fit within the 899 * container. 900 */ 901 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 902 903 /** @hide */ 904 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 905 AUTO_SIZE_TEXT_TYPE_NONE, 906 AUTO_SIZE_TEXT_TYPE_UNIFORM 907 }) 908 @Retention(RetentionPolicy.SOURCE) 909 public @interface AutoSizeTextType {} 910 // Default minimum size for auto-sizing text in scaled pixels. 911 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 912 // Default maximum size for auto-sizing text in scaled pixels. 913 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 914 // Default value for the step size in pixels. 915 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 916 // Use this to specify that any of the auto-size configuration int values have not been set. 917 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 918 // Auto-size text type. 919 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 920 // Specify if auto-size text is needed. 921 private boolean mNeedsAutoSizeText = false; 922 // Step size for auto-sizing in pixels. 923 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 924 // Minimum text size for auto-sizing in pixels. 925 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 926 // Maximum text size for auto-sizing in pixels. 927 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 928 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 929 // when auto-sizing text. 930 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 931 // Specifies whether auto-size should use the provided auto size steps set or if it should 932 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 933 // mAutoSizeStepGranularityInPx. 934 private boolean mHasPresetAutoSizeValues = false; 935 936 // Autofill-related attributes 937 // 938 // Indicates whether the text was set statically or dynamically, so it can be used to 939 // sanitize autofill requests. 940 private boolean mTextSetFromXmlOrResourceId = false; 941 // Resource id used to set the text. 942 private @StringRes int mTextId = Resources.ID_NULL; 943 // Resource id used to set the hint. 944 private @StringRes int mHintId = Resources.ID_NULL; 945 // 946 // End of autofill-related attributes 947 948 /** 949 * Kick-start the font cache for the zygote process (to pay the cost of 950 * initializing freetype for our default font only once). 951 * @hide 952 */ 953 public static void preloadFontCache() { 954 Paint p = new Paint(); 955 p.setAntiAlias(true); 956 // Ensure that the Typeface is loaded here. 957 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 958 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 959 // since Paint.measureText can not be called without Typeface static initializer. 960 p.setTypeface(Typeface.DEFAULT); 961 // We don't care about the result, just the side-effect of measuring. 962 p.measureText("H"); 963 } 964 965 /** 966 * Interface definition for a callback to be invoked when an action is 967 * performed on the editor. 968 */ 969 public interface OnEditorActionListener { 970 /** 971 * Called when an action is being performed. 972 * 973 * @param v The view that was clicked. 974 * @param actionId Identifier of the action. This will be either the 975 * identifier you supplied, or {@link EditorInfo#IME_NULL 976 * EditorInfo.IME_NULL} if being called due to the enter key 977 * being pressed. 978 * @param event If triggered by an enter key, this is the event; 979 * otherwise, this is null. 980 * @return Return true if you have consumed the action, else false. 981 */ 982 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 983 } 984 985 public TextView(Context context) { 986 this(context, null); 987 } 988 989 public TextView(Context context, @Nullable AttributeSet attrs) { 990 this(context, attrs, com.android.internal.R.attr.textViewStyle); 991 } 992 993 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 994 this(context, attrs, defStyleAttr, 0); 995 } 996 997 @SuppressWarnings("deprecation") 998 public TextView( 999 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1000 super(context, attrs, defStyleAttr, defStyleRes); 1001 1002 // TextView is important by default, unless app developer overrode attribute. 1003 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1004 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1005 } 1006 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1007 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1008 } 1009 1010 setTextInternal(""); 1011 1012 final Resources res = getResources(); 1013 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1014 1015 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1016 mTextPaint.density = res.getDisplayMetrics().density; 1017 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1018 1019 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1020 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1021 1022 mMovement = getDefaultMovementMethod(); 1023 1024 mTransformation = null; 1025 1026 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1027 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1028 attributes.mTextSize = 15; 1029 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1030 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1031 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1032 1033 final Resources.Theme theme = context.getTheme(); 1034 1035 /* 1036 * Look the appearance up without checking first if it exists because 1037 * almost every TextView has one and it greatly simplifies the logic 1038 * to be able to parse the appearance first and then let specific tags 1039 * for this View override it. 1040 */ 1041 TypedArray a = theme.obtainStyledAttributes(attrs, 1042 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1043 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1044 attrs, a, defStyleAttr, defStyleRes); 1045 TypedArray appearance = null; 1046 int ap = a.getResourceId( 1047 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1048 a.recycle(); 1049 if (ap != -1) { 1050 appearance = theme.obtainStyledAttributes( 1051 ap, com.android.internal.R.styleable.TextAppearance); 1052 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1053 null, appearance, 0, ap); 1054 } 1055 if (appearance != null) { 1056 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1057 attributes.mFontFamilyExplicit = false; 1058 appearance.recycle(); 1059 } 1060 1061 boolean editable = getDefaultEditable(); 1062 CharSequence inputMethod = null; 1063 int numeric = 0; 1064 CharSequence digits = null; 1065 boolean phone = false; 1066 boolean autotext = false; 1067 int autocap = -1; 1068 int buffertype = 0; 1069 boolean selectallonfocus = false; 1070 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1071 drawableBottom = null, drawableStart = null, drawableEnd = null; 1072 ColorStateList drawableTint = null; 1073 BlendMode drawableTintMode = null; 1074 int drawablePadding = 0; 1075 int ellipsize = ELLIPSIZE_NOT_SET; 1076 boolean singleLine = false; 1077 int maxlength = -1; 1078 CharSequence text = ""; 1079 CharSequence hint = null; 1080 boolean password = false; 1081 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1082 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1083 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1084 int inputType = EditorInfo.TYPE_NULL; 1085 a = theme.obtainStyledAttributes( 1086 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1087 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1088 defStyleAttr, defStyleRes); 1089 int firstBaselineToTopHeight = -1; 1090 int lastBaselineToBottomHeight = -1; 1091 int lineHeight = -1; 1092 1093 readTextAppearance(context, a, attributes, true /* styleArray */); 1094 1095 int n = a.getIndexCount(); 1096 1097 // Must set id in a temporary variable because it will be reset by setText() 1098 boolean textIsSetFromXml = false; 1099 for (int i = 0; i < n; i++) { 1100 int attr = a.getIndex(i); 1101 1102 switch (attr) { 1103 case com.android.internal.R.styleable.TextView_editable: 1104 editable = a.getBoolean(attr, editable); 1105 break; 1106 1107 case com.android.internal.R.styleable.TextView_inputMethod: 1108 inputMethod = a.getText(attr); 1109 break; 1110 1111 case com.android.internal.R.styleable.TextView_numeric: 1112 numeric = a.getInt(attr, numeric); 1113 break; 1114 1115 case com.android.internal.R.styleable.TextView_digits: 1116 digits = a.getText(attr); 1117 break; 1118 1119 case com.android.internal.R.styleable.TextView_phoneNumber: 1120 phone = a.getBoolean(attr, phone); 1121 break; 1122 1123 case com.android.internal.R.styleable.TextView_autoText: 1124 autotext = a.getBoolean(attr, autotext); 1125 break; 1126 1127 case com.android.internal.R.styleable.TextView_capitalize: 1128 autocap = a.getInt(attr, autocap); 1129 break; 1130 1131 case com.android.internal.R.styleable.TextView_bufferType: 1132 buffertype = a.getInt(attr, buffertype); 1133 break; 1134 1135 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1136 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1137 break; 1138 1139 case com.android.internal.R.styleable.TextView_autoLink: 1140 mAutoLinkMask = a.getInt(attr, 0); 1141 break; 1142 1143 case com.android.internal.R.styleable.TextView_linksClickable: 1144 mLinksClickable = a.getBoolean(attr, true); 1145 break; 1146 1147 case com.android.internal.R.styleable.TextView_drawableLeft: 1148 drawableLeft = a.getDrawable(attr); 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_drawableTop: 1152 drawableTop = a.getDrawable(attr); 1153 break; 1154 1155 case com.android.internal.R.styleable.TextView_drawableRight: 1156 drawableRight = a.getDrawable(attr); 1157 break; 1158 1159 case com.android.internal.R.styleable.TextView_drawableBottom: 1160 drawableBottom = a.getDrawable(attr); 1161 break; 1162 1163 case com.android.internal.R.styleable.TextView_drawableStart: 1164 drawableStart = a.getDrawable(attr); 1165 break; 1166 1167 case com.android.internal.R.styleable.TextView_drawableEnd: 1168 drawableEnd = a.getDrawable(attr); 1169 break; 1170 1171 case com.android.internal.R.styleable.TextView_drawableTint: 1172 drawableTint = a.getColorStateList(attr); 1173 break; 1174 1175 case com.android.internal.R.styleable.TextView_drawableTintMode: 1176 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1177 drawableTintMode); 1178 break; 1179 1180 case com.android.internal.R.styleable.TextView_drawablePadding: 1181 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1182 break; 1183 1184 case com.android.internal.R.styleable.TextView_maxLines: 1185 setMaxLines(a.getInt(attr, -1)); 1186 break; 1187 1188 case com.android.internal.R.styleable.TextView_maxHeight: 1189 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1190 break; 1191 1192 case com.android.internal.R.styleable.TextView_lines: 1193 setLines(a.getInt(attr, -1)); 1194 break; 1195 1196 case com.android.internal.R.styleable.TextView_height: 1197 setHeight(a.getDimensionPixelSize(attr, -1)); 1198 break; 1199 1200 case com.android.internal.R.styleable.TextView_minLines: 1201 setMinLines(a.getInt(attr, -1)); 1202 break; 1203 1204 case com.android.internal.R.styleable.TextView_minHeight: 1205 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1206 break; 1207 1208 case com.android.internal.R.styleable.TextView_maxEms: 1209 setMaxEms(a.getInt(attr, -1)); 1210 break; 1211 1212 case com.android.internal.R.styleable.TextView_maxWidth: 1213 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1214 break; 1215 1216 case com.android.internal.R.styleable.TextView_ems: 1217 setEms(a.getInt(attr, -1)); 1218 break; 1219 1220 case com.android.internal.R.styleable.TextView_width: 1221 setWidth(a.getDimensionPixelSize(attr, -1)); 1222 break; 1223 1224 case com.android.internal.R.styleable.TextView_minEms: 1225 setMinEms(a.getInt(attr, -1)); 1226 break; 1227 1228 case com.android.internal.R.styleable.TextView_minWidth: 1229 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1230 break; 1231 1232 case com.android.internal.R.styleable.TextView_gravity: 1233 setGravity(a.getInt(attr, -1)); 1234 break; 1235 1236 case com.android.internal.R.styleable.TextView_hint: 1237 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1238 hint = a.getText(attr); 1239 break; 1240 1241 case com.android.internal.R.styleable.TextView_text: 1242 textIsSetFromXml = true; 1243 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1244 text = a.getText(attr); 1245 break; 1246 1247 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1248 if (a.getBoolean(attr, false)) { 1249 setHorizontallyScrolling(true); 1250 } 1251 break; 1252 1253 case com.android.internal.R.styleable.TextView_singleLine: 1254 singleLine = a.getBoolean(attr, singleLine); 1255 break; 1256 1257 case com.android.internal.R.styleable.TextView_ellipsize: 1258 ellipsize = a.getInt(attr, ellipsize); 1259 break; 1260 1261 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1262 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1263 break; 1264 1265 case com.android.internal.R.styleable.TextView_includeFontPadding: 1266 if (!a.getBoolean(attr, true)) { 1267 setIncludeFontPadding(false); 1268 } 1269 break; 1270 1271 case com.android.internal.R.styleable.TextView_cursorVisible: 1272 if (!a.getBoolean(attr, true)) { 1273 setCursorVisible(false); 1274 } 1275 break; 1276 1277 case com.android.internal.R.styleable.TextView_maxLength: 1278 maxlength = a.getInt(attr, -1); 1279 break; 1280 1281 case com.android.internal.R.styleable.TextView_textScaleX: 1282 setTextScaleX(a.getFloat(attr, 1.0f)); 1283 break; 1284 1285 case com.android.internal.R.styleable.TextView_freezesText: 1286 mFreezesText = a.getBoolean(attr, false); 1287 break; 1288 1289 case com.android.internal.R.styleable.TextView_enabled: 1290 setEnabled(a.getBoolean(attr, isEnabled())); 1291 break; 1292 1293 case com.android.internal.R.styleable.TextView_password: 1294 password = a.getBoolean(attr, password); 1295 break; 1296 1297 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1298 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1299 break; 1300 1301 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1302 mSpacingMult = a.getFloat(attr, mSpacingMult); 1303 break; 1304 1305 case com.android.internal.R.styleable.TextView_inputType: 1306 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_allowUndo: 1310 createEditorIfNeeded(); 1311 mEditor.mAllowUndo = a.getBoolean(attr, true); 1312 break; 1313 1314 case com.android.internal.R.styleable.TextView_imeOptions: 1315 createEditorIfNeeded(); 1316 mEditor.createInputContentTypeIfNeeded(); 1317 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1318 mEditor.mInputContentType.imeOptions); 1319 break; 1320 1321 case com.android.internal.R.styleable.TextView_imeActionLabel: 1322 createEditorIfNeeded(); 1323 mEditor.createInputContentTypeIfNeeded(); 1324 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1325 break; 1326 1327 case com.android.internal.R.styleable.TextView_imeActionId: 1328 createEditorIfNeeded(); 1329 mEditor.createInputContentTypeIfNeeded(); 1330 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1331 mEditor.mInputContentType.imeActionId); 1332 break; 1333 1334 case com.android.internal.R.styleable.TextView_privateImeOptions: 1335 setPrivateImeOptions(a.getString(attr)); 1336 break; 1337 1338 case com.android.internal.R.styleable.TextView_editorExtras: 1339 try { 1340 setInputExtras(a.getResourceId(attr, 0)); 1341 } catch (XmlPullParserException e) { 1342 Log.w(LOG_TAG, "Failure reading input extras", e); 1343 } catch (IOException e) { 1344 Log.w(LOG_TAG, "Failure reading input extras", e); 1345 } 1346 break; 1347 1348 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1349 mCursorDrawableRes = a.getResourceId(attr, 0); 1350 break; 1351 1352 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1353 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1354 break; 1355 1356 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1357 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1358 break; 1359 1360 case com.android.internal.R.styleable.TextView_textSelectHandle: 1361 mTextSelectHandleRes = a.getResourceId(attr, 0); 1362 break; 1363 1364 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1365 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1366 break; 1367 1368 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1369 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1370 break; 1371 1372 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1373 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1374 break; 1375 1376 case com.android.internal.R.styleable.TextView_textIsSelectable: 1377 setTextIsSelectable(a.getBoolean(attr, false)); 1378 break; 1379 1380 case com.android.internal.R.styleable.TextView_breakStrategy: 1381 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1382 break; 1383 1384 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1385 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1386 break; 1387 1388 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1389 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1390 break; 1391 1392 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1393 autoSizeStepGranularityInPx = a.getDimension(attr, 1394 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1395 break; 1396 1397 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1398 autoSizeMinTextSizeInPx = a.getDimension(attr, 1399 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1400 break; 1401 1402 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1403 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1404 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1405 break; 1406 1407 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1408 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1409 if (autoSizeStepSizeArrayResId > 0) { 1410 final TypedArray autoSizePresetTextSizes = a.getResources() 1411 .obtainTypedArray(autoSizeStepSizeArrayResId); 1412 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1413 autoSizePresetTextSizes.recycle(); 1414 } 1415 break; 1416 case com.android.internal.R.styleable.TextView_justificationMode: 1417 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1418 break; 1419 1420 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1421 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1422 break; 1423 1424 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1425 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1426 break; 1427 1428 case com.android.internal.R.styleable.TextView_lineHeight: 1429 lineHeight = a.getDimensionPixelSize(attr, -1); 1430 break; 1431 } 1432 } 1433 1434 a.recycle(); 1435 1436 BufferType bufferType = BufferType.EDITABLE; 1437 1438 final int variation = 1439 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1440 final boolean passwordInputType = variation 1441 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1442 final boolean webPasswordInputType = variation 1443 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1444 final boolean numberPasswordInputType = variation 1445 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1446 1447 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1448 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1449 mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; 1450 1451 if (inputMethod != null) { 1452 Class<?> c; 1453 1454 try { 1455 c = Class.forName(inputMethod.toString()); 1456 } catch (ClassNotFoundException ex) { 1457 throw new RuntimeException(ex); 1458 } 1459 1460 try { 1461 createEditorIfNeeded(); 1462 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1463 } catch (InstantiationException ex) { 1464 throw new RuntimeException(ex); 1465 } catch (IllegalAccessException ex) { 1466 throw new RuntimeException(ex); 1467 } 1468 try { 1469 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1470 ? inputType 1471 : mEditor.mKeyListener.getInputType(); 1472 } catch (IncompatibleClassChangeError e) { 1473 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1474 } 1475 } else if (digits != null) { 1476 createEditorIfNeeded(); 1477 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1478 // If no input type was specified, we will default to generic 1479 // text, since we can't tell the IME about the set of digits 1480 // that was selected. 1481 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1482 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1483 } else if (inputType != EditorInfo.TYPE_NULL) { 1484 setInputType(inputType, true); 1485 // If set, the input type overrides what was set using the deprecated singleLine flag. 1486 singleLine = !isMultilineInputType(inputType); 1487 } else if (phone) { 1488 createEditorIfNeeded(); 1489 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1490 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1491 } else if (numeric != 0) { 1492 createEditorIfNeeded(); 1493 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1494 null, // locale 1495 (numeric & SIGNED) != 0, 1496 (numeric & DECIMAL) != 0); 1497 inputType = mEditor.mKeyListener.getInputType(); 1498 mEditor.mInputType = inputType; 1499 } else if (autotext || autocap != -1) { 1500 TextKeyListener.Capitalize cap; 1501 1502 inputType = EditorInfo.TYPE_CLASS_TEXT; 1503 1504 switch (autocap) { 1505 case 1: 1506 cap = TextKeyListener.Capitalize.SENTENCES; 1507 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1508 break; 1509 1510 case 2: 1511 cap = TextKeyListener.Capitalize.WORDS; 1512 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1513 break; 1514 1515 case 3: 1516 cap = TextKeyListener.Capitalize.CHARACTERS; 1517 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1518 break; 1519 1520 default: 1521 cap = TextKeyListener.Capitalize.NONE; 1522 break; 1523 } 1524 1525 createEditorIfNeeded(); 1526 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1527 mEditor.mInputType = inputType; 1528 } else if (editable) { 1529 createEditorIfNeeded(); 1530 mEditor.mKeyListener = TextKeyListener.getInstance(); 1531 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1532 } else if (isTextSelectable()) { 1533 // Prevent text changes from keyboard. 1534 if (mEditor != null) { 1535 mEditor.mKeyListener = null; 1536 mEditor.mInputType = EditorInfo.TYPE_NULL; 1537 } 1538 bufferType = BufferType.SPANNABLE; 1539 // So that selection can be changed using arrow keys and touch is handled. 1540 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1541 } else { 1542 if (mEditor != null) mEditor.mKeyListener = null; 1543 1544 switch (buffertype) { 1545 case 0: 1546 bufferType = BufferType.NORMAL; 1547 break; 1548 case 1: 1549 bufferType = BufferType.SPANNABLE; 1550 break; 1551 case 2: 1552 bufferType = BufferType.EDITABLE; 1553 break; 1554 } 1555 } 1556 1557 if (mEditor != null) { 1558 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1559 numberPasswordInputType); 1560 } 1561 1562 if (selectallonfocus) { 1563 createEditorIfNeeded(); 1564 mEditor.mSelectAllOnFocus = true; 1565 1566 if (bufferType == BufferType.NORMAL) { 1567 bufferType = BufferType.SPANNABLE; 1568 } 1569 } 1570 1571 // Set up the tint (if needed) before setting the drawables so that it 1572 // gets applied correctly. 1573 if (drawableTint != null || drawableTintMode != null) { 1574 if (mDrawables == null) { 1575 mDrawables = new Drawables(context); 1576 } 1577 if (drawableTint != null) { 1578 mDrawables.mTintList = drawableTint; 1579 mDrawables.mHasTint = true; 1580 } 1581 if (drawableTintMode != null) { 1582 mDrawables.mBlendMode = drawableTintMode; 1583 mDrawables.mHasTintMode = true; 1584 } 1585 } 1586 1587 // This call will save the initial left/right drawables 1588 setCompoundDrawablesWithIntrinsicBounds( 1589 drawableLeft, drawableTop, drawableRight, drawableBottom); 1590 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1591 setCompoundDrawablePadding(drawablePadding); 1592 1593 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1594 // of lines of height are unchanged for multi-line TextViews. 1595 setInputTypeSingleLine(singleLine); 1596 applySingleLine(singleLine, singleLine, singleLine, 1597 // Does not apply automated max length filter since length filter will be resolved 1598 // later in this function. 1599 false 1600 ); 1601 1602 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1603 ellipsize = ELLIPSIZE_END; 1604 } 1605 1606 switch (ellipsize) { 1607 case ELLIPSIZE_START: 1608 setEllipsize(TextUtils.TruncateAt.START); 1609 break; 1610 case ELLIPSIZE_MIDDLE: 1611 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1612 break; 1613 case ELLIPSIZE_END: 1614 setEllipsize(TextUtils.TruncateAt.END); 1615 break; 1616 case ELLIPSIZE_MARQUEE: 1617 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1618 setHorizontalFadingEdgeEnabled(true); 1619 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1620 } else { 1621 setHorizontalFadingEdgeEnabled(false); 1622 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1623 } 1624 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1625 break; 1626 } 1627 1628 final boolean isPassword = password || passwordInputType || webPasswordInputType 1629 || numberPasswordInputType; 1630 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1631 && (mEditor.mInputType 1632 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1633 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1634 if (isMonospaceEnforced) { 1635 attributes.mTypefaceIndex = MONOSPACE; 1636 } 1637 1638 applyTextAppearance(attributes); 1639 1640 if (isPassword) { 1641 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1642 } 1643 1644 // For addressing b/145128646 1645 // For the performance reason, we limit characters for single line text field. 1646 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1647 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1648 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1649 } 1650 1651 if (mSingleLineLengthFilter != null) { 1652 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1653 } else if (maxlength >= 0) { 1654 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1655 } else { 1656 setFilters(NO_FILTERS); 1657 } 1658 1659 setText(text, bufferType); 1660 if (mText == null) { 1661 mText = ""; 1662 } 1663 if (mTransformed == null) { 1664 mTransformed = ""; 1665 } 1666 1667 if (textIsSetFromXml) { 1668 mTextSetFromXmlOrResourceId = true; 1669 } 1670 1671 if (hint != null) setHint(hint); 1672 1673 /* 1674 * Views are not normally clickable unless specified to be. 1675 * However, TextViews that have input or movement methods *are* 1676 * clickable by default. By setting clickable here, we implicitly set focusable as well 1677 * if not overridden by the developer. 1678 */ 1679 a = context.obtainStyledAttributes( 1680 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1681 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1682 boolean clickable = canInputOrMove || isClickable(); 1683 boolean longClickable = canInputOrMove || isLongClickable(); 1684 int focusable = getFocusable(); 1685 1686 n = a.getIndexCount(); 1687 for (int i = 0; i < n; i++) { 1688 int attr = a.getIndex(i); 1689 1690 switch (attr) { 1691 case com.android.internal.R.styleable.View_focusable: 1692 TypedValue val = new TypedValue(); 1693 if (a.getValue(attr, val)) { 1694 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1695 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1696 : val.data; 1697 } 1698 break; 1699 1700 case com.android.internal.R.styleable.View_clickable: 1701 clickable = a.getBoolean(attr, clickable); 1702 break; 1703 1704 case com.android.internal.R.styleable.View_longClickable: 1705 longClickable = a.getBoolean(attr, longClickable); 1706 break; 1707 } 1708 } 1709 a.recycle(); 1710 1711 // Some apps were relying on the undefined behavior of focusable winning over 1712 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1713 // when starting with EditText and setting only focusable=false). To keep those apps from 1714 // breaking, re-apply the focusable attribute here. 1715 if (focusable != getFocusable()) { 1716 setFocusable(focusable); 1717 } 1718 setClickable(clickable); 1719 setLongClickable(longClickable); 1720 1721 if (mEditor != null) mEditor.prepareCursorControllers(); 1722 1723 // If not explicitly specified this view is important for accessibility. 1724 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1725 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1726 } 1727 1728 if (supportsAutoSizeText()) { 1729 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1730 // If uniform auto-size has been specified but preset values have not been set then 1731 // replace the auto-size configuration values that have not been specified with the 1732 // defaults. 1733 if (!mHasPresetAutoSizeValues) { 1734 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1735 1736 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1737 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1738 TypedValue.COMPLEX_UNIT_SP, 1739 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1740 displayMetrics); 1741 } 1742 1743 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1744 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1745 TypedValue.COMPLEX_UNIT_SP, 1746 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1747 displayMetrics); 1748 } 1749 1750 if (autoSizeStepGranularityInPx 1751 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1752 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1753 } 1754 1755 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1756 autoSizeMaxTextSizeInPx, 1757 autoSizeStepGranularityInPx); 1758 } 1759 1760 setupAutoSizeText(); 1761 } 1762 } else { 1763 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1764 } 1765 1766 if (firstBaselineToTopHeight >= 0) { 1767 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1768 } 1769 if (lastBaselineToBottomHeight >= 0) { 1770 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1771 } 1772 if (lineHeight >= 0) { 1773 setLineHeight(lineHeight); 1774 } 1775 } 1776 1777 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1778 private void setTextInternal(@Nullable CharSequence text) { 1779 mText = text; 1780 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1781 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1782 } 1783 1784 /** 1785 * Specify whether this widget should automatically scale the text to try to perfectly fit 1786 * within the layout bounds by using the default auto-size configuration. 1787 * 1788 * @param autoSizeTextType the type of auto-size. Must be one of 1789 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1790 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1791 * 1792 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1793 * 1794 * @attr ref android.R.styleable#TextView_autoSizeTextType 1795 * 1796 * @see #getAutoSizeTextType() 1797 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1798 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1799 if (supportsAutoSizeText()) { 1800 switch (autoSizeTextType) { 1801 case AUTO_SIZE_TEXT_TYPE_NONE: 1802 clearAutoSizeConfiguration(); 1803 break; 1804 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1805 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1806 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1807 TypedValue.COMPLEX_UNIT_SP, 1808 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1809 displayMetrics); 1810 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1811 TypedValue.COMPLEX_UNIT_SP, 1812 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1813 displayMetrics); 1814 1815 validateAndSetAutoSizeTextTypeUniformConfiguration( 1816 autoSizeMinTextSizeInPx, 1817 autoSizeMaxTextSizeInPx, 1818 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1819 if (setupAutoSizeText()) { 1820 autoSizeText(); 1821 invalidate(); 1822 } 1823 break; 1824 default: 1825 throw new IllegalArgumentException( 1826 "Unknown auto-size text type: " + autoSizeTextType); 1827 } 1828 } 1829 } 1830 1831 /** 1832 * Specify whether this widget should automatically scale the text to try to perfectly fit 1833 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1834 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1835 * 1836 * @param autoSizeMinTextSize the minimum text size available for auto-size 1837 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1838 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1839 * the minimum and maximum text size in order to build the set of 1840 * text sizes the system uses to choose from when auto-sizing 1841 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1842 * possible dimension units 1843 * 1844 * @throws IllegalArgumentException if any of the configuration params are invalid. 1845 * 1846 * @attr ref android.R.styleable#TextView_autoSizeTextType 1847 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1848 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1849 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1850 * 1851 * @see #setAutoSizeTextTypeWithDefaults(int) 1852 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1853 * @see #getAutoSizeMinTextSize() 1854 * @see #getAutoSizeMaxTextSize() 1855 * @see #getAutoSizeStepGranularity() 1856 * @see #getAutoSizeTextAvailableSizes() 1857 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)1858 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1859 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1860 if (supportsAutoSizeText()) { 1861 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1862 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1863 unit, autoSizeMinTextSize, displayMetrics); 1864 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1865 unit, autoSizeMaxTextSize, displayMetrics); 1866 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 1867 unit, autoSizeStepGranularity, displayMetrics); 1868 1869 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1870 autoSizeMaxTextSizeInPx, 1871 autoSizeStepGranularityInPx); 1872 1873 if (setupAutoSizeText()) { 1874 autoSizeText(); 1875 invalidate(); 1876 } 1877 } 1878 } 1879 1880 /** 1881 * Specify whether this widget should automatically scale the text to try to perfectly fit 1882 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1883 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1884 * 1885 * @param presetSizes an {@code int} array of sizes in pixels 1886 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1887 * the possible dimension units 1888 * 1889 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1890 * 1891 * @attr ref android.R.styleable#TextView_autoSizeTextType 1892 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1893 * 1894 * @see #setAutoSizeTextTypeWithDefaults(int) 1895 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1896 * @see #getAutoSizeMinTextSize() 1897 * @see #getAutoSizeMaxTextSize() 1898 * @see #getAutoSizeTextAvailableSizes() 1899 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)1900 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1901 if (supportsAutoSizeText()) { 1902 final int presetSizesLength = presetSizes.length; 1903 if (presetSizesLength > 0) { 1904 int[] presetSizesInPx = new int[presetSizesLength]; 1905 1906 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1907 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1908 } else { 1909 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1910 // Convert all to sizes to pixels. 1911 for (int i = 0; i < presetSizesLength; i++) { 1912 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 1913 presetSizes[i], displayMetrics)); 1914 } 1915 } 1916 1917 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1918 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1919 throw new IllegalArgumentException("None of the preset sizes is valid: " 1920 + Arrays.toString(presetSizes)); 1921 } 1922 } else { 1923 mHasPresetAutoSizeValues = false; 1924 } 1925 1926 if (setupAutoSizeText()) { 1927 autoSizeText(); 1928 invalidate(); 1929 } 1930 } 1931 } 1932 1933 /** 1934 * Returns the type of auto-size set for this widget. 1935 * 1936 * @return an {@code int} corresponding to one of the auto-size types: 1937 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1938 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1939 * 1940 * @attr ref android.R.styleable#TextView_autoSizeTextType 1941 * 1942 * @see #setAutoSizeTextTypeWithDefaults(int) 1943 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1944 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1945 */ 1946 @InspectableProperty(enumMapping = { 1947 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 1948 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 1949 }) 1950 @AutoSizeTextType getAutoSizeTextType()1951 public int getAutoSizeTextType() { 1952 return mAutoSizeTextType; 1953 } 1954 1955 /** 1956 * @return the current auto-size step granularity in pixels. 1957 * 1958 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1959 * 1960 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1961 */ 1962 @InspectableProperty getAutoSizeStepGranularity()1963 public int getAutoSizeStepGranularity() { 1964 return Math.round(mAutoSizeStepGranularityInPx); 1965 } 1966 1967 /** 1968 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 1969 * if auto-size has not been configured this function returns {@code -1}. 1970 * 1971 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1972 * 1973 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1974 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1975 */ 1976 @InspectableProperty getAutoSizeMinTextSize()1977 public int getAutoSizeMinTextSize() { 1978 return Math.round(mAutoSizeMinTextSizeInPx); 1979 } 1980 1981 /** 1982 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 1983 * if auto-size has not been configured this function returns {@code -1}. 1984 * 1985 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1986 * 1987 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1988 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1989 */ 1990 @InspectableProperty getAutoSizeMaxTextSize()1991 public int getAutoSizeMaxTextSize() { 1992 return Math.round(mAutoSizeMaxTextSizeInPx); 1993 } 1994 1995 /** 1996 * @return the current auto-size {@code int} sizes array (in pixels). 1997 * 1998 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1999 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2000 */ getAutoSizeTextAvailableSizes()2001 public int[] getAutoSizeTextAvailableSizes() { 2002 return mAutoSizeTextSizesInPx; 2003 } 2004 setupAutoSizeUniformPresetSizes(TypedArray textSizes)2005 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2006 final int textSizesLength = textSizes.length(); 2007 final int[] parsedSizes = new int[textSizesLength]; 2008 2009 if (textSizesLength > 0) { 2010 for (int i = 0; i < textSizesLength; i++) { 2011 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2012 } 2013 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2014 setupAutoSizeUniformPresetSizesConfiguration(); 2015 } 2016 } 2017 setupAutoSizeUniformPresetSizesConfiguration()2018 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2019 final int sizesLength = mAutoSizeTextSizesInPx.length; 2020 mHasPresetAutoSizeValues = sizesLength > 0; 2021 if (mHasPresetAutoSizeValues) { 2022 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2023 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2024 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2025 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2026 } 2027 return mHasPresetAutoSizeValues; 2028 } 2029 2030 /** 2031 * If all params are valid then save the auto-size configuration. 2032 * 2033 * @throws IllegalArgumentException if any of the params are invalid 2034 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2035 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2036 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2037 // First validate. 2038 if (autoSizeMinTextSizeInPx <= 0) { 2039 throw new IllegalArgumentException("Minimum auto-size text size (" 2040 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2041 } 2042 2043 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2044 throw new IllegalArgumentException("Maximum auto-size text size (" 2045 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2046 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2047 } 2048 2049 if (autoSizeStepGranularityInPx <= 0) { 2050 throw new IllegalArgumentException("The auto-size step granularity (" 2051 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2052 } 2053 2054 // All good, persist the configuration. 2055 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2056 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2057 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2058 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2059 mHasPresetAutoSizeValues = false; 2060 } 2061 clearAutoSizeConfiguration()2062 private void clearAutoSizeConfiguration() { 2063 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2064 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2065 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2066 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2067 mAutoSizeTextSizesInPx = EmptyArray.INT; 2068 mNeedsAutoSizeText = false; 2069 } 2070 2071 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2072 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2073 final int presetValuesLength = presetValues.length; 2074 if (presetValuesLength == 0) { 2075 return presetValues; 2076 } 2077 Arrays.sort(presetValues); 2078 2079 final IntArray uniqueValidSizes = new IntArray(); 2080 for (int i = 0; i < presetValuesLength; i++) { 2081 final int currentPresetValue = presetValues[i]; 2082 2083 if (currentPresetValue > 0 2084 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2085 uniqueValidSizes.add(currentPresetValue); 2086 } 2087 } 2088 2089 return presetValuesLength == uniqueValidSizes.size() 2090 ? presetValues 2091 : uniqueValidSizes.toArray(); 2092 } 2093 setupAutoSizeText()2094 private boolean setupAutoSizeText() { 2095 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2096 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2097 // not have a predefined set of sizes or if the current sizes array is empty. 2098 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2099 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2100 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2101 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2102 for (int i = 0; i < autoSizeValuesLength; i++) { 2103 autoSizeTextSizesInPx[i] = Math.round( 2104 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2105 } 2106 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2107 } 2108 2109 mNeedsAutoSizeText = true; 2110 } else { 2111 mNeedsAutoSizeText = false; 2112 } 2113 2114 return mNeedsAutoSizeText; 2115 } 2116 parseDimensionArray(TypedArray dimens)2117 private int[] parseDimensionArray(TypedArray dimens) { 2118 if (dimens == null) { 2119 return null; 2120 } 2121 int[] result = new int[dimens.length()]; 2122 for (int i = 0; i < result.length; i++) { 2123 result[i] = dimens.getDimensionPixelSize(i, 0); 2124 } 2125 return result; 2126 } 2127 2128 /** 2129 * @hide 2130 */ 2131 @Override onActivityResult(int requestCode, int resultCode, Intent data)2132 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2133 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2134 if (resultCode == Activity.RESULT_OK && data != null) { 2135 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2136 if (result != null) { 2137 if (isTextEditable()) { 2138 replaceSelectionWithText(result); 2139 if (mEditor != null) { 2140 mEditor.refreshTextActionMode(); 2141 } 2142 } else { 2143 if (result.length() > 0) { 2144 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2145 .show(); 2146 } 2147 } 2148 } 2149 } else if (mSpannable != null) { 2150 // Reset the selection. 2151 Selection.setSelection(mSpannable, getSelectionEnd()); 2152 } 2153 } 2154 } 2155 2156 /** 2157 * Sets the Typeface taking into account the given attributes. 2158 * 2159 * @param typeface a typeface 2160 * @param familyName family name string, e.g. "serif" 2161 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2162 * @param style a typeface style 2163 * @param weight a weight value for the Typeface or -1 if not specified. 2164 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2165 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2166 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2167 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2168 if (typeface == null && familyName != null) { 2169 // Lookup normal Typeface from system font map. 2170 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2171 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2172 } else if (typeface != null) { 2173 resolveStyleAndSetTypeface(typeface, style, weight); 2174 } else { // both typeface and familyName is null. 2175 switch (typefaceIndex) { 2176 case SANS: 2177 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2178 break; 2179 case SERIF: 2180 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2181 break; 2182 case MONOSPACE: 2183 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2184 break; 2185 case DEFAULT_TYPEFACE: 2186 default: 2187 resolveStyleAndSetTypeface(null, style, weight); 2188 break; 2189 } 2190 } 2191 } 2192 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight)2193 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2194 @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) { 2195 if (weight >= 0) { 2196 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2197 final boolean italic = (style & Typeface.ITALIC) != 0; 2198 setTypeface(Typeface.create(typeface, weight, italic)); 2199 } else { 2200 setTypeface(typeface, style); 2201 } 2202 } 2203 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2204 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2205 boolean hasRelativeDrawables = (start != null) || (end != null); 2206 if (hasRelativeDrawables) { 2207 Drawables dr = mDrawables; 2208 if (dr == null) { 2209 mDrawables = dr = new Drawables(getContext()); 2210 } 2211 mDrawables.mOverride = true; 2212 final Rect compoundRect = dr.mCompoundRect; 2213 int[] state = getDrawableState(); 2214 if (start != null) { 2215 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2216 start.setState(state); 2217 start.copyBounds(compoundRect); 2218 start.setCallback(this); 2219 2220 dr.mDrawableStart = start; 2221 dr.mDrawableSizeStart = compoundRect.width(); 2222 dr.mDrawableHeightStart = compoundRect.height(); 2223 } else { 2224 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2225 } 2226 if (end != null) { 2227 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2228 end.setState(state); 2229 end.copyBounds(compoundRect); 2230 end.setCallback(this); 2231 2232 dr.mDrawableEnd = end; 2233 dr.mDrawableSizeEnd = compoundRect.width(); 2234 dr.mDrawableHeightEnd = compoundRect.height(); 2235 } else { 2236 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2237 } 2238 resetResolvedDrawables(); 2239 resolveDrawables(); 2240 applyCompoundDrawableTint(); 2241 } 2242 } 2243 2244 @android.view.RemotableViewMethod 2245 @Override setEnabled(boolean enabled)2246 public void setEnabled(boolean enabled) { 2247 if (enabled == isEnabled()) { 2248 return; 2249 } 2250 2251 if (!enabled) { 2252 // Hide the soft input if the currently active TextView is disabled 2253 InputMethodManager imm = getInputMethodManager(); 2254 if (imm != null && imm.isActive(this)) { 2255 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2256 } 2257 } 2258 2259 super.setEnabled(enabled); 2260 2261 if (enabled) { 2262 // Make sure IME is updated with current editor info. 2263 InputMethodManager imm = getInputMethodManager(); 2264 if (imm != null) imm.restartInput(this); 2265 } 2266 2267 // Will change text color 2268 if (mEditor != null) { 2269 mEditor.invalidateTextDisplayList(); 2270 mEditor.prepareCursorControllers(); 2271 2272 // start or stop the cursor blinking as appropriate 2273 mEditor.makeBlink(); 2274 } 2275 } 2276 2277 /** 2278 * Sets the typeface and style in which the text should be displayed, 2279 * and turns on the fake bold and italic bits in the Paint if the 2280 * Typeface that you provided does not have all the bits in the 2281 * style that you specified. 2282 * 2283 * @attr ref android.R.styleable#TextView_typeface 2284 * @attr ref android.R.styleable#TextView_textStyle 2285 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2286 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2287 if (style > 0) { 2288 if (tf == null) { 2289 tf = Typeface.defaultFromStyle(style); 2290 } else { 2291 tf = Typeface.create(tf, style); 2292 } 2293 2294 setTypeface(tf); 2295 // now compute what (if any) algorithmic styling is needed 2296 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2297 int need = style & ~typefaceStyle; 2298 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2299 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2300 } else { 2301 mTextPaint.setFakeBoldText(false); 2302 mTextPaint.setTextSkewX(0); 2303 setTypeface(tf); 2304 } 2305 } 2306 2307 /** 2308 * Subclasses override this to specify that they have a KeyListener 2309 * by default even if not specifically called for in the XML options. 2310 */ getDefaultEditable()2311 protected boolean getDefaultEditable() { 2312 return false; 2313 } 2314 2315 /** 2316 * Subclasses override this to specify a default movement method. 2317 */ getDefaultMovementMethod()2318 protected MovementMethod getDefaultMovementMethod() { 2319 return null; 2320 } 2321 2322 /** 2323 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2324 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2325 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2326 * the return value from this method to Spannable or Editable, respectively. 2327 * 2328 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2329 * should make your own copy first.</p> 2330 * 2331 * @return The text displayed by the text view. 2332 * @attr ref android.R.styleable#TextView_text 2333 */ 2334 @ViewDebug.CapturedViewProperty 2335 @InspectableProperty getText()2336 public CharSequence getText() { 2337 return mText; 2338 } 2339 2340 /** 2341 * Returns the length, in characters, of the text managed by this TextView 2342 * @return The length of the text managed by the TextView in characters. 2343 */ length()2344 public int length() { 2345 return mText.length(); 2346 } 2347 2348 /** 2349 * Return the text that TextView is displaying as an Editable object. If the text is not 2350 * editable, null is returned. 2351 * 2352 * @see #getText 2353 */ getEditableText()2354 public Editable getEditableText() { 2355 return (mText instanceof Editable) ? (Editable) mText : null; 2356 } 2357 2358 /** 2359 * @hide 2360 */ 2361 @VisibleForTesting getTransformed()2362 public CharSequence getTransformed() { 2363 return mTransformed; 2364 } 2365 2366 /** 2367 * Gets the vertical distance between lines of text, in pixels. 2368 * Note that markup within the text can cause individual lines 2369 * to be taller or shorter than this height, and the layout may 2370 * contain additional first-or last-line padding. 2371 * @return The height of one standard line in pixels. 2372 */ 2373 @InspectableProperty getLineHeight()2374 public int getLineHeight() { 2375 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2376 } 2377 2378 /** 2379 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2380 * This value can be null if the text or width has recently changed. 2381 * @return The Layout that is currently being used to display the text. 2382 */ getLayout()2383 public final Layout getLayout() { 2384 return mLayout; 2385 } 2386 2387 /** 2388 * @return the {@link android.text.Layout} that is currently being used to 2389 * display the hint text. This can be null. 2390 */ 2391 @UnsupportedAppUsage getHintLayout()2392 final Layout getHintLayout() { 2393 return mHintLayout; 2394 } 2395 2396 /** 2397 * Retrieve the {@link android.content.UndoManager} that is currently associated 2398 * with this TextView. By default there is no associated UndoManager, so null 2399 * is returned. One can be associated with the TextView through 2400 * {@link #setUndoManager(android.content.UndoManager, String)} 2401 * 2402 * @hide 2403 */ getUndoManager()2404 public final UndoManager getUndoManager() { 2405 // TODO: Consider supporting a global undo manager. 2406 throw new UnsupportedOperationException("not implemented"); 2407 } 2408 2409 2410 /** 2411 * @hide 2412 */ 2413 @VisibleForTesting getEditorForTesting()2414 public final Editor getEditorForTesting() { 2415 return mEditor; 2416 } 2417 2418 /** 2419 * Associate an {@link android.content.UndoManager} with this TextView. Once 2420 * done, all edit operations on the TextView will result in appropriate 2421 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2422 * stack. 2423 * 2424 * @param undoManager The {@link android.content.UndoManager} to associate with 2425 * this TextView, or null to clear any existing association. 2426 * @param tag String tag identifying this particular TextView owner in the 2427 * UndoManager. This is used to keep the correct association with the 2428 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2429 * 2430 * @hide 2431 */ setUndoManager(UndoManager undoManager, String tag)2432 public final void setUndoManager(UndoManager undoManager, String tag) { 2433 // TODO: Consider supporting a global undo manager. An implementation will need to: 2434 // * createEditorIfNeeded() 2435 // * Promote to BufferType.EDITABLE if needed. 2436 // * Update the UndoManager and UndoOwner. 2437 // Likewise it will need to be able to restore the default UndoManager. 2438 throw new UnsupportedOperationException("not implemented"); 2439 } 2440 2441 /** 2442 * Gets the current {@link KeyListener} for the TextView. 2443 * This will frequently be null for non-EditText TextViews. 2444 * @return the current key listener for this TextView. 2445 * 2446 * @attr ref android.R.styleable#TextView_numeric 2447 * @attr ref android.R.styleable#TextView_digits 2448 * @attr ref android.R.styleable#TextView_phoneNumber 2449 * @attr ref android.R.styleable#TextView_inputMethod 2450 * @attr ref android.R.styleable#TextView_capitalize 2451 * @attr ref android.R.styleable#TextView_autoText 2452 */ getKeyListener()2453 public final KeyListener getKeyListener() { 2454 return mEditor == null ? null : mEditor.mKeyListener; 2455 } 2456 2457 /** 2458 * Sets the key listener to be used with this TextView. This can be null 2459 * to disallow user input. Note that this method has significant and 2460 * subtle interactions with soft keyboards and other input method: 2461 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2462 * for important details. Calling this method will replace the current 2463 * content type of the text view with the content type returned by the 2464 * key listener. 2465 * <p> 2466 * Be warned that if you want a TextView with a key listener or movement 2467 * method not to be focusable, or if you want a TextView without a 2468 * key listener or movement method to be focusable, you must call 2469 * {@link #setFocusable} again after calling this to get the focusability 2470 * back the way you want it. 2471 * 2472 * @attr ref android.R.styleable#TextView_numeric 2473 * @attr ref android.R.styleable#TextView_digits 2474 * @attr ref android.R.styleable#TextView_phoneNumber 2475 * @attr ref android.R.styleable#TextView_inputMethod 2476 * @attr ref android.R.styleable#TextView_capitalize 2477 * @attr ref android.R.styleable#TextView_autoText 2478 */ setKeyListener(KeyListener input)2479 public void setKeyListener(KeyListener input) { 2480 mListenerChanged = true; 2481 setKeyListenerOnly(input); 2482 fixFocusableAndClickableSettings(); 2483 2484 if (input != null) { 2485 createEditorIfNeeded(); 2486 setInputTypeFromEditor(); 2487 } else { 2488 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2489 } 2490 2491 InputMethodManager imm = getInputMethodManager(); 2492 if (imm != null) imm.restartInput(this); 2493 } 2494 setInputTypeFromEditor()2495 private void setInputTypeFromEditor() { 2496 try { 2497 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2498 } catch (IncompatibleClassChangeError e) { 2499 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2500 } 2501 // Change inputType, without affecting transformation. 2502 // No need to applySingleLine since mSingleLine is unchanged. 2503 setInputTypeSingleLine(mSingleLine); 2504 } 2505 setKeyListenerOnly(KeyListener input)2506 private void setKeyListenerOnly(KeyListener input) { 2507 if (mEditor == null && input == null) return; // null is the default value 2508 2509 createEditorIfNeeded(); 2510 if (mEditor.mKeyListener != input) { 2511 mEditor.mKeyListener = input; 2512 if (input != null && !(mText instanceof Editable)) { 2513 setText(mText); 2514 } 2515 2516 setFilters((Editable) mText, mFilters); 2517 } 2518 } 2519 2520 /** 2521 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2522 * which provides positioning, scrolling, and text selection functionality. 2523 * This will frequently be null for non-EditText TextViews. 2524 * @return the movement method being used for this TextView. 2525 * @see android.text.method.MovementMethod 2526 */ getMovementMethod()2527 public final MovementMethod getMovementMethod() { 2528 return mMovement; 2529 } 2530 2531 /** 2532 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2533 * for this TextView. This can be null to disallow using the arrow keys to move the 2534 * cursor or scroll the view. 2535 * <p> 2536 * Be warned that if you want a TextView with a key listener or movement 2537 * method not to be focusable, or if you want a TextView without a 2538 * key listener or movement method to be focusable, you must call 2539 * {@link #setFocusable} again after calling this to get the focusability 2540 * back the way you want it. 2541 */ setMovementMethod(MovementMethod movement)2542 public final void setMovementMethod(MovementMethod movement) { 2543 if (mMovement != movement) { 2544 mMovement = movement; 2545 2546 if (movement != null && mSpannable == null) { 2547 setText(mText); 2548 } 2549 2550 fixFocusableAndClickableSettings(); 2551 2552 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2553 // mMovement 2554 if (mEditor != null) mEditor.prepareCursorControllers(); 2555 } 2556 } 2557 fixFocusableAndClickableSettings()2558 private void fixFocusableAndClickableSettings() { 2559 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2560 setFocusable(FOCUSABLE); 2561 setClickable(true); 2562 setLongClickable(true); 2563 } else { 2564 setFocusable(FOCUSABLE_AUTO); 2565 setClickable(false); 2566 setLongClickable(false); 2567 } 2568 } 2569 2570 /** 2571 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2572 * This is frequently null, except for single-line and password fields. 2573 * @return the current transformation method for this TextView. 2574 * 2575 * @attr ref android.R.styleable#TextView_password 2576 * @attr ref android.R.styleable#TextView_singleLine 2577 */ getTransformationMethod()2578 public final TransformationMethod getTransformationMethod() { 2579 return mTransformation; 2580 } 2581 2582 /** 2583 * Sets the transformation that is applied to the text that this 2584 * TextView is displaying. 2585 * 2586 * @attr ref android.R.styleable#TextView_password 2587 * @attr ref android.R.styleable#TextView_singleLine 2588 */ setTransformationMethod(TransformationMethod method)2589 public final void setTransformationMethod(TransformationMethod method) { 2590 if (method == mTransformation) { 2591 // Avoid the setText() below if the transformation is 2592 // the same. 2593 return; 2594 } 2595 if (mTransformation != null) { 2596 if (mSpannable != null) { 2597 mSpannable.removeSpan(mTransformation); 2598 } 2599 } 2600 2601 mTransformation = method; 2602 2603 if (method instanceof TransformationMethod2) { 2604 TransformationMethod2 method2 = (TransformationMethod2) method; 2605 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2606 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2607 } else { 2608 mAllowTransformationLengthChange = false; 2609 } 2610 2611 setText(mText); 2612 2613 if (hasPasswordTransformationMethod()) { 2614 notifyViewAccessibilityStateChangedIfNeeded( 2615 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2616 } 2617 2618 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2619 // getTextDirectionHeuristic, needs reset 2620 mTextDir = getTextDirectionHeuristic(); 2621 } 2622 2623 /** 2624 * Returns the top padding of the view, plus space for the top 2625 * Drawable if any. 2626 */ getCompoundPaddingTop()2627 public int getCompoundPaddingTop() { 2628 final Drawables dr = mDrawables; 2629 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2630 return mPaddingTop; 2631 } else { 2632 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2633 } 2634 } 2635 2636 /** 2637 * Returns the bottom padding of the view, plus space for the bottom 2638 * Drawable if any. 2639 */ getCompoundPaddingBottom()2640 public int getCompoundPaddingBottom() { 2641 final Drawables dr = mDrawables; 2642 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2643 return mPaddingBottom; 2644 } else { 2645 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2646 } 2647 } 2648 2649 /** 2650 * Returns the left padding of the view, plus space for the left 2651 * Drawable if any. 2652 */ getCompoundPaddingLeft()2653 public int getCompoundPaddingLeft() { 2654 final Drawables dr = mDrawables; 2655 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2656 return mPaddingLeft; 2657 } else { 2658 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2659 } 2660 } 2661 2662 /** 2663 * Returns the right padding of the view, plus space for the right 2664 * Drawable if any. 2665 */ getCompoundPaddingRight()2666 public int getCompoundPaddingRight() { 2667 final Drawables dr = mDrawables; 2668 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2669 return mPaddingRight; 2670 } else { 2671 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2672 } 2673 } 2674 2675 /** 2676 * Returns the start padding of the view, plus space for the start 2677 * Drawable if any. 2678 */ getCompoundPaddingStart()2679 public int getCompoundPaddingStart() { 2680 resolveDrawables(); 2681 switch(getLayoutDirection()) { 2682 default: 2683 case LAYOUT_DIRECTION_LTR: 2684 return getCompoundPaddingLeft(); 2685 case LAYOUT_DIRECTION_RTL: 2686 return getCompoundPaddingRight(); 2687 } 2688 } 2689 2690 /** 2691 * Returns the end padding of the view, plus space for the end 2692 * Drawable if any. 2693 */ getCompoundPaddingEnd()2694 public int getCompoundPaddingEnd() { 2695 resolveDrawables(); 2696 switch(getLayoutDirection()) { 2697 default: 2698 case LAYOUT_DIRECTION_LTR: 2699 return getCompoundPaddingRight(); 2700 case LAYOUT_DIRECTION_RTL: 2701 return getCompoundPaddingLeft(); 2702 } 2703 } 2704 2705 /** 2706 * Returns the extended top padding of the view, including both the 2707 * top Drawable if any and any extra space to keep more than maxLines 2708 * of text from showing. It is only valid to call this after measuring. 2709 */ getExtendedPaddingTop()2710 public int getExtendedPaddingTop() { 2711 if (mMaxMode != LINES) { 2712 return getCompoundPaddingTop(); 2713 } 2714 2715 if (mLayout == null) { 2716 assumeLayout(); 2717 } 2718 2719 if (mLayout.getLineCount() <= mMaximum) { 2720 return getCompoundPaddingTop(); 2721 } 2722 2723 int top = getCompoundPaddingTop(); 2724 int bottom = getCompoundPaddingBottom(); 2725 int viewht = getHeight() - top - bottom; 2726 int layoutht = mLayout.getLineTop(mMaximum); 2727 2728 if (layoutht >= viewht) { 2729 return top; 2730 } 2731 2732 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2733 if (gravity == Gravity.TOP) { 2734 return top; 2735 } else if (gravity == Gravity.BOTTOM) { 2736 return top + viewht - layoutht; 2737 } else { // (gravity == Gravity.CENTER_VERTICAL) 2738 return top + (viewht - layoutht) / 2; 2739 } 2740 } 2741 2742 /** 2743 * Returns the extended bottom padding of the view, including both the 2744 * bottom Drawable if any and any extra space to keep more than maxLines 2745 * of text from showing. It is only valid to call this after measuring. 2746 */ getExtendedPaddingBottom()2747 public int getExtendedPaddingBottom() { 2748 if (mMaxMode != LINES) { 2749 return getCompoundPaddingBottom(); 2750 } 2751 2752 if (mLayout == null) { 2753 assumeLayout(); 2754 } 2755 2756 if (mLayout.getLineCount() <= mMaximum) { 2757 return getCompoundPaddingBottom(); 2758 } 2759 2760 int top = getCompoundPaddingTop(); 2761 int bottom = getCompoundPaddingBottom(); 2762 int viewht = getHeight() - top - bottom; 2763 int layoutht = mLayout.getLineTop(mMaximum); 2764 2765 if (layoutht >= viewht) { 2766 return bottom; 2767 } 2768 2769 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2770 if (gravity == Gravity.TOP) { 2771 return bottom + viewht - layoutht; 2772 } else if (gravity == Gravity.BOTTOM) { 2773 return bottom; 2774 } else { // (gravity == Gravity.CENTER_VERTICAL) 2775 return bottom + (viewht - layoutht) / 2; 2776 } 2777 } 2778 2779 /** 2780 * Returns the total left padding of the view, including the left 2781 * Drawable if any. 2782 */ getTotalPaddingLeft()2783 public int getTotalPaddingLeft() { 2784 return getCompoundPaddingLeft(); 2785 } 2786 2787 /** 2788 * Returns the total right padding of the view, including the right 2789 * Drawable if any. 2790 */ getTotalPaddingRight()2791 public int getTotalPaddingRight() { 2792 return getCompoundPaddingRight(); 2793 } 2794 2795 /** 2796 * Returns the total start padding of the view, including the start 2797 * Drawable if any. 2798 */ getTotalPaddingStart()2799 public int getTotalPaddingStart() { 2800 return getCompoundPaddingStart(); 2801 } 2802 2803 /** 2804 * Returns the total end padding of the view, including the end 2805 * Drawable if any. 2806 */ getTotalPaddingEnd()2807 public int getTotalPaddingEnd() { 2808 return getCompoundPaddingEnd(); 2809 } 2810 2811 /** 2812 * Returns the total top padding of the view, including the top 2813 * Drawable if any, the extra space to keep more than maxLines 2814 * from showing, and the vertical offset for gravity, if any. 2815 */ getTotalPaddingTop()2816 public int getTotalPaddingTop() { 2817 return getExtendedPaddingTop() + getVerticalOffset(true); 2818 } 2819 2820 /** 2821 * Returns the total bottom padding of the view, including the bottom 2822 * Drawable if any, the extra space to keep more than maxLines 2823 * from showing, and the vertical offset for gravity, if any. 2824 */ getTotalPaddingBottom()2825 public int getTotalPaddingBottom() { 2826 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2827 } 2828 2829 /** 2830 * Sets the Drawables (if any) to appear to the left of, above, to the 2831 * right of, and below the text. Use {@code null} if you do not want a 2832 * Drawable there. The Drawables must already have had 2833 * {@link Drawable#setBounds} called. 2834 * <p> 2835 * Calling this method will overwrite any Drawables previously set using 2836 * {@link #setCompoundDrawablesRelative} or related methods. 2837 * 2838 * @attr ref android.R.styleable#TextView_drawableLeft 2839 * @attr ref android.R.styleable#TextView_drawableTop 2840 * @attr ref android.R.styleable#TextView_drawableRight 2841 * @attr ref android.R.styleable#TextView_drawableBottom 2842 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2843 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2844 @Nullable Drawable right, @Nullable Drawable bottom) { 2845 Drawables dr = mDrawables; 2846 2847 // We're switching to absolute, discard relative. 2848 if (dr != null) { 2849 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2850 dr.mDrawableStart = null; 2851 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2852 dr.mDrawableEnd = null; 2853 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2854 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2855 } 2856 2857 final boolean drawables = left != null || top != null || right != null || bottom != null; 2858 if (!drawables) { 2859 // Clearing drawables... can we free the data structure? 2860 if (dr != null) { 2861 if (!dr.hasMetadata()) { 2862 mDrawables = null; 2863 } else { 2864 // We need to retain the last set padding, so just clear 2865 // out all of the fields in the existing structure. 2866 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2867 if (dr.mShowing[i] != null) { 2868 dr.mShowing[i].setCallback(null); 2869 } 2870 dr.mShowing[i] = null; 2871 } 2872 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2873 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2874 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2875 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2876 } 2877 } 2878 } else { 2879 if (dr == null) { 2880 mDrawables = dr = new Drawables(getContext()); 2881 } 2882 2883 mDrawables.mOverride = false; 2884 2885 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2886 dr.mShowing[Drawables.LEFT].setCallback(null); 2887 } 2888 dr.mShowing[Drawables.LEFT] = left; 2889 2890 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2891 dr.mShowing[Drawables.TOP].setCallback(null); 2892 } 2893 dr.mShowing[Drawables.TOP] = top; 2894 2895 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2896 dr.mShowing[Drawables.RIGHT].setCallback(null); 2897 } 2898 dr.mShowing[Drawables.RIGHT] = right; 2899 2900 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2901 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2902 } 2903 dr.mShowing[Drawables.BOTTOM] = bottom; 2904 2905 final Rect compoundRect = dr.mCompoundRect; 2906 int[] state; 2907 2908 state = getDrawableState(); 2909 2910 if (left != null) { 2911 left.setState(state); 2912 left.copyBounds(compoundRect); 2913 left.setCallback(this); 2914 dr.mDrawableSizeLeft = compoundRect.width(); 2915 dr.mDrawableHeightLeft = compoundRect.height(); 2916 } else { 2917 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2918 } 2919 2920 if (right != null) { 2921 right.setState(state); 2922 right.copyBounds(compoundRect); 2923 right.setCallback(this); 2924 dr.mDrawableSizeRight = compoundRect.width(); 2925 dr.mDrawableHeightRight = compoundRect.height(); 2926 } else { 2927 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2928 } 2929 2930 if (top != null) { 2931 top.setState(state); 2932 top.copyBounds(compoundRect); 2933 top.setCallback(this); 2934 dr.mDrawableSizeTop = compoundRect.height(); 2935 dr.mDrawableWidthTop = compoundRect.width(); 2936 } else { 2937 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2938 } 2939 2940 if (bottom != null) { 2941 bottom.setState(state); 2942 bottom.copyBounds(compoundRect); 2943 bottom.setCallback(this); 2944 dr.mDrawableSizeBottom = compoundRect.height(); 2945 dr.mDrawableWidthBottom = compoundRect.width(); 2946 } else { 2947 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2948 } 2949 } 2950 2951 // Save initial left/right drawables 2952 if (dr != null) { 2953 dr.mDrawableLeftInitial = left; 2954 dr.mDrawableRightInitial = right; 2955 } 2956 2957 resetResolvedDrawables(); 2958 resolveDrawables(); 2959 applyCompoundDrawableTint(); 2960 invalidate(); 2961 requestLayout(); 2962 } 2963 2964 /** 2965 * Sets the Drawables (if any) to appear to the left of, above, to the 2966 * right of, and below the text. Use 0 if you do not want a Drawable there. 2967 * The Drawables' bounds will be set to their intrinsic bounds. 2968 * <p> 2969 * Calling this method will overwrite any Drawables previously set using 2970 * {@link #setCompoundDrawablesRelative} or related methods. 2971 * 2972 * @param left Resource identifier of the left Drawable. 2973 * @param top Resource identifier of the top Drawable. 2974 * @param right Resource identifier of the right Drawable. 2975 * @param bottom Resource identifier of the bottom Drawable. 2976 * 2977 * @attr ref android.R.styleable#TextView_drawableLeft 2978 * @attr ref android.R.styleable#TextView_drawableTop 2979 * @attr ref android.R.styleable#TextView_drawableRight 2980 * @attr ref android.R.styleable#TextView_drawableBottom 2981 */ 2982 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)2983 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 2984 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 2985 final Context context = getContext(); 2986 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2987 top != 0 ? context.getDrawable(top) : null, 2988 right != 0 ? context.getDrawable(right) : null, 2989 bottom != 0 ? context.getDrawable(bottom) : null); 2990 } 2991 2992 /** 2993 * Sets the Drawables (if any) to appear to the left of, above, to the 2994 * right of, and below the text. Use {@code null} if you do not want a 2995 * Drawable there. The Drawables' bounds will be set to their intrinsic 2996 * bounds. 2997 * <p> 2998 * Calling this method will overwrite any Drawables previously set using 2999 * {@link #setCompoundDrawablesRelative} or related methods. 3000 * 3001 * @attr ref android.R.styleable#TextView_drawableLeft 3002 * @attr ref android.R.styleable#TextView_drawableTop 3003 * @attr ref android.R.styleable#TextView_drawableRight 3004 * @attr ref android.R.styleable#TextView_drawableBottom 3005 */ 3006 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3007 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3008 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3009 3010 if (left != null) { 3011 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3012 } 3013 if (right != null) { 3014 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3015 } 3016 if (top != null) { 3017 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3018 } 3019 if (bottom != null) { 3020 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3021 } 3022 setCompoundDrawables(left, top, right, bottom); 3023 } 3024 3025 /** 3026 * Sets the Drawables (if any) to appear to the start of, above, to the end 3027 * of, and below the text. Use {@code null} if you do not want a Drawable 3028 * there. The Drawables must already have had {@link Drawable#setBounds} 3029 * called. 3030 * <p> 3031 * Calling this method will overwrite any Drawables previously set using 3032 * {@link #setCompoundDrawables} or related methods. 3033 * 3034 * @attr ref android.R.styleable#TextView_drawableStart 3035 * @attr ref android.R.styleable#TextView_drawableTop 3036 * @attr ref android.R.styleable#TextView_drawableEnd 3037 * @attr ref android.R.styleable#TextView_drawableBottom 3038 */ 3039 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3040 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3041 @Nullable Drawable end, @Nullable Drawable bottom) { 3042 Drawables dr = mDrawables; 3043 3044 // We're switching to relative, discard absolute. 3045 if (dr != null) { 3046 if (dr.mShowing[Drawables.LEFT] != null) { 3047 dr.mShowing[Drawables.LEFT].setCallback(null); 3048 } 3049 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3050 if (dr.mShowing[Drawables.RIGHT] != null) { 3051 dr.mShowing[Drawables.RIGHT].setCallback(null); 3052 } 3053 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3054 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3055 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3056 } 3057 3058 final boolean drawables = start != null || top != null 3059 || end != null || bottom != null; 3060 3061 if (!drawables) { 3062 // Clearing drawables... can we free the data structure? 3063 if (dr != null) { 3064 if (!dr.hasMetadata()) { 3065 mDrawables = null; 3066 } else { 3067 // We need to retain the last set padding, so just clear 3068 // out all of the fields in the existing structure. 3069 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3070 dr.mDrawableStart = null; 3071 if (dr.mShowing[Drawables.TOP] != null) { 3072 dr.mShowing[Drawables.TOP].setCallback(null); 3073 } 3074 dr.mShowing[Drawables.TOP] = null; 3075 if (dr.mDrawableEnd != null) { 3076 dr.mDrawableEnd.setCallback(null); 3077 } 3078 dr.mDrawableEnd = null; 3079 if (dr.mShowing[Drawables.BOTTOM] != null) { 3080 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3081 } 3082 dr.mShowing[Drawables.BOTTOM] = null; 3083 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3084 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3085 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3086 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3087 } 3088 } 3089 } else { 3090 if (dr == null) { 3091 mDrawables = dr = new Drawables(getContext()); 3092 } 3093 3094 mDrawables.mOverride = true; 3095 3096 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3097 dr.mDrawableStart.setCallback(null); 3098 } 3099 dr.mDrawableStart = start; 3100 3101 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3102 dr.mShowing[Drawables.TOP].setCallback(null); 3103 } 3104 dr.mShowing[Drawables.TOP] = top; 3105 3106 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3107 dr.mDrawableEnd.setCallback(null); 3108 } 3109 dr.mDrawableEnd = end; 3110 3111 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3112 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3113 } 3114 dr.mShowing[Drawables.BOTTOM] = bottom; 3115 3116 final Rect compoundRect = dr.mCompoundRect; 3117 int[] state; 3118 3119 state = getDrawableState(); 3120 3121 if (start != null) { 3122 start.setState(state); 3123 start.copyBounds(compoundRect); 3124 start.setCallback(this); 3125 dr.mDrawableSizeStart = compoundRect.width(); 3126 dr.mDrawableHeightStart = compoundRect.height(); 3127 } else { 3128 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3129 } 3130 3131 if (end != null) { 3132 end.setState(state); 3133 end.copyBounds(compoundRect); 3134 end.setCallback(this); 3135 dr.mDrawableSizeEnd = compoundRect.width(); 3136 dr.mDrawableHeightEnd = compoundRect.height(); 3137 } else { 3138 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3139 } 3140 3141 if (top != null) { 3142 top.setState(state); 3143 top.copyBounds(compoundRect); 3144 top.setCallback(this); 3145 dr.mDrawableSizeTop = compoundRect.height(); 3146 dr.mDrawableWidthTop = compoundRect.width(); 3147 } else { 3148 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3149 } 3150 3151 if (bottom != null) { 3152 bottom.setState(state); 3153 bottom.copyBounds(compoundRect); 3154 bottom.setCallback(this); 3155 dr.mDrawableSizeBottom = compoundRect.height(); 3156 dr.mDrawableWidthBottom = compoundRect.width(); 3157 } else { 3158 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3159 } 3160 } 3161 3162 resetResolvedDrawables(); 3163 resolveDrawables(); 3164 invalidate(); 3165 requestLayout(); 3166 } 3167 3168 /** 3169 * Sets the Drawables (if any) to appear to the start of, above, to the end 3170 * of, and below the text. Use 0 if you do not want a Drawable there. The 3171 * Drawables' bounds will be set to their intrinsic bounds. 3172 * <p> 3173 * Calling this method will overwrite any Drawables previously set using 3174 * {@link #setCompoundDrawables} or related methods. 3175 * 3176 * @param start Resource identifier of the start Drawable. 3177 * @param top Resource identifier of the top Drawable. 3178 * @param end Resource identifier of the end Drawable. 3179 * @param bottom Resource identifier of the bottom Drawable. 3180 * 3181 * @attr ref android.R.styleable#TextView_drawableStart 3182 * @attr ref android.R.styleable#TextView_drawableTop 3183 * @attr ref android.R.styleable#TextView_drawableEnd 3184 * @attr ref android.R.styleable#TextView_drawableBottom 3185 */ 3186 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3187 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3188 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3189 final Context context = getContext(); 3190 setCompoundDrawablesRelativeWithIntrinsicBounds( 3191 start != 0 ? context.getDrawable(start) : null, 3192 top != 0 ? context.getDrawable(top) : null, 3193 end != 0 ? context.getDrawable(end) : null, 3194 bottom != 0 ? context.getDrawable(bottom) : null); 3195 } 3196 3197 /** 3198 * Sets the Drawables (if any) to appear to the start of, above, to the end 3199 * of, and below the text. Use {@code null} if you do not want a Drawable 3200 * there. The Drawables' bounds will be set to their intrinsic bounds. 3201 * <p> 3202 * Calling this method will overwrite any Drawables previously set using 3203 * {@link #setCompoundDrawables} or related methods. 3204 * 3205 * @attr ref android.R.styleable#TextView_drawableStart 3206 * @attr ref android.R.styleable#TextView_drawableTop 3207 * @attr ref android.R.styleable#TextView_drawableEnd 3208 * @attr ref android.R.styleable#TextView_drawableBottom 3209 */ 3210 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3211 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3212 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3213 3214 if (start != null) { 3215 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3216 } 3217 if (end != null) { 3218 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3219 } 3220 if (top != null) { 3221 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3222 } 3223 if (bottom != null) { 3224 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3225 } 3226 setCompoundDrawablesRelative(start, top, end, bottom); 3227 } 3228 3229 /** 3230 * Returns drawables for the left, top, right, and bottom borders. 3231 * 3232 * @attr ref android.R.styleable#TextView_drawableLeft 3233 * @attr ref android.R.styleable#TextView_drawableTop 3234 * @attr ref android.R.styleable#TextView_drawableRight 3235 * @attr ref android.R.styleable#TextView_drawableBottom 3236 */ 3237 @NonNull getCompoundDrawables()3238 public Drawable[] getCompoundDrawables() { 3239 final Drawables dr = mDrawables; 3240 if (dr != null) { 3241 return dr.mShowing.clone(); 3242 } else { 3243 return new Drawable[] { null, null, null, null }; 3244 } 3245 } 3246 3247 /** 3248 * Returns drawables for the start, top, end, and bottom borders. 3249 * 3250 * @attr ref android.R.styleable#TextView_drawableStart 3251 * @attr ref android.R.styleable#TextView_drawableTop 3252 * @attr ref android.R.styleable#TextView_drawableEnd 3253 * @attr ref android.R.styleable#TextView_drawableBottom 3254 */ 3255 @NonNull getCompoundDrawablesRelative()3256 public Drawable[] getCompoundDrawablesRelative() { 3257 final Drawables dr = mDrawables; 3258 if (dr != null) { 3259 return new Drawable[] { 3260 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3261 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3262 }; 3263 } else { 3264 return new Drawable[] { null, null, null, null }; 3265 } 3266 } 3267 3268 /** 3269 * Sets the size of the padding between the compound drawables and 3270 * the text. 3271 * 3272 * @attr ref android.R.styleable#TextView_drawablePadding 3273 */ 3274 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3275 public void setCompoundDrawablePadding(int pad) { 3276 Drawables dr = mDrawables; 3277 if (pad == 0) { 3278 if (dr != null) { 3279 dr.mDrawablePadding = pad; 3280 } 3281 } else { 3282 if (dr == null) { 3283 mDrawables = dr = new Drawables(getContext()); 3284 } 3285 dr.mDrawablePadding = pad; 3286 } 3287 3288 invalidate(); 3289 requestLayout(); 3290 } 3291 3292 /** 3293 * Returns the padding between the compound drawables and the text. 3294 * 3295 * @attr ref android.R.styleable#TextView_drawablePadding 3296 */ 3297 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3298 public int getCompoundDrawablePadding() { 3299 final Drawables dr = mDrawables; 3300 return dr != null ? dr.mDrawablePadding : 0; 3301 } 3302 3303 /** 3304 * Applies a tint to the compound drawables. Does not modify the 3305 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3306 * <p> 3307 * Subsequent calls to 3308 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3309 * and related methods will automatically mutate the drawables and apply 3310 * the specified tint and tint mode using 3311 * {@link Drawable#setTintList(ColorStateList)}. 3312 * 3313 * @param tint the tint to apply, may be {@code null} to clear tint 3314 * 3315 * @attr ref android.R.styleable#TextView_drawableTint 3316 * @see #getCompoundDrawableTintList() 3317 * @see Drawable#setTintList(ColorStateList) 3318 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3319 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3320 if (mDrawables == null) { 3321 mDrawables = new Drawables(getContext()); 3322 } 3323 mDrawables.mTintList = tint; 3324 mDrawables.mHasTint = true; 3325 3326 applyCompoundDrawableTint(); 3327 } 3328 3329 /** 3330 * @return the tint applied to the compound drawables 3331 * @attr ref android.R.styleable#TextView_drawableTint 3332 * @see #setCompoundDrawableTintList(ColorStateList) 3333 */ 3334 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3335 public ColorStateList getCompoundDrawableTintList() { 3336 return mDrawables != null ? mDrawables.mTintList : null; 3337 } 3338 3339 /** 3340 * Specifies the blending mode used to apply the tint specified by 3341 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3342 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3343 * 3344 * @param tintMode the blending mode used to apply the tint, may be 3345 * {@code null} to clear tint 3346 * @attr ref android.R.styleable#TextView_drawableTintMode 3347 * @see #setCompoundDrawableTintList(ColorStateList) 3348 * @see Drawable#setTintMode(PorterDuff.Mode) 3349 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3350 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3351 setCompoundDrawableTintBlendMode(tintMode != null 3352 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3353 } 3354 3355 /** 3356 * Specifies the blending mode used to apply the tint specified by 3357 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3358 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3359 * 3360 * @param blendMode the blending mode used to apply the tint, may be 3361 * {@code null} to clear tint 3362 * @attr ref android.R.styleable#TextView_drawableTintMode 3363 * @see #setCompoundDrawableTintList(ColorStateList) 3364 * @see Drawable#setTintBlendMode(BlendMode) 3365 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3366 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3367 if (mDrawables == null) { 3368 mDrawables = new Drawables(getContext()); 3369 } 3370 mDrawables.mBlendMode = blendMode; 3371 mDrawables.mHasTintMode = true; 3372 3373 applyCompoundDrawableTint(); 3374 } 3375 3376 /** 3377 * Returns the blending mode used to apply the tint to the compound 3378 * drawables, if specified. 3379 * 3380 * @return the blending mode used to apply the tint to the compound 3381 * drawables 3382 * @attr ref android.R.styleable#TextView_drawableTintMode 3383 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3384 * 3385 */ 3386 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3387 public PorterDuff.Mode getCompoundDrawableTintMode() { 3388 BlendMode mode = getCompoundDrawableTintBlendMode(); 3389 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3390 } 3391 3392 /** 3393 * Returns the blending mode used to apply the tint to the compound 3394 * drawables, if specified. 3395 * 3396 * @return the blending mode used to apply the tint to the compound 3397 * drawables 3398 * @attr ref android.R.styleable#TextView_drawableTintMode 3399 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3400 */ 3401 @InspectableProperty(name = "drawableBlendMode", 3402 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3403 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3404 return mDrawables != null ? mDrawables.mBlendMode : null; 3405 } 3406 applyCompoundDrawableTint()3407 private void applyCompoundDrawableTint() { 3408 if (mDrawables == null) { 3409 return; 3410 } 3411 3412 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3413 final ColorStateList tintList = mDrawables.mTintList; 3414 final BlendMode blendMode = mDrawables.mBlendMode; 3415 final boolean hasTint = mDrawables.mHasTint; 3416 final boolean hasTintMode = mDrawables.mHasTintMode; 3417 final int[] state = getDrawableState(); 3418 3419 for (Drawable dr : mDrawables.mShowing) { 3420 if (dr == null) { 3421 continue; 3422 } 3423 3424 if (dr == mDrawables.mDrawableError) { 3425 // From a developer's perspective, the error drawable isn't 3426 // a compound drawable. Don't apply the generic compound 3427 // drawable tint to it. 3428 continue; 3429 } 3430 3431 dr.mutate(); 3432 3433 if (hasTint) { 3434 dr.setTintList(tintList); 3435 } 3436 3437 if (hasTintMode) { 3438 dr.setTintBlendMode(blendMode); 3439 } 3440 3441 // The drawable (or one of its children) may not have been 3442 // stateful before applying the tint, so let's try again. 3443 if (dr.isStateful()) { 3444 dr.setState(state); 3445 } 3446 } 3447 } 3448 } 3449 3450 /** 3451 * @inheritDoc 3452 * 3453 * @see #setFirstBaselineToTopHeight(int) 3454 * @see #setLastBaselineToBottomHeight(int) 3455 */ 3456 @Override setPadding(int left, int top, int right, int bottom)3457 public void setPadding(int left, int top, int right, int bottom) { 3458 if (left != mPaddingLeft 3459 || right != mPaddingRight 3460 || top != mPaddingTop 3461 || bottom != mPaddingBottom) { 3462 nullLayouts(); 3463 } 3464 3465 // the super call will requestLayout() 3466 super.setPadding(left, top, right, bottom); 3467 invalidate(); 3468 } 3469 3470 /** 3471 * @inheritDoc 3472 * 3473 * @see #setFirstBaselineToTopHeight(int) 3474 * @see #setLastBaselineToBottomHeight(int) 3475 */ 3476 @Override setPaddingRelative(int start, int top, int end, int bottom)3477 public void setPaddingRelative(int start, int top, int end, int bottom) { 3478 if (start != getPaddingStart() 3479 || end != getPaddingEnd() 3480 || top != mPaddingTop 3481 || bottom != mPaddingBottom) { 3482 nullLayouts(); 3483 } 3484 3485 // the super call will requestLayout() 3486 super.setPaddingRelative(start, top, end, bottom); 3487 invalidate(); 3488 } 3489 3490 /** 3491 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3492 * the distance between the top of the TextView and first line's baseline. 3493 * <p> 3494 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3495 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3496 * 3497 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3498 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3499 * Moreover since this function sets the top padding, if the height of the TextView is less than 3500 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3501 * down and bottom will be clipped. 3502 * 3503 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3504 * in pixels 3505 * 3506 * @see #getFirstBaselineToTopHeight() 3507 * @see #setLastBaselineToBottomHeight(int) 3508 * @see #setPadding(int, int, int, int) 3509 * @see #setPaddingRelative(int, int, int, int) 3510 * 3511 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3512 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3513 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3514 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3515 3516 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3517 final int fontMetricsTop; 3518 if (getIncludeFontPadding()) { 3519 fontMetricsTop = fontMetrics.top; 3520 } else { 3521 fontMetricsTop = fontMetrics.ascent; 3522 } 3523 3524 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3525 // in settings). At the moment, we don't. 3526 3527 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3528 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3529 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3530 } 3531 } 3532 3533 /** 3534 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3535 * the distance between the bottom of the TextView and the last line's baseline. 3536 * <p> 3537 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3538 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3539 * 3540 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3541 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3542 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3543 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3544 * clipped. 3545 * 3546 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3547 * in pixels 3548 * 3549 * @see #getLastBaselineToBottomHeight() 3550 * @see #setFirstBaselineToTopHeight(int) 3551 * @see #setPadding(int, int, int, int) 3552 * @see #setPaddingRelative(int, int, int, int) 3553 * 3554 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3555 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3556 public void setLastBaselineToBottomHeight( 3557 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3558 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3559 3560 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3561 final int fontMetricsBottom; 3562 if (getIncludeFontPadding()) { 3563 fontMetricsBottom = fontMetrics.bottom; 3564 } else { 3565 fontMetricsBottom = fontMetrics.descent; 3566 } 3567 3568 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3569 // in settings). At the moment, we don't. 3570 3571 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3572 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3573 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3574 } 3575 } 3576 3577 /** 3578 * Returns the distance between the first text baseline and the top of this TextView. 3579 * 3580 * @see #setFirstBaselineToTopHeight(int) 3581 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3582 */ 3583 @InspectableProperty getFirstBaselineToTopHeight()3584 public int getFirstBaselineToTopHeight() { 3585 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3586 } 3587 3588 /** 3589 * Returns the distance between the last text baseline and the bottom of this TextView. 3590 * 3591 * @see #setLastBaselineToBottomHeight(int) 3592 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3593 */ 3594 @InspectableProperty getLastBaselineToBottomHeight()3595 public int getLastBaselineToBottomHeight() { 3596 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3597 } 3598 3599 /** 3600 * Gets the autolink mask of the text. 3601 * 3602 * See {@link Linkify#ALL} and peers for possible values. 3603 * 3604 * @attr ref android.R.styleable#TextView_autoLink 3605 */ 3606 @InspectableProperty(name = "autoLink", flagMapping = { 3607 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3608 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3609 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3610 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3611 }) getAutoLinkMask()3612 public final int getAutoLinkMask() { 3613 return mAutoLinkMask; 3614 } 3615 3616 /** 3617 * Sets the Drawable corresponding to the selection handle used for 3618 * positioning the cursor within text. The Drawable defaults to the value 3619 * of the textSelectHandle attribute. 3620 * Note that any change applied to the handle Drawable will not be visible 3621 * until the handle is hidden and then drawn again. 3622 * 3623 * @see #setTextSelectHandle(int) 3624 * @attr ref android.R.styleable#TextView_textSelectHandle 3625 */ 3626 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3627 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3628 Preconditions.checkNotNull(textSelectHandle, 3629 "The text select handle should not be null."); 3630 mTextSelectHandle = textSelectHandle; 3631 mTextSelectHandleRes = 0; 3632 if (mEditor != null) { 3633 mEditor.loadHandleDrawables(true /* overwrite */); 3634 } 3635 } 3636 3637 /** 3638 * Sets the Drawable corresponding to the selection handle used for 3639 * positioning the cursor within text. The Drawable defaults to the value 3640 * of the textSelectHandle attribute. 3641 * Note that any change applied to the handle Drawable will not be visible 3642 * until the handle is hidden and then drawn again. 3643 * 3644 * @see #setTextSelectHandle(Drawable) 3645 * @attr ref android.R.styleable#TextView_textSelectHandle 3646 */ 3647 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3648 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3649 Preconditions.checkArgument(textSelectHandle != 0, 3650 "The text select handle should be a valid drawable resource id."); 3651 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3652 } 3653 3654 /** 3655 * Returns the Drawable corresponding to the selection handle used 3656 * for positioning the cursor within text. 3657 * Note that any change applied to the handle Drawable will not be visible 3658 * until the handle is hidden and then drawn again. 3659 * 3660 * @return the text select handle drawable 3661 * 3662 * @see #setTextSelectHandle(Drawable) 3663 * @see #setTextSelectHandle(int) 3664 * @attr ref android.R.styleable#TextView_textSelectHandle 3665 */ getTextSelectHandle()3666 @Nullable public Drawable getTextSelectHandle() { 3667 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3668 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3669 } 3670 return mTextSelectHandle; 3671 } 3672 3673 /** 3674 * Sets the Drawable corresponding to the left handle used 3675 * for selecting text. The Drawable defaults to the value of the 3676 * textSelectHandleLeft attribute. 3677 * Note that any change applied to the handle Drawable will not be visible 3678 * until the handle is hidden and then drawn again. 3679 * 3680 * @see #setTextSelectHandleLeft(int) 3681 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3682 */ 3683 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3684 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3685 Preconditions.checkNotNull(textSelectHandleLeft, 3686 "The left text select handle should not be null."); 3687 mTextSelectHandleLeft = textSelectHandleLeft; 3688 mTextSelectHandleLeftRes = 0; 3689 if (mEditor != null) { 3690 mEditor.loadHandleDrawables(true /* overwrite */); 3691 } 3692 } 3693 3694 /** 3695 * Sets the Drawable corresponding to the left handle used 3696 * for selecting text. The Drawable defaults to the value of the 3697 * textSelectHandleLeft attribute. 3698 * Note that any change applied to the handle Drawable will not be visible 3699 * until the handle is hidden and then drawn again. 3700 * 3701 * @see #setTextSelectHandleLeft(Drawable) 3702 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3703 */ 3704 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3705 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3706 Preconditions.checkArgument(textSelectHandleLeft != 0, 3707 "The text select left handle should be a valid drawable resource id."); 3708 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3709 } 3710 3711 /** 3712 * Returns the Drawable corresponding to the left handle used 3713 * for selecting text. 3714 * Note that any change applied to the handle Drawable will not be visible 3715 * until the handle is hidden and then drawn again. 3716 * 3717 * @return the left text selection handle drawable 3718 * 3719 * @see #setTextSelectHandleLeft(Drawable) 3720 * @see #setTextSelectHandleLeft(int) 3721 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3722 */ getTextSelectHandleLeft()3723 @Nullable public Drawable getTextSelectHandleLeft() { 3724 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3725 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3726 } 3727 return mTextSelectHandleLeft; 3728 } 3729 3730 /** 3731 * Sets the Drawable corresponding to the right handle used 3732 * for selecting text. The Drawable defaults to the value of the 3733 * textSelectHandleRight attribute. 3734 * Note that any change applied to the handle Drawable will not be visible 3735 * until the handle is hidden and then drawn again. 3736 * 3737 * @see #setTextSelectHandleRight(int) 3738 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3739 */ 3740 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3741 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3742 Preconditions.checkNotNull(textSelectHandleRight, 3743 "The right text select handle should not be null."); 3744 mTextSelectHandleRight = textSelectHandleRight; 3745 mTextSelectHandleRightRes = 0; 3746 if (mEditor != null) { 3747 mEditor.loadHandleDrawables(true /* overwrite */); 3748 } 3749 } 3750 3751 /** 3752 * Sets the Drawable corresponding to the right handle used 3753 * for selecting text. The Drawable defaults to the value of the 3754 * textSelectHandleRight attribute. 3755 * Note that any change applied to the handle Drawable will not be visible 3756 * until the handle is hidden and then drawn again. 3757 * 3758 * @see #setTextSelectHandleRight(Drawable) 3759 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3760 */ 3761 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3762 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3763 Preconditions.checkArgument(textSelectHandleRight != 0, 3764 "The text select right handle should be a valid drawable resource id."); 3765 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3766 } 3767 3768 /** 3769 * Returns the Drawable corresponding to the right handle used 3770 * for selecting text. 3771 * Note that any change applied to the handle Drawable will not be visible 3772 * until the handle is hidden and then drawn again. 3773 * 3774 * @return the right text selection handle drawable 3775 * 3776 * @see #setTextSelectHandleRight(Drawable) 3777 * @see #setTextSelectHandleRight(int) 3778 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3779 */ getTextSelectHandleRight()3780 @Nullable public Drawable getTextSelectHandleRight() { 3781 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 3782 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 3783 } 3784 return mTextSelectHandleRight; 3785 } 3786 3787 /** 3788 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3789 * value of the textCursorDrawable attribute. 3790 * Note that any change applied to the cursor Drawable will not be visible 3791 * until the cursor is hidden and then drawn again. 3792 * 3793 * @see #setTextCursorDrawable(int) 3794 * @attr ref android.R.styleable#TextView_textCursorDrawable 3795 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)3796 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 3797 mCursorDrawable = textCursorDrawable; 3798 mCursorDrawableRes = 0; 3799 if (mEditor != null) { 3800 mEditor.loadCursorDrawable(); 3801 } 3802 } 3803 3804 /** 3805 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 3806 * value of the textCursorDrawable attribute. 3807 * Note that any change applied to the cursor Drawable will not be visible 3808 * until the cursor is hidden and then drawn again. 3809 * 3810 * @see #setTextCursorDrawable(Drawable) 3811 * @attr ref android.R.styleable#TextView_textCursorDrawable 3812 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)3813 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 3814 setTextCursorDrawable( 3815 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 3816 } 3817 3818 /** 3819 * Returns the Drawable corresponding to the text cursor. 3820 * Note that any change applied to the cursor Drawable will not be visible 3821 * until the cursor is hidden and then drawn again. 3822 * 3823 * @return the text cursor drawable 3824 * 3825 * @see #setTextCursorDrawable(Drawable) 3826 * @see #setTextCursorDrawable(int) 3827 * @attr ref android.R.styleable#TextView_textCursorDrawable 3828 */ getTextCursorDrawable()3829 @Nullable public Drawable getTextCursorDrawable() { 3830 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 3831 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 3832 } 3833 return mCursorDrawable; 3834 } 3835 3836 /** 3837 * Sets the text appearance from the specified style resource. 3838 * <p> 3839 * Use a framework-defined {@code TextAppearance} style like 3840 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3841 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3842 * set of attributes that can be used in a custom style. 3843 * 3844 * @param resId the resource identifier of the style to apply 3845 * @attr ref android.R.styleable#TextView_textAppearance 3846 */ 3847 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)3848 public void setTextAppearance(@StyleRes int resId) { 3849 setTextAppearance(mContext, resId); 3850 } 3851 3852 /** 3853 * Sets the text color, size, style, hint color, and highlight color 3854 * from the specified TextAppearance resource. 3855 * 3856 * @deprecated Use {@link #setTextAppearance(int)} instead. 3857 */ 3858 @Deprecated setTextAppearance(Context context, @StyleRes int resId)3859 public void setTextAppearance(Context context, @StyleRes int resId) { 3860 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3861 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 3862 readTextAppearance(context, ta, attributes, false /* styleArray */); 3863 ta.recycle(); 3864 applyTextAppearance(attributes); 3865 } 3866 3867 /** 3868 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 3869 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 3870 */ 3871 private static class TextAppearanceAttributes { 3872 int mTextColorHighlight = 0; 3873 ColorStateList mTextColor = null; 3874 ColorStateList mTextColorHint = null; 3875 ColorStateList mTextColorLink = null; 3876 int mTextSize = -1; 3877 int mTextSizeUnit = -1; 3878 LocaleList mTextLocales = null; 3879 String mFontFamily = null; 3880 Typeface mFontTypeface = null; 3881 boolean mFontFamilyExplicit = false; 3882 int mTypefaceIndex = -1; 3883 int mTextStyle = 0; 3884 int mFontWeight = -1; 3885 boolean mAllCaps = false; 3886 int mShadowColor = 0; 3887 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 3888 boolean mHasElegant = false; 3889 boolean mElegant = false; 3890 boolean mHasFallbackLineSpacing = false; 3891 boolean mFallbackLineSpacing = false; 3892 boolean mHasLetterSpacing = false; 3893 float mLetterSpacing = 0; 3894 String mFontFeatureSettings = null; 3895 String mFontVariationSettings = null; 3896 3897 @Override toString()3898 public String toString() { 3899 return "TextAppearanceAttributes {\n" 3900 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 3901 + " mTextColor:" + mTextColor + "\n" 3902 + " mTextColorHint:" + mTextColorHint + "\n" 3903 + " mTextColorLink:" + mTextColorLink + "\n" 3904 + " mTextSize:" + mTextSize + "\n" 3905 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 3906 + " mTextLocales:" + mTextLocales + "\n" 3907 + " mFontFamily:" + mFontFamily + "\n" 3908 + " mFontTypeface:" + mFontTypeface + "\n" 3909 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 3910 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 3911 + " mTextStyle:" + mTextStyle + "\n" 3912 + " mFontWeight:" + mFontWeight + "\n" 3913 + " mAllCaps:" + mAllCaps + "\n" 3914 + " mShadowColor:" + mShadowColor + "\n" 3915 + " mShadowDx:" + mShadowDx + "\n" 3916 + " mShadowDy:" + mShadowDy + "\n" 3917 + " mShadowRadius:" + mShadowRadius + "\n" 3918 + " mHasElegant:" + mHasElegant + "\n" 3919 + " mElegant:" + mElegant + "\n" 3920 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 3921 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 3922 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 3923 + " mLetterSpacing:" + mLetterSpacing + "\n" 3924 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 3925 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 3926 + "}"; 3927 } 3928 } 3929 3930 // Maps styleable attributes that exist both in TextView style and TextAppearance. 3931 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 3932 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)3933 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 3934 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)3935 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 3936 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)3937 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 3938 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)3939 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 3940 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)3941 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 3942 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)3943 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 3944 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)3945 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 3946 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)3947 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 3948 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)3949 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 3950 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)3951 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 3952 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)3953 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 3954 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)3955 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 3956 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)3957 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 3958 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)3959 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 3960 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)3961 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 3962 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)3963 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 3964 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)3965 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 3966 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)3967 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 3968 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)3969 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 3970 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)3971 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 3972 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); 3973 } 3974 3975 /** 3976 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 3977 * set. If the TypedArray contains a value that was already set in the given attributes, that 3978 * will be overridden. 3979 * 3980 * @param context The Context to be used 3981 * @param appearance The TypedArray to read properties from 3982 * @param attributes the TextAppearanceAttributes to fill in 3983 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 3984 * what attribute indexes will be used to read the properties. 3985 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)3986 private void readTextAppearance(Context context, TypedArray appearance, 3987 TextAppearanceAttributes attributes, boolean styleArray) { 3988 final int n = appearance.getIndexCount(); 3989 for (int i = 0; i < n; i++) { 3990 final int attr = appearance.getIndex(i); 3991 int index = attr; 3992 // Translate style array index ids to TextAppearance ids. 3993 if (styleArray) { 3994 index = sAppearanceValues.get(attr, -1); 3995 if (index == -1) { 3996 // This value is not part of a Text Appearance and should be ignored. 3997 continue; 3998 } 3999 } 4000 switch (index) { 4001 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4002 attributes.mTextColorHighlight = 4003 appearance.getColor(attr, attributes.mTextColorHighlight); 4004 break; 4005 case com.android.internal.R.styleable.TextAppearance_textColor: 4006 attributes.mTextColor = appearance.getColorStateList(attr); 4007 break; 4008 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4009 attributes.mTextColorHint = appearance.getColorStateList(attr); 4010 break; 4011 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4012 attributes.mTextColorLink = appearance.getColorStateList(attr); 4013 break; 4014 case com.android.internal.R.styleable.TextAppearance_textSize: 4015 attributes.mTextSize = 4016 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4017 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4018 break; 4019 case com.android.internal.R.styleable.TextAppearance_textLocale: 4020 final String localeString = appearance.getString(attr); 4021 if (localeString != null) { 4022 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4023 if (!localeList.isEmpty()) { 4024 attributes.mTextLocales = localeList; 4025 } 4026 } 4027 break; 4028 case com.android.internal.R.styleable.TextAppearance_typeface: 4029 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4030 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4031 attributes.mFontFamily = null; 4032 } 4033 break; 4034 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4035 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4036 try { 4037 attributes.mFontTypeface = appearance.getFont(attr); 4038 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4039 // Expected if it is not a font resource. 4040 } 4041 } 4042 if (attributes.mFontTypeface == null) { 4043 attributes.mFontFamily = appearance.getString(attr); 4044 } 4045 attributes.mFontFamilyExplicit = true; 4046 break; 4047 case com.android.internal.R.styleable.TextAppearance_textStyle: 4048 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4049 break; 4050 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4051 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4052 break; 4053 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4054 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4055 break; 4056 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4057 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4058 break; 4059 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4060 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4061 break; 4062 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4063 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4064 break; 4065 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4066 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4067 break; 4068 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4069 attributes.mHasElegant = true; 4070 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4071 break; 4072 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4073 attributes.mHasFallbackLineSpacing = true; 4074 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4075 attributes.mFallbackLineSpacing); 4076 break; 4077 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4078 attributes.mHasLetterSpacing = true; 4079 attributes.mLetterSpacing = 4080 appearance.getFloat(attr, attributes.mLetterSpacing); 4081 break; 4082 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4083 attributes.mFontFeatureSettings = appearance.getString(attr); 4084 break; 4085 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4086 attributes.mFontVariationSettings = appearance.getString(attr); 4087 break; 4088 default: 4089 } 4090 } 4091 } 4092 applyTextAppearance(TextAppearanceAttributes attributes)4093 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4094 if (attributes.mTextColor != null) { 4095 setTextColor(attributes.mTextColor); 4096 } 4097 4098 if (attributes.mTextColorHint != null) { 4099 setHintTextColor(attributes.mTextColorHint); 4100 } 4101 4102 if (attributes.mTextColorLink != null) { 4103 setLinkTextColor(attributes.mTextColorLink); 4104 } 4105 4106 if (attributes.mTextColorHighlight != 0) { 4107 setHighlightColor(attributes.mTextColorHighlight); 4108 } 4109 4110 if (attributes.mTextSize != -1) { 4111 mTextSizeUnit = attributes.mTextSizeUnit; 4112 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4113 } 4114 4115 if (attributes.mTextLocales != null) { 4116 setTextLocales(attributes.mTextLocales); 4117 } 4118 4119 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4120 attributes.mFontFamily = null; 4121 } 4122 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4123 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4124 4125 if (attributes.mShadowColor != 0) { 4126 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4127 attributes.mShadowColor); 4128 } 4129 4130 if (attributes.mAllCaps) { 4131 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4132 } 4133 4134 if (attributes.mHasElegant) { 4135 setElegantTextHeight(attributes.mElegant); 4136 } 4137 4138 if (attributes.mHasFallbackLineSpacing) { 4139 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4140 } 4141 4142 if (attributes.mHasLetterSpacing) { 4143 setLetterSpacing(attributes.mLetterSpacing); 4144 } 4145 4146 if (attributes.mFontFeatureSettings != null) { 4147 setFontFeatureSettings(attributes.mFontFeatureSettings); 4148 } 4149 4150 if (attributes.mFontVariationSettings != null) { 4151 setFontVariationSettings(attributes.mFontVariationSettings); 4152 } 4153 } 4154 4155 /** 4156 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4157 * the first member of {@link #getTextLocales()}. 4158 * @return the default primary {@link Locale} of the text in this TextView. 4159 */ 4160 @NonNull getTextLocale()4161 public Locale getTextLocale() { 4162 return mTextPaint.getTextLocale(); 4163 } 4164 4165 /** 4166 * Get the default {@link LocaleList} of the text in this TextView. 4167 * @return the default {@link LocaleList} of the text in this TextView. 4168 */ 4169 @NonNull @Size(min = 1) getTextLocales()4170 public LocaleList getTextLocales() { 4171 return mTextPaint.getTextLocales(); 4172 } 4173 changeListenerLocaleTo(@ullable Locale locale)4174 private void changeListenerLocaleTo(@Nullable Locale locale) { 4175 if (mListenerChanged) { 4176 // If a listener has been explicitly set, don't change it. We may break something. 4177 return; 4178 } 4179 // The following null check is not absolutely necessary since all calling points of 4180 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4181 // here in case others would want to call this method in the future. 4182 if (mEditor != null) { 4183 KeyListener listener = mEditor.mKeyListener; 4184 if (listener instanceof DigitsKeyListener) { 4185 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4186 } else if (listener instanceof DateKeyListener) { 4187 listener = DateKeyListener.getInstance(locale); 4188 } else if (listener instanceof TimeKeyListener) { 4189 listener = TimeKeyListener.getInstance(locale); 4190 } else if (listener instanceof DateTimeKeyListener) { 4191 listener = DateTimeKeyListener.getInstance(locale); 4192 } else { 4193 return; 4194 } 4195 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4196 setKeyListenerOnly(listener); 4197 setInputTypeFromEditor(); 4198 if (wasPasswordType) { 4199 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4200 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4201 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4202 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4203 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4204 } 4205 } 4206 } 4207 } 4208 4209 /** 4210 * Set the default {@link Locale} of the text in this TextView to a one-member 4211 * {@link LocaleList} containing just the given Locale. 4212 * 4213 * @param locale the {@link Locale} for drawing text, must not be null. 4214 * 4215 * @see #setTextLocales 4216 */ setTextLocale(@onNull Locale locale)4217 public void setTextLocale(@NonNull Locale locale) { 4218 mLocalesChanged = true; 4219 mTextPaint.setTextLocale(locale); 4220 if (mLayout != null) { 4221 nullLayouts(); 4222 requestLayout(); 4223 invalidate(); 4224 } 4225 } 4226 4227 /** 4228 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4229 * 4230 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4231 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4232 * other aspects of text display, including line breaking. 4233 * 4234 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4235 * 4236 * @see Paint#setTextLocales 4237 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4238 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4239 mLocalesChanged = true; 4240 mTextPaint.setTextLocales(locales); 4241 if (mLayout != null) { 4242 nullLayouts(); 4243 requestLayout(); 4244 invalidate(); 4245 } 4246 } 4247 4248 @Override onConfigurationChanged(Configuration newConfig)4249 protected void onConfigurationChanged(Configuration newConfig) { 4250 super.onConfigurationChanged(newConfig); 4251 if (!mLocalesChanged) { 4252 mTextPaint.setTextLocales(LocaleList.getDefault()); 4253 if (mLayout != null) { 4254 nullLayouts(); 4255 requestLayout(); 4256 invalidate(); 4257 } 4258 } 4259 } 4260 4261 /** 4262 * @return the size (in pixels) of the default text size in this TextView. 4263 */ 4264 @InspectableProperty 4265 @ViewDebug.ExportedProperty(category = "text") getTextSize()4266 public float getTextSize() { 4267 return mTextPaint.getTextSize(); 4268 } 4269 4270 /** 4271 * @return the size (in scaled pixels) of the default text size in this TextView. 4272 * @hide 4273 */ 4274 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4275 public float getScaledTextSize() { 4276 return mTextPaint.getTextSize() / mTextPaint.density; 4277 } 4278 4279 /** @hide */ 4280 @ViewDebug.ExportedProperty(category = "text", mapping = { 4281 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4282 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4283 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4284 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4285 }) getTypefaceStyle()4286 public int getTypefaceStyle() { 4287 Typeface typeface = mTextPaint.getTypeface(); 4288 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4289 } 4290 4291 /** 4292 * Set the default text size to the given value, interpreted as "scaled 4293 * pixel" units. This size is adjusted based on the current density and 4294 * user font size preference. 4295 * 4296 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4297 * 4298 * @param size The scaled pixel size. 4299 * 4300 * @attr ref android.R.styleable#TextView_textSize 4301 */ 4302 @android.view.RemotableViewMethod setTextSize(float size)4303 public void setTextSize(float size) { 4304 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4305 } 4306 4307 /** 4308 * Set the default text size to a given unit and value. See {@link 4309 * TypedValue} for the possible dimension units. 4310 * 4311 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 4312 * 4313 * @param unit The desired dimension unit. 4314 * @param size The desired size in the given units. 4315 * 4316 * @attr ref android.R.styleable#TextView_textSize 4317 */ setTextSize(int unit, float size)4318 public void setTextSize(int unit, float size) { 4319 if (!isAutoSizeEnabled()) { 4320 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4321 } 4322 } 4323 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4324 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4325 Context c = getContext(); 4326 Resources r; 4327 4328 if (c == null) { 4329 r = Resources.getSystem(); 4330 } else { 4331 r = c.getResources(); 4332 } 4333 4334 mTextSizeUnit = unit; 4335 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), 4336 shouldRequestLayout); 4337 } 4338 4339 @UnsupportedAppUsage setRawTextSize(float size, boolean shouldRequestLayout)4340 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4341 if (size != mTextPaint.getTextSize()) { 4342 mTextPaint.setTextSize(size); 4343 4344 if (shouldRequestLayout && mLayout != null) { 4345 // Do not auto-size right after setting the text size. 4346 mNeedsAutoSizeText = false; 4347 nullLayouts(); 4348 requestLayout(); 4349 invalidate(); 4350 } 4351 } 4352 } 4353 4354 /** 4355 * Gets the text size unit defined by the developer. It may be specified in resources or be 4356 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4357 * 4358 * @return the dimension type of the text size unit originally defined. 4359 * @see TypedValue#TYPE_DIMENSION 4360 */ getTextSizeUnit()4361 public int getTextSizeUnit() { 4362 return mTextSizeUnit; 4363 } 4364 4365 /** 4366 * Gets the extent by which text should be stretched horizontally. 4367 * This will usually be 1.0. 4368 * @return The horizontal scale factor. 4369 */ 4370 @InspectableProperty getTextScaleX()4371 public float getTextScaleX() { 4372 return mTextPaint.getTextScaleX(); 4373 } 4374 4375 /** 4376 * Sets the horizontal scale factor for text. The default value 4377 * is 1.0. Values greater than 1.0 stretch the text wider. 4378 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4379 * @param size The horizontal scale factor. 4380 * @attr ref android.R.styleable#TextView_textScaleX 4381 */ 4382 @android.view.RemotableViewMethod setTextScaleX(float size)4383 public void setTextScaleX(float size) { 4384 if (size != mTextPaint.getTextScaleX()) { 4385 mUserSetTextScaleX = true; 4386 mTextPaint.setTextScaleX(size); 4387 4388 if (mLayout != null) { 4389 nullLayouts(); 4390 requestLayout(); 4391 invalidate(); 4392 } 4393 } 4394 } 4395 4396 /** 4397 * Sets the typeface and style in which the text should be displayed. 4398 * Note that not all Typeface families actually have bold and italic 4399 * variants, so you may need to use 4400 * {@link #setTypeface(Typeface, int)} to get the appearance 4401 * that you actually want. 4402 * 4403 * @see #getTypeface() 4404 * 4405 * @attr ref android.R.styleable#TextView_fontFamily 4406 * @attr ref android.R.styleable#TextView_typeface 4407 * @attr ref android.R.styleable#TextView_textStyle 4408 */ setTypeface(@ullable Typeface tf)4409 public void setTypeface(@Nullable Typeface tf) { 4410 if (mTextPaint.getTypeface() != tf) { 4411 mTextPaint.setTypeface(tf); 4412 4413 if (mLayout != null) { 4414 nullLayouts(); 4415 requestLayout(); 4416 invalidate(); 4417 } 4418 } 4419 } 4420 4421 /** 4422 * Gets the current {@link Typeface} that is used to style the text. 4423 * @return The current Typeface. 4424 * 4425 * @see #setTypeface(Typeface) 4426 * 4427 * @attr ref android.R.styleable#TextView_fontFamily 4428 * @attr ref android.R.styleable#TextView_typeface 4429 * @attr ref android.R.styleable#TextView_textStyle 4430 */ 4431 @InspectableProperty getTypeface()4432 public Typeface getTypeface() { 4433 return mTextPaint.getTypeface(); 4434 } 4435 4436 /** 4437 * Set the TextView's elegant height metrics flag. This setting selects font 4438 * variants that have not been compacted to fit Latin-based vertical 4439 * metrics, and also increases top and bottom bounds to provide more space. 4440 * 4441 * @param elegant set the paint's elegant metrics flag. 4442 * 4443 * @see #isElegantTextHeight() 4444 * @see Paint#isElegantTextHeight() 4445 * 4446 * @attr ref android.R.styleable#TextView_elegantTextHeight 4447 */ setElegantTextHeight(boolean elegant)4448 public void setElegantTextHeight(boolean elegant) { 4449 if (elegant != mTextPaint.isElegantTextHeight()) { 4450 mTextPaint.setElegantTextHeight(elegant); 4451 if (mLayout != null) { 4452 nullLayouts(); 4453 requestLayout(); 4454 invalidate(); 4455 } 4456 } 4457 } 4458 4459 /** 4460 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4461 * displaying the text (which is needed to avoid text from consecutive lines running into 4462 * each other). If set, fallback fonts that end up getting used can increase the ascent 4463 * and descent of the lines that they are used on. 4464 * <p/> 4465 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4466 * is typically much taller or deeper than Latin text. 4467 * 4468 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4469 * 4470 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4471 * 4472 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4473 */ setFallbackLineSpacing(boolean enabled)4474 public void setFallbackLineSpacing(boolean enabled) { 4475 if (mUseFallbackLineSpacing != enabled) { 4476 mUseFallbackLineSpacing = enabled; 4477 if (mLayout != null) { 4478 nullLayouts(); 4479 requestLayout(); 4480 invalidate(); 4481 } 4482 } 4483 } 4484 4485 /** 4486 * @return whether fallback line spacing is enabled, {@code true} by default 4487 * 4488 * @see #setFallbackLineSpacing(boolean) 4489 * 4490 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4491 */ 4492 @InspectableProperty isFallbackLineSpacing()4493 public boolean isFallbackLineSpacing() { 4494 return mUseFallbackLineSpacing; 4495 } 4496 4497 /** 4498 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4499 * variants that have not been compacted to fit Latin-based vertical 4500 * metrics, and also increases top and bottom bounds to provide more space. 4501 * @return {@code true} if the elegant height metrics flag is set. 4502 * 4503 * @see #setElegantTextHeight(boolean) 4504 * @see Paint#setElegantTextHeight(boolean) 4505 */ 4506 @InspectableProperty isElegantTextHeight()4507 public boolean isElegantTextHeight() { 4508 return mTextPaint.isElegantTextHeight(); 4509 } 4510 4511 /** 4512 * Gets the text letter-space value, which determines the spacing between characters. 4513 * The value returned is in ems. Normally, this value is 0.0. 4514 * @return The text letter-space value in ems. 4515 * 4516 * @see #setLetterSpacing(float) 4517 * @see Paint#setLetterSpacing 4518 */ 4519 @InspectableProperty getLetterSpacing()4520 public float getLetterSpacing() { 4521 return mTextPaint.getLetterSpacing(); 4522 } 4523 4524 /** 4525 * Sets text letter-spacing in em units. Typical values 4526 * for slight expansion will be around 0.05. Negative values tighten text. 4527 * 4528 * @see #getLetterSpacing() 4529 * @see Paint#getLetterSpacing 4530 * 4531 * @param letterSpacing A text letter-space value in ems. 4532 * @attr ref android.R.styleable#TextView_letterSpacing 4533 */ 4534 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4535 public void setLetterSpacing(float letterSpacing) { 4536 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4537 mTextPaint.setLetterSpacing(letterSpacing); 4538 4539 if (mLayout != null) { 4540 nullLayouts(); 4541 requestLayout(); 4542 invalidate(); 4543 } 4544 } 4545 } 4546 4547 /** 4548 * Returns the font feature settings. The format is the same as the CSS 4549 * font-feature-settings attribute: 4550 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4551 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4552 * 4553 * @return the currently set font feature settings. Default is null. 4554 * 4555 * @see #setFontFeatureSettings(String) 4556 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4557 */ 4558 @InspectableProperty 4559 @Nullable getFontFeatureSettings()4560 public String getFontFeatureSettings() { 4561 return mTextPaint.getFontFeatureSettings(); 4562 } 4563 4564 /** 4565 * Returns the font variation settings. 4566 * 4567 * @return the currently set font variation settings. Returns null if no variation is 4568 * specified. 4569 * 4570 * @see #setFontVariationSettings(String) 4571 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4572 */ 4573 @Nullable getFontVariationSettings()4574 public String getFontVariationSettings() { 4575 return mTextPaint.getFontVariationSettings(); 4576 } 4577 4578 /** 4579 * Sets the break strategy for breaking paragraphs into lines. The default value for 4580 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4581 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4582 * text "dancing" when being edited. 4583 * <p/> 4584 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4585 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4586 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4587 * improves the structure of text layout however has performance impact and requires more time 4588 * to do the text layout. 4589 * 4590 * @attr ref android.R.styleable#TextView_breakStrategy 4591 * @see #getBreakStrategy() 4592 * @see #setHyphenationFrequency(int) 4593 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4594 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4595 mBreakStrategy = breakStrategy; 4596 if (mLayout != null) { 4597 nullLayouts(); 4598 requestLayout(); 4599 invalidate(); 4600 } 4601 } 4602 4603 /** 4604 * Gets the current strategy for breaking paragraphs into lines. 4605 * @return the current strategy for breaking paragraphs into lines. 4606 * 4607 * @attr ref android.R.styleable#TextView_breakStrategy 4608 * @see #setBreakStrategy(int) 4609 */ 4610 @InspectableProperty(enumMapping = { 4611 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4612 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4613 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4614 }) 4615 @Layout.BreakStrategy getBreakStrategy()4616 public int getBreakStrategy() { 4617 return mBreakStrategy; 4618 } 4619 4620 /** 4621 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4622 * The default value for both TextView and {@link EditText} is 4623 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4624 * is set from the theme. 4625 * <p/> 4626 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4627 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4628 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4629 * improves the structure of text layout however has performance impact and requires more time 4630 * to do the text layout. 4631 * <p/> 4632 * Note: Before Android Q, in the theme hyphenation frequency is set to 4633 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4634 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4635 * 4636 * @param hyphenationFrequency the hyphenation frequency to use, one of 4637 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4638 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4639 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 4640 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4641 * @see #getHyphenationFrequency() 4642 * @see #getBreakStrategy() 4643 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)4644 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 4645 mHyphenationFrequency = hyphenationFrequency; 4646 if (mLayout != null) { 4647 nullLayouts(); 4648 requestLayout(); 4649 invalidate(); 4650 } 4651 } 4652 4653 /** 4654 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 4655 * @return the current frequency of automatic hyphenation to be used when determining word 4656 * breaks. 4657 * 4658 * @attr ref android.R.styleable#TextView_hyphenationFrequency 4659 * @see #setHyphenationFrequency(int) 4660 */ 4661 @InspectableProperty(enumMapping = { 4662 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 4663 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 4664 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 4665 }) 4666 @Layout.HyphenationFrequency getHyphenationFrequency()4667 public int getHyphenationFrequency() { 4668 return mHyphenationFrequency; 4669 } 4670 4671 /** 4672 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 4673 * 4674 * @return a current {@link PrecomputedText.Params} 4675 * @see PrecomputedText 4676 */ getTextMetricsParams()4677 public @NonNull PrecomputedText.Params getTextMetricsParams() { 4678 return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), 4679 mBreakStrategy, mHyphenationFrequency); 4680 } 4681 4682 /** 4683 * Apply the text layout parameter. 4684 * 4685 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 4686 * @see PrecomputedText 4687 */ setTextMetricsParams(@onNull PrecomputedText.Params params)4688 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 4689 mTextPaint.set(params.getTextPaint()); 4690 mUserSetTextScaleX = true; 4691 mTextDir = params.getTextDirection(); 4692 mBreakStrategy = params.getBreakStrategy(); 4693 mHyphenationFrequency = params.getHyphenationFrequency(); 4694 if (mLayout != null) { 4695 nullLayouts(); 4696 requestLayout(); 4697 invalidate(); 4698 } 4699 } 4700 4701 /** 4702 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 4703 * last line is too short for justification, the last line will be displayed with the 4704 * alignment set by {@link android.view.View#setTextAlignment}. 4705 * 4706 * @see #getJustificationMode() 4707 */ 4708 @Layout.JustificationMode setJustificationMode(@ayout.JustificationMode int justificationMode)4709 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 4710 mJustificationMode = justificationMode; 4711 if (mLayout != null) { 4712 nullLayouts(); 4713 requestLayout(); 4714 invalidate(); 4715 } 4716 } 4717 4718 /** 4719 * @return true if currently paragraph justification mode. 4720 * 4721 * @see #setJustificationMode(int) 4722 */ 4723 @InspectableProperty(enumMapping = { 4724 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 4725 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 4726 }) getJustificationMode()4727 public @Layout.JustificationMode int getJustificationMode() { 4728 return mJustificationMode; 4729 } 4730 4731 /** 4732 * Sets font feature settings. The format is the same as the CSS 4733 * font-feature-settings attribute: 4734 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4735 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4736 * 4737 * @param fontFeatureSettings font feature settings represented as CSS compatible string 4738 * 4739 * @see #getFontFeatureSettings() 4740 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 4741 * 4742 * @attr ref android.R.styleable#TextView_fontFeatureSettings 4743 */ 4744 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)4745 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 4746 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 4747 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 4748 4749 if (mLayout != null) { 4750 nullLayouts(); 4751 requestLayout(); 4752 invalidate(); 4753 } 4754 } 4755 } 4756 4757 4758 /** 4759 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 4760 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 4761 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 4762 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 4763 * are invalid. If a specified axis name is not defined in the font, the settings will be 4764 * ignored. 4765 * 4766 * <p> 4767 * Examples, 4768 * <ul> 4769 * <li>Set font width to 150. 4770 * <pre> 4771 * <code> 4772 * TextView textView = (TextView) findViewById(R.id.textView); 4773 * textView.setFontVariationSettings("'wdth' 150"); 4774 * </code> 4775 * </pre> 4776 * </li> 4777 * 4778 * <li>Set the font slant to 20 degrees and ask for italic style. 4779 * <pre> 4780 * <code> 4781 * TextView textView = (TextView) findViewById(R.id.textView); 4782 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 4783 * </code> 4784 * </pre> 4785 * </p> 4786 * </li> 4787 * </ul> 4788 * 4789 * @param fontVariationSettings font variation settings. You can pass null or empty string as 4790 * no variation settings. 4791 * @return true if the given settings is effective to at least one font file underlying this 4792 * TextView. This function also returns true for empty settings string. Otherwise 4793 * returns false. 4794 * 4795 * @throws IllegalArgumentException If given string is not a valid font variation settings 4796 * format. 4797 * 4798 * @see #getFontVariationSettings() 4799 * @see FontVariationAxis 4800 * 4801 * @attr ref android.R.styleable#TextView_fontVariationSettings 4802 */ setFontVariationSettings(@ullable String fontVariationSettings)4803 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 4804 final String existingSettings = mTextPaint.getFontVariationSettings(); 4805 if (fontVariationSettings == existingSettings 4806 || (fontVariationSettings != null 4807 && fontVariationSettings.equals(existingSettings))) { 4808 return true; 4809 } 4810 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 4811 4812 if (effective && mLayout != null) { 4813 nullLayouts(); 4814 requestLayout(); 4815 invalidate(); 4816 } 4817 return effective; 4818 } 4819 4820 /** 4821 * Sets the text color for all the states (normal, selected, 4822 * focused) to be this color. 4823 * 4824 * @param color A color value in the form 0xAARRGGBB. 4825 * Do not pass a resource ID. To get a color value from a resource ID, call 4826 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 4827 * 4828 * @see #setTextColor(ColorStateList) 4829 * @see #getTextColors() 4830 * 4831 * @attr ref android.R.styleable#TextView_textColor 4832 */ 4833 @android.view.RemotableViewMethod setTextColor(@olorInt int color)4834 public void setTextColor(@ColorInt int color) { 4835 mTextColor = ColorStateList.valueOf(color); 4836 updateTextColors(); 4837 } 4838 4839 /** 4840 * Sets the text color. 4841 * 4842 * @see #setTextColor(int) 4843 * @see #getTextColors() 4844 * @see #setHintTextColor(ColorStateList) 4845 * @see #setLinkTextColor(ColorStateList) 4846 * 4847 * @attr ref android.R.styleable#TextView_textColor 4848 */ 4849 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)4850 public void setTextColor(ColorStateList colors) { 4851 if (colors == null) { 4852 throw new NullPointerException(); 4853 } 4854 4855 mTextColor = colors; 4856 updateTextColors(); 4857 } 4858 4859 /** 4860 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4861 * 4862 * @see #setTextColor(ColorStateList) 4863 * @see #setTextColor(int) 4864 * 4865 * @attr ref android.R.styleable#TextView_textColor 4866 */ 4867 @InspectableProperty(name = "textColor") getTextColors()4868 public final ColorStateList getTextColors() { 4869 return mTextColor; 4870 } 4871 4872 /** 4873 * Return the current color selected for normal text. 4874 * 4875 * @return Returns the current text color. 4876 */ 4877 @ColorInt getCurrentTextColor()4878 public final int getCurrentTextColor() { 4879 return mCurTextColor; 4880 } 4881 4882 /** 4883 * Sets the color used to display the selection highlight. 4884 * 4885 * @attr ref android.R.styleable#TextView_textColorHighlight 4886 */ 4887 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)4888 public void setHighlightColor(@ColorInt int color) { 4889 if (mHighlightColor != color) { 4890 mHighlightColor = color; 4891 invalidate(); 4892 } 4893 } 4894 4895 /** 4896 * @return the color used to display the selection highlight 4897 * 4898 * @see #setHighlightColor(int) 4899 * 4900 * @attr ref android.R.styleable#TextView_textColorHighlight 4901 */ 4902 @InspectableProperty(name = "textColorHighlight") 4903 @ColorInt getHighlightColor()4904 public int getHighlightColor() { 4905 return mHighlightColor; 4906 } 4907 4908 /** 4909 * Sets whether the soft input method will be made visible when this 4910 * TextView gets focused. The default is true. 4911 */ 4912 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)4913 public final void setShowSoftInputOnFocus(boolean show) { 4914 createEditorIfNeeded(); 4915 mEditor.mShowSoftInputOnFocus = show; 4916 } 4917 4918 /** 4919 * Returns whether the soft input method will be made visible when this 4920 * TextView gets focused. The default is true. 4921 */ getShowSoftInputOnFocus()4922 public final boolean getShowSoftInputOnFocus() { 4923 // When there is no Editor, return default true value 4924 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4925 } 4926 4927 /** 4928 * Gives the text a shadow of the specified blur radius and color, the specified 4929 * distance from its drawn position. 4930 * <p> 4931 * The text shadow produced does not interact with the properties on view 4932 * that are responsible for real time shadows, 4933 * {@link View#getElevation() elevation} and 4934 * {@link View#getTranslationZ() translationZ}. 4935 * 4936 * @see Paint#setShadowLayer(float, float, float, int) 4937 * 4938 * @attr ref android.R.styleable#TextView_shadowColor 4939 * @attr ref android.R.styleable#TextView_shadowDx 4940 * @attr ref android.R.styleable#TextView_shadowDy 4941 * @attr ref android.R.styleable#TextView_shadowRadius 4942 */ setShadowLayer(float radius, float dx, float dy, int color)4943 public void setShadowLayer(float radius, float dx, float dy, int color) { 4944 mTextPaint.setShadowLayer(radius, dx, dy, color); 4945 4946 mShadowRadius = radius; 4947 mShadowDx = dx; 4948 mShadowDy = dy; 4949 mShadowColor = color; 4950 4951 // Will change text clip region 4952 if (mEditor != null) { 4953 mEditor.invalidateTextDisplayList(); 4954 mEditor.invalidateHandlesAndActionMode(); 4955 } 4956 invalidate(); 4957 } 4958 4959 /** 4960 * Gets the radius of the shadow layer. 4961 * 4962 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 4963 * 4964 * @see #setShadowLayer(float, float, float, int) 4965 * 4966 * @attr ref android.R.styleable#TextView_shadowRadius 4967 */ 4968 @InspectableProperty getShadowRadius()4969 public float getShadowRadius() { 4970 return mShadowRadius; 4971 } 4972 4973 /** 4974 * @return the horizontal offset of the shadow layer 4975 * 4976 * @see #setShadowLayer(float, float, float, int) 4977 * 4978 * @attr ref android.R.styleable#TextView_shadowDx 4979 */ 4980 @InspectableProperty getShadowDx()4981 public float getShadowDx() { 4982 return mShadowDx; 4983 } 4984 4985 /** 4986 * Gets the vertical offset of the shadow layer. 4987 * @return The vertical offset of the shadow layer. 4988 * 4989 * @see #setShadowLayer(float, float, float, int) 4990 * 4991 * @attr ref android.R.styleable#TextView_shadowDy 4992 */ 4993 @InspectableProperty getShadowDy()4994 public float getShadowDy() { 4995 return mShadowDy; 4996 } 4997 4998 /** 4999 * Gets the color of the shadow layer. 5000 * @return the color of the shadow layer 5001 * 5002 * @see #setShadowLayer(float, float, float, int) 5003 * 5004 * @attr ref android.R.styleable#TextView_shadowColor 5005 */ 5006 @InspectableProperty 5007 @ColorInt getShadowColor()5008 public int getShadowColor() { 5009 return mShadowColor; 5010 } 5011 5012 /** 5013 * Gets the {@link TextPaint} used for the text. 5014 * Use this only to consult the Paint's properties and not to change them. 5015 * @return The base paint used for the text. 5016 */ getPaint()5017 public TextPaint getPaint() { 5018 return mTextPaint; 5019 } 5020 5021 /** 5022 * Sets the autolink mask of the text. See {@link 5023 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5024 * possible values. 5025 * 5026 * <p class="note"><b>Note:</b> 5027 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5028 * is deprecated and should be avoided; see its documentation. 5029 * 5030 * @attr ref android.R.styleable#TextView_autoLink 5031 */ 5032 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5033 public final void setAutoLinkMask(int mask) { 5034 mAutoLinkMask = mask; 5035 } 5036 5037 /** 5038 * Sets whether the movement method will automatically be set to 5039 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5040 * set to nonzero and links are detected in {@link #setText}. 5041 * The default is true. 5042 * 5043 * @attr ref android.R.styleable#TextView_linksClickable 5044 */ 5045 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5046 public final void setLinksClickable(boolean whether) { 5047 mLinksClickable = whether; 5048 } 5049 5050 /** 5051 * Returns whether the movement method will automatically be set to 5052 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5053 * set to nonzero and links are detected in {@link #setText}. 5054 * The default is true. 5055 * 5056 * @attr ref android.R.styleable#TextView_linksClickable 5057 */ 5058 @InspectableProperty getLinksClickable()5059 public final boolean getLinksClickable() { 5060 return mLinksClickable; 5061 } 5062 5063 /** 5064 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5065 * (by {@link Linkify} or otherwise) if any. You can call 5066 * {@link URLSpan#getURL} on them to find where they link to 5067 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5068 * to find the region of the text they are attached to. 5069 */ getUrls()5070 public URLSpan[] getUrls() { 5071 if (mText instanceof Spanned) { 5072 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5073 } else { 5074 return new URLSpan[0]; 5075 } 5076 } 5077 5078 /** 5079 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5080 * TextView. 5081 * 5082 * @see #setHintTextColor(ColorStateList) 5083 * @see #getHintTextColors() 5084 * @see #setTextColor(int) 5085 * 5086 * @attr ref android.R.styleable#TextView_textColorHint 5087 */ 5088 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5089 public final void setHintTextColor(@ColorInt int color) { 5090 mHintTextColor = ColorStateList.valueOf(color); 5091 updateTextColors(); 5092 } 5093 5094 /** 5095 * Sets the color of the hint text. 5096 * 5097 * @see #getHintTextColors() 5098 * @see #setHintTextColor(int) 5099 * @see #setTextColor(ColorStateList) 5100 * @see #setLinkTextColor(ColorStateList) 5101 * 5102 * @attr ref android.R.styleable#TextView_textColorHint 5103 */ setHintTextColor(ColorStateList colors)5104 public final void setHintTextColor(ColorStateList colors) { 5105 mHintTextColor = colors; 5106 updateTextColors(); 5107 } 5108 5109 /** 5110 * @return the color of the hint text, for the different states of this TextView. 5111 * 5112 * @see #setHintTextColor(ColorStateList) 5113 * @see #setHintTextColor(int) 5114 * @see #setTextColor(ColorStateList) 5115 * @see #setLinkTextColor(ColorStateList) 5116 * 5117 * @attr ref android.R.styleable#TextView_textColorHint 5118 */ 5119 @InspectableProperty(name = "textColorHint") getHintTextColors()5120 public final ColorStateList getHintTextColors() { 5121 return mHintTextColor; 5122 } 5123 5124 /** 5125 * <p>Return the current color selected to paint the hint text.</p> 5126 * 5127 * @return Returns the current hint text color. 5128 */ 5129 @ColorInt getCurrentHintTextColor()5130 public final int getCurrentHintTextColor() { 5131 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5132 } 5133 5134 /** 5135 * Sets the color of links in the text. 5136 * 5137 * @see #setLinkTextColor(ColorStateList) 5138 * @see #getLinkTextColors() 5139 * 5140 * @attr ref android.R.styleable#TextView_textColorLink 5141 */ 5142 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5143 public final void setLinkTextColor(@ColorInt int color) { 5144 mLinkTextColor = ColorStateList.valueOf(color); 5145 updateTextColors(); 5146 } 5147 5148 /** 5149 * Sets the color of links in the text. 5150 * 5151 * @see #setLinkTextColor(int) 5152 * @see #getLinkTextColors() 5153 * @see #setTextColor(ColorStateList) 5154 * @see #setHintTextColor(ColorStateList) 5155 * 5156 * @attr ref android.R.styleable#TextView_textColorLink 5157 */ setLinkTextColor(ColorStateList colors)5158 public final void setLinkTextColor(ColorStateList colors) { 5159 mLinkTextColor = colors; 5160 updateTextColors(); 5161 } 5162 5163 /** 5164 * @return the list of colors used to paint the links in the text, for the different states of 5165 * this TextView 5166 * 5167 * @see #setLinkTextColor(ColorStateList) 5168 * @see #setLinkTextColor(int) 5169 * 5170 * @attr ref android.R.styleable#TextView_textColorLink 5171 */ 5172 @InspectableProperty(name = "textColorLink") getLinkTextColors()5173 public final ColorStateList getLinkTextColors() { 5174 return mLinkTextColor; 5175 } 5176 5177 /** 5178 * Sets the horizontal alignment of the text and the 5179 * vertical gravity that will be used when there is extra space 5180 * in the TextView beyond what is required for the text itself. 5181 * 5182 * @see android.view.Gravity 5183 * @attr ref android.R.styleable#TextView_gravity 5184 */ setGravity(int gravity)5185 public void setGravity(int gravity) { 5186 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5187 gravity |= Gravity.START; 5188 } 5189 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5190 gravity |= Gravity.TOP; 5191 } 5192 5193 boolean newLayout = false; 5194 5195 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5196 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5197 newLayout = true; 5198 } 5199 5200 if (gravity != mGravity) { 5201 invalidate(); 5202 } 5203 5204 mGravity = gravity; 5205 5206 if (mLayout != null && newLayout) { 5207 // XXX this is heavy-handed because no actual content changes. 5208 int want = mLayout.getWidth(); 5209 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5210 5211 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5212 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5213 } 5214 } 5215 5216 /** 5217 * Returns the horizontal and vertical alignment of this TextView. 5218 * 5219 * @see android.view.Gravity 5220 * @attr ref android.R.styleable#TextView_gravity 5221 */ 5222 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5223 public int getGravity() { 5224 return mGravity; 5225 } 5226 5227 /** 5228 * Gets the flags on the Paint being used to display the text. 5229 * @return The flags on the Paint being used to display the text. 5230 * @see Paint#getFlags 5231 */ getPaintFlags()5232 public int getPaintFlags() { 5233 return mTextPaint.getFlags(); 5234 } 5235 5236 /** 5237 * Sets flags on the Paint being used to display the text and 5238 * reflows the text if they are different from the old flags. 5239 * @see Paint#setFlags 5240 */ 5241 @android.view.RemotableViewMethod setPaintFlags(int flags)5242 public void setPaintFlags(int flags) { 5243 if (mTextPaint.getFlags() != flags) { 5244 mTextPaint.setFlags(flags); 5245 5246 if (mLayout != null) { 5247 nullLayouts(); 5248 requestLayout(); 5249 invalidate(); 5250 } 5251 } 5252 } 5253 5254 /** 5255 * Sets whether the text should be allowed to be wider than the 5256 * View is. If false, it will be wrapped to the width of the View. 5257 * 5258 * @attr ref android.R.styleable#TextView_scrollHorizontally 5259 */ setHorizontallyScrolling(boolean whether)5260 public void setHorizontallyScrolling(boolean whether) { 5261 if (mHorizontallyScrolling != whether) { 5262 mHorizontallyScrolling = whether; 5263 5264 if (mLayout != null) { 5265 nullLayouts(); 5266 requestLayout(); 5267 invalidate(); 5268 } 5269 } 5270 } 5271 5272 /** 5273 * Returns whether the text is allowed to be wider than the View. 5274 * If false, the text will be wrapped to the width of the View. 5275 * 5276 * @attr ref android.R.styleable#TextView_scrollHorizontally 5277 * @see #setHorizontallyScrolling(boolean) 5278 */ 5279 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5280 public final boolean isHorizontallyScrollable() { 5281 return mHorizontallyScrolling; 5282 } 5283 5284 /** 5285 * Returns whether the text is allowed to be wider than the View. 5286 * If false, the text will be wrapped to the width of the View. 5287 * 5288 * @attr ref android.R.styleable#TextView_scrollHorizontally 5289 * @hide 5290 */ 5291 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5292 public boolean getHorizontallyScrolling() { 5293 return mHorizontallyScrolling; 5294 } 5295 5296 /** 5297 * Sets the height of the TextView to be at least {@code minLines} tall. 5298 * <p> 5299 * This value is used for height calculation if LayoutParams does not force TextView to have an 5300 * exact height. Setting this value overrides other previous minimum height configurations such 5301 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5302 * this value to 1. 5303 * 5304 * @param minLines the minimum height of TextView in terms of number of lines 5305 * 5306 * @see #getMinLines() 5307 * @see #setLines(int) 5308 * 5309 * @attr ref android.R.styleable#TextView_minLines 5310 */ 5311 @android.view.RemotableViewMethod setMinLines(int minLines)5312 public void setMinLines(int minLines) { 5313 mMinimum = minLines; 5314 mMinMode = LINES; 5315 5316 requestLayout(); 5317 invalidate(); 5318 } 5319 5320 /** 5321 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5322 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5323 * 5324 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5325 * height is not defined in lines 5326 * 5327 * @see #setMinLines(int) 5328 * @see #setLines(int) 5329 * 5330 * @attr ref android.R.styleable#TextView_minLines 5331 */ 5332 @InspectableProperty getMinLines()5333 public int getMinLines() { 5334 return mMinMode == LINES ? mMinimum : -1; 5335 } 5336 5337 /** 5338 * Sets the height of the TextView to be at least {@code minPixels} tall. 5339 * <p> 5340 * This value is used for height calculation if LayoutParams does not force TextView to have an 5341 * exact height. Setting this value overrides previous minimum height configurations such as 5342 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5343 * <p> 5344 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5345 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5346 * used to decide the final height. 5347 * 5348 * @param minPixels the minimum height of TextView in terms of pixels 5349 * 5350 * @see #getMinHeight() 5351 * @see #setHeight(int) 5352 * 5353 * @attr ref android.R.styleable#TextView_minHeight 5354 */ 5355 @android.view.RemotableViewMethod setMinHeight(int minPixels)5356 public void setMinHeight(int minPixels) { 5357 mMinimum = minPixels; 5358 mMinMode = PIXELS; 5359 5360 requestLayout(); 5361 invalidate(); 5362 } 5363 5364 /** 5365 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5366 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5367 * 5368 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5369 * defined in pixels 5370 * 5371 * @see #setMinHeight(int) 5372 * @see #setHeight(int) 5373 * 5374 * @attr ref android.R.styleable#TextView_minHeight 5375 */ getMinHeight()5376 public int getMinHeight() { 5377 return mMinMode == PIXELS ? mMinimum : -1; 5378 } 5379 5380 /** 5381 * Sets the height of the TextView to be at most {@code maxLines} tall. 5382 * <p> 5383 * This value is used for height calculation if LayoutParams does not force TextView to have an 5384 * exact height. Setting this value overrides previous maximum height configurations such as 5385 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5386 * 5387 * @param maxLines the maximum height of TextView in terms of number of lines 5388 * 5389 * @see #getMaxLines() 5390 * @see #setLines(int) 5391 * 5392 * @attr ref android.R.styleable#TextView_maxLines 5393 */ 5394 @android.view.RemotableViewMethod setMaxLines(int maxLines)5395 public void setMaxLines(int maxLines) { 5396 mMaximum = maxLines; 5397 mMaxMode = LINES; 5398 5399 requestLayout(); 5400 invalidate(); 5401 } 5402 5403 /** 5404 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5405 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5406 * 5407 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5408 * is not defined in lines. 5409 * 5410 * @see #setMaxLines(int) 5411 * @see #setLines(int) 5412 * 5413 * @attr ref android.R.styleable#TextView_maxLines 5414 */ 5415 @InspectableProperty getMaxLines()5416 public int getMaxLines() { 5417 return mMaxMode == LINES ? mMaximum : -1; 5418 } 5419 5420 /** 5421 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5422 * <p> 5423 * This value is used for height calculation if LayoutParams does not force TextView to have an 5424 * exact height. Setting this value overrides previous maximum height configurations such as 5425 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5426 * 5427 * @param maxPixels the maximum height of TextView in terms of pixels 5428 * 5429 * @see #getMaxHeight() 5430 * @see #setHeight(int) 5431 * 5432 * @attr ref android.R.styleable#TextView_maxHeight 5433 */ 5434 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5435 public void setMaxHeight(int maxPixels) { 5436 mMaximum = maxPixels; 5437 mMaxMode = PIXELS; 5438 5439 requestLayout(); 5440 invalidate(); 5441 } 5442 5443 /** 5444 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5445 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5446 * 5447 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5448 * is not defined in pixels 5449 * 5450 * @see #setMaxHeight(int) 5451 * @see #setHeight(int) 5452 * 5453 * @attr ref android.R.styleable#TextView_maxHeight 5454 */ 5455 @InspectableProperty getMaxHeight()5456 public int getMaxHeight() { 5457 return mMaxMode == PIXELS ? mMaximum : -1; 5458 } 5459 5460 /** 5461 * Sets the height of the TextView to be exactly {@code lines} tall. 5462 * <p> 5463 * This value is used for height calculation if LayoutParams does not force TextView to have an 5464 * exact height. Setting this value overrides previous minimum/maximum height configurations 5465 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5466 * set this value to 1. 5467 * 5468 * @param lines the exact height of the TextView in terms of lines 5469 * 5470 * @see #setHeight(int) 5471 * 5472 * @attr ref android.R.styleable#TextView_lines 5473 */ 5474 @android.view.RemotableViewMethod setLines(int lines)5475 public void setLines(int lines) { 5476 mMaximum = mMinimum = lines; 5477 mMaxMode = mMinMode = LINES; 5478 5479 requestLayout(); 5480 invalidate(); 5481 } 5482 5483 /** 5484 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5485 * <p> 5486 * This value is used for height calculation if LayoutParams does not force TextView to have an 5487 * exact height. Setting this value overrides previous minimum/maximum height configurations 5488 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5489 * 5490 * @param pixels the exact height of the TextView in terms of pixels 5491 * 5492 * @see #setLines(int) 5493 * 5494 * @attr ref android.R.styleable#TextView_height 5495 */ 5496 @android.view.RemotableViewMethod setHeight(int pixels)5497 public void setHeight(int pixels) { 5498 mMaximum = mMinimum = pixels; 5499 mMaxMode = mMinMode = PIXELS; 5500 5501 requestLayout(); 5502 invalidate(); 5503 } 5504 5505 /** 5506 * Sets the width of the TextView to be at least {@code minEms} wide. 5507 * <p> 5508 * This value is used for width calculation if LayoutParams does not force TextView to have an 5509 * exact width. Setting this value overrides previous minimum width configurations such as 5510 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5511 * 5512 * @param minEms the minimum width of TextView in terms of ems 5513 * 5514 * @see #getMinEms() 5515 * @see #setEms(int) 5516 * 5517 * @attr ref android.R.styleable#TextView_minEms 5518 */ 5519 @android.view.RemotableViewMethod setMinEms(int minEms)5520 public void setMinEms(int minEms) { 5521 mMinWidth = minEms; 5522 mMinWidthMode = EMS; 5523 5524 requestLayout(); 5525 invalidate(); 5526 } 5527 5528 /** 5529 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5530 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5531 * 5532 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5533 * defined in ems 5534 * 5535 * @see #setMinEms(int) 5536 * @see #setEms(int) 5537 * 5538 * @attr ref android.R.styleable#TextView_minEms 5539 */ 5540 @InspectableProperty getMinEms()5541 public int getMinEms() { 5542 return mMinWidthMode == EMS ? mMinWidth : -1; 5543 } 5544 5545 /** 5546 * Sets the width of the TextView to be at least {@code minPixels} wide. 5547 * <p> 5548 * This value is used for width calculation if LayoutParams does not force TextView to have an 5549 * exact width. Setting this value overrides previous minimum width configurations such as 5550 * {@link #setMinEms(int)} or {@link #setEms(int)}. 5551 * <p> 5552 * The value given here is different than {@link #setMinimumWidth(int)}. Between 5553 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 5554 * to decide the final width. 5555 * 5556 * @param minPixels the minimum width of TextView in terms of pixels 5557 * 5558 * @see #getMinWidth() 5559 * @see #setWidth(int) 5560 * 5561 * @attr ref android.R.styleable#TextView_minWidth 5562 */ 5563 @android.view.RemotableViewMethod setMinWidth(int minPixels)5564 public void setMinWidth(int minPixels) { 5565 mMinWidth = minPixels; 5566 mMinWidthMode = PIXELS; 5567 5568 requestLayout(); 5569 invalidate(); 5570 } 5571 5572 /** 5573 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 5574 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 5575 * 5576 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 5577 * defined in pixels 5578 * 5579 * @see #setMinWidth(int) 5580 * @see #setWidth(int) 5581 * 5582 * @attr ref android.R.styleable#TextView_minWidth 5583 */ 5584 @InspectableProperty getMinWidth()5585 public int getMinWidth() { 5586 return mMinWidthMode == PIXELS ? mMinWidth : -1; 5587 } 5588 5589 /** 5590 * Sets the width of the TextView to be at most {@code maxEms} wide. 5591 * <p> 5592 * This value is used for width calculation if LayoutParams does not force TextView to have an 5593 * exact width. Setting this value overrides previous maximum width configurations such as 5594 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5595 * 5596 * @param maxEms the maximum width of TextView in terms of ems 5597 * 5598 * @see #getMaxEms() 5599 * @see #setEms(int) 5600 * 5601 * @attr ref android.R.styleable#TextView_maxEms 5602 */ 5603 @android.view.RemotableViewMethod setMaxEms(int maxEms)5604 public void setMaxEms(int maxEms) { 5605 mMaxWidth = maxEms; 5606 mMaxWidthMode = EMS; 5607 5608 requestLayout(); 5609 invalidate(); 5610 } 5611 5612 /** 5613 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 5614 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 5615 * 5616 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 5617 * defined in ems 5618 * 5619 * @see #setMaxEms(int) 5620 * @see #setEms(int) 5621 * 5622 * @attr ref android.R.styleable#TextView_maxEms 5623 */ 5624 @InspectableProperty getMaxEms()5625 public int getMaxEms() { 5626 return mMaxWidthMode == EMS ? mMaxWidth : -1; 5627 } 5628 5629 /** 5630 * Sets the width of the TextView to be at most {@code maxPixels} wide. 5631 * <p> 5632 * This value is used for width calculation if LayoutParams does not force TextView to have an 5633 * exact width. Setting this value overrides previous maximum width configurations such as 5634 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 5635 * 5636 * @param maxPixels the maximum width of TextView in terms of pixels 5637 * 5638 * @see #getMaxWidth() 5639 * @see #setWidth(int) 5640 * 5641 * @attr ref android.R.styleable#TextView_maxWidth 5642 */ 5643 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)5644 public void setMaxWidth(int maxPixels) { 5645 mMaxWidth = maxPixels; 5646 mMaxWidthMode = PIXELS; 5647 5648 requestLayout(); 5649 invalidate(); 5650 } 5651 5652 /** 5653 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 5654 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 5655 * 5656 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 5657 * defined in pixels 5658 * 5659 * @see #setMaxWidth(int) 5660 * @see #setWidth(int) 5661 * 5662 * @attr ref android.R.styleable#TextView_maxWidth 5663 */ 5664 @InspectableProperty getMaxWidth()5665 public int getMaxWidth() { 5666 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 5667 } 5668 5669 /** 5670 * Sets the width of the TextView to be exactly {@code ems} wide. 5671 * 5672 * This value is used for width calculation if LayoutParams does not force TextView to have an 5673 * exact width. Setting this value overrides previous minimum/maximum configurations such as 5674 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 5675 * 5676 * @param ems the exact width of the TextView in terms of ems 5677 * 5678 * @see #setWidth(int) 5679 * 5680 * @attr ref android.R.styleable#TextView_ems 5681 */ 5682 @android.view.RemotableViewMethod setEms(int ems)5683 public void setEms(int ems) { 5684 mMaxWidth = mMinWidth = ems; 5685 mMaxWidthMode = mMinWidthMode = EMS; 5686 5687 requestLayout(); 5688 invalidate(); 5689 } 5690 5691 /** 5692 * Sets the width of the TextView to be exactly {@code pixels} wide. 5693 * <p> 5694 * This value is used for width calculation if LayoutParams does not force TextView to have an 5695 * exact width. Setting this value overrides previous minimum/maximum width configurations 5696 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 5697 * 5698 * @param pixels the exact width of the TextView in terms of pixels 5699 * 5700 * @see #setEms(int) 5701 * 5702 * @attr ref android.R.styleable#TextView_width 5703 */ 5704 @android.view.RemotableViewMethod setWidth(int pixels)5705 public void setWidth(int pixels) { 5706 mMaxWidth = mMinWidth = pixels; 5707 mMaxWidthMode = mMinWidthMode = PIXELS; 5708 5709 requestLayout(); 5710 invalidate(); 5711 } 5712 5713 /** 5714 * Sets line spacing for this TextView. Each line other than the last line will have its height 5715 * multiplied by {@code mult} and have {@code add} added to it. 5716 * 5717 * @param add The value in pixels that should be added to each line other than the last line. 5718 * This will be applied after the multiplier 5719 * @param mult The value by which each line height other than the last line will be multiplied 5720 * by 5721 * 5722 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5723 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5724 */ setLineSpacing(float add, float mult)5725 public void setLineSpacing(float add, float mult) { 5726 if (mSpacingAdd != add || mSpacingMult != mult) { 5727 mSpacingAdd = add; 5728 mSpacingMult = mult; 5729 5730 if (mLayout != null) { 5731 nullLayouts(); 5732 requestLayout(); 5733 invalidate(); 5734 } 5735 } 5736 } 5737 5738 /** 5739 * Gets the line spacing multiplier 5740 * 5741 * @return the value by which each line's height is multiplied to get its actual height. 5742 * 5743 * @see #setLineSpacing(float, float) 5744 * @see #getLineSpacingExtra() 5745 * 5746 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 5747 */ 5748 @InspectableProperty getLineSpacingMultiplier()5749 public float getLineSpacingMultiplier() { 5750 return mSpacingMult; 5751 } 5752 5753 /** 5754 * Gets the line spacing extra space 5755 * 5756 * @return the extra space that is added to the height of each lines of this TextView. 5757 * 5758 * @see #setLineSpacing(float, float) 5759 * @see #getLineSpacingMultiplier() 5760 * 5761 * @attr ref android.R.styleable#TextView_lineSpacingExtra 5762 */ 5763 @InspectableProperty getLineSpacingExtra()5764 public float getLineSpacingExtra() { 5765 return mSpacingAdd; 5766 } 5767 5768 /** 5769 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 5770 * between subsequent baselines in the TextView. 5771 * 5772 * @param lineHeight the line height in pixels 5773 * 5774 * @see #setLineSpacing(float, float) 5775 * @see #getLineSpacingExtra() 5776 * 5777 * @attr ref android.R.styleable#TextView_lineHeight 5778 */ setLineHeight(@x @ntRangefrom = 0) int lineHeight)5779 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 5780 Preconditions.checkArgumentNonnegative(lineHeight); 5781 5782 final int fontHeight = getPaint().getFontMetricsInt(null); 5783 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 5784 if (lineHeight != fontHeight) { 5785 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 5786 setLineSpacing(lineHeight - fontHeight, 1f); 5787 } 5788 } 5789 5790 /** 5791 * Convenience method to append the specified text to the TextView's 5792 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5793 * if it was not already editable. 5794 * 5795 * @param text text to be appended to the already displayed text 5796 */ append(CharSequence text)5797 public final void append(CharSequence text) { 5798 append(text, 0, text.length()); 5799 } 5800 5801 /** 5802 * Convenience method to append the specified text slice to the TextView's 5803 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 5804 * if it was not already editable. 5805 * 5806 * @param text text to be appended to the already displayed text 5807 * @param start the index of the first character in the {@code text} 5808 * @param end the index of the character following the last character in the {@code text} 5809 * 5810 * @see Appendable#append(CharSequence, int, int) 5811 */ append(CharSequence text, int start, int end)5812 public void append(CharSequence text, int start, int end) { 5813 if (!(mText instanceof Editable)) { 5814 setText(mText, BufferType.EDITABLE); 5815 } 5816 5817 ((Editable) mText).append(text, start, end); 5818 5819 if (mAutoLinkMask != 0) { 5820 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 5821 // Do not change the movement method for text that support text selection as it 5822 // would prevent an arbitrary cursor displacement. 5823 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 5824 setMovementMethod(LinkMovementMethod.getInstance()); 5825 } 5826 } 5827 } 5828 updateTextColors()5829 private void updateTextColors() { 5830 boolean inval = false; 5831 final int[] drawableState = getDrawableState(); 5832 int color = mTextColor.getColorForState(drawableState, 0); 5833 if (color != mCurTextColor) { 5834 mCurTextColor = color; 5835 inval = true; 5836 } 5837 if (mLinkTextColor != null) { 5838 color = mLinkTextColor.getColorForState(drawableState, 0); 5839 if (color != mTextPaint.linkColor) { 5840 mTextPaint.linkColor = color; 5841 inval = true; 5842 } 5843 } 5844 if (mHintTextColor != null) { 5845 color = mHintTextColor.getColorForState(drawableState, 0); 5846 if (color != mCurHintTextColor) { 5847 mCurHintTextColor = color; 5848 if (mText.length() == 0) { 5849 inval = true; 5850 } 5851 } 5852 } 5853 if (inval) { 5854 // Text needs to be redrawn with the new color 5855 if (mEditor != null) mEditor.invalidateTextDisplayList(); 5856 invalidate(); 5857 } 5858 } 5859 5860 @Override drawableStateChanged()5861 protected void drawableStateChanged() { 5862 super.drawableStateChanged(); 5863 5864 if (mTextColor != null && mTextColor.isStateful() 5865 || (mHintTextColor != null && mHintTextColor.isStateful()) 5866 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 5867 updateTextColors(); 5868 } 5869 5870 if (mDrawables != null) { 5871 final int[] state = getDrawableState(); 5872 for (Drawable dr : mDrawables.mShowing) { 5873 if (dr != null && dr.isStateful() && dr.setState(state)) { 5874 invalidateDrawable(dr); 5875 } 5876 } 5877 } 5878 } 5879 5880 @Override drawableHotspotChanged(float x, float y)5881 public void drawableHotspotChanged(float x, float y) { 5882 super.drawableHotspotChanged(x, y); 5883 5884 if (mDrawables != null) { 5885 for (Drawable dr : mDrawables.mShowing) { 5886 if (dr != null) { 5887 dr.setHotspot(x, y); 5888 } 5889 } 5890 } 5891 } 5892 5893 @Override onSaveInstanceState()5894 public Parcelable onSaveInstanceState() { 5895 Parcelable superState = super.onSaveInstanceState(); 5896 5897 // Save state if we are forced to 5898 final boolean freezesText = getFreezesText(); 5899 boolean hasSelection = false; 5900 int start = -1; 5901 int end = -1; 5902 5903 if (mText != null) { 5904 start = getSelectionStart(); 5905 end = getSelectionEnd(); 5906 if (start >= 0 || end >= 0) { 5907 // Or save state if there is a selection 5908 hasSelection = true; 5909 } 5910 } 5911 5912 if (freezesText || hasSelection) { 5913 SavedState ss = new SavedState(superState); 5914 5915 if (freezesText) { 5916 if (mText instanceof Spanned) { 5917 final Spannable sp = new SpannableStringBuilder(mText); 5918 5919 if (mEditor != null) { 5920 removeMisspelledSpans(sp); 5921 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5922 } 5923 5924 ss.text = sp; 5925 } else { 5926 ss.text = mText.toString(); 5927 } 5928 } 5929 5930 if (hasSelection) { 5931 // XXX Should also save the current scroll position! 5932 ss.selStart = start; 5933 ss.selEnd = end; 5934 } 5935 5936 if (isFocused() && start >= 0 && end >= 0) { 5937 ss.frozenWithFocus = true; 5938 } 5939 5940 ss.error = getError(); 5941 5942 if (mEditor != null) { 5943 ss.editorState = mEditor.saveInstanceState(); 5944 } 5945 return ss; 5946 } 5947 5948 return superState; 5949 } 5950 removeMisspelledSpans(Spannable spannable)5951 void removeMisspelledSpans(Spannable spannable) { 5952 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 5953 SuggestionSpan.class); 5954 for (int i = 0; i < suggestionSpans.length; i++) { 5955 int flags = suggestionSpans[i].getFlags(); 5956 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 5957 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 5958 spannable.removeSpan(suggestionSpans[i]); 5959 } 5960 } 5961 } 5962 5963 @Override onRestoreInstanceState(Parcelable state)5964 public void onRestoreInstanceState(Parcelable state) { 5965 if (!(state instanceof SavedState)) { 5966 super.onRestoreInstanceState(state); 5967 return; 5968 } 5969 5970 SavedState ss = (SavedState) state; 5971 super.onRestoreInstanceState(ss.getSuperState()); 5972 5973 // XXX restore buffer type too, as well as lots of other stuff 5974 if (ss.text != null) { 5975 setText(ss.text); 5976 } 5977 5978 if (ss.selStart >= 0 && ss.selEnd >= 0) { 5979 if (mSpannable != null) { 5980 int len = mText.length(); 5981 5982 if (ss.selStart > len || ss.selEnd > len) { 5983 String restored = ""; 5984 5985 if (ss.text != null) { 5986 restored = "(restored) "; 5987 } 5988 5989 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 5990 + " out of range for " + restored + "text " + mText); 5991 } else { 5992 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 5993 5994 if (ss.frozenWithFocus) { 5995 createEditorIfNeeded(); 5996 mEditor.mFrozenWithFocus = true; 5997 } 5998 } 5999 } 6000 } 6001 6002 if (ss.error != null) { 6003 final CharSequence error = ss.error; 6004 // Display the error later, after the first layout pass 6005 post(new Runnable() { 6006 public void run() { 6007 if (mEditor == null || !mEditor.mErrorWasChanged) { 6008 setError(error); 6009 } 6010 } 6011 }); 6012 } 6013 6014 if (ss.editorState != null) { 6015 createEditorIfNeeded(); 6016 mEditor.restoreInstanceState(ss.editorState); 6017 } 6018 } 6019 6020 /** 6021 * Control whether this text view saves its entire text contents when 6022 * freezing to an icicle, in addition to dynamic state such as cursor 6023 * position. By default this is false, not saving the text. Set to true 6024 * if the text in the text view is not being saved somewhere else in 6025 * persistent storage (such as in a content provider) so that if the 6026 * view is later thawed the user will not lose their data. For 6027 * {@link android.widget.EditText} it is always enabled, regardless of 6028 * the value of the attribute. 6029 * 6030 * @param freezesText Controls whether a frozen icicle should include the 6031 * entire text data: true to include it, false to not. 6032 * 6033 * @attr ref android.R.styleable#TextView_freezesText 6034 */ 6035 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)6036 public void setFreezesText(boolean freezesText) { 6037 mFreezesText = freezesText; 6038 } 6039 6040 /** 6041 * Return whether this text view is including its entire text contents 6042 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 6043 * 6044 * @return Returns true if text is included, false if it isn't. 6045 * 6046 * @see #setFreezesText 6047 */ 6048 @InspectableProperty getFreezesText()6049 public boolean getFreezesText() { 6050 return mFreezesText; 6051 } 6052 6053 /////////////////////////////////////////////////////////////////////////// 6054 6055 /** 6056 * Sets the Factory used to create new {@link Editable Editables}. 6057 * 6058 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6059 * 6060 * @see android.text.Editable.Factory 6061 * @see android.widget.TextView.BufferType#EDITABLE 6062 */ setEditableFactory(Editable.Factory factory)6063 public final void setEditableFactory(Editable.Factory factory) { 6064 mEditableFactory = factory; 6065 setText(mText); 6066 } 6067 6068 /** 6069 * Sets the Factory used to create new {@link Spannable Spannables}. 6070 * 6071 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6072 * 6073 * @see android.text.Spannable.Factory 6074 * @see android.widget.TextView.BufferType#SPANNABLE 6075 */ setSpannableFactory(Spannable.Factory factory)6076 public final void setSpannableFactory(Spannable.Factory factory) { 6077 mSpannableFactory = factory; 6078 setText(mText); 6079 } 6080 6081 /** 6082 * Sets the text to be displayed. TextView <em>does not</em> accept 6083 * HTML-like formatting, which you can do with text strings in XML resource files. 6084 * To style your strings, attach android.text.style.* objects to a 6085 * {@link android.text.SpannableString}, or see the 6086 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6087 * Available Resource Types</a> documentation for an example of setting 6088 * formatted text in the XML resource file. 6089 * <p/> 6090 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6091 * intermediate {@link Spannable Spannables}. Likewise it will use 6092 * {@link android.text.Editable.Factory} to create final or intermediate 6093 * {@link Editable Editables}. 6094 * 6095 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6096 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6097 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6098 * 6099 * @param text text to be displayed 6100 * 6101 * @attr ref android.R.styleable#TextView_text 6102 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6103 * parameters used to create the PrecomputedText mismatches 6104 * with this TextView. 6105 */ 6106 @android.view.RemotableViewMethod setText(CharSequence text)6107 public final void setText(CharSequence text) { 6108 setText(text, mBufferType); 6109 } 6110 6111 /** 6112 * Sets the text to be displayed but retains the cursor position. Same as 6113 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6114 * new text. 6115 * <p/> 6116 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6117 * intermediate {@link Spannable Spannables}. Likewise it will use 6118 * {@link android.text.Editable.Factory} to create final or intermediate 6119 * {@link Editable Editables}. 6120 * 6121 * @param text text to be displayed 6122 * 6123 * @see #setText(CharSequence) 6124 */ 6125 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6126 public final void setTextKeepState(CharSequence text) { 6127 setTextKeepState(text, mBufferType); 6128 } 6129 6130 /** 6131 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6132 * <p/> 6133 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6134 * intermediate {@link Spannable Spannables}. Likewise it will use 6135 * {@link android.text.Editable.Factory} to create final or intermediate 6136 * {@link Editable Editables}. 6137 * 6138 * Subclasses overriding this method should ensure that the following post condition holds, 6139 * in order to guarantee the safety of the view's measurement and layout operations: 6140 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6141 * will be different from {@code null}. 6142 * 6143 * @param text text to be displayed 6144 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6145 * stored as a static text, styleable/spannable text, or editable text 6146 * 6147 * @see #setText(CharSequence) 6148 * @see android.widget.TextView.BufferType 6149 * @see #setSpannableFactory(Spannable.Factory) 6150 * @see #setEditableFactory(Editable.Factory) 6151 * 6152 * @attr ref android.R.styleable#TextView_text 6153 * @attr ref android.R.styleable#TextView_bufferType 6154 */ setText(CharSequence text, BufferType type)6155 public void setText(CharSequence text, BufferType type) { 6156 setText(text, type, true, 0); 6157 6158 if (mCharWrapper != null) { 6159 mCharWrapper.mChars = null; 6160 } 6161 } 6162 6163 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)6164 private void setText(CharSequence text, BufferType type, 6165 boolean notifyBefore, int oldlen) { 6166 mTextSetFromXmlOrResourceId = false; 6167 if (text == null) { 6168 text = ""; 6169 } 6170 6171 // If suggestions are not enabled, remove the suggestion spans from the text 6172 if (!isSuggestionsEnabled()) { 6173 text = removeSuggestionSpans(text); 6174 } 6175 6176 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 6177 6178 if (text instanceof Spanned 6179 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 6180 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 6181 setHorizontalFadingEdgeEnabled(true); 6182 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 6183 } else { 6184 setHorizontalFadingEdgeEnabled(false); 6185 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 6186 } 6187 setEllipsize(TextUtils.TruncateAt.MARQUEE); 6188 } 6189 6190 int n = mFilters.length; 6191 for (int i = 0; i < n; i++) { 6192 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 6193 if (out != null) { 6194 text = out; 6195 } 6196 } 6197 6198 if (notifyBefore) { 6199 if (mText != null) { 6200 oldlen = mText.length(); 6201 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 6202 } else { 6203 sendBeforeTextChanged("", 0, 0, text.length()); 6204 } 6205 } 6206 6207 boolean needEditableForNotification = false; 6208 6209 if (mListeners != null && mListeners.size() != 0) { 6210 needEditableForNotification = true; 6211 } 6212 6213 PrecomputedText precomputed = 6214 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 6215 if (type == BufferType.EDITABLE || getKeyListener() != null 6216 || needEditableForNotification) { 6217 createEditorIfNeeded(); 6218 mEditor.forgetUndoRedo(); 6219 Editable t = mEditableFactory.newEditable(text); 6220 text = t; 6221 setFilters(t, mFilters); 6222 InputMethodManager imm = getInputMethodManager(); 6223 if (imm != null) imm.restartInput(this); 6224 } else if (precomputed != null) { 6225 if (mTextDir == null) { 6226 mTextDir = getTextDirectionHeuristic(); 6227 } 6228 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 6229 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 6230 mHyphenationFrequency); 6231 switch (checkResult) { 6232 case PrecomputedText.Params.UNUSABLE: 6233 throw new IllegalArgumentException( 6234 "PrecomputedText's Parameters don't match the parameters of this TextView." 6235 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 6236 + "to override the settings of this TextView: " 6237 + "PrecomputedText: " + precomputed.getParams() 6238 + "TextView: " + getTextMetricsParams()); 6239 case PrecomputedText.Params.NEED_RECOMPUTE: 6240 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 6241 break; 6242 case PrecomputedText.Params.USABLE: 6243 // pass through 6244 } 6245 } else if (type == BufferType.SPANNABLE || mMovement != null) { 6246 text = mSpannableFactory.newSpannable(text); 6247 } else if (!(text instanceof CharWrapper)) { 6248 text = TextUtils.stringOrSpannedString(text); 6249 } 6250 6251 if (mAutoLinkMask != 0) { 6252 Spannable s2; 6253 6254 if (type == BufferType.EDITABLE || text instanceof Spannable) { 6255 s2 = (Spannable) text; 6256 } else { 6257 s2 = mSpannableFactory.newSpannable(text); 6258 } 6259 6260 if (Linkify.addLinks(s2, mAutoLinkMask)) { 6261 text = s2; 6262 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 6263 6264 /* 6265 * We must go ahead and set the text before changing the 6266 * movement method, because setMovementMethod() may call 6267 * setText() again to try to upgrade the buffer type. 6268 */ 6269 setTextInternal(text); 6270 6271 // Do not change the movement method for text that support text selection as it 6272 // would prevent an arbitrary cursor displacement. 6273 if (mLinksClickable && !textCanBeSelected()) { 6274 setMovementMethod(LinkMovementMethod.getInstance()); 6275 } 6276 } 6277 } 6278 6279 mBufferType = type; 6280 setTextInternal(text); 6281 6282 if (mTransformation == null) { 6283 mTransformed = text; 6284 } else { 6285 mTransformed = mTransformation.getTransformation(text, this); 6286 } 6287 if (mTransformed == null) { 6288 // Should not happen if the transformation method follows the non-null postcondition. 6289 mTransformed = ""; 6290 } 6291 6292 final int textLength = text.length(); 6293 6294 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 6295 Spannable sp = (Spannable) text; 6296 6297 // Remove any ChangeWatchers that might have come from other TextViews. 6298 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 6299 final int count = watchers.length; 6300 for (int i = 0; i < count; i++) { 6301 sp.removeSpan(watchers[i]); 6302 } 6303 6304 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 6305 6306 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 6307 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 6308 6309 if (mEditor != null) mEditor.addSpanWatchers(sp); 6310 6311 if (mTransformation != null) { 6312 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 6313 } 6314 6315 if (mMovement != null) { 6316 mMovement.initialize(this, (Spannable) text); 6317 6318 /* 6319 * Initializing the movement method will have set the 6320 * selection, so reset mSelectionMoved to keep that from 6321 * interfering with the normal on-focus selection-setting. 6322 */ 6323 if (mEditor != null) mEditor.mSelectionMoved = false; 6324 } 6325 } 6326 6327 if (mLayout != null) { 6328 checkForRelayout(); 6329 } 6330 6331 sendOnTextChanged(text, 0, oldlen, textLength); 6332 onTextChanged(text, 0, oldlen, textLength); 6333 6334 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 6335 6336 if (needEditableForNotification) { 6337 sendAfterTextChanged((Editable) text); 6338 } else { 6339 notifyListeningManagersAfterTextChanged(); 6340 } 6341 6342 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 6343 if (mEditor != null) mEditor.prepareCursorControllers(); 6344 } 6345 6346 /** 6347 * Sets the TextView to display the specified slice of the specified 6348 * char array. You must promise that you will not change the contents 6349 * of the array except for right before another call to setText(), 6350 * since the TextView has no way to know that the text 6351 * has changed and that it needs to invalidate and re-layout. 6352 * 6353 * @param text char array to be displayed 6354 * @param start start index in the char array 6355 * @param len length of char count after {@code start} 6356 */ setText(char[] text, int start, int len)6357 public final void setText(char[] text, int start, int len) { 6358 int oldlen = 0; 6359 6360 if (start < 0 || len < 0 || start + len > text.length) { 6361 throw new IndexOutOfBoundsException(start + ", " + len); 6362 } 6363 6364 /* 6365 * We must do the before-notification here ourselves because if 6366 * the old text is a CharWrapper we destroy it before calling 6367 * into the normal path. 6368 */ 6369 if (mText != null) { 6370 oldlen = mText.length(); 6371 sendBeforeTextChanged(mText, 0, oldlen, len); 6372 } else { 6373 sendBeforeTextChanged("", 0, 0, len); 6374 } 6375 6376 if (mCharWrapper == null) { 6377 mCharWrapper = new CharWrapper(text, start, len); 6378 } else { 6379 mCharWrapper.set(text, start, len); 6380 } 6381 6382 setText(mCharWrapper, mBufferType, false, oldlen); 6383 } 6384 6385 /** 6386 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 6387 * the cursor position. Same as 6388 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 6389 * position (if any) is retained in the new text. 6390 * <p/> 6391 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6392 * intermediate {@link Spannable Spannables}. Likewise it will use 6393 * {@link android.text.Editable.Factory} to create final or intermediate 6394 * {@link Editable Editables}. 6395 * 6396 * @param text text to be displayed 6397 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6398 * stored as a static text, styleable/spannable text, or editable text 6399 * 6400 * @see #setText(CharSequence, android.widget.TextView.BufferType) 6401 */ setTextKeepState(CharSequence text, BufferType type)6402 public final void setTextKeepState(CharSequence text, BufferType type) { 6403 int start = getSelectionStart(); 6404 int end = getSelectionEnd(); 6405 int len = text.length(); 6406 6407 setText(text, type); 6408 6409 if (start >= 0 || end >= 0) { 6410 if (mSpannable != null) { 6411 Selection.setSelection(mSpannable, 6412 Math.max(0, Math.min(start, len)), 6413 Math.max(0, Math.min(end, len))); 6414 } 6415 } 6416 } 6417 6418 /** 6419 * Sets the text to be displayed using a string resource identifier. 6420 * 6421 * @param resid the resource identifier of the string resource to be displayed 6422 * 6423 * @see #setText(CharSequence) 6424 * 6425 * @attr ref android.R.styleable#TextView_text 6426 */ 6427 @android.view.RemotableViewMethod setText(@tringRes int resid)6428 public final void setText(@StringRes int resid) { 6429 setText(getContext().getResources().getText(resid)); 6430 mTextSetFromXmlOrResourceId = true; 6431 mTextId = resid; 6432 } 6433 6434 /** 6435 * Sets the text to be displayed using a string resource identifier and the 6436 * {@link android.widget.TextView.BufferType}. 6437 * <p/> 6438 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6439 * intermediate {@link Spannable Spannables}. Likewise it will use 6440 * {@link android.text.Editable.Factory} to create final or intermediate 6441 * {@link Editable Editables}. 6442 * 6443 * @param resid the resource identifier of the string resource to be displayed 6444 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 6445 * stored as a static text, styleable/spannable text, or editable text 6446 * 6447 * @see #setText(int) 6448 * @see #setText(CharSequence) 6449 * @see android.widget.TextView.BufferType 6450 * @see #setSpannableFactory(Spannable.Factory) 6451 * @see #setEditableFactory(Editable.Factory) 6452 * 6453 * @attr ref android.R.styleable#TextView_text 6454 * @attr ref android.R.styleable#TextView_bufferType 6455 */ setText(@tringRes int resid, BufferType type)6456 public final void setText(@StringRes int resid, BufferType type) { 6457 setText(getContext().getResources().getText(resid), type); 6458 mTextSetFromXmlOrResourceId = true; 6459 mTextId = resid; 6460 } 6461 6462 /** 6463 * Sets the text to be displayed when the text of the TextView is empty. 6464 * Null means to use the normal empty text. The hint does not currently 6465 * participate in determining the size of the view. 6466 * 6467 * @attr ref android.R.styleable#TextView_hint 6468 */ 6469 @android.view.RemotableViewMethod setHint(CharSequence hint)6470 public final void setHint(CharSequence hint) { 6471 setHintInternal(hint); 6472 6473 if (mEditor != null && isInputMethodTarget()) { 6474 mEditor.reportExtractedText(); 6475 } 6476 } 6477 setHintInternal(CharSequence hint)6478 private void setHintInternal(CharSequence hint) { 6479 mHint = TextUtils.stringOrSpannedString(hint); 6480 6481 if (mLayout != null) { 6482 checkForRelayout(); 6483 } 6484 6485 if (mText.length() == 0) { 6486 invalidate(); 6487 } 6488 6489 // Invalidate display list if hint is currently used 6490 if (mEditor != null && mText.length() == 0 && mHint != null) { 6491 mEditor.invalidateTextDisplayList(); 6492 } 6493 } 6494 6495 /** 6496 * Sets the text to be displayed when the text of the TextView is empty, 6497 * from a resource. 6498 * 6499 * @attr ref android.R.styleable#TextView_hint 6500 */ 6501 @android.view.RemotableViewMethod setHint(@tringRes int resid)6502 public final void setHint(@StringRes int resid) { 6503 mHintId = resid; 6504 setHint(getContext().getResources().getText(resid)); 6505 } 6506 6507 /** 6508 * Returns the hint that is displayed when the text of the TextView 6509 * is empty. 6510 * 6511 * @attr ref android.R.styleable#TextView_hint 6512 */ 6513 @InspectableProperty 6514 @ViewDebug.CapturedViewProperty getHint()6515 public CharSequence getHint() { 6516 return mHint; 6517 } 6518 6519 /** 6520 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 6521 * line characters instead of letting it wrap onto multiple lines. 6522 * 6523 * @attr ref android.R.styleable#TextView_singleLine 6524 */ 6525 @InspectableProperty isSingleLine()6526 public boolean isSingleLine() { 6527 return mSingleLine; 6528 } 6529 isMultilineInputType(int type)6530 private static boolean isMultilineInputType(int type) { 6531 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 6532 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 6533 } 6534 6535 /** 6536 * Removes the suggestion spans. 6537 */ removeSuggestionSpans(CharSequence text)6538 CharSequence removeSuggestionSpans(CharSequence text) { 6539 if (text instanceof Spanned) { 6540 Spannable spannable; 6541 if (text instanceof Spannable) { 6542 spannable = (Spannable) text; 6543 } else { 6544 spannable = mSpannableFactory.newSpannable(text); 6545 } 6546 6547 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 6548 if (spans.length == 0) { 6549 return text; 6550 } else { 6551 text = spannable; 6552 } 6553 6554 for (int i = 0; i < spans.length; i++) { 6555 spannable.removeSpan(spans[i]); 6556 } 6557 } 6558 return text; 6559 } 6560 6561 /** 6562 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 6563 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 6564 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 6565 * then a soft keyboard will not be displayed for this text view. 6566 * 6567 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 6568 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 6569 * type. 6570 * 6571 * @see #getInputType() 6572 * @see #setRawInputType(int) 6573 * @see android.text.InputType 6574 * @attr ref android.R.styleable#TextView_inputType 6575 */ setInputType(int type)6576 public void setInputType(int type) { 6577 final boolean wasPassword = isPasswordInputType(getInputType()); 6578 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 6579 setInputType(type, false); 6580 final boolean isPassword = isPasswordInputType(type); 6581 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 6582 boolean forceUpdate = false; 6583 if (isPassword) { 6584 setTransformationMethod(PasswordTransformationMethod.getInstance()); 6585 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6586 Typeface.NORMAL, -1 /* weight, not specifeid */); 6587 } else if (isVisiblePassword) { 6588 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6589 forceUpdate = true; 6590 } 6591 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 6592 Typeface.NORMAL, -1 /* weight, not specified */); 6593 } else if (wasPassword || wasVisiblePassword) { 6594 // not in password mode, clean up typeface and transformation 6595 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 6596 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 6597 -1 /* weight, not specified */); 6598 if (mTransformation == PasswordTransformationMethod.getInstance()) { 6599 forceUpdate = true; 6600 } 6601 } 6602 6603 boolean singleLine = !isMultilineInputType(type); 6604 6605 // We need to update the single line mode if it has changed or we 6606 // were previously in password mode. 6607 if (mSingleLine != singleLine || forceUpdate) { 6608 // Change single line mode, but only change the transformation if 6609 // we are not in password mode. 6610 applySingleLine(singleLine, !isPassword, true, true); 6611 } 6612 6613 if (!isSuggestionsEnabled()) { 6614 setTextInternal(removeSuggestionSpans(mText)); 6615 } 6616 6617 InputMethodManager imm = getInputMethodManager(); 6618 if (imm != null) imm.restartInput(this); 6619 } 6620 6621 /** 6622 * It would be better to rely on the input type for everything. A password inputType should have 6623 * a password transformation. We should hence use isPasswordInputType instead of this method. 6624 * 6625 * We should: 6626 * - Call setInputType in setKeyListener instead of changing the input type directly (which 6627 * would install the correct transformation). 6628 * - Refuse the installation of a non-password transformation in setTransformation if the input 6629 * type is password. 6630 * 6631 * However, this is like this for legacy reasons and we cannot break existing apps. This method 6632 * is useful since it matches what the user can see (obfuscated text or not). 6633 * 6634 * @return true if the current transformation method is of the password type. 6635 */ hasPasswordTransformationMethod()6636 boolean hasPasswordTransformationMethod() { 6637 return mTransformation instanceof PasswordTransformationMethod; 6638 } 6639 6640 /** 6641 * Returns true if the current inputType is any type of password. 6642 * 6643 * @hide 6644 */ isAnyPasswordInputType()6645 public boolean isAnyPasswordInputType() { 6646 final int inputType = getInputType(); 6647 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 6648 } 6649 isPasswordInputType(int inputType)6650 static boolean isPasswordInputType(int inputType) { 6651 final int variation = 6652 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6653 return variation 6654 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 6655 || variation 6656 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 6657 || variation 6658 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 6659 } 6660 isVisiblePasswordInputType(int inputType)6661 private static boolean isVisiblePasswordInputType(int inputType) { 6662 final int variation = 6663 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 6664 return variation 6665 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 6666 } 6667 6668 /** 6669 * Directly change the content type integer of the text view, without 6670 * modifying any other state. 6671 * @see #setInputType(int) 6672 * @see android.text.InputType 6673 * @attr ref android.R.styleable#TextView_inputType 6674 */ setRawInputType(int type)6675 public void setRawInputType(int type) { 6676 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 6677 createEditorIfNeeded(); 6678 mEditor.mInputType = type; 6679 } 6680 6681 /** 6682 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 6683 * a {@code Locale} object that can be used to customize key various listeners. 6684 * @see DateKeyListener#getInstance(Locale) 6685 * @see DateTimeKeyListener#getInstance(Locale) 6686 * @see DigitsKeyListener#getInstance(Locale) 6687 * @see TimeKeyListener#getInstance(Locale) 6688 */ 6689 @Nullable getCustomLocaleForKeyListenerOrNull()6690 private Locale getCustomLocaleForKeyListenerOrNull() { 6691 if (!mUseInternationalizedInput) { 6692 // If the application does not target O, stick to the previous behavior. 6693 return null; 6694 } 6695 final LocaleList locales = getImeHintLocales(); 6696 if (locales == null) { 6697 // If the application does not explicitly specify IME hint locale, also stick to the 6698 // previous behavior. 6699 return null; 6700 } 6701 return locales.get(0); 6702 } 6703 6704 @UnsupportedAppUsage setInputType(int type, boolean direct)6705 private void setInputType(int type, boolean direct) { 6706 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 6707 KeyListener input; 6708 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 6709 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 6710 TextKeyListener.Capitalize cap; 6711 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 6712 cap = TextKeyListener.Capitalize.CHARACTERS; 6713 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 6714 cap = TextKeyListener.Capitalize.WORDS; 6715 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 6716 cap = TextKeyListener.Capitalize.SENTENCES; 6717 } else { 6718 cap = TextKeyListener.Capitalize.NONE; 6719 } 6720 input = TextKeyListener.getInstance(autotext, cap); 6721 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 6722 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6723 input = DigitsKeyListener.getInstance( 6724 locale, 6725 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 6726 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 6727 if (locale != null) { 6728 // Override type, if necessary for i18n. 6729 int newType = input.getInputType(); 6730 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 6731 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 6732 // The class is different from the original class. So we need to override 6733 // 'type'. But we want to keep the password flag if it's there. 6734 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 6735 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 6736 } 6737 type = newType; 6738 } 6739 } 6740 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 6741 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 6742 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 6743 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 6744 input = DateKeyListener.getInstance(locale); 6745 break; 6746 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 6747 input = TimeKeyListener.getInstance(locale); 6748 break; 6749 default: 6750 input = DateTimeKeyListener.getInstance(locale); 6751 break; 6752 } 6753 if (mUseInternationalizedInput) { 6754 type = input.getInputType(); // Override type, if necessary for i18n. 6755 } 6756 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 6757 input = DialerKeyListener.getInstance(); 6758 } else { 6759 input = TextKeyListener.getInstance(); 6760 } 6761 setRawInputType(type); 6762 mListenerChanged = false; 6763 if (direct) { 6764 createEditorIfNeeded(); 6765 mEditor.mKeyListener = input; 6766 } else { 6767 setKeyListenerOnly(input); 6768 } 6769 } 6770 6771 /** 6772 * Get the type of the editable content. 6773 * 6774 * @see #setInputType(int) 6775 * @see android.text.InputType 6776 */ 6777 @InspectableProperty(flagMapping = { 6778 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 6779 @FlagEntry( 6780 name = "text", 6781 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6782 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 6783 @FlagEntry( 6784 name = "textUri", 6785 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6786 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 6787 @FlagEntry( 6788 name = "textEmailAddress", 6789 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6790 target = InputType.TYPE_CLASS_TEXT 6791 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 6792 @FlagEntry( 6793 name = "textEmailSubject", 6794 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6795 target = InputType.TYPE_CLASS_TEXT 6796 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 6797 @FlagEntry( 6798 name = "textShortMessage", 6799 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6800 target = InputType.TYPE_CLASS_TEXT 6801 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 6802 @FlagEntry( 6803 name = "textLongMessage", 6804 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6805 target = InputType.TYPE_CLASS_TEXT 6806 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 6807 @FlagEntry( 6808 name = "textPersonName", 6809 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6810 target = InputType.TYPE_CLASS_TEXT 6811 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 6812 @FlagEntry( 6813 name = "textPostalAddress", 6814 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6815 target = InputType.TYPE_CLASS_TEXT 6816 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 6817 @FlagEntry( 6818 name = "textPassword", 6819 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6820 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 6821 @FlagEntry( 6822 name = "textVisiblePassword", 6823 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6824 target = InputType.TYPE_CLASS_TEXT 6825 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 6826 @FlagEntry( 6827 name = "textWebEditText", 6828 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6829 target = InputType.TYPE_CLASS_TEXT 6830 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 6831 @FlagEntry( 6832 name = "textFilter", 6833 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6834 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 6835 @FlagEntry( 6836 name = "textPhonetic", 6837 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6838 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 6839 @FlagEntry( 6840 name = "textWebEmailAddress", 6841 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6842 target = InputType.TYPE_CLASS_TEXT 6843 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 6844 @FlagEntry( 6845 name = "textWebPassword", 6846 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6847 target = InputType.TYPE_CLASS_TEXT 6848 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 6849 @FlagEntry( 6850 name = "number", 6851 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6852 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 6853 @FlagEntry( 6854 name = "numberPassword", 6855 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6856 target = InputType.TYPE_CLASS_NUMBER 6857 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 6858 @FlagEntry( 6859 name = "phone", 6860 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6861 target = InputType.TYPE_CLASS_PHONE), 6862 @FlagEntry( 6863 name = "datetime", 6864 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6865 target = InputType.TYPE_CLASS_DATETIME 6866 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 6867 @FlagEntry( 6868 name = "date", 6869 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6870 target = InputType.TYPE_CLASS_DATETIME 6871 | InputType.TYPE_DATETIME_VARIATION_DATE), 6872 @FlagEntry( 6873 name = "time", 6874 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 6875 target = InputType.TYPE_CLASS_DATETIME 6876 | InputType.TYPE_DATETIME_VARIATION_TIME), 6877 @FlagEntry( 6878 name = "textCapCharacters", 6879 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6880 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 6881 @FlagEntry( 6882 name = "textCapWords", 6883 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6884 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 6885 @FlagEntry( 6886 name = "textCapSentences", 6887 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6888 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 6889 @FlagEntry( 6890 name = "textAutoCorrect", 6891 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6892 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 6893 @FlagEntry( 6894 name = "textAutoComplete", 6895 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6896 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 6897 @FlagEntry( 6898 name = "textMultiLine", 6899 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6900 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 6901 @FlagEntry( 6902 name = "textImeMultiLine", 6903 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6904 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 6905 @FlagEntry( 6906 name = "textNoSuggestions", 6907 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6908 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 6909 @FlagEntry( 6910 name = "numberSigned", 6911 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6912 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 6913 @FlagEntry( 6914 name = "numberDecimal", 6915 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 6916 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 6917 }) getInputType()6918 public int getInputType() { 6919 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 6920 } 6921 6922 /** 6923 * Change the editor type integer associated with the text view, which 6924 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 6925 * when it has focus. 6926 * @see #getImeOptions 6927 * @see android.view.inputmethod.EditorInfo 6928 * @attr ref android.R.styleable#TextView_imeOptions 6929 */ setImeOptions(int imeOptions)6930 public void setImeOptions(int imeOptions) { 6931 createEditorIfNeeded(); 6932 mEditor.createInputContentTypeIfNeeded(); 6933 mEditor.mInputContentType.imeOptions = imeOptions; 6934 } 6935 6936 /** 6937 * Get the type of the Input Method Editor (IME). 6938 * @return the type of the IME 6939 * @see #setImeOptions(int) 6940 * @see EditorInfo 6941 */ 6942 @InspectableProperty(flagMapping = { 6943 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 6944 @FlagEntry( 6945 name = "actionUnspecified", 6946 mask = EditorInfo.IME_MASK_ACTION, 6947 target = EditorInfo.IME_ACTION_UNSPECIFIED), 6948 @FlagEntry( 6949 name = "actionNone", 6950 mask = EditorInfo.IME_MASK_ACTION, 6951 target = EditorInfo.IME_ACTION_NONE), 6952 @FlagEntry( 6953 name = "actionGo", 6954 mask = EditorInfo.IME_MASK_ACTION, 6955 target = EditorInfo.IME_ACTION_GO), 6956 @FlagEntry( 6957 name = "actionSearch", 6958 mask = EditorInfo.IME_MASK_ACTION, 6959 target = EditorInfo.IME_ACTION_SEARCH), 6960 @FlagEntry( 6961 name = "actionSend", 6962 mask = EditorInfo.IME_MASK_ACTION, 6963 target = EditorInfo.IME_ACTION_SEND), 6964 @FlagEntry( 6965 name = "actionNext", 6966 mask = EditorInfo.IME_MASK_ACTION, 6967 target = EditorInfo.IME_ACTION_NEXT), 6968 @FlagEntry( 6969 name = "actionDone", 6970 mask = EditorInfo.IME_MASK_ACTION, 6971 target = EditorInfo.IME_ACTION_DONE), 6972 @FlagEntry( 6973 name = "actionPrevious", 6974 mask = EditorInfo.IME_MASK_ACTION, 6975 target = EditorInfo.IME_ACTION_PREVIOUS), 6976 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 6977 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 6978 @FlagEntry( 6979 name = "flagNavigatePrevious", 6980 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 6981 @FlagEntry( 6982 name = "flagNoAccessoryAction", 6983 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 6984 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 6985 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 6986 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 6987 @FlagEntry( 6988 name = "flagNoPersonalizedLearning", 6989 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 6990 }) getImeOptions()6991 public int getImeOptions() { 6992 return mEditor != null && mEditor.mInputContentType != null 6993 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 6994 } 6995 6996 /** 6997 * Change the custom IME action associated with the text view, which 6998 * will be reported to an IME with {@link EditorInfo#actionLabel} 6999 * and {@link EditorInfo#actionId} when it has focus. 7000 * @see #getImeActionLabel 7001 * @see #getImeActionId 7002 * @see android.view.inputmethod.EditorInfo 7003 * @attr ref android.R.styleable#TextView_imeActionLabel 7004 * @attr ref android.R.styleable#TextView_imeActionId 7005 */ setImeActionLabel(CharSequence label, int actionId)7006 public void setImeActionLabel(CharSequence label, int actionId) { 7007 createEditorIfNeeded(); 7008 mEditor.createInputContentTypeIfNeeded(); 7009 mEditor.mInputContentType.imeActionLabel = label; 7010 mEditor.mInputContentType.imeActionId = actionId; 7011 } 7012 7013 /** 7014 * Get the IME action label previous set with {@link #setImeActionLabel}. 7015 * 7016 * @see #setImeActionLabel 7017 * @see android.view.inputmethod.EditorInfo 7018 */ 7019 @InspectableProperty getImeActionLabel()7020 public CharSequence getImeActionLabel() { 7021 return mEditor != null && mEditor.mInputContentType != null 7022 ? mEditor.mInputContentType.imeActionLabel : null; 7023 } 7024 7025 /** 7026 * Get the IME action ID previous set with {@link #setImeActionLabel}. 7027 * 7028 * @see #setImeActionLabel 7029 * @see android.view.inputmethod.EditorInfo 7030 */ 7031 @InspectableProperty getImeActionId()7032 public int getImeActionId() { 7033 return mEditor != null && mEditor.mInputContentType != null 7034 ? mEditor.mInputContentType.imeActionId : 0; 7035 } 7036 7037 /** 7038 * Set a special listener to be called when an action is performed 7039 * on the text view. This will be called when the enter key is pressed, 7040 * or when an action supplied to the IME is selected by the user. Setting 7041 * this means that the normal hard key event will not insert a newline 7042 * into the text view, even if it is multi-line; holding down the ALT 7043 * modifier will, however, allow the user to insert a newline character. 7044 */ setOnEditorActionListener(OnEditorActionListener l)7045 public void setOnEditorActionListener(OnEditorActionListener l) { 7046 createEditorIfNeeded(); 7047 mEditor.createInputContentTypeIfNeeded(); 7048 mEditor.mInputContentType.onEditorActionListener = l; 7049 } 7050 7051 /** 7052 * Called when an attached input method calls 7053 * {@link InputConnection#performEditorAction(int) 7054 * InputConnection.performEditorAction()} 7055 * for this text view. The default implementation will call your action 7056 * listener supplied to {@link #setOnEditorActionListener}, or perform 7057 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7058 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7059 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7060 * EditorInfo.IME_ACTION_DONE}. 7061 * 7062 * <p>For backwards compatibility, if no IME options have been set and the 7063 * text view would not normally advance focus on enter, then 7064 * the NEXT and DONE actions received here will be turned into an enter 7065 * key down/up pair to go through the normal key handling. 7066 * 7067 * @param actionCode The code of the action being performed. 7068 * 7069 * @see #setOnEditorActionListener 7070 */ onEditorAction(int actionCode)7071 public void onEditorAction(int actionCode) { 7072 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7073 if (ict != null) { 7074 if (ict.onEditorActionListener != null) { 7075 if (ict.onEditorActionListener.onEditorAction(this, 7076 actionCode, null)) { 7077 return; 7078 } 7079 } 7080 7081 // This is the handling for some default action. 7082 // Note that for backwards compatibility we don't do this 7083 // default handling if explicit ime options have not been given, 7084 // instead turning this into the normal enter key codes that an 7085 // app may be expecting. 7086 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7087 View v = focusSearch(FOCUS_FORWARD); 7088 if (v != null) { 7089 if (!v.requestFocus(FOCUS_FORWARD)) { 7090 throw new IllegalStateException("focus search returned a view " 7091 + "that wasn't able to take focus!"); 7092 } 7093 } 7094 return; 7095 7096 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 7097 View v = focusSearch(FOCUS_BACKWARD); 7098 if (v != null) { 7099 if (!v.requestFocus(FOCUS_BACKWARD)) { 7100 throw new IllegalStateException("focus search returned a view " 7101 + "that wasn't able to take focus!"); 7102 } 7103 } 7104 return; 7105 7106 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 7107 InputMethodManager imm = getInputMethodManager(); 7108 if (imm != null && imm.isActive(this)) { 7109 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7110 } 7111 return; 7112 } 7113 } 7114 7115 ViewRootImpl viewRootImpl = getViewRootImpl(); 7116 if (viewRootImpl != null) { 7117 long eventTime = SystemClock.uptimeMillis(); 7118 viewRootImpl.dispatchKeyFromIme( 7119 new KeyEvent(eventTime, eventTime, 7120 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 7121 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7122 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7123 | KeyEvent.FLAG_EDITOR_ACTION)); 7124 viewRootImpl.dispatchKeyFromIme( 7125 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 7126 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 7127 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 7128 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 7129 | KeyEvent.FLAG_EDITOR_ACTION)); 7130 } 7131 } 7132 7133 /** 7134 * Set the private content type of the text, which is the 7135 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 7136 * field that will be filled in when creating an input connection. 7137 * 7138 * @see #getPrivateImeOptions() 7139 * @see EditorInfo#privateImeOptions 7140 * @attr ref android.R.styleable#TextView_privateImeOptions 7141 */ setPrivateImeOptions(String type)7142 public void setPrivateImeOptions(String type) { 7143 createEditorIfNeeded(); 7144 mEditor.createInputContentTypeIfNeeded(); 7145 mEditor.mInputContentType.privateImeOptions = type; 7146 } 7147 7148 /** 7149 * Get the private type of the content. 7150 * 7151 * @see #setPrivateImeOptions(String) 7152 * @see EditorInfo#privateImeOptions 7153 */ 7154 @InspectableProperty getPrivateImeOptions()7155 public String getPrivateImeOptions() { 7156 return mEditor != null && mEditor.mInputContentType != null 7157 ? mEditor.mInputContentType.privateImeOptions : null; 7158 } 7159 7160 /** 7161 * Set the extra input data of the text, which is the 7162 * {@link EditorInfo#extras TextBoxAttribute.extras} 7163 * Bundle that will be filled in when creating an input connection. The 7164 * given integer is the resource identifier of an XML resource holding an 7165 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 7166 * 7167 * @see #getInputExtras(boolean) 7168 * @see EditorInfo#extras 7169 * @attr ref android.R.styleable#TextView_editorExtras 7170 */ setInputExtras(@mlRes int xmlResId)7171 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 7172 createEditorIfNeeded(); 7173 XmlResourceParser parser = getResources().getXml(xmlResId); 7174 mEditor.createInputContentTypeIfNeeded(); 7175 mEditor.mInputContentType.extras = new Bundle(); 7176 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 7177 } 7178 7179 /** 7180 * Retrieve the input extras currently associated with the text view, which 7181 * can be viewed as well as modified. 7182 * 7183 * @param create If true, the extras will be created if they don't already 7184 * exist. Otherwise, null will be returned if none have been created. 7185 * @see #setInputExtras(int) 7186 * @see EditorInfo#extras 7187 * @attr ref android.R.styleable#TextView_editorExtras 7188 */ getInputExtras(boolean create)7189 public Bundle getInputExtras(boolean create) { 7190 if (mEditor == null && !create) return null; 7191 createEditorIfNeeded(); 7192 if (mEditor.mInputContentType == null) { 7193 if (!create) return null; 7194 mEditor.createInputContentTypeIfNeeded(); 7195 } 7196 if (mEditor.mInputContentType.extras == null) { 7197 if (!create) return null; 7198 mEditor.mInputContentType.extras = new Bundle(); 7199 } 7200 return mEditor.mInputContentType.extras; 7201 } 7202 7203 /** 7204 * Change "hint" locales associated with the text view, which will be reported to an IME with 7205 * {@link EditorInfo#hintLocales} when it has focus. 7206 * 7207 * Starting with Android O, this also causes internationalized listeners to be created (or 7208 * change locale) based on the first locale in the input locale list. 7209 * 7210 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 7211 * call {@link InputMethodManager#restartInput(View)}.</p> 7212 * @param hintLocales List of the languages that the user is supposed to switch to no matter 7213 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 7214 * @see #getImeHintLocales() 7215 * @see android.view.inputmethod.EditorInfo#hintLocales 7216 */ setImeHintLocales(@ullable LocaleList hintLocales)7217 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 7218 createEditorIfNeeded(); 7219 mEditor.createInputContentTypeIfNeeded(); 7220 mEditor.mInputContentType.imeHintLocales = hintLocales; 7221 if (mUseInternationalizedInput) { 7222 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 7223 } 7224 } 7225 7226 /** 7227 * @return The current languages list "hint". {@code null} when no "hint" is available. 7228 * @see #setImeHintLocales(LocaleList) 7229 * @see android.view.inputmethod.EditorInfo#hintLocales 7230 */ 7231 @Nullable getImeHintLocales()7232 public LocaleList getImeHintLocales() { 7233 if (mEditor == null) { 7234 return null; 7235 } 7236 if (mEditor.mInputContentType == null) { 7237 return null; 7238 } 7239 return mEditor.mInputContentType.imeHintLocales; 7240 } 7241 7242 /** 7243 * Returns the error message that was set to be displayed with 7244 * {@link #setError}, or <code>null</code> if no error was set 7245 * or if it the error was cleared by the widget after user input. 7246 */ getError()7247 public CharSequence getError() { 7248 return mEditor == null ? null : mEditor.mError; 7249 } 7250 7251 /** 7252 * Sets the right-hand compound drawable of the TextView to the "error" 7253 * icon and sets an error message that will be displayed in a popup when 7254 * the TextView has focus. The icon and error message will be reset to 7255 * null when any key events cause changes to the TextView's text. If the 7256 * <code>error</code> is <code>null</code>, the error message and icon 7257 * will be cleared. 7258 */ 7259 @android.view.RemotableViewMethod setError(CharSequence error)7260 public void setError(CharSequence error) { 7261 if (error == null) { 7262 setError(null, null); 7263 } else { 7264 Drawable dr = getContext().getDrawable( 7265 com.android.internal.R.drawable.indicator_input_error); 7266 7267 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 7268 setError(error, dr); 7269 } 7270 } 7271 7272 /** 7273 * Sets the right-hand compound drawable of the TextView to the specified 7274 * icon and sets an error message that will be displayed in a popup when 7275 * the TextView has focus. The icon and error message will be reset to 7276 * null when any key events cause changes to the TextView's text. The 7277 * drawable must already have had {@link Drawable#setBounds} set on it. 7278 * If the <code>error</code> is <code>null</code>, the error message will 7279 * be cleared (and you should provide a <code>null</code> icon as well). 7280 */ setError(CharSequence error, Drawable icon)7281 public void setError(CharSequence error, Drawable icon) { 7282 createEditorIfNeeded(); 7283 mEditor.setError(error, icon); 7284 notifyViewAccessibilityStateChangedIfNeeded( 7285 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7286 } 7287 7288 @Override setFrame(int l, int t, int r, int b)7289 protected boolean setFrame(int l, int t, int r, int b) { 7290 boolean result = super.setFrame(l, t, r, b); 7291 7292 if (mEditor != null) mEditor.setFrame(); 7293 7294 restartMarqueeIfNeeded(); 7295 7296 return result; 7297 } 7298 restartMarqueeIfNeeded()7299 private void restartMarqueeIfNeeded() { 7300 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7301 mRestartMarquee = false; 7302 startMarquee(); 7303 } 7304 } 7305 7306 /** 7307 * Sets the list of input filters that will be used if the buffer is 7308 * Editable. Has no effect otherwise. 7309 * 7310 * @attr ref android.R.styleable#TextView_maxLength 7311 */ setFilters(InputFilter[] filters)7312 public void setFilters(InputFilter[] filters) { 7313 if (filters == null) { 7314 throw new IllegalArgumentException(); 7315 } 7316 7317 mFilters = filters; 7318 7319 if (mText instanceof Editable) { 7320 setFilters((Editable) mText, filters); 7321 } 7322 } 7323 7324 /** 7325 * Sets the list of input filters on the specified Editable, 7326 * and includes mInput in the list if it is an InputFilter. 7327 */ setFilters(Editable e, InputFilter[] filters)7328 private void setFilters(Editable e, InputFilter[] filters) { 7329 if (mEditor != null) { 7330 final boolean undoFilter = mEditor.mUndoInputFilter != null; 7331 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 7332 int num = 0; 7333 if (undoFilter) num++; 7334 if (keyFilter) num++; 7335 if (num > 0) { 7336 InputFilter[] nf = new InputFilter[filters.length + num]; 7337 7338 System.arraycopy(filters, 0, nf, 0, filters.length); 7339 num = 0; 7340 if (undoFilter) { 7341 nf[filters.length] = mEditor.mUndoInputFilter; 7342 num++; 7343 } 7344 if (keyFilter) { 7345 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 7346 } 7347 7348 e.setFilters(nf); 7349 return; 7350 } 7351 } 7352 e.setFilters(filters); 7353 } 7354 7355 /** 7356 * Returns the current list of input filters. 7357 * 7358 * @attr ref android.R.styleable#TextView_maxLength 7359 */ getFilters()7360 public InputFilter[] getFilters() { 7361 return mFilters; 7362 } 7363 7364 ///////////////////////////////////////////////////////////////////////// 7365 getBoxHeight(Layout l)7366 private int getBoxHeight(Layout l) { 7367 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 7368 int padding = (l == mHintLayout) 7369 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 7370 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 7371 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 7372 } 7373 7374 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)7375 int getVerticalOffset(boolean forceNormal) { 7376 int voffset = 0; 7377 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7378 7379 Layout l = mLayout; 7380 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7381 l = mHintLayout; 7382 } 7383 7384 if (gravity != Gravity.TOP) { 7385 int boxht = getBoxHeight(l); 7386 int textht = l.getHeight(); 7387 7388 if (textht < boxht) { 7389 if (gravity == Gravity.BOTTOM) { 7390 voffset = boxht - textht; 7391 } else { // (gravity == Gravity.CENTER_VERTICAL) 7392 voffset = (boxht - textht) >> 1; 7393 } 7394 } 7395 } 7396 return voffset; 7397 } 7398 getBottomVerticalOffset(boolean forceNormal)7399 private int getBottomVerticalOffset(boolean forceNormal) { 7400 int voffset = 0; 7401 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 7402 7403 Layout l = mLayout; 7404 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 7405 l = mHintLayout; 7406 } 7407 7408 if (gravity != Gravity.BOTTOM) { 7409 int boxht = getBoxHeight(l); 7410 int textht = l.getHeight(); 7411 7412 if (textht < boxht) { 7413 if (gravity == Gravity.TOP) { 7414 voffset = boxht - textht; 7415 } else { // (gravity == Gravity.CENTER_VERTICAL) 7416 voffset = (boxht - textht) >> 1; 7417 } 7418 } 7419 } 7420 return voffset; 7421 } 7422 invalidateCursorPath()7423 void invalidateCursorPath() { 7424 if (mHighlightPathBogus) { 7425 invalidateCursor(); 7426 } else { 7427 final int horizontalPadding = getCompoundPaddingLeft(); 7428 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7429 7430 if (mEditor.mDrawableForCursor == null) { 7431 synchronized (TEMP_RECTF) { 7432 /* 7433 * The reason for this concern about the thickness of the 7434 * cursor and doing the floor/ceil on the coordinates is that 7435 * some EditTexts (notably textfields in the Browser) have 7436 * anti-aliased text where not all the characters are 7437 * necessarily at integer-multiple locations. This should 7438 * make sure the entire cursor gets invalidated instead of 7439 * sometimes missing half a pixel. 7440 */ 7441 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 7442 if (thick < 1.0f) { 7443 thick = 1.0f; 7444 } 7445 7446 thick /= 2.0f; 7447 7448 // mHighlightPath is guaranteed to be non null at that point. 7449 mHighlightPath.computeBounds(TEMP_RECTF, false); 7450 7451 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 7452 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 7453 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 7454 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 7455 } 7456 } else { 7457 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7458 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 7459 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 7460 } 7461 } 7462 } 7463 invalidateCursor()7464 void invalidateCursor() { 7465 int where = getSelectionEnd(); 7466 7467 invalidateCursor(where, where, where); 7468 } 7469 invalidateCursor(int a, int b, int c)7470 private void invalidateCursor(int a, int b, int c) { 7471 if (a >= 0 || b >= 0 || c >= 0) { 7472 int start = Math.min(Math.min(a, b), c); 7473 int end = Math.max(Math.max(a, b), c); 7474 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 7475 } 7476 } 7477 7478 /** 7479 * Invalidates the region of text enclosed between the start and end text offsets. 7480 */ invalidateRegion(int start, int end, boolean invalidateCursor)7481 void invalidateRegion(int start, int end, boolean invalidateCursor) { 7482 if (mLayout == null) { 7483 invalidate(); 7484 } else { 7485 int lineStart = mLayout.getLineForOffset(start); 7486 int top = mLayout.getLineTop(lineStart); 7487 7488 // This is ridiculous, but the descent from the line above 7489 // can hang down into the line we really want to redraw, 7490 // so we have to invalidate part of the line above to make 7491 // sure everything that needs to be redrawn really is. 7492 // (But not the whole line above, because that would cause 7493 // the same problem with the descenders on the line above it!) 7494 if (lineStart > 0) { 7495 top -= mLayout.getLineDescent(lineStart - 1); 7496 } 7497 7498 int lineEnd; 7499 7500 if (start == end) { 7501 lineEnd = lineStart; 7502 } else { 7503 lineEnd = mLayout.getLineForOffset(end); 7504 } 7505 7506 int bottom = mLayout.getLineBottom(lineEnd); 7507 7508 // mEditor can be null in case selection is set programmatically. 7509 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 7510 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 7511 top = Math.min(top, bounds.top); 7512 bottom = Math.max(bottom, bounds.bottom); 7513 } 7514 7515 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7516 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 7517 7518 int left, right; 7519 if (lineStart == lineEnd && !invalidateCursor) { 7520 left = (int) mLayout.getPrimaryHorizontal(start); 7521 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 7522 left += compoundPaddingLeft; 7523 right += compoundPaddingLeft; 7524 } else { 7525 // Rectangle bounding box when the region spans several lines 7526 left = compoundPaddingLeft; 7527 right = getWidth() - getCompoundPaddingRight(); 7528 } 7529 7530 invalidate(mScrollX + left, verticalPadding + top, 7531 mScrollX + right, verticalPadding + bottom); 7532 } 7533 } 7534 registerForPreDraw()7535 private void registerForPreDraw() { 7536 if (!mPreDrawRegistered) { 7537 getViewTreeObserver().addOnPreDrawListener(this); 7538 mPreDrawRegistered = true; 7539 } 7540 } 7541 unregisterForPreDraw()7542 private void unregisterForPreDraw() { 7543 getViewTreeObserver().removeOnPreDrawListener(this); 7544 mPreDrawRegistered = false; 7545 mPreDrawListenerDetached = false; 7546 } 7547 7548 /** 7549 * {@inheritDoc} 7550 */ 7551 @Override onPreDraw()7552 public boolean onPreDraw() { 7553 if (mLayout == null) { 7554 assumeLayout(); 7555 } 7556 7557 if (mMovement != null) { 7558 /* This code also provides auto-scrolling when a cursor is moved using a 7559 * CursorController (insertion point or selection limits). 7560 * For selection, ensure start or end is visible depending on controller's state. 7561 */ 7562 int curs = getSelectionEnd(); 7563 // Do not create the controller if it is not already created. 7564 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 7565 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 7566 curs = getSelectionStart(); 7567 } 7568 7569 /* 7570 * TODO: This should really only keep the end in view if 7571 * it already was before the text changed. I'm not sure 7572 * of a good way to tell from here if it was. 7573 */ 7574 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7575 curs = mText.length(); 7576 } 7577 7578 if (curs >= 0) { 7579 bringPointIntoView(curs); 7580 } 7581 } else { 7582 bringTextIntoView(); 7583 } 7584 7585 // This has to be checked here since: 7586 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 7587 // a screen rotation) since layout is not yet initialized at that point. 7588 if (mEditor != null && mEditor.mCreatedWithASelection) { 7589 mEditor.refreshTextActionMode(); 7590 mEditor.mCreatedWithASelection = false; 7591 } 7592 7593 unregisterForPreDraw(); 7594 7595 return true; 7596 } 7597 7598 @Override onAttachedToWindow()7599 protected void onAttachedToWindow() { 7600 super.onAttachedToWindow(); 7601 7602 if (mEditor != null) mEditor.onAttachedToWindow(); 7603 7604 if (mPreDrawListenerDetached) { 7605 getViewTreeObserver().addOnPreDrawListener(this); 7606 mPreDrawListenerDetached = false; 7607 } 7608 } 7609 7610 /** @hide */ 7611 @Override onDetachedFromWindowInternal()7612 protected void onDetachedFromWindowInternal() { 7613 if (mPreDrawRegistered) { 7614 getViewTreeObserver().removeOnPreDrawListener(this); 7615 mPreDrawListenerDetached = true; 7616 } 7617 7618 resetResolvedDrawables(); 7619 7620 if (mEditor != null) mEditor.onDetachedFromWindow(); 7621 7622 super.onDetachedFromWindowInternal(); 7623 } 7624 7625 @Override onScreenStateChanged(int screenState)7626 public void onScreenStateChanged(int screenState) { 7627 super.onScreenStateChanged(screenState); 7628 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 7629 } 7630 7631 @Override isPaddingOffsetRequired()7632 protected boolean isPaddingOffsetRequired() { 7633 return mShadowRadius != 0 || mDrawables != null; 7634 } 7635 7636 @Override getLeftPaddingOffset()7637 protected int getLeftPaddingOffset() { 7638 return getCompoundPaddingLeft() - mPaddingLeft 7639 + (int) Math.min(0, mShadowDx - mShadowRadius); 7640 } 7641 7642 @Override getTopPaddingOffset()7643 protected int getTopPaddingOffset() { 7644 return (int) Math.min(0, mShadowDy - mShadowRadius); 7645 } 7646 7647 @Override getBottomPaddingOffset()7648 protected int getBottomPaddingOffset() { 7649 return (int) Math.max(0, mShadowDy + mShadowRadius); 7650 } 7651 7652 @Override getRightPaddingOffset()7653 protected int getRightPaddingOffset() { 7654 return -(getCompoundPaddingRight() - mPaddingRight) 7655 + (int) Math.max(0, mShadowDx + mShadowRadius); 7656 } 7657 7658 @Override verifyDrawable(@onNull Drawable who)7659 protected boolean verifyDrawable(@NonNull Drawable who) { 7660 final boolean verified = super.verifyDrawable(who); 7661 if (!verified && mDrawables != null) { 7662 for (Drawable dr : mDrawables.mShowing) { 7663 if (who == dr) { 7664 return true; 7665 } 7666 } 7667 } 7668 return verified; 7669 } 7670 7671 @Override jumpDrawablesToCurrentState()7672 public void jumpDrawablesToCurrentState() { 7673 super.jumpDrawablesToCurrentState(); 7674 if (mDrawables != null) { 7675 for (Drawable dr : mDrawables.mShowing) { 7676 if (dr != null) { 7677 dr.jumpToCurrentState(); 7678 } 7679 } 7680 } 7681 } 7682 7683 @Override invalidateDrawable(@onNull Drawable drawable)7684 public void invalidateDrawable(@NonNull Drawable drawable) { 7685 boolean handled = false; 7686 7687 if (verifyDrawable(drawable)) { 7688 final Rect dirty = drawable.getBounds(); 7689 int scrollX = mScrollX; 7690 int scrollY = mScrollY; 7691 7692 // IMPORTANT: The coordinates below are based on the coordinates computed 7693 // for each compound drawable in onDraw(). Make sure to update each section 7694 // accordingly. 7695 final TextView.Drawables drawables = mDrawables; 7696 if (drawables != null) { 7697 if (drawable == drawables.mShowing[Drawables.LEFT]) { 7698 final int compoundPaddingTop = getCompoundPaddingTop(); 7699 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7700 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7701 7702 scrollX += mPaddingLeft; 7703 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 7704 handled = true; 7705 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 7706 final int compoundPaddingTop = getCompoundPaddingTop(); 7707 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7708 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7709 7710 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 7711 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 7712 handled = true; 7713 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 7714 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7715 final int compoundPaddingRight = getCompoundPaddingRight(); 7716 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7717 7718 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 7719 scrollY += mPaddingTop; 7720 handled = true; 7721 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 7722 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7723 final int compoundPaddingRight = getCompoundPaddingRight(); 7724 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 7725 7726 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 7727 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 7728 handled = true; 7729 } 7730 } 7731 7732 if (handled) { 7733 invalidate(dirty.left + scrollX, dirty.top + scrollY, 7734 dirty.right + scrollX, dirty.bottom + scrollY); 7735 } 7736 } 7737 7738 if (!handled) { 7739 super.invalidateDrawable(drawable); 7740 } 7741 } 7742 7743 @Override hasOverlappingRendering()7744 public boolean hasOverlappingRendering() { 7745 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 7746 return ((getBackground() != null && getBackground().getCurrent() != null) 7747 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 7748 || mShadowColor != 0); 7749 } 7750 7751 /** 7752 * 7753 * Returns the state of the {@code textIsSelectable} flag (See 7754 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 7755 * to allow users to select and copy text in a non-editable TextView, the content of an 7756 * {@link EditText} can always be selected, independently of the value of this flag. 7757 * <p> 7758 * 7759 * @return True if the text displayed in this TextView can be selected by the user. 7760 * 7761 * @attr ref android.R.styleable#TextView_textIsSelectable 7762 */ 7763 @InspectableProperty(name = "textIsSelectable") isTextSelectable()7764 public boolean isTextSelectable() { 7765 return mEditor == null ? false : mEditor.mTextIsSelectable; 7766 } 7767 7768 /** 7769 * Sets whether the content of this view is selectable by the user. The default is 7770 * {@code false}, meaning that the content is not selectable. 7771 * <p> 7772 * When you use a TextView to display a useful piece of information to the user (such as a 7773 * contact's address), make it selectable, so that the user can select and copy its 7774 * content. You can also use set the XML attribute 7775 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 7776 * <p> 7777 * When you call this method to set the value of {@code textIsSelectable}, it sets 7778 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 7779 * and {@code longClickable} to the same value. These flags correspond to the attributes 7780 * {@link android.R.styleable#View_focusable android:focusable}, 7781 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 7782 * {@link android.R.styleable#View_clickable android:clickable}, and 7783 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 7784 * flags to a state you had set previously, call one or more of the following methods: 7785 * {@link #setFocusable(boolean) setFocusable()}, 7786 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 7787 * {@link #setClickable(boolean) setClickable()} or 7788 * {@link #setLongClickable(boolean) setLongClickable()}. 7789 * 7790 * @param selectable Whether the content of this TextView should be selectable. 7791 */ setTextIsSelectable(boolean selectable)7792 public void setTextIsSelectable(boolean selectable) { 7793 if (!selectable && mEditor == null) return; // false is default value with no edit data 7794 7795 createEditorIfNeeded(); 7796 if (mEditor.mTextIsSelectable == selectable) return; 7797 7798 mEditor.mTextIsSelectable = selectable; 7799 setFocusableInTouchMode(selectable); 7800 setFocusable(FOCUSABLE_AUTO); 7801 setClickable(selectable); 7802 setLongClickable(selectable); 7803 7804 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 7805 7806 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 7807 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 7808 7809 // Called by setText above, but safer in case of future code changes 7810 mEditor.prepareCursorControllers(); 7811 } 7812 7813 @Override onCreateDrawableState(int extraSpace)7814 protected int[] onCreateDrawableState(int extraSpace) { 7815 final int[] drawableState; 7816 7817 if (mSingleLine) { 7818 drawableState = super.onCreateDrawableState(extraSpace); 7819 } else { 7820 drawableState = super.onCreateDrawableState(extraSpace + 1); 7821 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 7822 } 7823 7824 if (isTextSelectable()) { 7825 // Disable pressed state, which was introduced when TextView was made clickable. 7826 // Prevents text color change. 7827 // setClickable(false) would have a similar effect, but it also disables focus changes 7828 // and long press actions, which are both needed by text selection. 7829 final int length = drawableState.length; 7830 for (int i = 0; i < length; i++) { 7831 if (drawableState[i] == R.attr.state_pressed) { 7832 final int[] nonPressedState = new int[length - 1]; 7833 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 7834 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 7835 return nonPressedState; 7836 } 7837 } 7838 } 7839 7840 return drawableState; 7841 } 7842 7843 @UnsupportedAppUsage getUpdatedHighlightPath()7844 private Path getUpdatedHighlightPath() { 7845 Path highlight = null; 7846 Paint highlightPaint = mHighlightPaint; 7847 7848 final int selStart = getSelectionStart(); 7849 final int selEnd = getSelectionEnd(); 7850 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 7851 if (selStart == selEnd) { 7852 if (mEditor != null && mEditor.shouldRenderCursor()) { 7853 if (mHighlightPathBogus) { 7854 if (mHighlightPath == null) mHighlightPath = new Path(); 7855 mHighlightPath.reset(); 7856 mLayout.getCursorPath(selStart, mHighlightPath, mText); 7857 mEditor.updateCursorPosition(); 7858 mHighlightPathBogus = false; 7859 } 7860 7861 // XXX should pass to skin instead of drawing directly 7862 highlightPaint.setColor(mCurTextColor); 7863 highlightPaint.setStyle(Paint.Style.STROKE); 7864 highlight = mHighlightPath; 7865 } 7866 } else { 7867 if (mHighlightPathBogus) { 7868 if (mHighlightPath == null) mHighlightPath = new Path(); 7869 mHighlightPath.reset(); 7870 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 7871 mHighlightPathBogus = false; 7872 } 7873 7874 // XXX should pass to skin instead of drawing directly 7875 highlightPaint.setColor(mHighlightColor); 7876 highlightPaint.setStyle(Paint.Style.FILL); 7877 7878 highlight = mHighlightPath; 7879 } 7880 } 7881 return highlight; 7882 } 7883 7884 /** 7885 * @hide 7886 */ getHorizontalOffsetForDrawables()7887 public int getHorizontalOffsetForDrawables() { 7888 return 0; 7889 } 7890 7891 @Override onDraw(Canvas canvas)7892 protected void onDraw(Canvas canvas) { 7893 restartMarqueeIfNeeded(); 7894 7895 // Draw the background for this view 7896 super.onDraw(canvas); 7897 7898 final int compoundPaddingLeft = getCompoundPaddingLeft(); 7899 final int compoundPaddingTop = getCompoundPaddingTop(); 7900 final int compoundPaddingRight = getCompoundPaddingRight(); 7901 final int compoundPaddingBottom = getCompoundPaddingBottom(); 7902 final int scrollX = mScrollX; 7903 final int scrollY = mScrollY; 7904 final int right = mRight; 7905 final int left = mLeft; 7906 final int bottom = mBottom; 7907 final int top = mTop; 7908 final boolean isLayoutRtl = isLayoutRtl(); 7909 final int offset = getHorizontalOffsetForDrawables(); 7910 final int leftOffset = isLayoutRtl ? 0 : offset; 7911 final int rightOffset = isLayoutRtl ? offset : 0; 7912 7913 final Drawables dr = mDrawables; 7914 if (dr != null) { 7915 /* 7916 * Compound, not extended, because the icon is not clipped 7917 * if the text height is smaller. 7918 */ 7919 7920 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 7921 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 7922 7923 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7924 // Make sure to update invalidateDrawable() when changing this code. 7925 if (dr.mShowing[Drawables.LEFT] != null) { 7926 canvas.save(); 7927 canvas.translate(scrollX + mPaddingLeft + leftOffset, 7928 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 7929 dr.mShowing[Drawables.LEFT].draw(canvas); 7930 canvas.restore(); 7931 } 7932 7933 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7934 // Make sure to update invalidateDrawable() when changing this code. 7935 if (dr.mShowing[Drawables.RIGHT] != null) { 7936 canvas.save(); 7937 canvas.translate(scrollX + right - left - mPaddingRight 7938 - dr.mDrawableSizeRight - rightOffset, 7939 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 7940 dr.mShowing[Drawables.RIGHT].draw(canvas); 7941 canvas.restore(); 7942 } 7943 7944 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7945 // Make sure to update invalidateDrawable() when changing this code. 7946 if (dr.mShowing[Drawables.TOP] != null) { 7947 canvas.save(); 7948 canvas.translate(scrollX + compoundPaddingLeft 7949 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 7950 dr.mShowing[Drawables.TOP].draw(canvas); 7951 canvas.restore(); 7952 } 7953 7954 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 7955 // Make sure to update invalidateDrawable() when changing this code. 7956 if (dr.mShowing[Drawables.BOTTOM] != null) { 7957 canvas.save(); 7958 canvas.translate(scrollX + compoundPaddingLeft 7959 + (hspace - dr.mDrawableWidthBottom) / 2, 7960 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 7961 dr.mShowing[Drawables.BOTTOM].draw(canvas); 7962 canvas.restore(); 7963 } 7964 } 7965 7966 int color = mCurTextColor; 7967 7968 if (mLayout == null) { 7969 assumeLayout(); 7970 } 7971 7972 Layout layout = mLayout; 7973 7974 if (mHint != null && mText.length() == 0) { 7975 if (mHintTextColor != null) { 7976 color = mCurHintTextColor; 7977 } 7978 7979 layout = mHintLayout; 7980 } 7981 7982 mTextPaint.setColor(color); 7983 mTextPaint.drawableState = getDrawableState(); 7984 7985 canvas.save(); 7986 /* Would be faster if we didn't have to do this. Can we chop the 7987 (displayable) text so that we don't need to do this ever? 7988 */ 7989 7990 int extendedPaddingTop = getExtendedPaddingTop(); 7991 int extendedPaddingBottom = getExtendedPaddingBottom(); 7992 7993 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 7994 final int maxScrollY = mLayout.getHeight() - vspace; 7995 7996 float clipLeft = compoundPaddingLeft + scrollX; 7997 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 7998 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 7999 float clipBottom = bottom - top + scrollY 8000 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 8001 8002 if (mShadowRadius != 0) { 8003 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 8004 clipRight += Math.max(0, mShadowDx + mShadowRadius); 8005 8006 clipTop += Math.min(0, mShadowDy - mShadowRadius); 8007 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 8008 } 8009 8010 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 8011 8012 int voffsetText = 0; 8013 int voffsetCursor = 0; 8014 8015 // translate in by our padding 8016 /* shortcircuit calling getVerticaOffset() */ 8017 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8018 voffsetText = getVerticalOffset(false); 8019 voffsetCursor = getVerticalOffset(true); 8020 } 8021 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 8022 8023 final int layoutDirection = getLayoutDirection(); 8024 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8025 if (isMarqueeFadeEnabled()) { 8026 if (!mSingleLine && getLineCount() == 1 && canMarquee() 8027 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 8028 final int width = mRight - mLeft; 8029 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 8030 final float dx = mLayout.getLineRight(0) - (width - padding); 8031 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8032 } 8033 8034 if (mMarquee != null && mMarquee.isRunning()) { 8035 final float dx = -mMarquee.getScroll(); 8036 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8037 } 8038 } 8039 8040 final int cursorOffsetVertical = voffsetCursor - voffsetText; 8041 8042 Path highlight = getUpdatedHighlightPath(); 8043 if (mEditor != null) { 8044 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 8045 } else { 8046 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8047 } 8048 8049 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 8050 final float dx = mMarquee.getGhostOffset(); 8051 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 8052 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 8053 } 8054 8055 canvas.restore(); 8056 } 8057 8058 @Override getFocusedRect(Rect r)8059 public void getFocusedRect(Rect r) { 8060 if (mLayout == null) { 8061 super.getFocusedRect(r); 8062 return; 8063 } 8064 8065 int selEnd = getSelectionEnd(); 8066 if (selEnd < 0) { 8067 super.getFocusedRect(r); 8068 return; 8069 } 8070 8071 int selStart = getSelectionStart(); 8072 if (selStart < 0 || selStart >= selEnd) { 8073 int line = mLayout.getLineForOffset(selEnd); 8074 r.top = mLayout.getLineTop(line); 8075 r.bottom = mLayout.getLineBottom(line); 8076 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 8077 r.right = r.left + 4; 8078 } else { 8079 int lineStart = mLayout.getLineForOffset(selStart); 8080 int lineEnd = mLayout.getLineForOffset(selEnd); 8081 r.top = mLayout.getLineTop(lineStart); 8082 r.bottom = mLayout.getLineBottom(lineEnd); 8083 if (lineStart == lineEnd) { 8084 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 8085 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 8086 } else { 8087 // Selection extends across multiple lines -- make the focused 8088 // rect cover the entire width. 8089 if (mHighlightPathBogus) { 8090 if (mHighlightPath == null) mHighlightPath = new Path(); 8091 mHighlightPath.reset(); 8092 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8093 mHighlightPathBogus = false; 8094 } 8095 synchronized (TEMP_RECTF) { 8096 mHighlightPath.computeBounds(TEMP_RECTF, true); 8097 r.left = (int) TEMP_RECTF.left - 1; 8098 r.right = (int) TEMP_RECTF.right + 1; 8099 } 8100 } 8101 } 8102 8103 // Adjust for padding and gravity. 8104 int paddingLeft = getCompoundPaddingLeft(); 8105 int paddingTop = getExtendedPaddingTop(); 8106 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8107 paddingTop += getVerticalOffset(false); 8108 } 8109 r.offset(paddingLeft, paddingTop); 8110 int paddingBottom = getExtendedPaddingBottom(); 8111 r.bottom += paddingBottom; 8112 } 8113 8114 /** 8115 * Return the number of lines of text, or 0 if the internal Layout has not 8116 * been built. 8117 */ getLineCount()8118 public int getLineCount() { 8119 return mLayout != null ? mLayout.getLineCount() : 0; 8120 } 8121 8122 /** 8123 * Return the baseline for the specified line (0...getLineCount() - 1) 8124 * If bounds is not null, return the top, left, right, bottom extents 8125 * of the specified line in it. If the internal Layout has not been built, 8126 * return 0 and set bounds to (0, 0, 0, 0) 8127 * @param line which line to examine (0..getLineCount() - 1) 8128 * @param bounds Optional. If not null, it returns the extent of the line 8129 * @return the Y-coordinate of the baseline 8130 */ getLineBounds(int line, Rect bounds)8131 public int getLineBounds(int line, Rect bounds) { 8132 if (mLayout == null) { 8133 if (bounds != null) { 8134 bounds.set(0, 0, 0, 0); 8135 } 8136 return 0; 8137 } else { 8138 int baseline = mLayout.getLineBounds(line, bounds); 8139 8140 int voffset = getExtendedPaddingTop(); 8141 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8142 voffset += getVerticalOffset(true); 8143 } 8144 if (bounds != null) { 8145 bounds.offset(getCompoundPaddingLeft(), voffset); 8146 } 8147 return baseline + voffset; 8148 } 8149 } 8150 8151 @Override getBaseline()8152 public int getBaseline() { 8153 if (mLayout == null) { 8154 return super.getBaseline(); 8155 } 8156 8157 return getBaselineOffset() + mLayout.getLineBaseline(0); 8158 } 8159 getBaselineOffset()8160 int getBaselineOffset() { 8161 int voffset = 0; 8162 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8163 voffset = getVerticalOffset(true); 8164 } 8165 8166 if (isLayoutModeOptical(mParent)) { 8167 voffset -= getOpticalInsets().top; 8168 } 8169 8170 return getExtendedPaddingTop() + voffset; 8171 } 8172 8173 /** 8174 * @hide 8175 */ 8176 @Override getFadeTop(boolean offsetRequired)8177 protected int getFadeTop(boolean offsetRequired) { 8178 if (mLayout == null) return 0; 8179 8180 int voffset = 0; 8181 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8182 voffset = getVerticalOffset(true); 8183 } 8184 8185 if (offsetRequired) voffset += getTopPaddingOffset(); 8186 8187 return getExtendedPaddingTop() + voffset; 8188 } 8189 8190 /** 8191 * @hide 8192 */ 8193 @Override getFadeHeight(boolean offsetRequired)8194 protected int getFadeHeight(boolean offsetRequired) { 8195 return mLayout != null ? mLayout.getHeight() : 0; 8196 } 8197 8198 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)8199 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 8200 if (mSpannable != null && mLinksClickable) { 8201 final float x = event.getX(pointerIndex); 8202 final float y = event.getY(pointerIndex); 8203 final int offset = getOffsetForPosition(x, y); 8204 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 8205 ClickableSpan.class); 8206 if (clickables.length > 0) { 8207 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 8208 } 8209 } 8210 if (isTextSelectable() || isTextEditable()) { 8211 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 8212 } 8213 return super.onResolvePointerIcon(event, pointerIndex); 8214 } 8215 8216 @Override onKeyPreIme(int keyCode, KeyEvent event)8217 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 8218 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 8219 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 8220 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 8221 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 8222 return true; 8223 } 8224 return super.onKeyPreIme(keyCode, event); 8225 } 8226 8227 /** 8228 * @hide 8229 */ handleBackInTextActionModeIfNeeded(KeyEvent event)8230 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 8231 // Do nothing unless mEditor is in text action mode. 8232 if (mEditor == null || mEditor.getTextActionMode() == null) { 8233 return false; 8234 } 8235 8236 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 8237 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8238 if (state != null) { 8239 state.startTracking(event, this); 8240 } 8241 return true; 8242 } else if (event.getAction() == KeyEvent.ACTION_UP) { 8243 KeyEvent.DispatcherState state = getKeyDispatcherState(); 8244 if (state != null) { 8245 state.handleUpEvent(event); 8246 } 8247 if (event.isTracking() && !event.isCanceled()) { 8248 stopTextActionMode(); 8249 return true; 8250 } 8251 } 8252 return false; 8253 } 8254 8255 @Override onKeyDown(int keyCode, KeyEvent event)8256 public boolean onKeyDown(int keyCode, KeyEvent event) { 8257 final int which = doKeyDown(keyCode, event, null); 8258 if (which == KEY_EVENT_NOT_HANDLED) { 8259 return super.onKeyDown(keyCode, event); 8260 } 8261 8262 return true; 8263 } 8264 8265 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)8266 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 8267 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 8268 final int which = doKeyDown(keyCode, down, event); 8269 if (which == KEY_EVENT_NOT_HANDLED) { 8270 // Go through default dispatching. 8271 return super.onKeyMultiple(keyCode, repeatCount, event); 8272 } 8273 if (which == KEY_EVENT_HANDLED) { 8274 // Consumed the whole thing. 8275 return true; 8276 } 8277 8278 repeatCount--; 8279 8280 // We are going to dispatch the remaining events to either the input 8281 // or movement method. To do this, we will just send a repeated stream 8282 // of down and up events until we have done the complete repeatCount. 8283 // It would be nice if those interfaces had an onKeyMultiple() method, 8284 // but adding that is a more complicated change. 8285 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 8286 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 8287 // mEditor and mEditor.mInput are not null from doKeyDown 8288 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8289 while (--repeatCount > 0) { 8290 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 8291 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 8292 } 8293 hideErrorIfUnchanged(); 8294 8295 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 8296 // mMovement is not null from doKeyDown 8297 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8298 while (--repeatCount > 0) { 8299 mMovement.onKeyDown(this, mSpannable, keyCode, down); 8300 mMovement.onKeyUp(this, mSpannable, keyCode, up); 8301 } 8302 } 8303 8304 return true; 8305 } 8306 8307 /** 8308 * Returns true if pressing ENTER in this field advances focus instead 8309 * of inserting the character. This is true mostly in single-line fields, 8310 * but also in mail addresses and subjects which will display on multiple 8311 * lines but where it doesn't make sense to insert newlines. 8312 */ shouldAdvanceFocusOnEnter()8313 private boolean shouldAdvanceFocusOnEnter() { 8314 if (getKeyListener() == null) { 8315 return false; 8316 } 8317 8318 if (mSingleLine) { 8319 return true; 8320 } 8321 8322 if (mEditor != null 8323 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8324 == EditorInfo.TYPE_CLASS_TEXT) { 8325 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8326 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 8327 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 8328 return true; 8329 } 8330 } 8331 8332 return false; 8333 } 8334 isDirectionalNavigationKey(int keyCode)8335 private boolean isDirectionalNavigationKey(int keyCode) { 8336 switch(keyCode) { 8337 case KeyEvent.KEYCODE_DPAD_UP: 8338 case KeyEvent.KEYCODE_DPAD_DOWN: 8339 case KeyEvent.KEYCODE_DPAD_LEFT: 8340 case KeyEvent.KEYCODE_DPAD_RIGHT: 8341 return true; 8342 } 8343 return false; 8344 } 8345 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)8346 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 8347 if (!isEnabled()) { 8348 return KEY_EVENT_NOT_HANDLED; 8349 } 8350 8351 // If this is the initial keydown, we don't want to prevent a movement away from this view. 8352 // While this shouldn't be necessary because any time we're preventing default movement we 8353 // should be restricting the focus to remain within this view, thus we'll also receive 8354 // the key up event, occasionally key up events will get dropped and we don't want to 8355 // prevent the user from traversing out of this on the next key down. 8356 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8357 mPreventDefaultMovement = false; 8358 } 8359 8360 switch (keyCode) { 8361 case KeyEvent.KEYCODE_ENTER: 8362 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8363 if (event.hasNoModifiers()) { 8364 // When mInputContentType is set, we know that we are 8365 // running in a "modern" cupcake environment, so don't need 8366 // to worry about the application trying to capture 8367 // enter key events. 8368 if (mEditor != null && mEditor.mInputContentType != null) { 8369 // If there is an action listener, given them a 8370 // chance to consume the event. 8371 if (mEditor.mInputContentType.onEditorActionListener != null 8372 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8373 this, EditorInfo.IME_NULL, event)) { 8374 mEditor.mInputContentType.enterDown = true; 8375 // We are consuming the enter key for them. 8376 return KEY_EVENT_HANDLED; 8377 } 8378 } 8379 8380 // If our editor should move focus when enter is pressed, or 8381 // this is a generated event from an IME action button, then 8382 // don't let it be inserted into the text. 8383 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8384 || shouldAdvanceFocusOnEnter()) { 8385 if (hasOnClickListeners()) { 8386 return KEY_EVENT_NOT_HANDLED; 8387 } 8388 return KEY_EVENT_HANDLED; 8389 } 8390 } 8391 break; 8392 8393 case KeyEvent.KEYCODE_DPAD_CENTER: 8394 if (event.hasNoModifiers()) { 8395 if (shouldAdvanceFocusOnEnter()) { 8396 return KEY_EVENT_NOT_HANDLED; 8397 } 8398 } 8399 break; 8400 8401 case KeyEvent.KEYCODE_TAB: 8402 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 8403 // Tab is used to move focus. 8404 return KEY_EVENT_NOT_HANDLED; 8405 } 8406 break; 8407 8408 // Has to be done on key down (and not on key up) to correctly be intercepted. 8409 case KeyEvent.KEYCODE_BACK: 8410 if (mEditor != null && mEditor.getTextActionMode() != null) { 8411 stopTextActionMode(); 8412 return KEY_EVENT_HANDLED; 8413 } 8414 break; 8415 8416 case KeyEvent.KEYCODE_CUT: 8417 if (event.hasNoModifiers() && canCut()) { 8418 if (onTextContextMenuItem(ID_CUT)) { 8419 return KEY_EVENT_HANDLED; 8420 } 8421 } 8422 break; 8423 8424 case KeyEvent.KEYCODE_COPY: 8425 if (event.hasNoModifiers() && canCopy()) { 8426 if (onTextContextMenuItem(ID_COPY)) { 8427 return KEY_EVENT_HANDLED; 8428 } 8429 } 8430 break; 8431 8432 case KeyEvent.KEYCODE_PASTE: 8433 if (event.hasNoModifiers() && canPaste()) { 8434 if (onTextContextMenuItem(ID_PASTE)) { 8435 return KEY_EVENT_HANDLED; 8436 } 8437 } 8438 break; 8439 8440 case KeyEvent.KEYCODE_FORWARD_DEL: 8441 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 8442 if (onTextContextMenuItem(ID_CUT)) { 8443 return KEY_EVENT_HANDLED; 8444 } 8445 } 8446 break; 8447 8448 case KeyEvent.KEYCODE_INSERT: 8449 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 8450 if (onTextContextMenuItem(ID_COPY)) { 8451 return KEY_EVENT_HANDLED; 8452 } 8453 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 8454 if (onTextContextMenuItem(ID_PASTE)) { 8455 return KEY_EVENT_HANDLED; 8456 } 8457 } 8458 break; 8459 } 8460 8461 if (mEditor != null && mEditor.mKeyListener != null) { 8462 boolean doDown = true; 8463 if (otherEvent != null) { 8464 try { 8465 beginBatchEdit(); 8466 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 8467 otherEvent); 8468 hideErrorIfUnchanged(); 8469 doDown = false; 8470 if (handled) { 8471 return KEY_EVENT_HANDLED; 8472 } 8473 } catch (AbstractMethodError e) { 8474 // onKeyOther was added after 1.0, so if it isn't 8475 // implemented we need to try to dispatch as a regular down. 8476 } finally { 8477 endBatchEdit(); 8478 } 8479 } 8480 8481 if (doDown) { 8482 beginBatchEdit(); 8483 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 8484 keyCode, event); 8485 endBatchEdit(); 8486 hideErrorIfUnchanged(); 8487 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 8488 } 8489 } 8490 8491 // bug 650865: sometimes we get a key event before a layout. 8492 // don't try to move around if we don't know the layout. 8493 8494 if (mMovement != null && mLayout != null) { 8495 boolean doDown = true; 8496 if (otherEvent != null) { 8497 try { 8498 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 8499 doDown = false; 8500 if (handled) { 8501 return KEY_EVENT_HANDLED; 8502 } 8503 } catch (AbstractMethodError e) { 8504 // onKeyOther was added after 1.0, so if it isn't 8505 // implemented we need to try to dispatch as a regular down. 8506 } 8507 } 8508 if (doDown) { 8509 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 8510 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 8511 mPreventDefaultMovement = true; 8512 } 8513 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 8514 } 8515 } 8516 // Consume arrows from keyboard devices to prevent focus leaving the editor. 8517 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 8518 // to move focus with arrows. 8519 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 8520 && isDirectionalNavigationKey(keyCode)) { 8521 return KEY_EVENT_HANDLED; 8522 } 8523 } 8524 8525 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 8526 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 8527 } 8528 8529 /** 8530 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 8531 * can be recorded. 8532 * @hide 8533 */ resetErrorChangedFlag()8534 public void resetErrorChangedFlag() { 8535 /* 8536 * Keep track of what the error was before doing the input 8537 * so that if an input filter changed the error, we leave 8538 * that error showing. Otherwise, we take down whatever 8539 * error was showing when the user types something. 8540 */ 8541 if (mEditor != null) mEditor.mErrorWasChanged = false; 8542 } 8543 8544 /** 8545 * @hide 8546 */ hideErrorIfUnchanged()8547 public void hideErrorIfUnchanged() { 8548 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 8549 setError(null, null); 8550 } 8551 } 8552 8553 @Override onKeyUp(int keyCode, KeyEvent event)8554 public boolean onKeyUp(int keyCode, KeyEvent event) { 8555 if (!isEnabled()) { 8556 return super.onKeyUp(keyCode, event); 8557 } 8558 8559 if (!KeyEvent.isModifierKey(keyCode)) { 8560 mPreventDefaultMovement = false; 8561 } 8562 8563 switch (keyCode) { 8564 case KeyEvent.KEYCODE_DPAD_CENTER: 8565 if (event.hasNoModifiers()) { 8566 /* 8567 * If there is a click listener, just call through to 8568 * super, which will invoke it. 8569 * 8570 * If there isn't a click listener, try to show the soft 8571 * input method. (It will also 8572 * call performClick(), but that won't do anything in 8573 * this case.) 8574 */ 8575 if (!hasOnClickListeners()) { 8576 if (mMovement != null && mText instanceof Editable 8577 && mLayout != null && onCheckIsTextEditor()) { 8578 InputMethodManager imm = getInputMethodManager(); 8579 viewClicked(imm); 8580 if (imm != null && getShowSoftInputOnFocus()) { 8581 imm.showSoftInput(this, 0); 8582 } 8583 } 8584 } 8585 } 8586 return super.onKeyUp(keyCode, event); 8587 8588 case KeyEvent.KEYCODE_ENTER: 8589 case KeyEvent.KEYCODE_NUMPAD_ENTER: 8590 if (event.hasNoModifiers()) { 8591 if (mEditor != null && mEditor.mInputContentType != null 8592 && mEditor.mInputContentType.onEditorActionListener != null 8593 && mEditor.mInputContentType.enterDown) { 8594 mEditor.mInputContentType.enterDown = false; 8595 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 8596 this, EditorInfo.IME_NULL, event)) { 8597 return true; 8598 } 8599 } 8600 8601 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 8602 || shouldAdvanceFocusOnEnter()) { 8603 /* 8604 * If there is a click listener, just call through to 8605 * super, which will invoke it. 8606 * 8607 * If there isn't a click listener, try to advance focus, 8608 * but still call through to super, which will reset the 8609 * pressed state and longpress state. (It will also 8610 * call performClick(), but that won't do anything in 8611 * this case.) 8612 */ 8613 if (!hasOnClickListeners()) { 8614 View v = focusSearch(FOCUS_DOWN); 8615 8616 if (v != null) { 8617 if (!v.requestFocus(FOCUS_DOWN)) { 8618 throw new IllegalStateException("focus search returned a view " 8619 + "that wasn't able to take focus!"); 8620 } 8621 8622 /* 8623 * Return true because we handled the key; super 8624 * will return false because there was no click 8625 * listener. 8626 */ 8627 super.onKeyUp(keyCode, event); 8628 return true; 8629 } else if ((event.getFlags() 8630 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 8631 // No target for next focus, but make sure the IME 8632 // if this came from it. 8633 InputMethodManager imm = getInputMethodManager(); 8634 if (imm != null && imm.isActive(this)) { 8635 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8636 } 8637 } 8638 } 8639 } 8640 return super.onKeyUp(keyCode, event); 8641 } 8642 break; 8643 } 8644 8645 if (mEditor != null && mEditor.mKeyListener != null) { 8646 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 8647 return true; 8648 } 8649 } 8650 8651 if (mMovement != null && mLayout != null) { 8652 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 8653 return true; 8654 } 8655 } 8656 8657 return super.onKeyUp(keyCode, event); 8658 } 8659 8660 @Override onCheckIsTextEditor()8661 public boolean onCheckIsTextEditor() { 8662 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 8663 } 8664 8665 @Override onCreateInputConnection(EditorInfo outAttrs)8666 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 8667 if (onCheckIsTextEditor() && isEnabled()) { 8668 mEditor.createInputMethodStateIfNeeded(); 8669 outAttrs.inputType = getInputType(); 8670 if (mEditor.mInputContentType != null) { 8671 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 8672 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 8673 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 8674 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 8675 outAttrs.extras = mEditor.mInputContentType.extras; 8676 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 8677 } else { 8678 outAttrs.imeOptions = EditorInfo.IME_NULL; 8679 outAttrs.hintLocales = null; 8680 } 8681 if (focusSearch(FOCUS_DOWN) != null) { 8682 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 8683 } 8684 if (focusSearch(FOCUS_UP) != null) { 8685 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 8686 } 8687 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 8688 == EditorInfo.IME_ACTION_UNSPECIFIED) { 8689 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 8690 // An action has not been set, but the enter key will move to 8691 // the next focus, so set the action to that. 8692 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 8693 } else { 8694 // An action has not been set, and there is no focus to move 8695 // to, so let's just supply a "done" action. 8696 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 8697 } 8698 if (!shouldAdvanceFocusOnEnter()) { 8699 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8700 } 8701 } 8702 if (isMultilineInputType(outAttrs.inputType)) { 8703 // Multi-line text editors should always show an enter key. 8704 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 8705 } 8706 outAttrs.hintText = mHint; 8707 outAttrs.targetInputMethodUser = mTextOperationUser; 8708 if (mText instanceof Editable) { 8709 InputConnection ic = new EditableInputConnection(this); 8710 outAttrs.initialSelStart = getSelectionStart(); 8711 outAttrs.initialSelEnd = getSelectionEnd(); 8712 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 8713 outAttrs.setInitialSurroundingText(mText); 8714 return ic; 8715 } 8716 } 8717 return null; 8718 } 8719 8720 /** 8721 * If this TextView contains editable content, extract a portion of it 8722 * based on the information in <var>request</var> in to <var>outText</var>. 8723 * @return Returns true if the text was successfully extracted, else false. 8724 */ extractText(ExtractedTextRequest request, ExtractedText outText)8725 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 8726 createEditorIfNeeded(); 8727 return mEditor.extractText(request, outText); 8728 } 8729 8730 /** 8731 * This is used to remove all style-impacting spans from text before new 8732 * extracted text is being replaced into it, so that we don't have any 8733 * lingering spans applied during the replace. 8734 */ removeParcelableSpans(Spannable spannable, int start, int end)8735 static void removeParcelableSpans(Spannable spannable, int start, int end) { 8736 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 8737 int i = spans.length; 8738 while (i > 0) { 8739 i--; 8740 spannable.removeSpan(spans[i]); 8741 } 8742 } 8743 8744 /** 8745 * Apply to this text view the given extracted text, as previously 8746 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 8747 */ setExtractedText(ExtractedText text)8748 public void setExtractedText(ExtractedText text) { 8749 Editable content = getEditableText(); 8750 if (text.text != null) { 8751 if (content == null) { 8752 setText(text.text, TextView.BufferType.EDITABLE); 8753 } else { 8754 int start = 0; 8755 int end = content.length(); 8756 8757 if (text.partialStartOffset >= 0) { 8758 final int N = content.length(); 8759 start = text.partialStartOffset; 8760 if (start > N) start = N; 8761 end = text.partialEndOffset; 8762 if (end > N) end = N; 8763 } 8764 8765 removeParcelableSpans(content, start, end); 8766 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 8767 if (text.text instanceof Spanned) { 8768 // OK to copy spans only. 8769 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 8770 Object.class, content, start); 8771 } 8772 } else { 8773 content.replace(start, end, text.text); 8774 } 8775 } 8776 } 8777 8778 // Now set the selection position... make sure it is in range, to 8779 // avoid crashes. If this is a partial update, it is possible that 8780 // the underlying text may have changed, causing us problems here. 8781 // Also we just don't want to trust clients to do the right thing. 8782 Spannable sp = (Spannable) getText(); 8783 final int N = sp.length(); 8784 int start = text.selectionStart; 8785 if (start < 0) { 8786 start = 0; 8787 } else if (start > N) { 8788 start = N; 8789 } 8790 int end = text.selectionEnd; 8791 if (end < 0) { 8792 end = 0; 8793 } else if (end > N) { 8794 end = N; 8795 } 8796 Selection.setSelection(sp, start, end); 8797 8798 // Finally, update the selection mode. 8799 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 8800 MetaKeyKeyListener.startSelecting(this, sp); 8801 } else { 8802 MetaKeyKeyListener.stopSelecting(this, sp); 8803 } 8804 8805 setHintInternal(text.hint); 8806 } 8807 8808 /** 8809 * @hide 8810 */ setExtracting(ExtractedTextRequest req)8811 public void setExtracting(ExtractedTextRequest req) { 8812 if (mEditor.mInputMethodState != null) { 8813 mEditor.mInputMethodState.mExtractedTextRequest = req; 8814 } 8815 // This would stop a possible selection mode, but no such mode is started in case 8816 // extracted mode will start. Some text is selected though, and will trigger an action mode 8817 // in the extracted view. 8818 mEditor.hideCursorAndSpanControllers(); 8819 stopTextActionMode(); 8820 if (mEditor.mSelectionModifierCursorController != null) { 8821 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 8822 } 8823 } 8824 8825 /** 8826 * Called by the framework in response to a text completion from 8827 * the current input method, provided by it calling 8828 * {@link InputConnection#commitCompletion 8829 * InputConnection.commitCompletion()}. The default implementation does 8830 * nothing; text views that are supporting auto-completion should override 8831 * this to do their desired behavior. 8832 * 8833 * @param text The auto complete text the user has selected. 8834 */ onCommitCompletion(CompletionInfo text)8835 public void onCommitCompletion(CompletionInfo text) { 8836 // intentionally empty 8837 } 8838 8839 /** 8840 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 8841 * dictionary) from the current input method, provided by it calling 8842 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 8843 * The default implementation flashes the background of the corrected word to provide 8844 * feedback to the user. 8845 * 8846 * @param info The auto correct info about the text that was corrected. 8847 */ onCommitCorrection(CorrectionInfo info)8848 public void onCommitCorrection(CorrectionInfo info) { 8849 if (mEditor != null) mEditor.onCommitCorrection(info); 8850 } 8851 beginBatchEdit()8852 public void beginBatchEdit() { 8853 if (mEditor != null) mEditor.beginBatchEdit(); 8854 } 8855 endBatchEdit()8856 public void endBatchEdit() { 8857 if (mEditor != null) mEditor.endBatchEdit(); 8858 } 8859 8860 /** 8861 * Called by the framework in response to a request to begin a batch 8862 * of edit operations through a call to link {@link #beginBatchEdit()}. 8863 */ onBeginBatchEdit()8864 public void onBeginBatchEdit() { 8865 // intentionally empty 8866 } 8867 8868 /** 8869 * Called by the framework in response to a request to end a batch 8870 * of edit operations through a call to link {@link #endBatchEdit}. 8871 */ onEndBatchEdit()8872 public void onEndBatchEdit() { 8873 // intentionally empty 8874 } 8875 8876 /** 8877 * Called by the framework in response to a private command from the 8878 * current method, provided by it calling 8879 * {@link InputConnection#performPrivateCommand 8880 * InputConnection.performPrivateCommand()}. 8881 * 8882 * @param action The action name of the command. 8883 * @param data Any additional data for the command. This may be null. 8884 * @return Return true if you handled the command, else false. 8885 */ onPrivateIMECommand(String action, Bundle data)8886 public boolean onPrivateIMECommand(String action, Bundle data) { 8887 return false; 8888 } 8889 8890 /** @hide */ 8891 @VisibleForTesting 8892 @UnsupportedAppUsage nullLayouts()8893 public void nullLayouts() { 8894 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 8895 mSavedLayout = (BoringLayout) mLayout; 8896 } 8897 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 8898 mSavedHintLayout = (BoringLayout) mHintLayout; 8899 } 8900 8901 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 8902 8903 mBoring = mHintBoring = null; 8904 8905 // Since it depends on the value of mLayout 8906 if (mEditor != null) mEditor.prepareCursorControllers(); 8907 } 8908 8909 /** 8910 * Make a new Layout based on the already-measured size of the view, 8911 * on the assumption that it was measured correctly at some point. 8912 */ 8913 @UnsupportedAppUsage assumeLayout()8914 private void assumeLayout() { 8915 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8916 8917 if (width < 1) { 8918 width = 0; 8919 } 8920 8921 int physicalWidth = width; 8922 8923 if (mHorizontallyScrolling) { 8924 width = VERY_WIDE; 8925 } 8926 8927 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 8928 physicalWidth, false); 8929 } 8930 8931 @UnsupportedAppUsage getLayoutAlignment()8932 private Layout.Alignment getLayoutAlignment() { 8933 Layout.Alignment alignment; 8934 switch (getTextAlignment()) { 8935 case TEXT_ALIGNMENT_GRAVITY: 8936 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 8937 case Gravity.START: 8938 alignment = Layout.Alignment.ALIGN_NORMAL; 8939 break; 8940 case Gravity.END: 8941 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8942 break; 8943 case Gravity.LEFT: 8944 alignment = Layout.Alignment.ALIGN_LEFT; 8945 break; 8946 case Gravity.RIGHT: 8947 alignment = Layout.Alignment.ALIGN_RIGHT; 8948 break; 8949 case Gravity.CENTER_HORIZONTAL: 8950 alignment = Layout.Alignment.ALIGN_CENTER; 8951 break; 8952 default: 8953 alignment = Layout.Alignment.ALIGN_NORMAL; 8954 break; 8955 } 8956 break; 8957 case TEXT_ALIGNMENT_TEXT_START: 8958 alignment = Layout.Alignment.ALIGN_NORMAL; 8959 break; 8960 case TEXT_ALIGNMENT_TEXT_END: 8961 alignment = Layout.Alignment.ALIGN_OPPOSITE; 8962 break; 8963 case TEXT_ALIGNMENT_CENTER: 8964 alignment = Layout.Alignment.ALIGN_CENTER; 8965 break; 8966 case TEXT_ALIGNMENT_VIEW_START: 8967 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8968 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8969 break; 8970 case TEXT_ALIGNMENT_VIEW_END: 8971 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 8972 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8973 break; 8974 case TEXT_ALIGNMENT_INHERIT: 8975 // This should never happen as we have already resolved the text alignment 8976 // but better safe than sorry so we just fall through 8977 default: 8978 alignment = Layout.Alignment.ALIGN_NORMAL; 8979 break; 8980 } 8981 return alignment; 8982 } 8983 8984 /** 8985 * The width passed in is now the desired layout width, 8986 * not the full view width with padding. 8987 * {@hide} 8988 */ 8989 @VisibleForTesting 8990 @UnsupportedAppUsage makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)8991 public void makeNewLayout(int wantWidth, int hintWidth, 8992 BoringLayout.Metrics boring, 8993 BoringLayout.Metrics hintBoring, 8994 int ellipsisWidth, boolean bringIntoView) { 8995 stopMarquee(); 8996 8997 // Update "old" cached values 8998 mOldMaximum = mMaximum; 8999 mOldMaxMode = mMaxMode; 9000 9001 mHighlightPathBogus = true; 9002 9003 if (wantWidth < 0) { 9004 wantWidth = 0; 9005 } 9006 if (hintWidth < 0) { 9007 hintWidth = 0; 9008 } 9009 9010 Layout.Alignment alignment = getLayoutAlignment(); 9011 final boolean testDirChange = mSingleLine && mLayout != null 9012 && (alignment == Layout.Alignment.ALIGN_NORMAL 9013 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 9014 int oldDir = 0; 9015 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 9016 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 9017 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 9018 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 9019 TruncateAt effectiveEllipsize = mEllipsize; 9020 if (mEllipsize == TruncateAt.MARQUEE 9021 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 9022 effectiveEllipsize = TruncateAt.END_SMALL; 9023 } 9024 9025 if (mTextDir == null) { 9026 mTextDir = getTextDirectionHeuristic(); 9027 } 9028 9029 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 9030 effectiveEllipsize, effectiveEllipsize == mEllipsize); 9031 if (switchEllipsize) { 9032 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 9033 ? TruncateAt.END : TruncateAt.MARQUEE; 9034 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 9035 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 9036 } 9037 9038 shouldEllipsize = mEllipsize != null; 9039 mHintLayout = null; 9040 9041 if (mHint != null) { 9042 if (shouldEllipsize) hintWidth = wantWidth; 9043 9044 if (hintBoring == UNKNOWN_BORING) { 9045 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 9046 mHintBoring); 9047 if (hintBoring != null) { 9048 mHintBoring = hintBoring; 9049 } 9050 } 9051 9052 if (hintBoring != null) { 9053 if (hintBoring.width <= hintWidth 9054 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 9055 if (mSavedHintLayout != null) { 9056 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9057 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9058 hintBoring, mIncludePad); 9059 } else { 9060 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9061 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9062 hintBoring, mIncludePad); 9063 } 9064 9065 mSavedHintLayout = (BoringLayout) mHintLayout; 9066 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 9067 if (mSavedHintLayout != null) { 9068 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 9069 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9070 hintBoring, mIncludePad, mEllipsize, 9071 ellipsisWidth); 9072 } else { 9073 mHintLayout = BoringLayout.make(mHint, mTextPaint, 9074 hintWidth, alignment, mSpacingMult, mSpacingAdd, 9075 hintBoring, mIncludePad, mEllipsize, 9076 ellipsisWidth); 9077 } 9078 } 9079 } 9080 // TODO: code duplication with makeSingleLayout() 9081 if (mHintLayout == null) { 9082 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 9083 mHint.length(), mTextPaint, hintWidth) 9084 .setAlignment(alignment) 9085 .setTextDirection(mTextDir) 9086 .setLineSpacing(mSpacingAdd, mSpacingMult) 9087 .setIncludePad(mIncludePad) 9088 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9089 .setBreakStrategy(mBreakStrategy) 9090 .setHyphenationFrequency(mHyphenationFrequency) 9091 .setJustificationMode(mJustificationMode) 9092 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9093 if (shouldEllipsize) { 9094 builder.setEllipsize(mEllipsize) 9095 .setEllipsizedWidth(ellipsisWidth); 9096 } 9097 mHintLayout = builder.build(); 9098 } 9099 } 9100 9101 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 9102 registerForPreDraw(); 9103 } 9104 9105 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9106 if (!compressText(ellipsisWidth)) { 9107 final int height = mLayoutParams.height; 9108 // If the size of the view does not depend on the size of the text, try to 9109 // start the marquee immediately 9110 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 9111 startMarquee(); 9112 } else { 9113 // Defer the start of the marquee until we know our width (see setFrame()) 9114 mRestartMarquee = true; 9115 } 9116 } 9117 } 9118 9119 // CursorControllers need a non-null mLayout 9120 if (mEditor != null) mEditor.prepareCursorControllers(); 9121 } 9122 9123 /** 9124 * Returns true if DynamicLayout is required 9125 * 9126 * @hide 9127 */ 9128 @VisibleForTesting useDynamicLayout()9129 public boolean useDynamicLayout() { 9130 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 9131 } 9132 9133 /** 9134 * @hide 9135 */ makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)9136 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 9137 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 9138 boolean useSaved) { 9139 Layout result = null; 9140 if (useDynamicLayout()) { 9141 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 9142 wantWidth) 9143 .setDisplayText(mTransformed) 9144 .setAlignment(alignment) 9145 .setTextDirection(mTextDir) 9146 .setLineSpacing(mSpacingAdd, mSpacingMult) 9147 .setIncludePad(mIncludePad) 9148 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9149 .setBreakStrategy(mBreakStrategy) 9150 .setHyphenationFrequency(mHyphenationFrequency) 9151 .setJustificationMode(mJustificationMode) 9152 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 9153 .setEllipsizedWidth(ellipsisWidth); 9154 result = builder.build(); 9155 } else { 9156 if (boring == UNKNOWN_BORING) { 9157 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9158 if (boring != null) { 9159 mBoring = boring; 9160 } 9161 } 9162 9163 if (boring != null) { 9164 if (boring.width <= wantWidth 9165 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 9166 if (useSaved && mSavedLayout != null) { 9167 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9168 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9169 boring, mIncludePad); 9170 } else { 9171 result = BoringLayout.make(mTransformed, mTextPaint, 9172 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9173 boring, mIncludePad); 9174 } 9175 9176 if (useSaved) { 9177 mSavedLayout = (BoringLayout) result; 9178 } 9179 } else if (shouldEllipsize && boring.width <= wantWidth) { 9180 if (useSaved && mSavedLayout != null) { 9181 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 9182 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9183 boring, mIncludePad, effectiveEllipsize, 9184 ellipsisWidth); 9185 } else { 9186 result = BoringLayout.make(mTransformed, mTextPaint, 9187 wantWidth, alignment, mSpacingMult, mSpacingAdd, 9188 boring, mIncludePad, effectiveEllipsize, 9189 ellipsisWidth); 9190 } 9191 } 9192 } 9193 } 9194 if (result == null) { 9195 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 9196 0, mTransformed.length(), mTextPaint, wantWidth) 9197 .setAlignment(alignment) 9198 .setTextDirection(mTextDir) 9199 .setLineSpacing(mSpacingAdd, mSpacingMult) 9200 .setIncludePad(mIncludePad) 9201 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9202 .setBreakStrategy(mBreakStrategy) 9203 .setHyphenationFrequency(mHyphenationFrequency) 9204 .setJustificationMode(mJustificationMode) 9205 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 9206 if (shouldEllipsize) { 9207 builder.setEllipsize(effectiveEllipsize) 9208 .setEllipsizedWidth(ellipsisWidth); 9209 } 9210 result = builder.build(); 9211 } 9212 return result; 9213 } 9214 9215 @UnsupportedAppUsage compressText(float width)9216 private boolean compressText(float width) { 9217 if (isHardwareAccelerated()) return false; 9218 9219 // Only compress the text if it hasn't been compressed by the previous pass 9220 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 9221 && mTextPaint.getTextScaleX() == 1.0f) { 9222 final float textWidth = mLayout.getLineWidth(0); 9223 final float overflow = (textWidth + 1.0f - width) / width; 9224 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 9225 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 9226 post(new Runnable() { 9227 public void run() { 9228 requestLayout(); 9229 } 9230 }); 9231 return true; 9232 } 9233 } 9234 9235 return false; 9236 } 9237 desired(Layout layout)9238 private static int desired(Layout layout) { 9239 int n = layout.getLineCount(); 9240 CharSequence text = layout.getText(); 9241 float max = 0; 9242 9243 // if any line was wrapped, we can't use it. 9244 // but it's ok for the last line not to have a newline 9245 9246 for (int i = 0; i < n - 1; i++) { 9247 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 9248 return -1; 9249 } 9250 } 9251 9252 for (int i = 0; i < n; i++) { 9253 max = Math.max(max, layout.getLineWidth(i)); 9254 } 9255 9256 return (int) Math.ceil(max); 9257 } 9258 9259 /** 9260 * Set whether the TextView includes extra top and bottom padding to make 9261 * room for accents that go above the normal ascent and descent. 9262 * The default is true. 9263 * 9264 * @see #getIncludeFontPadding() 9265 * 9266 * @attr ref android.R.styleable#TextView_includeFontPadding 9267 */ setIncludeFontPadding(boolean includepad)9268 public void setIncludeFontPadding(boolean includepad) { 9269 if (mIncludePad != includepad) { 9270 mIncludePad = includepad; 9271 9272 if (mLayout != null) { 9273 nullLayouts(); 9274 requestLayout(); 9275 invalidate(); 9276 } 9277 } 9278 } 9279 9280 /** 9281 * Gets whether the TextView includes extra top and bottom padding to make 9282 * room for accents that go above the normal ascent and descent. 9283 * 9284 * @see #setIncludeFontPadding(boolean) 9285 * 9286 * @attr ref android.R.styleable#TextView_includeFontPadding 9287 */ 9288 @InspectableProperty getIncludeFontPadding()9289 public boolean getIncludeFontPadding() { 9290 return mIncludePad; 9291 } 9292 9293 /** @hide */ 9294 @VisibleForTesting 9295 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 9296 9297 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)9298 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9299 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 9300 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 9301 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 9302 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 9303 9304 int width; 9305 int height; 9306 9307 BoringLayout.Metrics boring = UNKNOWN_BORING; 9308 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 9309 9310 if (mTextDir == null) { 9311 mTextDir = getTextDirectionHeuristic(); 9312 } 9313 9314 int des = -1; 9315 boolean fromexisting = false; 9316 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 9317 ? (float) widthSize : Float.MAX_VALUE; 9318 9319 if (widthMode == MeasureSpec.EXACTLY) { 9320 // Parent has told us how big to be. So be it. 9321 width = widthSize; 9322 } else { 9323 if (mLayout != null && mEllipsize == null) { 9324 des = desired(mLayout); 9325 } 9326 9327 if (des < 0) { 9328 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 9329 if (boring != null) { 9330 mBoring = boring; 9331 } 9332 } else { 9333 fromexisting = true; 9334 } 9335 9336 if (boring == null || boring == UNKNOWN_BORING) { 9337 if (des < 0) { 9338 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 9339 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 9340 } 9341 width = des; 9342 } else { 9343 width = boring.width; 9344 } 9345 9346 final Drawables dr = mDrawables; 9347 if (dr != null) { 9348 width = Math.max(width, dr.mDrawableWidthTop); 9349 width = Math.max(width, dr.mDrawableWidthBottom); 9350 } 9351 9352 if (mHint != null) { 9353 int hintDes = -1; 9354 int hintWidth; 9355 9356 if (mHintLayout != null && mEllipsize == null) { 9357 hintDes = desired(mHintLayout); 9358 } 9359 9360 if (hintDes < 0) { 9361 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 9362 if (hintBoring != null) { 9363 mHintBoring = hintBoring; 9364 } 9365 } 9366 9367 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 9368 if (hintDes < 0) { 9369 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 9370 mHint.length(), mTextPaint, mTextDir, widthLimit)); 9371 } 9372 hintWidth = hintDes; 9373 } else { 9374 hintWidth = hintBoring.width; 9375 } 9376 9377 if (hintWidth > width) { 9378 width = hintWidth; 9379 } 9380 } 9381 9382 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 9383 9384 if (mMaxWidthMode == EMS) { 9385 width = Math.min(width, mMaxWidth * getLineHeight()); 9386 } else { 9387 width = Math.min(width, mMaxWidth); 9388 } 9389 9390 if (mMinWidthMode == EMS) { 9391 width = Math.max(width, mMinWidth * getLineHeight()); 9392 } else { 9393 width = Math.max(width, mMinWidth); 9394 } 9395 9396 // Check against our minimum width 9397 width = Math.max(width, getSuggestedMinimumWidth()); 9398 9399 if (widthMode == MeasureSpec.AT_MOST) { 9400 width = Math.min(widthSize, width); 9401 } 9402 } 9403 9404 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9405 int unpaddedWidth = want; 9406 9407 if (mHorizontallyScrolling) want = VERY_WIDE; 9408 9409 int hintWant = want; 9410 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 9411 9412 if (mLayout == null) { 9413 makeNewLayout(want, hintWant, boring, hintBoring, 9414 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9415 } else { 9416 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 9417 || (mLayout.getEllipsizedWidth() 9418 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9419 9420 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 9421 && (want > mLayout.getWidth()) 9422 && (mLayout instanceof BoringLayout 9423 || (fromexisting && des >= 0 && des <= want)); 9424 9425 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 9426 9427 if (layoutChanged || maximumChanged) { 9428 if (!maximumChanged && widthChanged) { 9429 mLayout.increaseWidthTo(want); 9430 } else { 9431 makeNewLayout(want, hintWant, boring, hintBoring, 9432 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 9433 } 9434 } else { 9435 // Nothing has changed 9436 } 9437 } 9438 9439 if (heightMode == MeasureSpec.EXACTLY) { 9440 // Parent has told us how big to be. So be it. 9441 height = heightSize; 9442 mDesiredHeightAtMeasure = -1; 9443 } else { 9444 int desired = getDesiredHeight(); 9445 9446 height = desired; 9447 mDesiredHeightAtMeasure = desired; 9448 9449 if (heightMode == MeasureSpec.AT_MOST) { 9450 height = Math.min(desired, heightSize); 9451 } 9452 } 9453 9454 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9455 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 9456 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 9457 } 9458 9459 /* 9460 * We didn't let makeNewLayout() register to bring the cursor into view, 9461 * so do it here if there is any possibility that it is needed. 9462 */ 9463 if (mMovement != null 9464 || mLayout.getWidth() > unpaddedWidth 9465 || mLayout.getHeight() > unpaddedHeight) { 9466 registerForPreDraw(); 9467 } else { 9468 scrollTo(0, 0); 9469 } 9470 9471 setMeasuredDimension(width, height); 9472 } 9473 9474 /** 9475 * Automatically computes and sets the text size. 9476 */ autoSizeText()9477 private void autoSizeText() { 9478 if (!isAutoSizeEnabled()) { 9479 return; 9480 } 9481 9482 if (mNeedsAutoSizeText) { 9483 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 9484 return; 9485 } 9486 9487 final int availableWidth = mHorizontallyScrolling 9488 ? VERY_WIDE 9489 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 9490 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 9491 - getExtendedPaddingTop(); 9492 9493 if (availableWidth <= 0 || availableHeight <= 0) { 9494 return; 9495 } 9496 9497 synchronized (TEMP_RECTF) { 9498 TEMP_RECTF.setEmpty(); 9499 TEMP_RECTF.right = availableWidth; 9500 TEMP_RECTF.bottom = availableHeight; 9501 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 9502 9503 if (optimalTextSize != getTextSize()) { 9504 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 9505 false /* shouldRequestLayout */); 9506 9507 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 9508 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9509 false /* bringIntoView */); 9510 } 9511 } 9512 } 9513 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 9514 // after the next layout pass should set this to false. 9515 mNeedsAutoSizeText = true; 9516 } 9517 9518 /** 9519 * Performs a binary search to find the largest text size that will still fit within the size 9520 * available to this view. 9521 */ findLargestTextSizeWhichFits(RectF availableSpace)9522 private int findLargestTextSizeWhichFits(RectF availableSpace) { 9523 final int sizesCount = mAutoSizeTextSizesInPx.length; 9524 if (sizesCount == 0) { 9525 throw new IllegalStateException("No available text sizes to choose from."); 9526 } 9527 9528 int bestSizeIndex = 0; 9529 int lowIndex = bestSizeIndex + 1; 9530 int highIndex = sizesCount - 1; 9531 int sizeToTryIndex; 9532 while (lowIndex <= highIndex) { 9533 sizeToTryIndex = (lowIndex + highIndex) / 2; 9534 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 9535 bestSizeIndex = lowIndex; 9536 lowIndex = sizeToTryIndex + 1; 9537 } else { 9538 highIndex = sizeToTryIndex - 1; 9539 bestSizeIndex = highIndex; 9540 } 9541 } 9542 9543 return mAutoSizeTextSizesInPx[bestSizeIndex]; 9544 } 9545 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)9546 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 9547 final CharSequence text = mTransformed != null 9548 ? mTransformed 9549 : getText(); 9550 final int maxLines = getMaxLines(); 9551 if (mTempTextPaint == null) { 9552 mTempTextPaint = new TextPaint(); 9553 } else { 9554 mTempTextPaint.reset(); 9555 } 9556 mTempTextPaint.set(getPaint()); 9557 mTempTextPaint.setTextSize(suggestedSizeInPx); 9558 9559 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 9560 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 9561 9562 layoutBuilder.setAlignment(getLayoutAlignment()) 9563 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 9564 .setIncludePad(getIncludeFontPadding()) 9565 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) 9566 .setBreakStrategy(getBreakStrategy()) 9567 .setHyphenationFrequency(getHyphenationFrequency()) 9568 .setJustificationMode(getJustificationMode()) 9569 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 9570 .setTextDirection(getTextDirectionHeuristic()); 9571 9572 final StaticLayout layout = layoutBuilder.build(); 9573 9574 // Lines overflow. 9575 if (maxLines != -1 && layout.getLineCount() > maxLines) { 9576 return false; 9577 } 9578 9579 // Height overflow. 9580 if (layout.getHeight() > availableSpace.bottom) { 9581 return false; 9582 } 9583 9584 return true; 9585 } 9586 getDesiredHeight()9587 private int getDesiredHeight() { 9588 return Math.max( 9589 getDesiredHeight(mLayout, true), 9590 getDesiredHeight(mHintLayout, mEllipsize != null)); 9591 } 9592 getDesiredHeight(Layout layout, boolean cap)9593 private int getDesiredHeight(Layout layout, boolean cap) { 9594 if (layout == null) { 9595 return 0; 9596 } 9597 9598 /* 9599 * Don't cap the hint to a certain number of lines. 9600 * (Do cap it, though, if we have a maximum pixel height.) 9601 */ 9602 int desired = layout.getHeight(cap); 9603 9604 final Drawables dr = mDrawables; 9605 if (dr != null) { 9606 desired = Math.max(desired, dr.mDrawableHeightLeft); 9607 desired = Math.max(desired, dr.mDrawableHeightRight); 9608 } 9609 9610 int linecount = layout.getLineCount(); 9611 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 9612 desired += padding; 9613 9614 if (mMaxMode != LINES) { 9615 desired = Math.min(desired, mMaximum); 9616 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 9617 || layout instanceof BoringLayout)) { 9618 desired = layout.getLineTop(mMaximum); 9619 9620 if (dr != null) { 9621 desired = Math.max(desired, dr.mDrawableHeightLeft); 9622 desired = Math.max(desired, dr.mDrawableHeightRight); 9623 } 9624 9625 desired += padding; 9626 linecount = mMaximum; 9627 } 9628 9629 if (mMinMode == LINES) { 9630 if (linecount < mMinimum) { 9631 desired += getLineHeight() * (mMinimum - linecount); 9632 } 9633 } else { 9634 desired = Math.max(desired, mMinimum); 9635 } 9636 9637 // Check against our minimum height 9638 desired = Math.max(desired, getSuggestedMinimumHeight()); 9639 9640 return desired; 9641 } 9642 9643 /** 9644 * Check whether a change to the existing text layout requires a 9645 * new view layout. 9646 */ checkForResize()9647 private void checkForResize() { 9648 boolean sizeChanged = false; 9649 9650 if (mLayout != null) { 9651 // Check if our width changed 9652 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 9653 sizeChanged = true; 9654 invalidate(); 9655 } 9656 9657 // Check if our height changed 9658 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 9659 int desiredHeight = getDesiredHeight(); 9660 9661 if (desiredHeight != this.getHeight()) { 9662 sizeChanged = true; 9663 } 9664 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 9665 if (mDesiredHeightAtMeasure >= 0) { 9666 int desiredHeight = getDesiredHeight(); 9667 9668 if (desiredHeight != mDesiredHeightAtMeasure) { 9669 sizeChanged = true; 9670 } 9671 } 9672 } 9673 } 9674 9675 if (sizeChanged) { 9676 requestLayout(); 9677 // caller will have already invalidated 9678 } 9679 } 9680 9681 /** 9682 * Check whether entirely new text requires a new view layout 9683 * or merely a new text layout. 9684 */ 9685 @UnsupportedAppUsage checkForRelayout()9686 private void checkForRelayout() { 9687 // If we have a fixed width, we can just swap in a new text layout 9688 // if the text height stays the same or if the view height is fixed. 9689 9690 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 9691 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 9692 && (mHint == null || mHintLayout != null) 9693 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 9694 // Static width, so try making a new text layout. 9695 9696 int oldht = mLayout.getHeight(); 9697 int want = mLayout.getWidth(); 9698 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 9699 9700 /* 9701 * No need to bring the text into view, since the size is not 9702 * changing (unless we do the requestLayout(), in which case it 9703 * will happen at measure). 9704 */ 9705 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 9706 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 9707 false); 9708 9709 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 9710 // In a fixed-height view, so use our new text layout. 9711 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 9712 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 9713 autoSizeText(); 9714 invalidate(); 9715 return; 9716 } 9717 9718 // Dynamic height, but height has stayed the same, 9719 // so use our new text layout. 9720 if (mLayout.getHeight() == oldht 9721 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 9722 autoSizeText(); 9723 invalidate(); 9724 return; 9725 } 9726 } 9727 9728 // We lose: the height has changed and we have a dynamic height. 9729 // Request a new view layout using our new text layout. 9730 requestLayout(); 9731 invalidate(); 9732 } else { 9733 // Dynamic width, so we have no choice but to request a new 9734 // view layout with a new text layout. 9735 nullLayouts(); 9736 requestLayout(); 9737 invalidate(); 9738 } 9739 } 9740 9741 @Override onLayout(boolean changed, int left, int top, int right, int bottom)9742 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 9743 super.onLayout(changed, left, top, right, bottom); 9744 if (mDeferScroll >= 0) { 9745 int curs = mDeferScroll; 9746 mDeferScroll = -1; 9747 bringPointIntoView(Math.min(curs, mText.length())); 9748 } 9749 // Call auto-size after the width and height have been calculated. 9750 autoSizeText(); 9751 } 9752 isShowingHint()9753 private boolean isShowingHint() { 9754 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 9755 } 9756 9757 /** 9758 * Returns true if anything changed. 9759 */ 9760 @UnsupportedAppUsage bringTextIntoView()9761 private boolean bringTextIntoView() { 9762 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9763 int line = 0; 9764 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9765 line = layout.getLineCount() - 1; 9766 } 9767 9768 Layout.Alignment a = layout.getParagraphAlignment(line); 9769 int dir = layout.getParagraphDirection(line); 9770 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9771 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9772 int ht = layout.getHeight(); 9773 9774 int scrollx, scrolly; 9775 9776 // Convert to left, center, or right alignment. 9777 if (a == Layout.Alignment.ALIGN_NORMAL) { 9778 a = dir == Layout.DIR_LEFT_TO_RIGHT 9779 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 9780 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 9781 a = dir == Layout.DIR_LEFT_TO_RIGHT 9782 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 9783 } 9784 9785 if (a == Layout.Alignment.ALIGN_CENTER) { 9786 /* 9787 * Keep centered if possible, or, if it is too wide to fit, 9788 * keep leading edge in view. 9789 */ 9790 9791 int left = (int) Math.floor(layout.getLineLeft(line)); 9792 int right = (int) Math.ceil(layout.getLineRight(line)); 9793 9794 if (right - left < hspace) { 9795 scrollx = (right + left) / 2 - hspace / 2; 9796 } else { 9797 if (dir < 0) { 9798 scrollx = right - hspace; 9799 } else { 9800 scrollx = left; 9801 } 9802 } 9803 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 9804 int right = (int) Math.ceil(layout.getLineRight(line)); 9805 scrollx = right - hspace; 9806 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 9807 scrollx = (int) Math.floor(layout.getLineLeft(line)); 9808 } 9809 9810 if (ht < vspace) { 9811 scrolly = 0; 9812 } else { 9813 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9814 scrolly = ht - vspace; 9815 } else { 9816 scrolly = 0; 9817 } 9818 } 9819 9820 if (scrollx != mScrollX || scrolly != mScrollY) { 9821 scrollTo(scrollx, scrolly); 9822 return true; 9823 } else { 9824 return false; 9825 } 9826 } 9827 9828 /** 9829 * Move the point, specified by the offset, into the view if it is needed. 9830 * This has to be called after layout. Returns true if anything changed. 9831 */ bringPointIntoView(int offset)9832 public boolean bringPointIntoView(int offset) { 9833 if (isLayoutRequested()) { 9834 mDeferScroll = offset; 9835 return false; 9836 } 9837 boolean changed = false; 9838 9839 Layout layout = isShowingHint() ? mHintLayout : mLayout; 9840 9841 if (layout == null) return changed; 9842 9843 int line = layout.getLineForOffset(offset); 9844 9845 int grav; 9846 9847 switch (layout.getParagraphAlignment(line)) { 9848 case ALIGN_LEFT: 9849 grav = 1; 9850 break; 9851 case ALIGN_RIGHT: 9852 grav = -1; 9853 break; 9854 case ALIGN_NORMAL: 9855 grav = layout.getParagraphDirection(line); 9856 break; 9857 case ALIGN_OPPOSITE: 9858 grav = -layout.getParagraphDirection(line); 9859 break; 9860 case ALIGN_CENTER: 9861 default: 9862 grav = 0; 9863 break; 9864 } 9865 9866 // We only want to clamp the cursor to fit within the layout width 9867 // in left-to-right modes, because in a right to left alignment, 9868 // we want to scroll to keep the line-right on the screen, as other 9869 // lines are likely to have text flush with the right margin, which 9870 // we want to keep visible. 9871 // A better long-term solution would probably be to measure both 9872 // the full line and a blank-trimmed version, and, for example, use 9873 // the latter measurement for centering and right alignment, but for 9874 // the time being we only implement the cursor clamping in left to 9875 // right where it is most likely to be annoying. 9876 final boolean clamped = grav > 0; 9877 // FIXME: Is it okay to truncate this, or should we round? 9878 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 9879 final int top = layout.getLineTop(line); 9880 final int bottom = layout.getLineTop(line + 1); 9881 9882 int left = (int) Math.floor(layout.getLineLeft(line)); 9883 int right = (int) Math.ceil(layout.getLineRight(line)); 9884 int ht = layout.getHeight(); 9885 9886 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9887 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 9888 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 9889 // If cursor has been clamped, make sure we don't scroll. 9890 right = Math.max(x, left + hspace); 9891 } 9892 9893 int hslack = (bottom - top) / 2; 9894 int vslack = hslack; 9895 9896 if (vslack > vspace / 4) { 9897 vslack = vspace / 4; 9898 } 9899 if (hslack > hspace / 4) { 9900 hslack = hspace / 4; 9901 } 9902 9903 int hs = mScrollX; 9904 int vs = mScrollY; 9905 9906 if (top - vs < vslack) { 9907 vs = top - vslack; 9908 } 9909 if (bottom - vs > vspace - vslack) { 9910 vs = bottom - (vspace - vslack); 9911 } 9912 if (ht - vs < vspace) { 9913 vs = ht - vspace; 9914 } 9915 if (0 - vs > 0) { 9916 vs = 0; 9917 } 9918 9919 if (grav != 0) { 9920 if (x - hs < hslack) { 9921 hs = x - hslack; 9922 } 9923 if (x - hs > hspace - hslack) { 9924 hs = x - (hspace - hslack); 9925 } 9926 } 9927 9928 if (grav < 0) { 9929 if (left - hs > 0) { 9930 hs = left; 9931 } 9932 if (right - hs < hspace) { 9933 hs = right - hspace; 9934 } 9935 } else if (grav > 0) { 9936 if (right - hs < hspace) { 9937 hs = right - hspace; 9938 } 9939 if (left - hs > 0) { 9940 hs = left; 9941 } 9942 } else /* grav == 0 */ { 9943 if (right - left <= hspace) { 9944 /* 9945 * If the entire text fits, center it exactly. 9946 */ 9947 hs = left - (hspace - (right - left)) / 2; 9948 } else if (x > right - hslack) { 9949 /* 9950 * If we are near the right edge, keep the right edge 9951 * at the edge of the view. 9952 */ 9953 hs = right - hspace; 9954 } else if (x < left + hslack) { 9955 /* 9956 * If we are near the left edge, keep the left edge 9957 * at the edge of the view. 9958 */ 9959 hs = left; 9960 } else if (left > hs) { 9961 /* 9962 * Is there whitespace visible at the left? Fix it if so. 9963 */ 9964 hs = left; 9965 } else if (right < hs + hspace) { 9966 /* 9967 * Is there whitespace visible at the right? Fix it if so. 9968 */ 9969 hs = right - hspace; 9970 } else { 9971 /* 9972 * Otherwise, float as needed. 9973 */ 9974 if (x - hs < hslack) { 9975 hs = x - hslack; 9976 } 9977 if (x - hs > hspace - hslack) { 9978 hs = x - (hspace - hslack); 9979 } 9980 } 9981 } 9982 9983 if (hs != mScrollX || vs != mScrollY) { 9984 if (mScroller == null) { 9985 scrollTo(hs, vs); 9986 } else { 9987 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 9988 int dx = hs - mScrollX; 9989 int dy = vs - mScrollY; 9990 9991 if (duration > ANIMATED_SCROLL_GAP) { 9992 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 9993 awakenScrollBars(mScroller.getDuration()); 9994 invalidate(); 9995 } else { 9996 if (!mScroller.isFinished()) { 9997 mScroller.abortAnimation(); 9998 } 9999 10000 scrollBy(dx, dy); 10001 } 10002 10003 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 10004 } 10005 10006 changed = true; 10007 } 10008 10009 if (isFocused()) { 10010 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 10011 // requestRectangleOnScreen() is in terms of content coordinates. 10012 10013 // The offsets here are to ensure the rectangle we are using is 10014 // within our view bounds, in case the cursor is on the far left 10015 // or right. If it isn't withing the bounds, then this request 10016 // will be ignored. 10017 if (mTempRect == null) mTempRect = new Rect(); 10018 mTempRect.set(x - 2, top, x + 2, bottom); 10019 getInterestingRect(mTempRect, line); 10020 mTempRect.offset(mScrollX, mScrollY); 10021 10022 if (requestRectangleOnScreen(mTempRect)) { 10023 changed = true; 10024 } 10025 } 10026 10027 return changed; 10028 } 10029 10030 /** 10031 * Move the cursor, if needed, so that it is at an offset that is visible 10032 * to the user. This will not move the cursor if it represents more than 10033 * one character (a selection range). This will only work if the 10034 * TextView contains spannable text; otherwise it will do nothing. 10035 * 10036 * @return True if the cursor was actually moved, false otherwise. 10037 */ moveCursorToVisibleOffset()10038 public boolean moveCursorToVisibleOffset() { 10039 if (!(mText instanceof Spannable)) { 10040 return false; 10041 } 10042 int start = getSelectionStart(); 10043 int end = getSelectionEnd(); 10044 if (start != end) { 10045 return false; 10046 } 10047 10048 // First: make sure the line is visible on screen: 10049 10050 int line = mLayout.getLineForOffset(start); 10051 10052 final int top = mLayout.getLineTop(line); 10053 final int bottom = mLayout.getLineTop(line + 1); 10054 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 10055 int vslack = (bottom - top) / 2; 10056 if (vslack > vspace / 4) { 10057 vslack = vspace / 4; 10058 } 10059 final int vs = mScrollY; 10060 10061 if (top < (vs + vslack)) { 10062 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 10063 } else if (bottom > (vspace + vs - vslack)) { 10064 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 10065 } 10066 10067 // Next: make sure the character is visible on screen: 10068 10069 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10070 final int hs = mScrollX; 10071 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 10072 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 10073 10074 // line might contain bidirectional text 10075 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 10076 final int highChar = leftChar > rightChar ? leftChar : rightChar; 10077 10078 int newStart = start; 10079 if (newStart < lowChar) { 10080 newStart = lowChar; 10081 } else if (newStart > highChar) { 10082 newStart = highChar; 10083 } 10084 10085 if (newStart != start) { 10086 Selection.setSelection(mSpannable, newStart); 10087 return true; 10088 } 10089 10090 return false; 10091 } 10092 10093 @Override computeScroll()10094 public void computeScroll() { 10095 if (mScroller != null) { 10096 if (mScroller.computeScrollOffset()) { 10097 mScrollX = mScroller.getCurrX(); 10098 mScrollY = mScroller.getCurrY(); 10099 invalidateParentCaches(); 10100 postInvalidate(); // So we draw again 10101 } 10102 } 10103 } 10104 getInterestingRect(Rect r, int line)10105 private void getInterestingRect(Rect r, int line) { 10106 convertFromViewportToContentCoordinates(r); 10107 10108 // Rectangle can can be expanded on first and last line to take 10109 // padding into account. 10110 // TODO Take left/right padding into account too? 10111 if (line == 0) r.top -= getExtendedPaddingTop(); 10112 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 10113 } 10114 convertFromViewportToContentCoordinates(Rect r)10115 private void convertFromViewportToContentCoordinates(Rect r) { 10116 final int horizontalOffset = viewportToContentHorizontalOffset(); 10117 r.left += horizontalOffset; 10118 r.right += horizontalOffset; 10119 10120 final int verticalOffset = viewportToContentVerticalOffset(); 10121 r.top += verticalOffset; 10122 r.bottom += verticalOffset; 10123 } 10124 viewportToContentHorizontalOffset()10125 int viewportToContentHorizontalOffset() { 10126 return getCompoundPaddingLeft() - mScrollX; 10127 } 10128 10129 @UnsupportedAppUsage viewportToContentVerticalOffset()10130 int viewportToContentVerticalOffset() { 10131 int offset = getExtendedPaddingTop() - mScrollY; 10132 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 10133 offset += getVerticalOffset(false); 10134 } 10135 return offset; 10136 } 10137 10138 @Override debug(int depth)10139 public void debug(int depth) { 10140 super.debug(depth); 10141 10142 String output = debugIndent(depth); 10143 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 10144 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 10145 + "} "; 10146 10147 if (mText != null) { 10148 10149 output += "mText=\"" + mText + "\" "; 10150 if (mLayout != null) { 10151 output += "mLayout width=" + mLayout.getWidth() 10152 + " height=" + mLayout.getHeight(); 10153 } 10154 } else { 10155 output += "mText=NULL"; 10156 } 10157 Log.d(VIEW_LOG_TAG, output); 10158 } 10159 10160 /** 10161 * Convenience for {@link Selection#getSelectionStart}. 10162 */ 10163 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()10164 public int getSelectionStart() { 10165 return Selection.getSelectionStart(getText()); 10166 } 10167 10168 /** 10169 * Convenience for {@link Selection#getSelectionEnd}. 10170 */ 10171 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()10172 public int getSelectionEnd() { 10173 return Selection.getSelectionEnd(getText()); 10174 } 10175 10176 /** 10177 * Return true iff there is a selection of nonzero length inside this text view. 10178 */ hasSelection()10179 public boolean hasSelection() { 10180 final int selectionStart = getSelectionStart(); 10181 final int selectionEnd = getSelectionEnd(); 10182 10183 return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; 10184 } 10185 getSelectedText()10186 String getSelectedText() { 10187 if (!hasSelection()) { 10188 return null; 10189 } 10190 10191 final int start = getSelectionStart(); 10192 final int end = getSelectionEnd(); 10193 return String.valueOf( 10194 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 10195 } 10196 10197 /** 10198 * Sets the properties of this field (lines, horizontally scrolling, 10199 * transformation method) to be for a single-line input. 10200 * 10201 * @attr ref android.R.styleable#TextView_singleLine 10202 */ setSingleLine()10203 public void setSingleLine() { 10204 setSingleLine(true); 10205 } 10206 10207 /** 10208 * Sets the properties of this field to transform input to ALL CAPS 10209 * display. This may use a "small caps" formatting if available. 10210 * This setting will be ignored if this field is editable or selectable. 10211 * 10212 * This call replaces the current transformation method. Disabling this 10213 * will not necessarily restore the previous behavior from before this 10214 * was enabled. 10215 * 10216 * @see #setTransformationMethod(TransformationMethod) 10217 * @attr ref android.R.styleable#TextView_textAllCaps 10218 */ setAllCaps(boolean allCaps)10219 public void setAllCaps(boolean allCaps) { 10220 if (allCaps) { 10221 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 10222 } else { 10223 setTransformationMethod(null); 10224 } 10225 } 10226 10227 /** 10228 * 10229 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 10230 * @return Whether the current transformation method is for ALL CAPS. 10231 * 10232 * @see #setAllCaps(boolean) 10233 * @see #setTransformationMethod(TransformationMethod) 10234 */ 10235 @InspectableProperty(name = "textAllCaps") isAllCaps()10236 public boolean isAllCaps() { 10237 final TransformationMethod method = getTransformationMethod(); 10238 return method != null && method instanceof AllCapsTransformationMethod; 10239 } 10240 10241 /** 10242 * If true, sets the properties of this field (number of lines, horizontally scrolling, 10243 * transformation method) to be for a single-line input; if false, restores these to the default 10244 * conditions. 10245 * 10246 * Note that the default conditions are not necessarily those that were in effect prior this 10247 * method, and you may want to reset these properties to your custom values. 10248 * 10249 * Note that due to performance reasons, by setting single line for the EditText, the maximum 10250 * text length is set to 5000 if no other character limitation are applied. 10251 * 10252 * @attr ref android.R.styleable#TextView_singleLine 10253 */ 10254 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)10255 public void setSingleLine(boolean singleLine) { 10256 // Could be used, but may break backward compatibility. 10257 // if (mSingleLine == singleLine) return; 10258 setInputTypeSingleLine(singleLine); 10259 applySingleLine(singleLine, true, true, true); 10260 } 10261 10262 /** 10263 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 10264 * @param singleLine 10265 */ setInputTypeSingleLine(boolean singleLine)10266 private void setInputTypeSingleLine(boolean singleLine) { 10267 if (mEditor != null 10268 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 10269 == EditorInfo.TYPE_CLASS_TEXT) { 10270 if (singleLine) { 10271 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10272 } else { 10273 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 10274 } 10275 } 10276 } 10277 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)10278 private void applySingleLine(boolean singleLine, boolean applyTransformation, 10279 boolean changeMaxLines, boolean changeMaxLength) { 10280 mSingleLine = singleLine; 10281 10282 if (singleLine) { 10283 setLines(1); 10284 setHorizontallyScrolling(true); 10285 if (applyTransformation) { 10286 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 10287 } 10288 10289 if (!changeMaxLength) return; 10290 10291 // Single line length filter is only applicable editable text. 10292 if (mBufferType != BufferType.EDITABLE) return; 10293 10294 final InputFilter[] prevFilters = getFilters(); 10295 for (InputFilter filter: getFilters()) { 10296 // We don't add LengthFilter if already there. 10297 if (filter instanceof InputFilter.LengthFilter) return; 10298 } 10299 10300 if (mSingleLineLengthFilter == null) { 10301 mSingleLineLengthFilter = new InputFilter.LengthFilter( 10302 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 10303 } 10304 10305 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 10306 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 10307 newFilters[prevFilters.length] = mSingleLineLengthFilter; 10308 10309 setFilters(newFilters); 10310 10311 // Since filter doesn't apply to existing text, trigger filter by setting text. 10312 setText(getText()); 10313 } else { 10314 if (changeMaxLines) { 10315 setMaxLines(Integer.MAX_VALUE); 10316 } 10317 setHorizontallyScrolling(false); 10318 if (applyTransformation) { 10319 setTransformationMethod(null); 10320 } 10321 10322 if (!changeMaxLength) return; 10323 10324 // Single line length filter is only applicable editable text. 10325 if (mBufferType != BufferType.EDITABLE) return; 10326 10327 final InputFilter[] prevFilters = getFilters(); 10328 if (prevFilters.length == 0) return; 10329 10330 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 10331 // single line char limit filter. 10332 if (mSingleLineLengthFilter == null) return; 10333 10334 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 10335 // Since filter list is expected to be small and want to avoid unnecessary array 10336 // allocation, check if there is mSingleLengthFilter first. 10337 int targetIndex = -1; 10338 for (int i = 0; i < prevFilters.length; ++i) { 10339 if (prevFilters[i] == mSingleLineLengthFilter) { 10340 targetIndex = i; 10341 break; 10342 } 10343 } 10344 if (targetIndex == -1) return; // not found. Do nothing. 10345 10346 if (prevFilters.length == 1) { 10347 setFilters(NO_FILTERS); 10348 return; 10349 } 10350 10351 // Create new array which doesn't include mSingleLengthFilter. 10352 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 10353 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 10354 System.arraycopy( 10355 prevFilters, 10356 targetIndex + 1, 10357 newFilters, 10358 targetIndex, 10359 prevFilters.length - targetIndex - 1); 10360 setFilters(newFilters); 10361 mSingleLineLengthFilter = null; 10362 } 10363 } 10364 10365 /** 10366 * Causes words in the text that are longer than the view's width 10367 * to be ellipsized instead of broken in the middle. You may also 10368 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 10369 * to constrain the text to a single line. Use <code>null</code> 10370 * to turn off ellipsizing. 10371 * 10372 * If {@link #setMaxLines} has been used to set two or more lines, 10373 * only {@link android.text.TextUtils.TruncateAt#END} and 10374 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 10375 * (other ellipsizing types will not do anything). 10376 * 10377 * @attr ref android.R.styleable#TextView_ellipsize 10378 */ setEllipsize(TextUtils.TruncateAt where)10379 public void setEllipsize(TextUtils.TruncateAt where) { 10380 // TruncateAt is an enum. != comparison is ok between these singleton objects. 10381 if (mEllipsize != where) { 10382 mEllipsize = where; 10383 10384 if (mLayout != null) { 10385 nullLayouts(); 10386 requestLayout(); 10387 invalidate(); 10388 } 10389 } 10390 } 10391 10392 /** 10393 * Sets how many times to repeat the marquee animation. Only applied if the 10394 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 10395 * 10396 * @see #getMarqueeRepeatLimit() 10397 * 10398 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10399 */ setMarqueeRepeatLimit(int marqueeLimit)10400 public void setMarqueeRepeatLimit(int marqueeLimit) { 10401 mMarqueeRepeatLimit = marqueeLimit; 10402 } 10403 10404 /** 10405 * Gets the number of times the marquee animation is repeated. Only meaningful if the 10406 * TextView has marquee enabled. 10407 * 10408 * @return the number of times the marquee animation is repeated. -1 if the animation 10409 * repeats indefinitely 10410 * 10411 * @see #setMarqueeRepeatLimit(int) 10412 * 10413 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 10414 */ 10415 @InspectableProperty getMarqueeRepeatLimit()10416 public int getMarqueeRepeatLimit() { 10417 return mMarqueeRepeatLimit; 10418 } 10419 10420 /** 10421 * Returns where, if anywhere, words that are longer than the view 10422 * is wide should be ellipsized. 10423 */ 10424 @InspectableProperty 10425 @ViewDebug.ExportedProperty getEllipsize()10426 public TextUtils.TruncateAt getEllipsize() { 10427 return mEllipsize; 10428 } 10429 10430 /** 10431 * Set the TextView so that when it takes focus, all the text is 10432 * selected. 10433 * 10434 * @attr ref android.R.styleable#TextView_selectAllOnFocus 10435 */ 10436 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)10437 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 10438 createEditorIfNeeded(); 10439 mEditor.mSelectAllOnFocus = selectAllOnFocus; 10440 10441 if (selectAllOnFocus && !(mText instanceof Spannable)) { 10442 setText(mText, BufferType.SPANNABLE); 10443 } 10444 } 10445 10446 /** 10447 * Set whether the cursor is visible. The default is true. Note that this property only 10448 * makes sense for editable TextView. 10449 * 10450 * @see #isCursorVisible() 10451 * 10452 * @attr ref android.R.styleable#TextView_cursorVisible 10453 */ 10454 @android.view.RemotableViewMethod setCursorVisible(boolean visible)10455 public void setCursorVisible(boolean visible) { 10456 if (visible && mEditor == null) return; // visible is the default value with no edit data 10457 createEditorIfNeeded(); 10458 if (mEditor.mCursorVisible != visible) { 10459 mEditor.mCursorVisible = visible; 10460 invalidate(); 10461 10462 mEditor.makeBlink(); 10463 10464 // InsertionPointCursorController depends on mCursorVisible 10465 mEditor.prepareCursorControllers(); 10466 } 10467 } 10468 10469 /** 10470 * @return whether or not the cursor is visible (assuming this TextView is editable) 10471 * 10472 * @see #setCursorVisible(boolean) 10473 * 10474 * @attr ref android.R.styleable#TextView_cursorVisible 10475 */ 10476 @InspectableProperty isCursorVisible()10477 public boolean isCursorVisible() { 10478 // true is the default value 10479 return mEditor == null ? true : mEditor.mCursorVisible; 10480 } 10481 canMarquee()10482 private boolean canMarquee() { 10483 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10484 return width > 0 && (mLayout.getLineWidth(0) > width 10485 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 10486 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 10487 } 10488 10489 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()10490 private void startMarquee() { 10491 // Do not ellipsize EditText 10492 if (getKeyListener() != null) return; 10493 10494 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 10495 return; 10496 } 10497 10498 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 10499 && getLineCount() == 1 && canMarquee()) { 10500 10501 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10502 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 10503 final Layout tmp = mLayout; 10504 mLayout = mSavedMarqueeModeLayout; 10505 mSavedMarqueeModeLayout = tmp; 10506 setHorizontalFadingEdgeEnabled(true); 10507 requestLayout(); 10508 invalidate(); 10509 } 10510 10511 if (mMarquee == null) mMarquee = new Marquee(this); 10512 mMarquee.start(mMarqueeRepeatLimit); 10513 } 10514 } 10515 stopMarquee()10516 private void stopMarquee() { 10517 if (mMarquee != null && !mMarquee.isStopped()) { 10518 mMarquee.stop(); 10519 } 10520 10521 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 10522 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 10523 final Layout tmp = mSavedMarqueeModeLayout; 10524 mSavedMarqueeModeLayout = mLayout; 10525 mLayout = tmp; 10526 setHorizontalFadingEdgeEnabled(false); 10527 requestLayout(); 10528 invalidate(); 10529 } 10530 } 10531 10532 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)10533 private void startStopMarquee(boolean start) { 10534 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10535 if (start) { 10536 startMarquee(); 10537 } else { 10538 stopMarquee(); 10539 } 10540 } 10541 } 10542 10543 /** 10544 * This method is called when the text is changed, in case any subclasses 10545 * would like to know. 10546 * 10547 * Within <code>text</code>, the <code>lengthAfter</code> characters 10548 * beginning at <code>start</code> have just replaced old text that had 10549 * length <code>lengthBefore</code>. It is an error to attempt to make 10550 * changes to <code>text</code> from this callback. 10551 * 10552 * @param text The text the TextView is displaying 10553 * @param start The offset of the start of the range of the text that was 10554 * modified 10555 * @param lengthBefore The length of the former text that has been replaced 10556 * @param lengthAfter The length of the replacement modified text 10557 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)10558 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 10559 // intentionally empty, template pattern method can be overridden by subclasses 10560 } 10561 10562 /** 10563 * This method is called when the selection has changed, in case any 10564 * subclasses would like to know. 10565 * </p> 10566 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 10567 * the accessibility subsystem about the selection change. 10568 * </p> 10569 * 10570 * @param selStart The new selection start location. 10571 * @param selEnd The new selection end location. 10572 */ 10573 @CallSuper onSelectionChanged(int selStart, int selEnd)10574 protected void onSelectionChanged(int selStart, int selEnd) { 10575 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 10576 } 10577 10578 /** 10579 * Adds a TextWatcher to the list of those whose methods are called 10580 * whenever this TextView's text changes. 10581 * <p> 10582 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 10583 * not called after {@link #setText} calls. Now, doing {@link #setText} 10584 * if there are any text changed listeners forces the buffer type to 10585 * Editable if it would not otherwise be and does call this method. 10586 */ addTextChangedListener(TextWatcher watcher)10587 public void addTextChangedListener(TextWatcher watcher) { 10588 if (mListeners == null) { 10589 mListeners = new ArrayList<TextWatcher>(); 10590 } 10591 10592 mListeners.add(watcher); 10593 } 10594 10595 /** 10596 * Removes the specified TextWatcher from the list of those whose 10597 * methods are called 10598 * whenever this TextView's text changes. 10599 */ removeTextChangedListener(TextWatcher watcher)10600 public void removeTextChangedListener(TextWatcher watcher) { 10601 if (mListeners != null) { 10602 int i = mListeners.indexOf(watcher); 10603 10604 if (i >= 0) { 10605 mListeners.remove(i); 10606 } 10607 } 10608 } 10609 sendBeforeTextChanged(CharSequence text, int start, int before, int after)10610 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 10611 if (mListeners != null) { 10612 final ArrayList<TextWatcher> list = mListeners; 10613 final int count = list.size(); 10614 for (int i = 0; i < count; i++) { 10615 list.get(i).beforeTextChanged(text, start, before, after); 10616 } 10617 } 10618 10619 // The spans that are inside or intersect the modified region no longer make sense 10620 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 10621 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 10622 } 10623 10624 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)10625 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 10626 if (!(mText instanceof Editable)) return; 10627 Editable text = (Editable) mText; 10628 10629 T[] spans = text.getSpans(start, end, type); 10630 final int length = spans.length; 10631 for (int i = 0; i < length; i++) { 10632 final int spanStart = text.getSpanStart(spans[i]); 10633 final int spanEnd = text.getSpanEnd(spans[i]); 10634 if (spanEnd == start || spanStart == end) break; 10635 text.removeSpan(spans[i]); 10636 } 10637 } 10638 removeAdjacentSuggestionSpans(final int pos)10639 void removeAdjacentSuggestionSpans(final int pos) { 10640 if (!(mText instanceof Editable)) return; 10641 final Editable text = (Editable) mText; 10642 10643 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 10644 final int length = spans.length; 10645 for (int i = 0; i < length; i++) { 10646 final int spanStart = text.getSpanStart(spans[i]); 10647 final int spanEnd = text.getSpanEnd(spans[i]); 10648 if (spanEnd == pos || spanStart == pos) { 10649 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 10650 text.removeSpan(spans[i]); 10651 } 10652 } 10653 } 10654 } 10655 10656 /** 10657 * Not private so it can be called from an inner class without going 10658 * through a thunk. 10659 */ sendOnTextChanged(CharSequence text, int start, int before, int after)10660 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 10661 if (mListeners != null) { 10662 final ArrayList<TextWatcher> list = mListeners; 10663 final int count = list.size(); 10664 for (int i = 0; i < count; i++) { 10665 list.get(i).onTextChanged(text, start, before, after); 10666 } 10667 } 10668 10669 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 10670 } 10671 10672 /** 10673 * Not private so it can be called from an inner class without going 10674 * through a thunk. 10675 */ sendAfterTextChanged(Editable text)10676 void sendAfterTextChanged(Editable text) { 10677 if (mListeners != null) { 10678 final ArrayList<TextWatcher> list = mListeners; 10679 final int count = list.size(); 10680 for (int i = 0; i < count; i++) { 10681 list.get(i).afterTextChanged(text); 10682 } 10683 } 10684 10685 notifyListeningManagersAfterTextChanged(); 10686 10687 hideErrorIfUnchanged(); 10688 } 10689 10690 /** 10691 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 10692 * interested on text changes. 10693 */ notifyListeningManagersAfterTextChanged()10694 private void notifyListeningManagersAfterTextChanged() { 10695 10696 // Autofill 10697 if (isAutofillable()) { 10698 // It is important to not check whether the view is important for autofill 10699 // since the user can trigger autofill manually on not important views. 10700 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10701 if (afm != null) { 10702 if (android.view.autofill.Helper.sVerbose) { 10703 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 10704 } 10705 afm.notifyValueChanged(TextView.this); 10706 } 10707 } 10708 10709 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 10710 // of using isLaidout(), so it's not called in cases where it's laid out but a 10711 // notifyAppeared was not sent. 10712 10713 // ContentCapture 10714 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 10715 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 10716 if (cm != null && cm.isContentCaptureEnabled()) { 10717 final ContentCaptureSession session = getContentCaptureSession(); 10718 if (session != null) { 10719 // TODO(b/111276913): pass flags when edited by user / add CTS test 10720 session.notifyViewTextChanged(getAutofillId(), getText()); 10721 } 10722 } 10723 } 10724 } 10725 isAutofillable()10726 private boolean isAutofillable() { 10727 // It is important to not check whether the view is important for autofill 10728 // since the user can trigger autofill manually on not important views. 10729 return getAutofillType() != AUTOFILL_TYPE_NONE; 10730 } 10731 updateAfterEdit()10732 void updateAfterEdit() { 10733 invalidate(); 10734 int curs = getSelectionStart(); 10735 10736 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 10737 registerForPreDraw(); 10738 } 10739 10740 checkForResize(); 10741 10742 if (curs >= 0) { 10743 mHighlightPathBogus = true; 10744 if (mEditor != null) mEditor.makeBlink(); 10745 bringPointIntoView(curs); 10746 } 10747 } 10748 10749 /** 10750 * Not private so it can be called from an inner class without going 10751 * through a thunk. 10752 */ handleTextChanged(CharSequence buffer, int start, int before, int after)10753 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 10754 sLastCutCopyOrTextChangedTime = 0; 10755 10756 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10757 if (ims == null || ims.mBatchEditNesting == 0) { 10758 updateAfterEdit(); 10759 } 10760 if (ims != null) { 10761 ims.mContentChanged = true; 10762 if (ims.mChangedStart < 0) { 10763 ims.mChangedStart = start; 10764 ims.mChangedEnd = start + before; 10765 } else { 10766 ims.mChangedStart = Math.min(ims.mChangedStart, start); 10767 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 10768 } 10769 ims.mChangedDelta += after - before; 10770 } 10771 resetErrorChangedFlag(); 10772 sendOnTextChanged(buffer, start, before, after); 10773 onTextChanged(buffer, start, before, after); 10774 } 10775 10776 /** 10777 * Not private so it can be called from an inner class without going 10778 * through a thunk. 10779 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)10780 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 10781 // XXX Make the start and end move together if this ends up 10782 // spending too much time invalidating. 10783 10784 boolean selChanged = false; 10785 int newSelStart = -1, newSelEnd = -1; 10786 10787 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 10788 10789 if (what == Selection.SELECTION_END) { 10790 selChanged = true; 10791 newSelEnd = newStart; 10792 10793 if (oldStart >= 0 || newStart >= 0) { 10794 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 10795 checkForResize(); 10796 registerForPreDraw(); 10797 if (mEditor != null) mEditor.makeBlink(); 10798 } 10799 } 10800 10801 if (what == Selection.SELECTION_START) { 10802 selChanged = true; 10803 newSelStart = newStart; 10804 10805 if (oldStart >= 0 || newStart >= 0) { 10806 int end = Selection.getSelectionEnd(buf); 10807 invalidateCursor(end, oldStart, newStart); 10808 } 10809 } 10810 10811 if (selChanged) { 10812 mHighlightPathBogus = true; 10813 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 10814 10815 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 10816 if (newSelStart < 0) { 10817 newSelStart = Selection.getSelectionStart(buf); 10818 } 10819 if (newSelEnd < 0) { 10820 newSelEnd = Selection.getSelectionEnd(buf); 10821 } 10822 10823 if (mEditor != null) { 10824 mEditor.refreshTextActionMode(); 10825 if (!hasSelection() 10826 && mEditor.getTextActionMode() == null && hasTransientState()) { 10827 // User generated selection has been removed. 10828 setHasTransientState(false); 10829 } 10830 } 10831 onSelectionChanged(newSelStart, newSelEnd); 10832 } 10833 } 10834 10835 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 10836 || what instanceof CharacterStyle) { 10837 if (ims == null || ims.mBatchEditNesting == 0) { 10838 invalidate(); 10839 mHighlightPathBogus = true; 10840 checkForResize(); 10841 } else { 10842 ims.mContentChanged = true; 10843 } 10844 if (mEditor != null) { 10845 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 10846 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 10847 mEditor.invalidateHandlesAndActionMode(); 10848 } 10849 } 10850 10851 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 10852 mHighlightPathBogus = true; 10853 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 10854 ims.mSelectionModeChanged = true; 10855 } 10856 10857 if (Selection.getSelectionStart(buf) >= 0) { 10858 if (ims == null || ims.mBatchEditNesting == 0) { 10859 invalidateCursor(); 10860 } else { 10861 ims.mCursorChanged = true; 10862 } 10863 } 10864 } 10865 10866 if (what instanceof ParcelableSpan) { 10867 // If this is a span that can be sent to a remote process, 10868 // the current extract editor would be interested in it. 10869 if (ims != null && ims.mExtractedTextRequest != null) { 10870 if (ims.mBatchEditNesting != 0) { 10871 if (oldStart >= 0) { 10872 if (ims.mChangedStart > oldStart) { 10873 ims.mChangedStart = oldStart; 10874 } 10875 if (ims.mChangedStart > oldEnd) { 10876 ims.mChangedStart = oldEnd; 10877 } 10878 } 10879 if (newStart >= 0) { 10880 if (ims.mChangedStart > newStart) { 10881 ims.mChangedStart = newStart; 10882 } 10883 if (ims.mChangedStart > newEnd) { 10884 ims.mChangedStart = newEnd; 10885 } 10886 } 10887 } else { 10888 if (DEBUG_EXTRACT) { 10889 Log.v(LOG_TAG, "Span change outside of batch: " 10890 + oldStart + "-" + oldEnd + "," 10891 + newStart + "-" + newEnd + " " + what); 10892 } 10893 ims.mContentChanged = true; 10894 } 10895 } 10896 } 10897 10898 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 10899 && what instanceof SpellCheckSpan) { 10900 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 10901 } 10902 } 10903 10904 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)10905 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 10906 if (isTemporarilyDetached()) { 10907 // If we are temporarily in the detach state, then do nothing. 10908 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10909 return; 10910 } 10911 10912 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 10913 10914 if (focused) { 10915 if (mSpannable != null) { 10916 MetaKeyKeyListener.resetMetaState(mSpannable); 10917 } 10918 } 10919 10920 startStopMarquee(focused); 10921 10922 if (mTransformation != null) { 10923 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 10924 } 10925 10926 super.onFocusChanged(focused, direction, previouslyFocusedRect); 10927 } 10928 10929 @Override onWindowFocusChanged(boolean hasWindowFocus)10930 public void onWindowFocusChanged(boolean hasWindowFocus) { 10931 super.onWindowFocusChanged(hasWindowFocus); 10932 10933 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 10934 10935 startStopMarquee(hasWindowFocus); 10936 } 10937 10938 @Override onVisibilityChanged(View changedView, int visibility)10939 protected void onVisibilityChanged(View changedView, int visibility) { 10940 super.onVisibilityChanged(changedView, visibility); 10941 if (mEditor != null && visibility != VISIBLE) { 10942 mEditor.hideCursorAndSpanControllers(); 10943 stopTextActionMode(); 10944 } 10945 } 10946 10947 /** 10948 * Use {@link BaseInputConnection#removeComposingSpans 10949 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 10950 * state from this text view. 10951 */ clearComposingText()10952 public void clearComposingText() { 10953 if (mText instanceof Spannable) { 10954 BaseInputConnection.removeComposingSpans(mSpannable); 10955 } 10956 } 10957 10958 @Override setSelected(boolean selected)10959 public void setSelected(boolean selected) { 10960 boolean wasSelected = isSelected(); 10961 10962 super.setSelected(selected); 10963 10964 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10965 if (selected) { 10966 startMarquee(); 10967 } else { 10968 stopMarquee(); 10969 } 10970 } 10971 } 10972 10973 /** 10974 * Called from onTouchEvent() to prevent the touches by secondary fingers. 10975 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 10976 * This method is a lock to avoid processing multiple fingers on both text view and handles. 10977 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 10978 * 10979 * @param event The motion event that is being handled and carries the pointer info. 10980 * @param fromHandleView true if the event is delivered to selection handle or insertion 10981 * handle; false if this event is delivered to TextView. 10982 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 10983 * event, otherwise false. 10984 * - Always returns true for the first finger. 10985 * - For secondary fingers, if the first or current finger is from TextView, returns false. 10986 * This is to make touch mutually exclusive between the TextView and the handles, but 10987 * not among the handles. 10988 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)10989 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 10990 boolean res = true; 10991 if (mPrimePointerId == NO_POINTER_ID) { 10992 mPrimePointerId = event.getPointerId(0); 10993 mIsPrimePointerFromHandleView = fromHandleView; 10994 } else if (mPrimePointerId != event.getPointerId(0)) { 10995 res = mIsPrimePointerFromHandleView && fromHandleView; 10996 } 10997 if (event.getActionMasked() == MotionEvent.ACTION_UP 10998 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 10999 mPrimePointerId = -1; 11000 } 11001 return res; 11002 } 11003 11004 @Override onTouchEvent(MotionEvent event)11005 public boolean onTouchEvent(MotionEvent event) { 11006 if (DEBUG_CURSOR) { 11007 logCursor("onTouchEvent", "%d: %s (%f,%f)", 11008 event.getSequenceNumber(), 11009 MotionEvent.actionToString(event.getActionMasked()), 11010 event.getX(), event.getY()); 11011 } 11012 final int action = event.getActionMasked(); 11013 if (mEditor != null) { 11014 if (!isFromPrimePointer(event, false)) { 11015 return true; 11016 } 11017 11018 mEditor.onTouchEvent(event); 11019 11020 if (mEditor.mInsertionPointCursorController != null 11021 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 11022 return true; 11023 } 11024 if (mEditor.mSelectionModifierCursorController != null 11025 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 11026 return true; 11027 } 11028 } 11029 11030 final boolean superResult = super.onTouchEvent(event); 11031 if (DEBUG_CURSOR) { 11032 logCursor("onTouchEvent", "superResult=%s", superResult); 11033 } 11034 11035 /* 11036 * Don't handle the release after a long press, because it will move the selection away from 11037 * whatever the menu action was trying to affect. If the long press should have triggered an 11038 * insertion action mode, we can now actually show it. 11039 */ 11040 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 11041 mEditor.mDiscardNextActionUp = false; 11042 if (DEBUG_CURSOR) { 11043 logCursor("onTouchEvent", "release after long press detected"); 11044 } 11045 if (mEditor.mIsInsertionActionModeStartPending) { 11046 mEditor.startInsertionActionMode(); 11047 mEditor.mIsInsertionActionModeStartPending = false; 11048 } 11049 return superResult; 11050 } 11051 11052 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 11053 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 11054 11055 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 11056 && mText instanceof Spannable && mLayout != null) { 11057 boolean handled = false; 11058 11059 if (mMovement != null) { 11060 handled |= mMovement.onTouchEvent(this, mSpannable, event); 11061 } 11062 11063 final boolean textIsSelectable = isTextSelectable(); 11064 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 11065 // The LinkMovementMethod which should handle taps on links has not been installed 11066 // on non editable text that support text selection. 11067 // We reproduce its behavior here to open links for these. 11068 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 11069 getSelectionEnd(), ClickableSpan.class); 11070 11071 if (links.length > 0) { 11072 links[0].onClick(this); 11073 handled = true; 11074 } 11075 } 11076 11077 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 11078 // Show the IME, except when selecting in read-only text. 11079 final InputMethodManager imm = getInputMethodManager(); 11080 viewClicked(imm); 11081 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 11082 imm.showSoftInput(this, 0); 11083 } 11084 11085 // The above condition ensures that the mEditor is not null 11086 mEditor.onTouchUpEvent(event); 11087 11088 handled = true; 11089 } 11090 11091 if (handled) { 11092 return true; 11093 } 11094 } 11095 11096 return superResult; 11097 } 11098 11099 @Override onGenericMotionEvent(MotionEvent event)11100 public boolean onGenericMotionEvent(MotionEvent event) { 11101 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 11102 try { 11103 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 11104 return true; 11105 } 11106 } catch (AbstractMethodError ex) { 11107 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 11108 // Ignore its absence in case third party applications implemented the 11109 // interface directly. 11110 } 11111 } 11112 return super.onGenericMotionEvent(event); 11113 } 11114 11115 @Override onCreateContextMenu(ContextMenu menu)11116 protected void onCreateContextMenu(ContextMenu menu) { 11117 if (mEditor != null) { 11118 mEditor.onCreateContextMenu(menu); 11119 } 11120 } 11121 11122 @Override showContextMenu()11123 public boolean showContextMenu() { 11124 if (mEditor != null) { 11125 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 11126 } 11127 return super.showContextMenu(); 11128 } 11129 11130 @Override showContextMenu(float x, float y)11131 public boolean showContextMenu(float x, float y) { 11132 if (mEditor != null) { 11133 mEditor.setContextMenuAnchor(x, y); 11134 } 11135 return super.showContextMenu(x, y); 11136 } 11137 11138 /** 11139 * @return True iff this TextView contains a text that can be edited, or if this is 11140 * a selectable TextView. 11141 */ 11142 @UnsupportedAppUsage isTextEditable()11143 boolean isTextEditable() { 11144 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 11145 } 11146 11147 /** 11148 * Returns true, only while processing a touch gesture, if the initial 11149 * touch down event caused focus to move to the text view and as a result 11150 * its selection changed. Only valid while processing the touch gesture 11151 * of interest, in an editable text view. 11152 */ didTouchFocusSelect()11153 public boolean didTouchFocusSelect() { 11154 return mEditor != null && mEditor.mTouchFocusSelected; 11155 } 11156 11157 @Override cancelLongPress()11158 public void cancelLongPress() { 11159 super.cancelLongPress(); 11160 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 11161 } 11162 11163 @Override onTrackballEvent(MotionEvent event)11164 public boolean onTrackballEvent(MotionEvent event) { 11165 if (mMovement != null && mSpannable != null && mLayout != null) { 11166 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 11167 return true; 11168 } 11169 } 11170 11171 return super.onTrackballEvent(event); 11172 } 11173 11174 /** 11175 * Sets the Scroller used for producing a scrolling animation 11176 * 11177 * @param s A Scroller instance 11178 */ setScroller(Scroller s)11179 public void setScroller(Scroller s) { 11180 mScroller = s; 11181 } 11182 11183 @Override getLeftFadingEdgeStrength()11184 protected float getLeftFadingEdgeStrength() { 11185 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11186 final Marquee marquee = mMarquee; 11187 if (marquee.shouldDrawLeftFade()) { 11188 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 11189 } else { 11190 return 0.0f; 11191 } 11192 } else if (getLineCount() == 1) { 11193 final float lineLeft = getLayout().getLineLeft(0); 11194 if (lineLeft > mScrollX) return 0.0f; 11195 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 11196 } 11197 return super.getLeftFadingEdgeStrength(); 11198 } 11199 11200 @Override getRightFadingEdgeStrength()11201 protected float getRightFadingEdgeStrength() { 11202 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 11203 final Marquee marquee = mMarquee; 11204 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 11205 } else if (getLineCount() == 1) { 11206 final float rightEdge = mScrollX + 11207 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 11208 final float lineRight = getLayout().getLineRight(0); 11209 if (lineRight < rightEdge) return 0.0f; 11210 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 11211 } 11212 return super.getRightFadingEdgeStrength(); 11213 } 11214 11215 /** 11216 * Calculates the fading edge strength as the ratio of the distance between two 11217 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 11218 * value for the distance calculation. 11219 * 11220 * @param position1 A horizontal position. 11221 * @param position2 A horizontal position. 11222 * @return Fading edge strength between [0.0f, 1.0f]. 11223 */ 11224 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)11225 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 11226 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 11227 if (horizontalFadingEdgeLength == 0) return 0.0f; 11228 final float diff = Math.abs(position1 - position2); 11229 if (diff > horizontalFadingEdgeLength) return 1.0f; 11230 return diff / horizontalFadingEdgeLength; 11231 } 11232 isMarqueeFadeEnabled()11233 private boolean isMarqueeFadeEnabled() { 11234 return mEllipsize == TextUtils.TruncateAt.MARQUEE 11235 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 11236 } 11237 11238 @Override computeHorizontalScrollRange()11239 protected int computeHorizontalScrollRange() { 11240 if (mLayout != null) { 11241 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 11242 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 11243 } 11244 11245 return super.computeHorizontalScrollRange(); 11246 } 11247 11248 @Override computeVerticalScrollRange()11249 protected int computeVerticalScrollRange() { 11250 if (mLayout != null) { 11251 return mLayout.getHeight(); 11252 } 11253 return super.computeVerticalScrollRange(); 11254 } 11255 11256 @Override computeVerticalScrollExtent()11257 protected int computeVerticalScrollExtent() { 11258 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11259 } 11260 11261 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)11262 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 11263 super.findViewsWithText(outViews, searched, flags); 11264 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 11265 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 11266 String searchedLowerCase = searched.toString().toLowerCase(); 11267 String textLowerCase = mText.toString().toLowerCase(); 11268 if (textLowerCase.contains(searchedLowerCase)) { 11269 outViews.add(this); 11270 } 11271 } 11272 } 11273 11274 /** 11275 * Type of the text buffer that defines the characteristics of the text such as static, 11276 * styleable, or editable. 11277 */ 11278 public enum BufferType { 11279 NORMAL, SPANNABLE, EDITABLE 11280 } 11281 11282 /** 11283 * Returns the TextView_textColor attribute from the TypedArray, if set, or 11284 * the TextAppearance_textColor from the TextView_textAppearance attribute, 11285 * if TextView_textColor was not set directly. 11286 * 11287 * @removed 11288 */ getTextColors(Context context, TypedArray attrs)11289 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 11290 if (attrs == null) { 11291 // Preserve behavior prior to removal of this API. 11292 throw new NullPointerException(); 11293 } 11294 11295 // It's not safe to use this method from apps. The parameter 'attrs' 11296 // must have been obtained using the TextView filter array which is not 11297 // available to the SDK. As such, we grab a default TypedArray with the 11298 // right filter instead here. 11299 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 11300 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 11301 if (colors == null) { 11302 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 11303 if (ap != 0) { 11304 final TypedArray appearance = context.obtainStyledAttributes( 11305 ap, R.styleable.TextAppearance); 11306 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 11307 appearance.recycle(); 11308 } 11309 } 11310 a.recycle(); 11311 11312 return colors; 11313 } 11314 11315 /** 11316 * Returns the default color from the TextView_textColor attribute from the 11317 * AttributeSet, if set, or the default color from the 11318 * TextAppearance_textColor from the TextView_textAppearance attribute, if 11319 * TextView_textColor was not set directly. 11320 * 11321 * @removed 11322 */ getTextColor(Context context, TypedArray attrs, int def)11323 public static int getTextColor(Context context, TypedArray attrs, int def) { 11324 final ColorStateList colors = getTextColors(context, attrs); 11325 if (colors == null) { 11326 return def; 11327 } else { 11328 return colors.getDefaultColor(); 11329 } 11330 } 11331 11332 @Override onKeyShortcut(int keyCode, KeyEvent event)11333 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 11334 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 11335 // Handle Ctrl-only shortcuts. 11336 switch (keyCode) { 11337 case KeyEvent.KEYCODE_A: 11338 if (canSelectText()) { 11339 return onTextContextMenuItem(ID_SELECT_ALL); 11340 } 11341 break; 11342 case KeyEvent.KEYCODE_Z: 11343 if (canUndo()) { 11344 return onTextContextMenuItem(ID_UNDO); 11345 } 11346 break; 11347 case KeyEvent.KEYCODE_X: 11348 if (canCut()) { 11349 return onTextContextMenuItem(ID_CUT); 11350 } 11351 break; 11352 case KeyEvent.KEYCODE_C: 11353 if (canCopy()) { 11354 return onTextContextMenuItem(ID_COPY); 11355 } 11356 break; 11357 case KeyEvent.KEYCODE_V: 11358 if (canPaste()) { 11359 return onTextContextMenuItem(ID_PASTE); 11360 } 11361 break; 11362 } 11363 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 11364 // Handle Ctrl-Shift shortcuts. 11365 switch (keyCode) { 11366 case KeyEvent.KEYCODE_Z: 11367 if (canRedo()) { 11368 return onTextContextMenuItem(ID_REDO); 11369 } 11370 break; 11371 case KeyEvent.KEYCODE_V: 11372 if (canPaste()) { 11373 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 11374 } 11375 } 11376 } 11377 return super.onKeyShortcut(keyCode, event); 11378 } 11379 11380 /** 11381 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 11382 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 11383 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 11384 * sufficient. 11385 */ canSelectText()11386 boolean canSelectText() { 11387 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 11388 } 11389 11390 /** 11391 * Test based on the <i>intrinsic</i> charateristics of the TextView. 11392 * The text must be spannable and the movement method must allow for arbitary selection. 11393 * 11394 * See also {@link #canSelectText()}. 11395 */ textCanBeSelected()11396 boolean textCanBeSelected() { 11397 // prepareCursorController() relies on this method. 11398 // If you change this condition, make sure prepareCursorController is called anywhere 11399 // the value of this condition might be changed. 11400 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 11401 return isTextEditable() 11402 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 11403 } 11404 11405 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)11406 private Locale getTextServicesLocale(boolean allowNullLocale) { 11407 // Start fetching the text services locale asynchronously. 11408 updateTextServicesLocaleAsync(); 11409 // If !allowNullLocale and there is no cached text services locale, just return the default 11410 // locale. 11411 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 11412 : mCurrentSpellCheckerLocaleCache; 11413 } 11414 11415 /** 11416 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 11417 * this {@link TextView}. 11418 * 11419 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 11420 * other apps may need to set this so that the system can user right user's resources and 11421 * services such as input methods and spell checkers.</p> 11422 * 11423 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 11424 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 11425 * @hide 11426 */ 11427 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)11428 public final void setTextOperationUser(@Nullable UserHandle user) { 11429 if (Objects.equals(mTextOperationUser, user)) { 11430 return; 11431 } 11432 if (user != null && !Process.myUserHandle().equals(user)) { 11433 // Just for preventing people from accidentally using this hidden API without 11434 // the required permission. The same permission is also checked in the system server. 11435 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 11436 != PackageManager.PERMISSION_GRANTED) { 11437 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 11438 + " userId=" + user.getIdentifier() 11439 + " callingUserId" + UserHandle.myUserId()); 11440 } 11441 } 11442 mTextOperationUser = user; 11443 // Invalidate some resources 11444 mCurrentSpellCheckerLocaleCache = null; 11445 if (mEditor != null) { 11446 mEditor.onTextOperationUserChanged(); 11447 } 11448 } 11449 11450 @Nullable getTextServicesManagerForUser()11451 final TextServicesManager getTextServicesManagerForUser() { 11452 return getServiceManagerForUser("android", TextServicesManager.class); 11453 } 11454 11455 @Nullable getClipboardManagerForUser()11456 final ClipboardManager getClipboardManagerForUser() { 11457 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 11458 } 11459 11460 @Nullable getTextClassificationManagerForUser()11461 final TextClassificationManager getTextClassificationManagerForUser() { 11462 return getServiceManagerForUser( 11463 getContext().getPackageName(), TextClassificationManager.class); 11464 } 11465 11466 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)11467 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 11468 if (mTextOperationUser == null) { 11469 return getContext().getSystemService(managerClazz); 11470 } 11471 try { 11472 Context context = getContext().createPackageContextAsUser( 11473 packageName, 0 /* flags */, mTextOperationUser); 11474 return context.getSystemService(managerClazz); 11475 } catch (PackageManager.NameNotFoundException e) { 11476 return null; 11477 } 11478 } 11479 11480 /** 11481 * Starts {@link Activity} as a text-operation user if it is specified with 11482 * {@link #setTextOperationUser(UserHandle)}. 11483 * 11484 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 11485 * 11486 * @param intent The description of the activity to start. 11487 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)11488 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 11489 if (mTextOperationUser != null) { 11490 getContext().startActivityAsUser(intent, mTextOperationUser); 11491 } else { 11492 getContext().startActivity(intent); 11493 } 11494 } 11495 11496 /** 11497 * This is a temporary method. Future versions may support multi-locale text. 11498 * Caveat: This method may not return the latest text services locale, but this should be 11499 * acceptable and it's more important to make this method asynchronous. 11500 * 11501 * @return The locale that should be used for a word iterator 11502 * in this TextView, based on the current spell checker settings, 11503 * the current IME's locale, or the system default locale. 11504 * Please note that a word iterator in this TextView is different from another word iterator 11505 * used by SpellChecker.java of TextView. This method should be used for the former. 11506 * @hide 11507 */ 11508 // TODO: Support multi-locale 11509 // TODO: Update the text services locale immediately after the keyboard locale is switched 11510 // by catching intent of keyboard switch event getTextServicesLocale()11511 public Locale getTextServicesLocale() { 11512 return getTextServicesLocale(false /* allowNullLocale */); 11513 } 11514 11515 /** 11516 * @return {@code true} if this TextView is specialized for showing and interacting with the 11517 * extracted text in a full-screen input method. 11518 * @hide 11519 */ isInExtractedMode()11520 public boolean isInExtractedMode() { 11521 return false; 11522 } 11523 11524 /** 11525 * @return {@code true} if this widget supports auto-sizing text and has been configured to 11526 * auto-size. 11527 */ isAutoSizeEnabled()11528 private boolean isAutoSizeEnabled() { 11529 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 11530 } 11531 11532 /** 11533 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 11534 * @hide 11535 */ supportsAutoSizeText()11536 protected boolean supportsAutoSizeText() { 11537 return true; 11538 } 11539 11540 /** 11541 * This is a temporary method. Future versions may support multi-locale text. 11542 * Caveat: This method may not return the latest spell checker locale, but this should be 11543 * acceptable and it's more important to make this method asynchronous. 11544 * 11545 * @return The locale that should be used for a spell checker in this TextView, 11546 * based on the current spell checker settings, the current IME's locale, or the system default 11547 * locale. 11548 * @hide 11549 */ getSpellCheckerLocale()11550 public Locale getSpellCheckerLocale() { 11551 return getTextServicesLocale(true /* allowNullLocale */); 11552 } 11553 updateTextServicesLocaleAsync()11554 private void updateTextServicesLocaleAsync() { 11555 // AsyncTask.execute() uses a serial executor which means we don't have 11556 // to lock around updateTextServicesLocaleLocked() to prevent it from 11557 // being executed n times in parallel. 11558 AsyncTask.execute(new Runnable() { 11559 @Override 11560 public void run() { 11561 updateTextServicesLocaleLocked(); 11562 } 11563 }); 11564 } 11565 11566 @UnsupportedAppUsage updateTextServicesLocaleLocked()11567 private void updateTextServicesLocaleLocked() { 11568 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 11569 if (textServicesManager == null) { 11570 return; 11571 } 11572 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 11573 final Locale locale; 11574 if (subtype != null) { 11575 locale = subtype.getLocaleObject(); 11576 } else { 11577 locale = null; 11578 } 11579 mCurrentSpellCheckerLocaleCache = locale; 11580 } 11581 onLocaleChanged()11582 void onLocaleChanged() { 11583 mEditor.onLocaleChanged(); 11584 } 11585 11586 /** 11587 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 11588 * Made available to achieve a consistent behavior. 11589 * @hide 11590 */ getWordIterator()11591 public WordIterator getWordIterator() { 11592 if (mEditor != null) { 11593 return mEditor.getWordIterator(); 11594 } else { 11595 return null; 11596 } 11597 } 11598 11599 /** @hide */ 11600 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)11601 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 11602 super.onPopulateAccessibilityEventInternal(event); 11603 11604 final CharSequence text = getTextForAccessibility(); 11605 if (!TextUtils.isEmpty(text)) { 11606 event.getText().add(text); 11607 } 11608 } 11609 11610 @Override getAccessibilityClassName()11611 public CharSequence getAccessibilityClassName() { 11612 return TextView.class.getName(); 11613 } 11614 11615 /** @hide */ 11616 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)11617 protected void onProvideStructure(@NonNull ViewStructure structure, 11618 @ViewStructureType int viewFor, int flags) { 11619 super.onProvideStructure(structure, viewFor, flags); 11620 11621 final boolean isPassword = hasPasswordTransformationMethod() 11622 || isPasswordInputType(getInputType()); 11623 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11624 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11625 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11626 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 11627 } 11628 if (mTextId != Resources.ID_NULL) { 11629 try { 11630 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 11631 } catch (Resources.NotFoundException e) { 11632 if (android.view.autofill.Helper.sVerbose) { 11633 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 11634 + mTextId + ": " + e.getMessage()); 11635 } 11636 } 11637 } 11638 } 11639 11640 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11641 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11642 if (mLayout == null) { 11643 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11644 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 11645 } 11646 assumeLayout(); 11647 } 11648 Layout layout = mLayout; 11649 final int lineCount = layout.getLineCount(); 11650 if (lineCount <= 1) { 11651 // Simple case: this is a single line. 11652 final CharSequence text = getText(); 11653 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11654 structure.setText(text); 11655 } else { 11656 structure.setText(text, getSelectionStart(), getSelectionEnd()); 11657 } 11658 } else { 11659 // Complex case: multi-line, could be scrolled or within a scroll container 11660 // so some lines are not visible. 11661 final int[] tmpCords = new int[2]; 11662 getLocationInWindow(tmpCords); 11663 final int topWindowLocation = tmpCords[1]; 11664 View root = this; 11665 ViewParent viewParent = getParent(); 11666 while (viewParent instanceof View) { 11667 root = (View) viewParent; 11668 viewParent = root.getParent(); 11669 } 11670 final int windowHeight = root.getHeight(); 11671 final int topLine; 11672 final int bottomLine; 11673 if (topWindowLocation >= 0) { 11674 // The top of the view is fully within its window; start text at line 0. 11675 topLine = getLineAtCoordinateUnclamped(0); 11676 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 11677 } else { 11678 // The top of hte window has scrolled off the top of the window; figure out 11679 // the starting line for this. 11680 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 11681 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 11682 } 11683 // We want to return some contextual lines above/below the lines that are 11684 // actually visible. 11685 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 11686 if (expandedTopLine < 0) { 11687 expandedTopLine = 0; 11688 } 11689 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 11690 if (expandedBottomLine >= lineCount) { 11691 expandedBottomLine = lineCount - 1; 11692 } 11693 11694 // Convert lines into character offsets. 11695 int expandedTopChar = layout.getLineStart(expandedTopLine); 11696 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 11697 11698 // Take into account selection -- if there is a selection, we need to expand 11699 // the text we are returning to include that selection. 11700 final int selStart = getSelectionStart(); 11701 final int selEnd = getSelectionEnd(); 11702 if (selStart < selEnd) { 11703 if (selStart < expandedTopChar) { 11704 expandedTopChar = selStart; 11705 } 11706 if (selEnd > expandedBottomChar) { 11707 expandedBottomChar = selEnd; 11708 } 11709 } 11710 11711 // Get the text and trim it to the range we are reporting. 11712 CharSequence text = getText(); 11713 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 11714 text = text.subSequence(expandedTopChar, expandedBottomChar); 11715 } 11716 11717 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 11718 structure.setText(text); 11719 } else { 11720 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 11721 11722 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 11723 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 11724 final int baselineOffset = getBaselineOffset(); 11725 for (int i = topLine; i <= bottomLine; i++) { 11726 lineOffsets[i - topLine] = layout.getLineStart(i); 11727 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 11728 } 11729 structure.setTextLines(lineOffsets, lineBaselines); 11730 } 11731 } 11732 11733 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 11734 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11735 // Extract style information that applies to the TextView as a whole. 11736 int style = 0; 11737 int typefaceStyle = getTypefaceStyle(); 11738 if ((typefaceStyle & Typeface.BOLD) != 0) { 11739 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11740 } 11741 if ((typefaceStyle & Typeface.ITALIC) != 0) { 11742 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 11743 } 11744 11745 // Global styles can also be set via TextView.setPaintFlags(). 11746 int paintFlags = mTextPaint.getFlags(); 11747 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 11748 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 11749 } 11750 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 11751 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 11752 } 11753 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 11754 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 11755 } 11756 11757 // TextView does not have its own text background color. A background is either part 11758 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 11759 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 11760 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 11761 } 11762 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 11763 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 11764 structure.setMinTextEms(getMinEms()); 11765 structure.setMaxTextEms(getMaxEms()); 11766 int maxLength = -1; 11767 for (InputFilter filter: getFilters()) { 11768 if (filter instanceof InputFilter.LengthFilter) { 11769 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 11770 break; 11771 } 11772 } 11773 structure.setMaxTextLength(maxLength); 11774 } 11775 } 11776 if (mHintId != Resources.ID_NULL) { 11777 try { 11778 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 11779 } catch (Resources.NotFoundException e) { 11780 if (android.view.autofill.Helper.sVerbose) { 11781 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 11782 + mHintId + ": " + e.getMessage()); 11783 } 11784 } 11785 } 11786 structure.setHint(getHint()); 11787 structure.setInputType(getInputType()); 11788 } 11789 canRequestAutofill()11790 boolean canRequestAutofill() { 11791 if (!isAutofillable()) { 11792 return false; 11793 } 11794 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11795 if (afm != null) { 11796 return afm.isEnabled(); 11797 } 11798 return false; 11799 } 11800 requestAutofill()11801 private void requestAutofill() { 11802 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 11803 if (afm != null) { 11804 afm.requestAutofill(this); 11805 } 11806 } 11807 11808 @Override autofill(AutofillValue value)11809 public void autofill(AutofillValue value) { 11810 if (!value.isText() || !isTextEditable()) { 11811 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 11812 return; 11813 } 11814 11815 final CharSequence autofilledValue = value.getTextValue(); 11816 11817 // First autofill it... 11818 setText(autofilledValue, mBufferType, true, 0); 11819 11820 // ...then move cursor to the end. 11821 final CharSequence text = getText(); 11822 if ((text instanceof Spannable)) { 11823 Selection.setSelection((Spannable) text, text.length()); 11824 } 11825 } 11826 11827 @Override getAutofillType()11828 public @AutofillType int getAutofillType() { 11829 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 11830 } 11831 11832 /** 11833 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 11834 * {@code char}s if longer. 11835 * 11836 * @return current text, {@code null} if the text is not editable 11837 * 11838 * @see View#getAutofillValue() 11839 */ 11840 @Override 11841 @Nullable getAutofillValue()11842 public AutofillValue getAutofillValue() { 11843 if (isTextEditable()) { 11844 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 11845 return AutofillValue.forText(text); 11846 } 11847 return null; 11848 } 11849 11850 /** @hide */ 11851 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)11852 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 11853 super.onInitializeAccessibilityEventInternal(event); 11854 11855 final boolean isPassword = hasPasswordTransformationMethod(); 11856 event.setPassword(isPassword); 11857 11858 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 11859 event.setFromIndex(Selection.getSelectionStart(mText)); 11860 event.setToIndex(Selection.getSelectionEnd(mText)); 11861 event.setItemCount(mText.length()); 11862 } 11863 } 11864 11865 /** @hide */ 11866 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)11867 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 11868 super.onInitializeAccessibilityNodeInfoInternal(info); 11869 11870 final boolean isPassword = hasPasswordTransformationMethod(); 11871 info.setPassword(isPassword); 11872 info.setText(getTextForAccessibility()); 11873 info.setHintText(mHint); 11874 info.setShowingHintText(isShowingHint()); 11875 11876 if (mBufferType == BufferType.EDITABLE) { 11877 info.setEditable(true); 11878 if (isEnabled()) { 11879 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 11880 } 11881 } 11882 11883 if (mEditor != null) { 11884 info.setInputType(mEditor.mInputType); 11885 11886 if (mEditor.mError != null) { 11887 info.setContentInvalid(true); 11888 info.setError(mEditor.mError); 11889 } 11890 // TextView will expose this action if it is editable and has focus. 11891 if (isTextEditable() && isFocused()) { 11892 CharSequence imeActionLabel = mContext.getResources().getString( 11893 com.android.internal.R.string.keyboardview_keycode_enter); 11894 if (getImeActionLabel() != null) { 11895 imeActionLabel = getImeActionLabel(); 11896 } 11897 AccessibilityNodeInfo.AccessibilityAction action = 11898 new AccessibilityNodeInfo.AccessibilityAction( 11899 R.id.accessibilityActionImeEnter, imeActionLabel); 11900 info.addAction(action); 11901 } 11902 } 11903 11904 if (!TextUtils.isEmpty(mText)) { 11905 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 11906 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 11907 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 11908 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 11909 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 11910 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 11911 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 11912 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 11913 info.setAvailableExtraData(Arrays.asList( 11914 EXTRA_DATA_RENDERING_INFO_KEY, 11915 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 11916 )); 11917 } else { 11918 info.setAvailableExtraData(Arrays.asList( 11919 EXTRA_DATA_RENDERING_INFO_KEY 11920 )); 11921 } 11922 11923 if (isFocused()) { 11924 if (canCopy()) { 11925 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 11926 } 11927 if (canPaste()) { 11928 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 11929 } 11930 if (canCut()) { 11931 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 11932 } 11933 if (canShare()) { 11934 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 11935 ACCESSIBILITY_ACTION_SHARE, 11936 getResources().getString(com.android.internal.R.string.share))); 11937 } 11938 if (canProcessText()) { // also implies mEditor is not null. 11939 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 11940 } 11941 } 11942 11943 // Check for known input filter types. 11944 final int numFilters = mFilters.length; 11945 for (int i = 0; i < numFilters; i++) { 11946 final InputFilter filter = mFilters[i]; 11947 if (filter instanceof InputFilter.LengthFilter) { 11948 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 11949 } 11950 } 11951 11952 if (!isSingleLine()) { 11953 info.setMultiLine(true); 11954 } 11955 11956 // A view should not be exposed as clickable/long-clickable to a service because of a 11957 // LinkMovementMethod. 11958 if ((info.isClickable() || info.isLongClickable()) 11959 && mMovement instanceof LinkMovementMethod) { 11960 if (!hasOnClickListeners()) { 11961 info.setClickable(false); 11962 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 11963 } 11964 if (!hasOnLongClickListeners()) { 11965 info.setLongClickable(false); 11966 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 11967 } 11968 } 11969 } 11970 11971 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)11972 public void addExtraDataToAccessibilityNodeInfo( 11973 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 11974 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 11975 int positionInfoStartIndex = arguments.getInt( 11976 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 11977 int positionInfoLength = arguments.getInt( 11978 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 11979 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 11980 || (positionInfoStartIndex >= mText.length())) { 11981 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 11982 return; 11983 } 11984 RectF[] boundingRects = new RectF[positionInfoLength]; 11985 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 11986 populateCharacterBounds(builder, positionInfoStartIndex, 11987 positionInfoStartIndex + positionInfoLength, 11988 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 11989 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 11990 for (int i = 0; i < positionInfoLength; i++) { 11991 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 11992 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 11993 RectF bounds = cursorAnchorInfo 11994 .getCharacterBounds(positionInfoStartIndex + i); 11995 if (bounds != null) { 11996 mapRectFromViewToScreenCoords(bounds, true); 11997 boundingRects[i] = bounds; 11998 } 11999 } 12000 } 12001 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 12002 return; 12003 } 12004 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 12005 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 12006 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 12007 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 12008 extraRenderingInfo.setTextSizeInPx(getTextSize()); 12009 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 12010 info.setExtraRenderingInfo(extraRenderingInfo); 12011 } 12012 } 12013 12014 /** 12015 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 12016 * 12017 * @param builder The builder to populate 12018 * @param startIndex The starting character index to populate 12019 * @param endIndex The ending character index to populate 12020 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 12021 * content 12022 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 12023 * @hide 12024 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)12025 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 12026 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 12027 float viewportToContentVerticalOffset) { 12028 final int minLine = mLayout.getLineForOffset(startIndex); 12029 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 12030 for (int line = minLine; line <= maxLine; ++line) { 12031 final int lineStart = mLayout.getLineStart(line); 12032 final int lineEnd = mLayout.getLineEnd(line); 12033 final int offsetStart = Math.max(lineStart, startIndex); 12034 final int offsetEnd = Math.min(lineEnd, endIndex); 12035 final boolean ltrLine = 12036 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 12037 final float[] widths = new float[offsetEnd - offsetStart]; 12038 mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); 12039 final float top = mLayout.getLineTop(line); 12040 final float bottom = mLayout.getLineBottom(line); 12041 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 12042 final float charWidth = widths[offset - offsetStart]; 12043 final boolean isRtl = mLayout.isRtlCharAt(offset); 12044 final float primary = mLayout.getPrimaryHorizontal(offset); 12045 final float secondary = mLayout.getSecondaryHorizontal(offset); 12046 // TODO: This doesn't work perfectly for text with custom styles and 12047 // TAB chars. 12048 final float left; 12049 final float right; 12050 if (ltrLine) { 12051 if (isRtl) { 12052 left = secondary - charWidth; 12053 right = secondary; 12054 } else { 12055 left = primary; 12056 right = primary + charWidth; 12057 } 12058 } else { 12059 if (!isRtl) { 12060 left = secondary; 12061 right = secondary + charWidth; 12062 } else { 12063 left = primary - charWidth; 12064 right = primary; 12065 } 12066 } 12067 // TODO: Check top-right and bottom-left as well. 12068 final float localLeft = left + viewportToContentHorizontalOffset; 12069 final float localRight = right + viewportToContentHorizontalOffset; 12070 final float localTop = top + viewportToContentVerticalOffset; 12071 final float localBottom = bottom + viewportToContentVerticalOffset; 12072 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 12073 final boolean isBottomRightVisible = 12074 isPositionVisible(localRight, localBottom); 12075 int characterBoundsFlags = 0; 12076 if (isTopLeftVisible || isBottomRightVisible) { 12077 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 12078 } 12079 if (!isTopLeftVisible || !isBottomRightVisible) { 12080 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 12081 } 12082 if (isRtl) { 12083 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 12084 } 12085 // Here offset is the index in Java chars. 12086 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 12087 localBottom, characterBoundsFlags); 12088 } 12089 } 12090 } 12091 12092 /** 12093 * @hide 12094 */ isPositionVisible(final float positionX, final float positionY)12095 public boolean isPositionVisible(final float positionX, final float positionY) { 12096 synchronized (TEMP_POSITION) { 12097 final float[] position = TEMP_POSITION; 12098 position[0] = positionX; 12099 position[1] = positionY; 12100 View view = this; 12101 12102 while (view != null) { 12103 if (view != this) { 12104 // Local scroll is already taken into account in positionX/Y 12105 position[0] -= view.getScrollX(); 12106 position[1] -= view.getScrollY(); 12107 } 12108 12109 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 12110 || position[1] > view.getHeight()) { 12111 return false; 12112 } 12113 12114 if (!view.getMatrix().isIdentity()) { 12115 view.getMatrix().mapPoints(position); 12116 } 12117 12118 position[0] += view.getLeft(); 12119 position[1] += view.getTop(); 12120 12121 final ViewParent parent = view.getParent(); 12122 if (parent instanceof View) { 12123 view = (View) parent; 12124 } else { 12125 // We've reached the ViewRoot, stop iterating 12126 view = null; 12127 } 12128 } 12129 } 12130 12131 // We've been able to walk up the view hierarchy and the position was never clipped 12132 return true; 12133 } 12134 12135 /** 12136 * Performs an accessibility action after it has been offered to the 12137 * delegate. 12138 * 12139 * @hide 12140 */ 12141 @Override performAccessibilityActionInternal(int action, Bundle arguments)12142 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 12143 if (mEditor != null 12144 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 12145 return true; 12146 } 12147 switch (action) { 12148 case AccessibilityNodeInfo.ACTION_CLICK: { 12149 return performAccessibilityActionClick(arguments); 12150 } 12151 case AccessibilityNodeInfo.ACTION_COPY: { 12152 if (isFocused() && canCopy()) { 12153 if (onTextContextMenuItem(ID_COPY)) { 12154 return true; 12155 } 12156 } 12157 } return false; 12158 case AccessibilityNodeInfo.ACTION_PASTE: { 12159 if (isFocused() && canPaste()) { 12160 if (onTextContextMenuItem(ID_PASTE)) { 12161 return true; 12162 } 12163 } 12164 } return false; 12165 case AccessibilityNodeInfo.ACTION_CUT: { 12166 if (isFocused() && canCut()) { 12167 if (onTextContextMenuItem(ID_CUT)) { 12168 return true; 12169 } 12170 } 12171 } return false; 12172 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 12173 ensureIterableTextForAccessibilitySelectable(); 12174 CharSequence text = getIterableTextForAccessibility(); 12175 if (text == null) { 12176 return false; 12177 } 12178 final int start = (arguments != null) ? arguments.getInt( 12179 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 12180 final int end = (arguments != null) ? arguments.getInt( 12181 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 12182 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 12183 // No arguments clears the selection. 12184 if (start == end && end == -1) { 12185 Selection.removeSelection((Spannable) text); 12186 return true; 12187 } 12188 if (start >= 0 && start <= end && end <= text.length()) { 12189 Selection.setSelection((Spannable) text, start, end); 12190 // Make sure selection mode is engaged. 12191 if (mEditor != null) { 12192 mEditor.startSelectionActionModeAsync(false); 12193 } 12194 return true; 12195 } 12196 } 12197 } return false; 12198 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 12199 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 12200 ensureIterableTextForAccessibilitySelectable(); 12201 return super.performAccessibilityActionInternal(action, arguments); 12202 } 12203 case ACCESSIBILITY_ACTION_SHARE: { 12204 if (isFocused() && canShare()) { 12205 if (onTextContextMenuItem(ID_SHARE)) { 12206 return true; 12207 } 12208 } 12209 } return false; 12210 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 12211 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 12212 return false; 12213 } 12214 CharSequence text = (arguments != null) ? arguments.getCharSequence( 12215 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 12216 setText(text); 12217 if (mText != null) { 12218 int updatedTextLength = mText.length(); 12219 if (updatedTextLength > 0) { 12220 Selection.setSelection(mSpannable, updatedTextLength); 12221 } 12222 } 12223 } return true; 12224 case R.id.accessibilityActionImeEnter: { 12225 if (isFocused() && isTextEditable()) { 12226 onEditorAction(getImeActionId()); 12227 } 12228 } return true; 12229 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 12230 if (isLongClickable()) { 12231 boolean handled; 12232 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 12233 mEditor.mIsBeingLongClickedByAccessibility = true; 12234 try { 12235 handled = performLongClick(); 12236 } finally { 12237 mEditor.mIsBeingLongClickedByAccessibility = false; 12238 } 12239 } else { 12240 handled = performLongClick(); 12241 } 12242 return handled; 12243 } 12244 } 12245 return false; 12246 default: { 12247 return super.performAccessibilityActionInternal(action, arguments); 12248 } 12249 } 12250 } 12251 performAccessibilityActionClick(Bundle arguments)12252 private boolean performAccessibilityActionClick(Bundle arguments) { 12253 boolean handled = false; 12254 12255 if (!isEnabled()) { 12256 return false; 12257 } 12258 12259 if (isClickable() || isLongClickable()) { 12260 // Simulate View.onTouchEvent for an ACTION_UP event 12261 if (isFocusable() && !isFocused()) { 12262 requestFocus(); 12263 } 12264 12265 performClick(); 12266 handled = true; 12267 } 12268 12269 // Show the IME, except when selecting in read-only text. 12270 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 12271 && (isTextEditable() || isTextSelectable()) && isFocused()) { 12272 final InputMethodManager imm = getInputMethodManager(); 12273 viewClicked(imm); 12274 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 12275 handled |= imm.showSoftInput(this, 0); 12276 } 12277 } 12278 12279 return handled; 12280 } 12281 hasSpannableText()12282 private boolean hasSpannableText() { 12283 return mText != null && mText instanceof Spannable; 12284 } 12285 12286 /** @hide */ 12287 @Override sendAccessibilityEventInternal(int eventType)12288 public void sendAccessibilityEventInternal(int eventType) { 12289 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 12290 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 12291 } 12292 12293 super.sendAccessibilityEventInternal(eventType); 12294 } 12295 12296 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)12297 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 12298 // Do not send scroll events since first they are not interesting for 12299 // accessibility and second such events a generated too frequently. 12300 // For details see the implementation of bringTextIntoView(). 12301 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 12302 return; 12303 } 12304 super.sendAccessibilityEventUnchecked(event); 12305 } 12306 12307 /** 12308 * Returns the text that should be exposed to accessibility services. 12309 * <p> 12310 * This approximates what is displayed visually. If the user has specified 12311 * that accessibility services should speak passwords, this method will 12312 * bypass any password transformation method and return unobscured text. 12313 * 12314 * @return the text that should be exposed to accessibility services, may 12315 * be {@code null} if no text is set 12316 */ 12317 @Nullable 12318 @UnsupportedAppUsage getTextForAccessibility()12319 private CharSequence getTextForAccessibility() { 12320 // If the text is empty, we must be showing the hint text. 12321 if (TextUtils.isEmpty(mText)) { 12322 return mHint; 12323 } 12324 12325 // Otherwise, return whatever text is being displayed. 12326 return TextUtils.trimToParcelableSize(mTransformed); 12327 } 12328 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)12329 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 12330 int fromIndex, int removedCount, int addedCount) { 12331 AccessibilityEvent event = 12332 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 12333 event.setFromIndex(fromIndex); 12334 event.setRemovedCount(removedCount); 12335 event.setAddedCount(addedCount); 12336 event.setBeforeText(beforeText); 12337 sendAccessibilityEventUnchecked(event); 12338 } 12339 getInputMethodManager()12340 private InputMethodManager getInputMethodManager() { 12341 return getContext().getSystemService(InputMethodManager.class); 12342 } 12343 12344 /** 12345 * Returns whether this text view is a current input method target. The 12346 * default implementation just checks with {@link InputMethodManager}. 12347 * @return True if the TextView is a current input method target; false otherwise. 12348 */ isInputMethodTarget()12349 public boolean isInputMethodTarget() { 12350 InputMethodManager imm = getInputMethodManager(); 12351 return imm != null && imm.isActive(this); 12352 } 12353 12354 static final int ID_SELECT_ALL = android.R.id.selectAll; 12355 static final int ID_UNDO = android.R.id.undo; 12356 static final int ID_REDO = android.R.id.redo; 12357 static final int ID_CUT = android.R.id.cut; 12358 static final int ID_COPY = android.R.id.copy; 12359 static final int ID_PASTE = android.R.id.paste; 12360 static final int ID_SHARE = android.R.id.shareText; 12361 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 12362 static final int ID_REPLACE = android.R.id.replaceText; 12363 static final int ID_ASSIST = android.R.id.textAssist; 12364 static final int ID_AUTOFILL = android.R.id.autofill; 12365 12366 /** 12367 * Called when a context menu option for the text view is selected. Currently 12368 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 12369 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 12370 * 12371 * @return true if the context menu item action was performed. 12372 */ onTextContextMenuItem(int id)12373 public boolean onTextContextMenuItem(int id) { 12374 int min = 0; 12375 int max = mText.length(); 12376 12377 if (isFocused()) { 12378 final int selStart = getSelectionStart(); 12379 final int selEnd = getSelectionEnd(); 12380 12381 min = Math.max(0, Math.min(selStart, selEnd)); 12382 max = Math.max(0, Math.max(selStart, selEnd)); 12383 } 12384 12385 switch (id) { 12386 case ID_SELECT_ALL: 12387 final boolean hadSelection = hasSelection(); 12388 selectAllText(); 12389 if (mEditor != null && hadSelection) { 12390 mEditor.invalidateActionModeAsync(); 12391 } 12392 return true; 12393 12394 case ID_UNDO: 12395 if (mEditor != null) { 12396 mEditor.undo(); 12397 } 12398 return true; // Returns true even if nothing was undone. 12399 12400 case ID_REDO: 12401 if (mEditor != null) { 12402 mEditor.redo(); 12403 } 12404 return true; // Returns true even if nothing was undone. 12405 12406 case ID_PASTE: 12407 paste(min, max, true /* withFormatting */); 12408 return true; 12409 12410 case ID_PASTE_AS_PLAIN_TEXT: 12411 paste(min, max, false /* withFormatting */); 12412 return true; 12413 12414 case ID_CUT: 12415 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 12416 if (setPrimaryClip(cutData)) { 12417 deleteText_internal(min, max); 12418 } else { 12419 Toast.makeText(getContext(), 12420 com.android.internal.R.string.failed_to_copy_to_clipboard, 12421 Toast.LENGTH_SHORT).show(); 12422 } 12423 return true; 12424 12425 case ID_COPY: 12426 // For link action mode in a non-selectable/non-focusable TextView, 12427 // make sure that we set the appropriate min/max. 12428 final int selStart = getSelectionStart(); 12429 final int selEnd = getSelectionEnd(); 12430 min = Math.max(0, Math.min(selStart, selEnd)); 12431 max = Math.max(0, Math.max(selStart, selEnd)); 12432 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 12433 if (setPrimaryClip(copyData)) { 12434 stopTextActionMode(); 12435 } else { 12436 Toast.makeText(getContext(), 12437 com.android.internal.R.string.failed_to_copy_to_clipboard, 12438 Toast.LENGTH_SHORT).show(); 12439 } 12440 return true; 12441 12442 case ID_REPLACE: 12443 if (mEditor != null) { 12444 mEditor.replace(); 12445 } 12446 return true; 12447 12448 case ID_SHARE: 12449 shareSelectedText(); 12450 return true; 12451 12452 case ID_AUTOFILL: 12453 requestAutofill(); 12454 stopTextActionMode(); 12455 return true; 12456 } 12457 return false; 12458 } 12459 12460 @UnsupportedAppUsage getTransformedText(int start, int end)12461 CharSequence getTransformedText(int start, int end) { 12462 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 12463 } 12464 12465 @Override performLongClick()12466 public boolean performLongClick() { 12467 if (DEBUG_CURSOR) { 12468 logCursor("performLongClick", null); 12469 } 12470 12471 boolean handled = false; 12472 boolean performedHapticFeedback = false; 12473 12474 if (mEditor != null) { 12475 mEditor.mIsBeingLongClicked = true; 12476 } 12477 12478 if (super.performLongClick()) { 12479 handled = true; 12480 performedHapticFeedback = true; 12481 } 12482 12483 if (mEditor != null) { 12484 handled |= mEditor.performLongClick(handled); 12485 mEditor.mIsBeingLongClicked = false; 12486 } 12487 12488 if (handled) { 12489 if (!performedHapticFeedback) { 12490 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 12491 } 12492 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 12493 } else { 12494 MetricsLogger.action( 12495 mContext, 12496 MetricsEvent.TEXT_LONGPRESS, 12497 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 12498 } 12499 12500 return handled; 12501 } 12502 12503 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)12504 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 12505 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 12506 if (mEditor != null) { 12507 mEditor.onScrollChanged(); 12508 } 12509 } 12510 12511 /** 12512 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 12513 * by the IME or by the spell checker as the user types. This is done by adding 12514 * {@link SuggestionSpan}s to the text. 12515 * 12516 * When suggestions are enabled (default), this list of suggestions will be displayed when the 12517 * user asks for them on these parts of the text. This value depends on the inputType of this 12518 * TextView. 12519 * 12520 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 12521 * 12522 * In addition, the type variation must be one of 12523 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 12524 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 12525 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 12526 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 12527 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 12528 * 12529 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 12530 * 12531 * @return true if the suggestions popup window is enabled, based on the inputType. 12532 */ isSuggestionsEnabled()12533 public boolean isSuggestionsEnabled() { 12534 if (mEditor == null) return false; 12535 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 12536 return false; 12537 } 12538 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 12539 12540 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 12541 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 12542 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 12543 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 12544 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 12545 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 12546 } 12547 12548 /** 12549 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12550 * selection is initiated in this View. 12551 * 12552 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 12553 * Paste, Replace and Share actions, depending on what this View supports. 12554 * 12555 * <p>A custom implementation can add new entries in the default menu in its 12556 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 12557 * method. The default actions can also be removed from the menu using 12558 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12559 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 12560 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 12561 * 12562 * <p>Returning false from 12563 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 12564 * will prevent the action mode from being started. 12565 * 12566 * <p>Action click events should be handled by the custom implementation of 12567 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 12568 * android.view.MenuItem)}. 12569 * 12570 * <p>Note that text selection mode is not started when a TextView receives focus and the 12571 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 12572 * that case, to allow for quick replacement. 12573 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)12574 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 12575 createEditorIfNeeded(); 12576 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 12577 } 12578 12579 /** 12580 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 12581 * 12582 * @return The current custom selection callback. 12583 */ getCustomSelectionActionModeCallback()12584 public ActionMode.Callback getCustomSelectionActionModeCallback() { 12585 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 12586 } 12587 12588 /** 12589 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 12590 * insertion is initiated in this View. 12591 * The standard implementation populates the menu with a subset of Select All, 12592 * Paste and Replace actions, depending on what this View supports. 12593 * 12594 * <p>A custom implementation can add new entries in the default menu in its 12595 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 12596 * android.view.Menu)} method. The default actions can also be removed from the menu using 12597 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 12598 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 12599 * 12600 * <p>Returning false from 12601 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 12602 * android.view.Menu)} will prevent the action mode from being started.</p> 12603 * 12604 * <p>Action click events should be handled by the custom implementation of 12605 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 12606 * android.view.MenuItem)}.</p> 12607 * 12608 * <p>Note that text insertion mode is not started when a TextView receives focus and the 12609 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 12610 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)12611 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 12612 createEditorIfNeeded(); 12613 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 12614 } 12615 12616 /** 12617 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 12618 * 12619 * @return The current custom insertion callback. 12620 */ getCustomInsertionActionModeCallback()12621 public ActionMode.Callback getCustomInsertionActionModeCallback() { 12622 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 12623 } 12624 12625 /** 12626 * Sets the {@link TextClassifier} for this TextView. 12627 */ setTextClassifier(@ullable TextClassifier textClassifier)12628 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 12629 mTextClassifier = textClassifier; 12630 } 12631 12632 /** 12633 * Returns the {@link TextClassifier} used by this TextView. 12634 * If no TextClassifier has been set, this TextView uses the default set by the 12635 * {@link TextClassificationManager}. 12636 */ 12637 @NonNull getTextClassifier()12638 public TextClassifier getTextClassifier() { 12639 if (mTextClassifier == null) { 12640 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12641 if (tcm != null) { 12642 return tcm.getTextClassifier(); 12643 } 12644 return TextClassifier.NO_OP; 12645 } 12646 return mTextClassifier; 12647 } 12648 12649 /** 12650 * Returns a session-aware text classifier. 12651 * This method creates one if none already exists or the current one is destroyed. 12652 */ 12653 @NonNull getTextClassificationSession()12654 TextClassifier getTextClassificationSession() { 12655 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 12656 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 12657 if (tcm != null) { 12658 final String widgetType; 12659 if (isTextEditable()) { 12660 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 12661 } else if (isTextSelectable()) { 12662 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 12663 } else { 12664 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 12665 } 12666 mTextClassificationContext = new TextClassificationContext.Builder( 12667 mContext.getPackageName(), widgetType) 12668 .build(); 12669 if (mTextClassifier != null) { 12670 mTextClassificationSession = tcm.createTextClassificationSession( 12671 mTextClassificationContext, mTextClassifier); 12672 } else { 12673 mTextClassificationSession = tcm.createTextClassificationSession( 12674 mTextClassificationContext); 12675 } 12676 } else { 12677 mTextClassificationSession = TextClassifier.NO_OP; 12678 } 12679 } 12680 return mTextClassificationSession; 12681 } 12682 12683 /** 12684 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 12685 * @see #getTextClassificationSession() 12686 */ 12687 @Nullable getTextClassificationContext()12688 TextClassificationContext getTextClassificationContext() { 12689 return mTextClassificationContext; 12690 } 12691 12692 /** 12693 * Returns true if this TextView uses a no-op TextClassifier. 12694 */ usesNoOpTextClassifier()12695 boolean usesNoOpTextClassifier() { 12696 return getTextClassifier() == TextClassifier.NO_OP; 12697 } 12698 12699 /** 12700 * Starts an ActionMode for the specified TextLinkSpan. 12701 * 12702 * @return Whether or not we're attempting to start the action mode. 12703 * @hide 12704 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)12705 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12706 Preconditions.checkNotNull(clickedSpan); 12707 12708 if (!(mText instanceof Spanned)) { 12709 return false; 12710 } 12711 12712 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 12713 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 12714 12715 if (start < 0 || end > mText.length() || start >= end) { 12716 return false; 12717 } 12718 12719 createEditorIfNeeded(); 12720 mEditor.startLinkActionModeAsync(start, end); 12721 return true; 12722 } 12723 12724 /** 12725 * Handles a click on the specified TextLinkSpan. 12726 * 12727 * @return Whether or not the click is being handled. 12728 * @hide 12729 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)12730 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 12731 Preconditions.checkNotNull(clickedSpan); 12732 if (mText instanceof Spanned) { 12733 final Spanned spanned = (Spanned) mText; 12734 final int start = spanned.getSpanStart(clickedSpan); 12735 final int end = spanned.getSpanEnd(clickedSpan); 12736 if (start >= 0 && end <= mText.length() && start < end) { 12737 final TextClassification.Request request = new TextClassification.Request.Builder( 12738 mText, start, end) 12739 .setDefaultLocales(getTextLocales()) 12740 .build(); 12741 final Supplier<TextClassification> supplier = () -> 12742 getTextClassificationSession().classifyText(request); 12743 final Consumer<TextClassification> consumer = classification -> { 12744 if (classification != null) { 12745 if (!classification.getActions().isEmpty()) { 12746 try { 12747 classification.getActions().get(0).getActionIntent().send(); 12748 } catch (PendingIntent.CanceledException e) { 12749 Log.e(LOG_TAG, "Error sending PendingIntent", e); 12750 } 12751 } else { 12752 Log.d(LOG_TAG, "No link action to perform"); 12753 } 12754 } else { 12755 // classification == null 12756 Log.d(LOG_TAG, "Timeout while classifying text"); 12757 } 12758 }; 12759 CompletableFuture.supplyAsync(supplier) 12760 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 12761 .thenAccept(consumer); 12762 return true; 12763 } 12764 } 12765 return false; 12766 } 12767 12768 /** 12769 * @hide 12770 */ 12771 @UnsupportedAppUsage stopTextActionMode()12772 protected void stopTextActionMode() { 12773 if (mEditor != null) { 12774 mEditor.stopTextActionMode(); 12775 } 12776 } 12777 12778 /** @hide */ hideFloatingToolbar(int durationMs)12779 public void hideFloatingToolbar(int durationMs) { 12780 if (mEditor != null) { 12781 mEditor.hideFloatingToolbar(durationMs); 12782 } 12783 } 12784 canUndo()12785 boolean canUndo() { 12786 return mEditor != null && mEditor.canUndo(); 12787 } 12788 canRedo()12789 boolean canRedo() { 12790 return mEditor != null && mEditor.canRedo(); 12791 } 12792 canCut()12793 boolean canCut() { 12794 if (hasPasswordTransformationMethod()) { 12795 return false; 12796 } 12797 12798 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 12799 && mEditor.mKeyListener != null) { 12800 return true; 12801 } 12802 12803 return false; 12804 } 12805 canCopy()12806 boolean canCopy() { 12807 if (hasPasswordTransformationMethod()) { 12808 return false; 12809 } 12810 12811 if (mText.length() > 0 && hasSelection() && mEditor != null) { 12812 return true; 12813 } 12814 12815 return false; 12816 } 12817 canShare()12818 boolean canShare() { 12819 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 12820 return false; 12821 } 12822 return canCopy(); 12823 } 12824 isDeviceProvisioned()12825 boolean isDeviceProvisioned() { 12826 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 12827 mDeviceProvisionedState = Settings.Global.getInt( 12828 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 12829 ? DEVICE_PROVISIONED_YES 12830 : DEVICE_PROVISIONED_NO; 12831 } 12832 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 12833 } 12834 12835 @UnsupportedAppUsage canPaste()12836 boolean canPaste() { 12837 return (mText instanceof Editable 12838 && mEditor != null && mEditor.mKeyListener != null 12839 && getSelectionStart() >= 0 12840 && getSelectionEnd() >= 0 12841 && getClipboardManagerForUser().hasPrimaryClip()); 12842 } 12843 canPasteAsPlainText()12844 boolean canPasteAsPlainText() { 12845 if (!canPaste()) { 12846 return false; 12847 } 12848 12849 final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); 12850 final ClipDescription description = clipData.getDescription(); 12851 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 12852 final CharSequence text = clipData.getItemAt(0).getText(); 12853 if (isPlainType && (text instanceof Spanned)) { 12854 Spanned spanned = (Spanned) text; 12855 if (TextUtils.hasStyleSpan(spanned)) { 12856 return true; 12857 } 12858 } 12859 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 12860 } 12861 canProcessText()12862 boolean canProcessText() { 12863 if (getId() == View.NO_ID) { 12864 return false; 12865 } 12866 return canShare(); 12867 } 12868 canSelectAllText()12869 boolean canSelectAllText() { 12870 return canSelectText() && !hasPasswordTransformationMethod() 12871 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 12872 } 12873 selectAllText()12874 boolean selectAllText() { 12875 if (mEditor != null) { 12876 // Hide the toolbar before changing the selection to avoid flickering. 12877 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 12878 } 12879 final int length = mText.length(); 12880 Selection.setSelection(mSpannable, 0, length); 12881 return length > 0; 12882 } 12883 replaceSelectionWithText(CharSequence text)12884 void replaceSelectionWithText(CharSequence text) { 12885 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 12886 } 12887 12888 /** 12889 * Paste clipboard content between min and max positions. 12890 */ paste(int min, int max, boolean withFormatting)12891 private void paste(int min, int max, boolean withFormatting) { 12892 ClipboardManager clipboard = getClipboardManagerForUser(); 12893 ClipData clip = clipboard.getPrimaryClip(); 12894 if (clip != null) { 12895 boolean didFirst = false; 12896 for (int i = 0; i < clip.getItemCount(); i++) { 12897 final CharSequence paste; 12898 if (withFormatting) { 12899 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 12900 } else { 12901 // Get an item as text and remove all spans by toString(). 12902 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 12903 paste = (text instanceof Spanned) ? text.toString() : text; 12904 } 12905 if (paste != null) { 12906 if (!didFirst) { 12907 Selection.setSelection(mSpannable, max); 12908 ((Editable) mText).replace(min, max, paste); 12909 didFirst = true; 12910 } else { 12911 ((Editable) mText).insert(getSelectionEnd(), "\n"); 12912 ((Editable) mText).insert(getSelectionEnd(), paste); 12913 } 12914 } 12915 } 12916 sLastCutCopyOrTextChangedTime = 0; 12917 } 12918 } 12919 shareSelectedText()12920 private void shareSelectedText() { 12921 String selectedText = getSelectedText(); 12922 if (selectedText != null && !selectedText.isEmpty()) { 12923 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 12924 sharingIntent.setType("text/plain"); 12925 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 12926 selectedText = TextUtils.trimToParcelableSize(selectedText); 12927 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 12928 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 12929 Selection.setSelection(mSpannable, getSelectionEnd()); 12930 } 12931 } 12932 12933 @CheckResult setPrimaryClip(ClipData clip)12934 private boolean setPrimaryClip(ClipData clip) { 12935 ClipboardManager clipboard = getClipboardManagerForUser(); 12936 try { 12937 clipboard.setPrimaryClip(clip); 12938 } catch (Throwable t) { 12939 return false; 12940 } 12941 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 12942 return true; 12943 } 12944 12945 /** 12946 * Get the character offset closest to the specified absolute position. A typical use case is to 12947 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 12948 * 12949 * @param x The horizontal absolute position of a point on screen 12950 * @param y The vertical absolute position of a point on screen 12951 * @return the character offset for the character whose position is closest to the specified 12952 * position. Returns -1 if there is no layout. 12953 */ getOffsetForPosition(float x, float y)12954 public int getOffsetForPosition(float x, float y) { 12955 if (getLayout() == null) return -1; 12956 final int line = getLineAtCoordinate(y); 12957 final int offset = getOffsetAtCoordinate(line, x); 12958 return offset; 12959 } 12960 convertToLocalHorizontalCoordinate(float x)12961 float convertToLocalHorizontalCoordinate(float x) { 12962 x -= getTotalPaddingLeft(); 12963 // Clamp the position to inside of the view. 12964 x = Math.max(0.0f, x); 12965 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 12966 x += getScrollX(); 12967 return x; 12968 } 12969 12970 @UnsupportedAppUsage getLineAtCoordinate(float y)12971 int getLineAtCoordinate(float y) { 12972 y -= getTotalPaddingTop(); 12973 // Clamp the position to inside of the view. 12974 y = Math.max(0.0f, y); 12975 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 12976 y += getScrollY(); 12977 return getLayout().getLineForVertical((int) y); 12978 } 12979 getLineAtCoordinateUnclamped(float y)12980 int getLineAtCoordinateUnclamped(float y) { 12981 y -= getTotalPaddingTop(); 12982 y += getScrollY(); 12983 return getLayout().getLineForVertical((int) y); 12984 } 12985 getOffsetAtCoordinate(int line, float x)12986 int getOffsetAtCoordinate(int line, float x) { 12987 x = convertToLocalHorizontalCoordinate(x); 12988 return getLayout().getOffsetForHorizontal(line, x); 12989 } 12990 12991 @Override onDragEvent(DragEvent event)12992 public boolean onDragEvent(DragEvent event) { 12993 switch (event.getAction()) { 12994 case DragEvent.ACTION_DRAG_STARTED: 12995 return mEditor != null && mEditor.hasInsertionController(); 12996 12997 case DragEvent.ACTION_DRAG_ENTERED: 12998 TextView.this.requestFocus(); 12999 return true; 13000 13001 case DragEvent.ACTION_DRAG_LOCATION: 13002 if (mText instanceof Spannable) { 13003 final int offset = getOffsetForPosition(event.getX(), event.getY()); 13004 Selection.setSelection(mSpannable, offset); 13005 } 13006 return true; 13007 13008 case DragEvent.ACTION_DROP: 13009 if (mEditor != null) mEditor.onDrop(event); 13010 return true; 13011 13012 case DragEvent.ACTION_DRAG_ENDED: 13013 case DragEvent.ACTION_DRAG_EXITED: 13014 default: 13015 return true; 13016 } 13017 } 13018 isInBatchEditMode()13019 boolean isInBatchEditMode() { 13020 if (mEditor == null) return false; 13021 final Editor.InputMethodState ims = mEditor.mInputMethodState; 13022 if (ims != null) { 13023 return ims.mBatchEditNesting > 0; 13024 } 13025 return mEditor.mInBatchEditControllers; 13026 } 13027 13028 @Override onRtlPropertiesChanged(int layoutDirection)13029 public void onRtlPropertiesChanged(int layoutDirection) { 13030 super.onRtlPropertiesChanged(layoutDirection); 13031 13032 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 13033 if (mTextDir != newTextDir) { 13034 mTextDir = newTextDir; 13035 if (mLayout != null) { 13036 checkForRelayout(); 13037 } 13038 } 13039 } 13040 13041 /** 13042 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 13043 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 13044 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 13045 * return value may not be the same as the one TextView uses if the View's layout direction is 13046 * not resolved or detached from parent root view. 13047 */ getTextDirectionHeuristic()13048 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 13049 if (hasPasswordTransformationMethod()) { 13050 // passwords fields should be LTR 13051 return TextDirectionHeuristics.LTR; 13052 } 13053 13054 if (mEditor != null 13055 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 13056 == EditorInfo.TYPE_CLASS_PHONE) { 13057 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 13058 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 13059 // RTL digits. 13060 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 13061 final String zero = symbols.getDigitStrings()[0]; 13062 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 13063 // direction. 13064 final int firstCodepoint = zero.codePointAt(0); 13065 final byte digitDirection = Character.getDirectionality(firstCodepoint); 13066 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 13067 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 13068 return TextDirectionHeuristics.RTL; 13069 } else { 13070 return TextDirectionHeuristics.LTR; 13071 } 13072 } 13073 13074 // Always need to resolve layout direction first 13075 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 13076 13077 // Now, we can select the heuristic 13078 switch (getTextDirection()) { 13079 default: 13080 case TEXT_DIRECTION_FIRST_STRONG: 13081 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 13082 TextDirectionHeuristics.FIRSTSTRONG_LTR); 13083 case TEXT_DIRECTION_ANY_RTL: 13084 return TextDirectionHeuristics.ANYRTL_LTR; 13085 case TEXT_DIRECTION_LTR: 13086 return TextDirectionHeuristics.LTR; 13087 case TEXT_DIRECTION_RTL: 13088 return TextDirectionHeuristics.RTL; 13089 case TEXT_DIRECTION_LOCALE: 13090 return TextDirectionHeuristics.LOCALE; 13091 case TEXT_DIRECTION_FIRST_STRONG_LTR: 13092 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 13093 case TEXT_DIRECTION_FIRST_STRONG_RTL: 13094 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 13095 } 13096 } 13097 13098 /** 13099 * @hide 13100 */ 13101 @Override onResolveDrawables(int layoutDirection)13102 public void onResolveDrawables(int layoutDirection) { 13103 // No need to resolve twice 13104 if (mLastLayoutDirection == layoutDirection) { 13105 return; 13106 } 13107 mLastLayoutDirection = layoutDirection; 13108 13109 // Resolve drawables 13110 if (mDrawables != null) { 13111 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 13112 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 13113 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 13114 applyCompoundDrawableTint(); 13115 } 13116 } 13117 } 13118 13119 /** 13120 * Prepares a drawable for display by propagating layout direction and 13121 * drawable state. 13122 * 13123 * @param dr the drawable to prepare 13124 */ prepareDrawableForDisplay(@ullable Drawable dr)13125 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 13126 if (dr == null) { 13127 return; 13128 } 13129 13130 dr.setLayoutDirection(getLayoutDirection()); 13131 13132 if (dr.isStateful()) { 13133 dr.setState(getDrawableState()); 13134 dr.jumpToCurrentState(); 13135 } 13136 } 13137 13138 /** 13139 * @hide 13140 */ resetResolvedDrawables()13141 protected void resetResolvedDrawables() { 13142 super.resetResolvedDrawables(); 13143 mLastLayoutDirection = -1; 13144 } 13145 13146 /** 13147 * @hide 13148 */ viewClicked(InputMethodManager imm)13149 protected void viewClicked(InputMethodManager imm) { 13150 if (imm != null) { 13151 imm.viewClicked(this); 13152 } 13153 } 13154 13155 /** 13156 * Deletes the range of text [start, end[. 13157 * @hide 13158 */ 13159 @UnsupportedAppUsage deleteText_internal(int start, int end)13160 protected void deleteText_internal(int start, int end) { 13161 ((Editable) mText).delete(start, end); 13162 } 13163 13164 /** 13165 * Replaces the range of text [start, end[ by replacement text 13166 * @hide 13167 */ replaceText_internal(int start, int end, CharSequence text)13168 protected void replaceText_internal(int start, int end, CharSequence text) { 13169 ((Editable) mText).replace(start, end, text); 13170 } 13171 13172 /** 13173 * Sets a span on the specified range of text 13174 * @hide 13175 */ setSpan_internal(Object span, int start, int end, int flags)13176 protected void setSpan_internal(Object span, int start, int end, int flags) { 13177 ((Editable) mText).setSpan(span, start, end, flags); 13178 } 13179 13180 /** 13181 * Moves the cursor to the specified offset position in text 13182 * @hide 13183 */ setCursorPosition_internal(int start, int end)13184 protected void setCursorPosition_internal(int start, int end) { 13185 Selection.setSelection(((Editable) mText), start, end); 13186 } 13187 13188 /** 13189 * An Editor should be created as soon as any of the editable-specific fields (grouped 13190 * inside the Editor object) is assigned to a non-default value. 13191 * This method will create the Editor if needed. 13192 * 13193 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 13194 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 13195 * Editor for backward compatibility, as soon as one of these fields is assigned. 13196 * 13197 * Also note that for performance reasons, the mEditor is created when needed, but not 13198 * reset when no more edit-specific fields are needed. 13199 */ 13200 @UnsupportedAppUsage createEditorIfNeeded()13201 private void createEditorIfNeeded() { 13202 if (mEditor == null) { 13203 mEditor = new Editor(this); 13204 } 13205 } 13206 13207 /** 13208 * @hide 13209 */ 13210 @Override 13211 @UnsupportedAppUsage getIterableTextForAccessibility()13212 public CharSequence getIterableTextForAccessibility() { 13213 return mText; 13214 } 13215 ensureIterableTextForAccessibilitySelectable()13216 private void ensureIterableTextForAccessibilitySelectable() { 13217 if (!(mText instanceof Spannable)) { 13218 setText(mText, BufferType.SPANNABLE); 13219 } 13220 } 13221 13222 /** 13223 * @hide 13224 */ 13225 @Override getIteratorForGranularity(int granularity)13226 public TextSegmentIterator getIteratorForGranularity(int granularity) { 13227 switch (granularity) { 13228 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 13229 Spannable text = (Spannable) getIterableTextForAccessibility(); 13230 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13231 AccessibilityIterators.LineTextSegmentIterator iterator = 13232 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 13233 iterator.initialize(text, getLayout()); 13234 return iterator; 13235 } 13236 } break; 13237 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 13238 Spannable text = (Spannable) getIterableTextForAccessibility(); 13239 if (!TextUtils.isEmpty(text) && getLayout() != null) { 13240 AccessibilityIterators.PageTextSegmentIterator iterator = 13241 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 13242 iterator.initialize(this); 13243 return iterator; 13244 } 13245 } break; 13246 } 13247 return super.getIteratorForGranularity(granularity); 13248 } 13249 13250 /** 13251 * @hide 13252 */ 13253 @Override getAccessibilitySelectionStart()13254 public int getAccessibilitySelectionStart() { 13255 return getSelectionStart(); 13256 } 13257 13258 /** 13259 * @hide 13260 */ isAccessibilitySelectionExtendable()13261 public boolean isAccessibilitySelectionExtendable() { 13262 return true; 13263 } 13264 13265 /** 13266 * @hide 13267 */ 13268 @Override getAccessibilitySelectionEnd()13269 public int getAccessibilitySelectionEnd() { 13270 return getSelectionEnd(); 13271 } 13272 13273 /** 13274 * @hide 13275 */ 13276 @Override setAccessibilitySelection(int start, int end)13277 public void setAccessibilitySelection(int start, int end) { 13278 if (getAccessibilitySelectionStart() == start 13279 && getAccessibilitySelectionEnd() == end) { 13280 return; 13281 } 13282 CharSequence text = getIterableTextForAccessibility(); 13283 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 13284 Selection.setSelection((Spannable) text, start, end); 13285 } else { 13286 Selection.removeSelection((Spannable) text); 13287 } 13288 // Hide all selection controllers used for adjusting selection 13289 // since we are doing so explicitlty by other means and these 13290 // controllers interact with how selection behaves. 13291 if (mEditor != null) { 13292 mEditor.hideCursorAndSpanControllers(); 13293 mEditor.stopTextActionMode(); 13294 } 13295 } 13296 13297 /** @hide */ 13298 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)13299 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 13300 super.encodeProperties(stream); 13301 13302 TruncateAt ellipsize = getEllipsize(); 13303 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 13304 stream.addProperty("text:textSize", getTextSize()); 13305 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 13306 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 13307 stream.addProperty("text:selectionStart", getSelectionStart()); 13308 stream.addProperty("text:selectionEnd", getSelectionEnd()); 13309 stream.addProperty("text:curTextColor", mCurTextColor); 13310 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 13311 stream.addProperty("text:gravity", mGravity); 13312 } 13313 13314 /** 13315 * User interface state that is stored by TextView for implementing 13316 * {@link View#onSaveInstanceState}. 13317 */ 13318 public static class SavedState extends BaseSavedState { 13319 int selStart = -1; 13320 int selEnd = -1; 13321 @UnsupportedAppUsage 13322 CharSequence text; 13323 boolean frozenWithFocus; 13324 CharSequence error; 13325 ParcelableParcel editorState; // Optional state from Editor. 13326 SavedState(Parcelable superState)13327 SavedState(Parcelable superState) { 13328 super(superState); 13329 } 13330 13331 @Override writeToParcel(Parcel out, int flags)13332 public void writeToParcel(Parcel out, int flags) { 13333 super.writeToParcel(out, flags); 13334 out.writeInt(selStart); 13335 out.writeInt(selEnd); 13336 out.writeInt(frozenWithFocus ? 1 : 0); 13337 TextUtils.writeToParcel(text, out, flags); 13338 13339 if (error == null) { 13340 out.writeInt(0); 13341 } else { 13342 out.writeInt(1); 13343 TextUtils.writeToParcel(error, out, flags); 13344 } 13345 13346 if (editorState == null) { 13347 out.writeInt(0); 13348 } else { 13349 out.writeInt(1); 13350 editorState.writeToParcel(out, flags); 13351 } 13352 } 13353 13354 @Override toString()13355 public String toString() { 13356 String str = "TextView.SavedState{" 13357 + Integer.toHexString(System.identityHashCode(this)) 13358 + " start=" + selStart + " end=" + selEnd; 13359 if (text != null) { 13360 str += " text=" + text; 13361 } 13362 return str + "}"; 13363 } 13364 13365 @SuppressWarnings("hiding") 13366 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 13367 new Parcelable.Creator<SavedState>() { 13368 public SavedState createFromParcel(Parcel in) { 13369 return new SavedState(in); 13370 } 13371 13372 public SavedState[] newArray(int size) { 13373 return new SavedState[size]; 13374 } 13375 }; 13376 SavedState(Parcel in)13377 private SavedState(Parcel in) { 13378 super(in); 13379 selStart = in.readInt(); 13380 selEnd = in.readInt(); 13381 frozenWithFocus = (in.readInt() != 0); 13382 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13383 13384 if (in.readInt() != 0) { 13385 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 13386 } 13387 13388 if (in.readInt() != 0) { 13389 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 13390 } 13391 } 13392 } 13393 13394 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 13395 private char[] mChars; 13396 private int mStart, mLength; 13397 CharWrapper(char[] chars, int start, int len)13398 public CharWrapper(char[] chars, int start, int len) { 13399 mChars = chars; 13400 mStart = start; 13401 mLength = len; 13402 } 13403 set(char[] chars, int start, int len)13404 /* package */ void set(char[] chars, int start, int len) { 13405 mChars = chars; 13406 mStart = start; 13407 mLength = len; 13408 } 13409 length()13410 public int length() { 13411 return mLength; 13412 } 13413 charAt(int off)13414 public char charAt(int off) { 13415 return mChars[off + mStart]; 13416 } 13417 13418 @Override toString()13419 public String toString() { 13420 return new String(mChars, mStart, mLength); 13421 } 13422 subSequence(int start, int end)13423 public CharSequence subSequence(int start, int end) { 13424 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13425 throw new IndexOutOfBoundsException(start + ", " + end); 13426 } 13427 13428 return new String(mChars, start + mStart, end - start); 13429 } 13430 getChars(int start, int end, char[] buf, int off)13431 public void getChars(int start, int end, char[] buf, int off) { 13432 if (start < 0 || end < 0 || start > mLength || end > mLength) { 13433 throw new IndexOutOfBoundsException(start + ", " + end); 13434 } 13435 13436 System.arraycopy(mChars, start + mStart, buf, off, end - start); 13437 } 13438 13439 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)13440 public void drawText(BaseCanvas c, int start, int end, 13441 float x, float y, Paint p) { 13442 c.drawText(mChars, start + mStart, end - start, x, y, p); 13443 } 13444 13445 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)13446 public void drawTextRun(BaseCanvas c, int start, int end, 13447 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 13448 int count = end - start; 13449 int contextCount = contextEnd - contextStart; 13450 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 13451 contextCount, x, y, isRtl, p); 13452 } 13453 measureText(int start, int end, Paint p)13454 public float measureText(int start, int end, Paint p) { 13455 return p.measureText(mChars, start + mStart, end - start); 13456 } 13457 getTextWidths(int start, int end, float[] widths, Paint p)13458 public int getTextWidths(int start, int end, float[] widths, Paint p) { 13459 return p.getTextWidths(mChars, start + mStart, end - start, widths); 13460 } 13461 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)13462 public float getTextRunAdvances(int start, int end, int contextStart, 13463 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 13464 Paint p) { 13465 int count = end - start; 13466 int contextCount = contextEnd - contextStart; 13467 return p.getTextRunAdvances(mChars, start + mStart, count, 13468 contextStart + mStart, contextCount, isRtl, advances, 13469 advancesIndex); 13470 } 13471 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)13472 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 13473 int offset, int cursorOpt, Paint p) { 13474 int contextCount = contextEnd - contextStart; 13475 return p.getTextRunCursor(mChars, contextStart + mStart, 13476 contextCount, isRtl, offset + mStart, cursorOpt); 13477 } 13478 } 13479 13480 private static final class Marquee { 13481 // TODO: Add an option to configure this 13482 private static final float MARQUEE_DELTA_MAX = 0.07f; 13483 private static final int MARQUEE_DELAY = 1200; 13484 private static final int MARQUEE_DP_PER_SECOND = 30; 13485 13486 private static final byte MARQUEE_STOPPED = 0x0; 13487 private static final byte MARQUEE_STARTING = 0x1; 13488 private static final byte MARQUEE_RUNNING = 0x2; 13489 13490 private final WeakReference<TextView> mView; 13491 private final Choreographer mChoreographer; 13492 13493 private byte mStatus = MARQUEE_STOPPED; 13494 private final float mPixelsPerMs; 13495 private float mMaxScroll; 13496 private float mMaxFadeScroll; 13497 private float mGhostStart; 13498 private float mGhostOffset; 13499 private float mFadeStop; 13500 private int mRepeatLimit; 13501 13502 private float mScroll; 13503 private long mLastAnimationMs; 13504 Marquee(TextView v)13505 Marquee(TextView v) { 13506 final float density = v.getContext().getResources().getDisplayMetrics().density; 13507 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 13508 mView = new WeakReference<TextView>(v); 13509 mChoreographer = Choreographer.getInstance(); 13510 } 13511 13512 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 13513 @Override 13514 public void doFrame(long frameTimeNanos) { 13515 tick(); 13516 } 13517 }; 13518 13519 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 13520 @Override 13521 public void doFrame(long frameTimeNanos) { 13522 mStatus = MARQUEE_RUNNING; 13523 mLastAnimationMs = mChoreographer.getFrameTime(); 13524 tick(); 13525 } 13526 }; 13527 13528 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 13529 @Override 13530 public void doFrame(long frameTimeNanos) { 13531 if (mStatus == MARQUEE_RUNNING) { 13532 if (mRepeatLimit >= 0) { 13533 mRepeatLimit--; 13534 } 13535 start(mRepeatLimit); 13536 } 13537 } 13538 }; 13539 tick()13540 void tick() { 13541 if (mStatus != MARQUEE_RUNNING) { 13542 return; 13543 } 13544 13545 mChoreographer.removeFrameCallback(mTickCallback); 13546 13547 final TextView textView = mView.get(); 13548 if (textView != null && (textView.isFocused() || textView.isSelected())) { 13549 long currentMs = mChoreographer.getFrameTime(); 13550 long deltaMs = currentMs - mLastAnimationMs; 13551 mLastAnimationMs = currentMs; 13552 float deltaPx = deltaMs * mPixelsPerMs; 13553 mScroll += deltaPx; 13554 if (mScroll > mMaxScroll) { 13555 mScroll = mMaxScroll; 13556 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 13557 } else { 13558 mChoreographer.postFrameCallback(mTickCallback); 13559 } 13560 textView.invalidate(); 13561 } 13562 } 13563 stop()13564 void stop() { 13565 mStatus = MARQUEE_STOPPED; 13566 mChoreographer.removeFrameCallback(mStartCallback); 13567 mChoreographer.removeFrameCallback(mRestartCallback); 13568 mChoreographer.removeFrameCallback(mTickCallback); 13569 resetScroll(); 13570 } 13571 resetScroll()13572 private void resetScroll() { 13573 mScroll = 0.0f; 13574 final TextView textView = mView.get(); 13575 if (textView != null) textView.invalidate(); 13576 } 13577 start(int repeatLimit)13578 void start(int repeatLimit) { 13579 if (repeatLimit == 0) { 13580 stop(); 13581 return; 13582 } 13583 mRepeatLimit = repeatLimit; 13584 final TextView textView = mView.get(); 13585 if (textView != null && textView.mLayout != null) { 13586 mStatus = MARQUEE_STARTING; 13587 mScroll = 0.0f; 13588 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 13589 - textView.getCompoundPaddingRight(); 13590 final float lineWidth = textView.mLayout.getLineWidth(0); 13591 final float gap = textWidth / 3.0f; 13592 mGhostStart = lineWidth - textWidth + gap; 13593 mMaxScroll = mGhostStart + textWidth; 13594 mGhostOffset = lineWidth + gap; 13595 mFadeStop = lineWidth + textWidth / 6.0f; 13596 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 13597 13598 textView.invalidate(); 13599 mChoreographer.postFrameCallback(mStartCallback); 13600 } 13601 } 13602 getGhostOffset()13603 float getGhostOffset() { 13604 return mGhostOffset; 13605 } 13606 getScroll()13607 float getScroll() { 13608 return mScroll; 13609 } 13610 getMaxFadeScroll()13611 float getMaxFadeScroll() { 13612 return mMaxFadeScroll; 13613 } 13614 shouldDrawLeftFade()13615 boolean shouldDrawLeftFade() { 13616 return mScroll <= mFadeStop; 13617 } 13618 shouldDrawGhost()13619 boolean shouldDrawGhost() { 13620 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 13621 } 13622 isRunning()13623 boolean isRunning() { 13624 return mStatus == MARQUEE_RUNNING; 13625 } 13626 isStopped()13627 boolean isStopped() { 13628 return mStatus == MARQUEE_STOPPED; 13629 } 13630 } 13631 13632 private class ChangeWatcher implements TextWatcher, SpanWatcher { 13633 13634 private CharSequence mBeforeText; 13635 beforeTextChanged(CharSequence buffer, int start, int before, int after)13636 public void beforeTextChanged(CharSequence buffer, int start, 13637 int before, int after) { 13638 if (DEBUG_EXTRACT) { 13639 Log.v(LOG_TAG, "beforeTextChanged start=" + start 13640 + " before=" + before + " after=" + after + ": " + buffer); 13641 } 13642 13643 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 13644 mBeforeText = mTransformed.toString(); 13645 } 13646 13647 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 13648 } 13649 onTextChanged(CharSequence buffer, int start, int before, int after)13650 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 13651 if (DEBUG_EXTRACT) { 13652 Log.v(LOG_TAG, "onTextChanged start=" + start 13653 + " before=" + before + " after=" + after + ": " + buffer); 13654 } 13655 TextView.this.handleTextChanged(buffer, start, before, after); 13656 13657 if (AccessibilityManager.getInstance(mContext).isEnabled() 13658 && (isFocused() || isSelected() && isShown())) { 13659 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 13660 mBeforeText = null; 13661 } 13662 } 13663 afterTextChanged(Editable buffer)13664 public void afterTextChanged(Editable buffer) { 13665 if (DEBUG_EXTRACT) { 13666 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 13667 } 13668 TextView.this.sendAfterTextChanged(buffer); 13669 13670 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 13671 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 13672 } 13673 } 13674 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)13675 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 13676 if (DEBUG_EXTRACT) { 13677 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 13678 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 13679 } 13680 TextView.this.spanChange(buf, what, s, st, e, en); 13681 } 13682 onSpanAdded(Spannable buf, Object what, int s, int e)13683 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 13684 if (DEBUG_EXTRACT) { 13685 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 13686 } 13687 TextView.this.spanChange(buf, what, -1, s, -1, e); 13688 } 13689 onSpanRemoved(Spannable buf, Object what, int s, int e)13690 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 13691 if (DEBUG_EXTRACT) { 13692 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 13693 } 13694 TextView.this.spanChange(buf, what, s, -1, e, -1); 13695 } 13696 } 13697 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)13698 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 13699 if (msgFormat == null) { 13700 Log.d(LOG_TAG, location); 13701 } else { 13702 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 13703 } 13704 } 13705 } 13706