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