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