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