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