TextView.java revision 7843f0a799690bea661c3ff859b11c547601d45c
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 request. 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 } 5223 5224 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 5225 if (mEditor != null) mEditor.prepareCursorControllers(); 5226 } 5227 5228 /** 5229 * Sets the TextView to display the specified slice of the specified 5230 * char array. You must promise that you will not change the contents 5231 * of the array except for right before another call to setText(), 5232 * since the TextView has no way to know that the text 5233 * has changed and that it needs to invalidate and re-layout. 5234 * 5235 * @param text char array to be displayed 5236 * @param start start index in the char array 5237 * @param len length of char count after {@code start} 5238 */ 5239 public final void setText(char[] text, int start, int len) { 5240 int oldlen = 0; 5241 5242 if (start < 0 || len < 0 || start + len > text.length) { 5243 throw new IndexOutOfBoundsException(start + ", " + len); 5244 } 5245 5246 /* 5247 * We must do the before-notification here ourselves because if 5248 * the old text is a CharWrapper we destroy it before calling 5249 * into the normal path. 5250 */ 5251 if (mText != null) { 5252 oldlen = mText.length(); 5253 sendBeforeTextChanged(mText, 0, oldlen, len); 5254 } else { 5255 sendBeforeTextChanged("", 0, 0, len); 5256 } 5257 5258 if (mCharWrapper == null) { 5259 mCharWrapper = new CharWrapper(text, start, len); 5260 } else { 5261 mCharWrapper.set(text, start, len); 5262 } 5263 5264 setText(mCharWrapper, mBufferType, false, oldlen); 5265 } 5266 5267 /** 5268 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 5269 * the cursor position. Same as 5270 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 5271 * position (if any) is retained in the new text. 5272 * <p/> 5273 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5274 * intermediate {@link Spannable Spannables}. Likewise it will use 5275 * {@link android.text.Editable.Factory} to create final or intermediate 5276 * {@link Editable Editables}. 5277 * 5278 * @param text text to be displayed 5279 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 5280 * stored as a static text, styleable/spannable text, or editable text 5281 * 5282 * @see #setText(CharSequence, android.widget.TextView.BufferType) 5283 */ 5284 public final void setTextKeepState(CharSequence text, BufferType type) { 5285 int start = getSelectionStart(); 5286 int end = getSelectionEnd(); 5287 int len = text.length(); 5288 5289 setText(text, type); 5290 5291 if (start >= 0 || end >= 0) { 5292 if (mText instanceof Spannable) { 5293 Selection.setSelection((Spannable) mText, 5294 Math.max(0, Math.min(start, len)), 5295 Math.max(0, Math.min(end, len))); 5296 } 5297 } 5298 } 5299 5300 /** 5301 * Sets the text to be displayed using a string resource identifier. 5302 * 5303 * @param resid the resource identifier of the string resource to be displayed 5304 * 5305 * @see #setText(CharSequence) 5306 * 5307 * @attr ref android.R.styleable#TextView_text 5308 */ 5309 @android.view.RemotableViewMethod 5310 public final void setText(@StringRes int resid) { 5311 setText(getContext().getResources().getText(resid)); 5312 mTextFromResource = true; 5313 } 5314 5315 /** 5316 * Sets the text to be displayed using a string resource identifier and the 5317 * {@link android.widget.TextView.BufferType}. 5318 * <p/> 5319 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5320 * intermediate {@link Spannable Spannables}. Likewise it will use 5321 * {@link android.text.Editable.Factory} to create final or intermediate 5322 * {@link Editable Editables}. 5323 * 5324 * @param resid the resource identifier of the string resource to be displayed 5325 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 5326 * stored as a static text, styleable/spannable text, or editable text 5327 * 5328 * @see #setText(int) 5329 * @see #setText(CharSequence) 5330 * @see android.widget.TextView.BufferType 5331 * @see #setSpannableFactory(Spannable.Factory) 5332 * @see #setEditableFactory(Editable.Factory) 5333 * 5334 * @attr ref android.R.styleable#TextView_text 5335 * @attr ref android.R.styleable#TextView_bufferType 5336 */ 5337 public final void setText(@StringRes int resid, BufferType type) { 5338 setText(getContext().getResources().getText(resid), type); 5339 mTextFromResource = true; 5340 } 5341 5342 /** 5343 * Sets the text to be displayed when the text of the TextView is empty. 5344 * Null means to use the normal empty text. The hint does not currently 5345 * participate in determining the size of the view. 5346 * 5347 * @attr ref android.R.styleable#TextView_hint 5348 */ 5349 @android.view.RemotableViewMethod 5350 public final void setHint(CharSequence hint) { 5351 mHint = TextUtils.stringOrSpannedString(hint); 5352 5353 if (mLayout != null) { 5354 checkForRelayout(); 5355 } 5356 5357 if (mText.length() == 0) { 5358 invalidate(); 5359 } 5360 5361 // Invalidate display list if hint is currently used 5362 if (mEditor != null && mText.length() == 0 && mHint != null) { 5363 mEditor.invalidateTextDisplayList(); 5364 } 5365 } 5366 5367 /** 5368 * Sets the text to be displayed when the text of the TextView is empty, 5369 * from a resource. 5370 * 5371 * @attr ref android.R.styleable#TextView_hint 5372 */ 5373 @android.view.RemotableViewMethod 5374 public final void setHint(@StringRes int resid) { 5375 setHint(getContext().getResources().getText(resid)); 5376 } 5377 5378 /** 5379 * Returns the hint that is displayed when the text of the TextView 5380 * is empty. 5381 * 5382 * @attr ref android.R.styleable#TextView_hint 5383 */ 5384 @ViewDebug.CapturedViewProperty 5385 public CharSequence getHint() { 5386 return mHint; 5387 } 5388 5389 boolean isSingleLine() { 5390 return mSingleLine; 5391 } 5392 5393 private static boolean isMultilineInputType(int type) { 5394 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 5395 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 5396 } 5397 5398 /** 5399 * Removes the suggestion spans. 5400 */ 5401 CharSequence removeSuggestionSpans(CharSequence text) { 5402 if (text instanceof Spanned) { 5403 Spannable spannable; 5404 if (text instanceof Spannable) { 5405 spannable = (Spannable) text; 5406 } else { 5407 spannable = mSpannableFactory.newSpannable(text); 5408 text = spannable; 5409 } 5410 5411 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 5412 for (int i = 0; i < spans.length; i++) { 5413 spannable.removeSpan(spans[i]); 5414 } 5415 } 5416 return text; 5417 } 5418 5419 /** 5420 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 5421 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 5422 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 5423 * then a soft keyboard will not be displayed for this text view. 5424 * 5425 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 5426 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 5427 * type. 5428 * 5429 * @see #getInputType() 5430 * @see #setRawInputType(int) 5431 * @see android.text.InputType 5432 * @attr ref android.R.styleable#TextView_inputType 5433 */ 5434 public void setInputType(int type) { 5435 final boolean wasPassword = isPasswordInputType(getInputType()); 5436 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 5437 setInputType(type, false); 5438 final boolean isPassword = isPasswordInputType(type); 5439 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 5440 boolean forceUpdate = false; 5441 if (isPassword) { 5442 setTransformationMethod(PasswordTransformationMethod.getInstance()); 5443 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0); 5444 } else if (isVisiblePassword) { 5445 if (mTransformation == PasswordTransformationMethod.getInstance()) { 5446 forceUpdate = true; 5447 } 5448 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0); 5449 } else if (wasPassword || wasVisiblePassword) { 5450 // not in password mode, clean up typeface and transformation 5451 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1); 5452 if (mTransformation == PasswordTransformationMethod.getInstance()) { 5453 forceUpdate = true; 5454 } 5455 } 5456 5457 boolean singleLine = !isMultilineInputType(type); 5458 5459 // We need to update the single line mode if it has changed or we 5460 // were previously in password mode. 5461 if (mSingleLine != singleLine || forceUpdate) { 5462 // Change single line mode, but only change the transformation if 5463 // we are not in password mode. 5464 applySingleLine(singleLine, !isPassword, true); 5465 } 5466 5467 if (!isSuggestionsEnabled()) { 5468 mText = removeSuggestionSpans(mText); 5469 } 5470 5471 InputMethodManager imm = InputMethodManager.peekInstance(); 5472 if (imm != null) imm.restartInput(this); 5473 } 5474 5475 /** 5476 * It would be better to rely on the input type for everything. A password inputType should have 5477 * a password transformation. We should hence use isPasswordInputType instead of this method. 5478 * 5479 * We should: 5480 * - Call setInputType in setKeyListener instead of changing the input type directly (which 5481 * would install the correct transformation). 5482 * - Refuse the installation of a non-password transformation in setTransformation if the input 5483 * type is password. 5484 * 5485 * However, this is like this for legacy reasons and we cannot break existing apps. This method 5486 * is useful since it matches what the user can see (obfuscated text or not). 5487 * 5488 * @return true if the current transformation method is of the password type. 5489 */ 5490 boolean hasPasswordTransformationMethod() { 5491 return mTransformation instanceof PasswordTransformationMethod; 5492 } 5493 5494 private static boolean isPasswordInputType(int inputType) { 5495 final int variation = 5496 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 5497 return variation 5498 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 5499 || variation 5500 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 5501 || variation 5502 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 5503 } 5504 5505 private static boolean isVisiblePasswordInputType(int inputType) { 5506 final int variation = 5507 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 5508 return variation 5509 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 5510 } 5511 5512 /** 5513 * Directly change the content type integer of the text view, without 5514 * modifying any other state. 5515 * @see #setInputType(int) 5516 * @see android.text.InputType 5517 * @attr ref android.R.styleable#TextView_inputType 5518 */ 5519 public void setRawInputType(int type) { 5520 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 5521 createEditorIfNeeded(); 5522 mEditor.mInputType = type; 5523 } 5524 5525 private void setInputType(int type, boolean direct) { 5526 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 5527 KeyListener input; 5528 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 5529 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 5530 TextKeyListener.Capitalize cap; 5531 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 5532 cap = TextKeyListener.Capitalize.CHARACTERS; 5533 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 5534 cap = TextKeyListener.Capitalize.WORDS; 5535 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 5536 cap = TextKeyListener.Capitalize.SENTENCES; 5537 } else { 5538 cap = TextKeyListener.Capitalize.NONE; 5539 } 5540 input = TextKeyListener.getInstance(autotext, cap); 5541 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 5542 input = DigitsKeyListener.getInstance( 5543 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 5544 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 5545 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 5546 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 5547 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 5548 input = DateKeyListener.getInstance(); 5549 break; 5550 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 5551 input = TimeKeyListener.getInstance(); 5552 break; 5553 default: 5554 input = DateTimeKeyListener.getInstance(); 5555 break; 5556 } 5557 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 5558 input = DialerKeyListener.getInstance(); 5559 } else { 5560 input = TextKeyListener.getInstance(); 5561 } 5562 setRawInputType(type); 5563 if (direct) { 5564 createEditorIfNeeded(); 5565 mEditor.mKeyListener = input; 5566 } else { 5567 setKeyListenerOnly(input); 5568 } 5569 } 5570 5571 /** 5572 * Get the type of the editable content. 5573 * 5574 * @see #setInputType(int) 5575 * @see android.text.InputType 5576 */ 5577 public int getInputType() { 5578 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 5579 } 5580 5581 /** 5582 * Change the editor type integer associated with the text view, which 5583 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 5584 * has focus. 5585 * @see #getImeOptions 5586 * @see android.view.inputmethod.EditorInfo 5587 * @attr ref android.R.styleable#TextView_imeOptions 5588 */ 5589 public void setImeOptions(int imeOptions) { 5590 createEditorIfNeeded(); 5591 mEditor.createInputContentTypeIfNeeded(); 5592 mEditor.mInputContentType.imeOptions = imeOptions; 5593 } 5594 5595 /** 5596 * Get the type of the IME editor. 5597 * 5598 * @see #setImeOptions(int) 5599 * @see android.view.inputmethod.EditorInfo 5600 */ 5601 public int getImeOptions() { 5602 return mEditor != null && mEditor.mInputContentType != null 5603 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 5604 } 5605 5606 /** 5607 * Change the custom IME action associated with the text view, which 5608 * will be reported to an IME with {@link EditorInfo#actionLabel} 5609 * and {@link EditorInfo#actionId} when it has focus. 5610 * @see #getImeActionLabel 5611 * @see #getImeActionId 5612 * @see android.view.inputmethod.EditorInfo 5613 * @attr ref android.R.styleable#TextView_imeActionLabel 5614 * @attr ref android.R.styleable#TextView_imeActionId 5615 */ 5616 public void setImeActionLabel(CharSequence label, int actionId) { 5617 createEditorIfNeeded(); 5618 mEditor.createInputContentTypeIfNeeded(); 5619 mEditor.mInputContentType.imeActionLabel = label; 5620 mEditor.mInputContentType.imeActionId = actionId; 5621 } 5622 5623 /** 5624 * Get the IME action label previous set with {@link #setImeActionLabel}. 5625 * 5626 * @see #setImeActionLabel 5627 * @see android.view.inputmethod.EditorInfo 5628 */ 5629 public CharSequence getImeActionLabel() { 5630 return mEditor != null && mEditor.mInputContentType != null 5631 ? mEditor.mInputContentType.imeActionLabel : null; 5632 } 5633 5634 /** 5635 * Get the IME action ID previous set with {@link #setImeActionLabel}. 5636 * 5637 * @see #setImeActionLabel 5638 * @see android.view.inputmethod.EditorInfo 5639 */ 5640 public int getImeActionId() { 5641 return mEditor != null && mEditor.mInputContentType != null 5642 ? mEditor.mInputContentType.imeActionId : 0; 5643 } 5644 5645 /** 5646 * Set a special listener to be called when an action is performed 5647 * on the text view. This will be called when the enter key is pressed, 5648 * or when an action supplied to the IME is selected by the user. Setting 5649 * this means that the normal hard key event will not insert a newline 5650 * into the text view, even if it is multi-line; holding down the ALT 5651 * modifier will, however, allow the user to insert a newline character. 5652 */ 5653 public void setOnEditorActionListener(OnEditorActionListener l) { 5654 createEditorIfNeeded(); 5655 mEditor.createInputContentTypeIfNeeded(); 5656 mEditor.mInputContentType.onEditorActionListener = l; 5657 } 5658 5659 /** 5660 * Called when an attached input method calls 5661 * {@link InputConnection#performEditorAction(int) 5662 * InputConnection.performEditorAction()} 5663 * for this text view. The default implementation will call your action 5664 * listener supplied to {@link #setOnEditorActionListener}, or perform 5665 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 5666 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 5667 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 5668 * EditorInfo.IME_ACTION_DONE}. 5669 * 5670 * <p>For backwards compatibility, if no IME options have been set and the 5671 * text view would not normally advance focus on enter, then 5672 * the NEXT and DONE actions received here will be turned into an enter 5673 * key down/up pair to go through the normal key handling. 5674 * 5675 * @param actionCode The code of the action being performed. 5676 * 5677 * @see #setOnEditorActionListener 5678 */ 5679 public void onEditorAction(int actionCode) { 5680 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 5681 if (ict != null) { 5682 if (ict.onEditorActionListener != null) { 5683 if (ict.onEditorActionListener.onEditorAction(this, 5684 actionCode, null)) { 5685 return; 5686 } 5687 } 5688 5689 // This is the handling for some default action. 5690 // Note that for backwards compatibility we don't do this 5691 // default handling if explicit ime options have not been given, 5692 // instead turning this into the normal enter key codes that an 5693 // app may be expecting. 5694 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 5695 View v = focusSearch(FOCUS_FORWARD); 5696 if (v != null) { 5697 if (!v.requestFocus(FOCUS_FORWARD)) { 5698 throw new IllegalStateException("focus search returned a view " 5699 + "that wasn't able to take focus!"); 5700 } 5701 } 5702 return; 5703 5704 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 5705 View v = focusSearch(FOCUS_BACKWARD); 5706 if (v != null) { 5707 if (!v.requestFocus(FOCUS_BACKWARD)) { 5708 throw new IllegalStateException("focus search returned a view " 5709 + "that wasn't able to take focus!"); 5710 } 5711 } 5712 return; 5713 5714 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 5715 InputMethodManager imm = InputMethodManager.peekInstance(); 5716 if (imm != null && imm.isActive(this)) { 5717 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5718 } 5719 return; 5720 } 5721 } 5722 5723 ViewRootImpl viewRootImpl = getViewRootImpl(); 5724 if (viewRootImpl != null) { 5725 long eventTime = SystemClock.uptimeMillis(); 5726 viewRootImpl.dispatchKeyFromIme( 5727 new KeyEvent(eventTime, eventTime, 5728 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 5729 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 5730 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 5731 | KeyEvent.FLAG_EDITOR_ACTION)); 5732 viewRootImpl.dispatchKeyFromIme( 5733 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 5734 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 5735 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 5736 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 5737 | KeyEvent.FLAG_EDITOR_ACTION)); 5738 } 5739 } 5740 5741 /** 5742 * Set the private content type of the text, which is the 5743 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 5744 * field that will be filled in when creating an input connection. 5745 * 5746 * @see #getPrivateImeOptions() 5747 * @see EditorInfo#privateImeOptions 5748 * @attr ref android.R.styleable#TextView_privateImeOptions 5749 */ 5750 public void setPrivateImeOptions(String type) { 5751 createEditorIfNeeded(); 5752 mEditor.createInputContentTypeIfNeeded(); 5753 mEditor.mInputContentType.privateImeOptions = type; 5754 } 5755 5756 /** 5757 * Get the private type of the content. 5758 * 5759 * @see #setPrivateImeOptions(String) 5760 * @see EditorInfo#privateImeOptions 5761 */ 5762 public String getPrivateImeOptions() { 5763 return mEditor != null && mEditor.mInputContentType != null 5764 ? mEditor.mInputContentType.privateImeOptions : null; 5765 } 5766 5767 /** 5768 * Set the extra input data of the text, which is the 5769 * {@link EditorInfo#extras TextBoxAttribute.extras} 5770 * Bundle that will be filled in when creating an input connection. The 5771 * given integer is the resource identifier of an XML resource holding an 5772 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 5773 * 5774 * @see #getInputExtras(boolean) 5775 * @see EditorInfo#extras 5776 * @attr ref android.R.styleable#TextView_editorExtras 5777 */ 5778 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 5779 createEditorIfNeeded(); 5780 XmlResourceParser parser = getResources().getXml(xmlResId); 5781 mEditor.createInputContentTypeIfNeeded(); 5782 mEditor.mInputContentType.extras = new Bundle(); 5783 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 5784 } 5785 5786 /** 5787 * Retrieve the input extras currently associated with the text view, which 5788 * can be viewed as well as modified. 5789 * 5790 * @param create If true, the extras will be created if they don't already 5791 * exist. Otherwise, null will be returned if none have been created. 5792 * @see #setInputExtras(int) 5793 * @see EditorInfo#extras 5794 * @attr ref android.R.styleable#TextView_editorExtras 5795 */ 5796 public Bundle getInputExtras(boolean create) { 5797 if (mEditor == null && !create) return null; 5798 createEditorIfNeeded(); 5799 if (mEditor.mInputContentType == null) { 5800 if (!create) return null; 5801 mEditor.createInputContentTypeIfNeeded(); 5802 } 5803 if (mEditor.mInputContentType.extras == null) { 5804 if (!create) return null; 5805 mEditor.mInputContentType.extras = new Bundle(); 5806 } 5807 return mEditor.mInputContentType.extras; 5808 } 5809 5810 /** 5811 * Change "hint" locales associated with the text view, which will be reported to an IME with 5812 * {@link EditorInfo#hintLocales} when it has focus. 5813 * 5814 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 5815 * call {@link InputMethodManager#restartInput(View)}.</p> 5816 * @param hintLocales List of the languages that the user is supposed to switch to no matter 5817 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 5818 * @see #getImeHintLocales() 5819 * @see android.view.inputmethod.EditorInfo#hintLocales 5820 */ 5821 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 5822 createEditorIfNeeded(); 5823 mEditor.createInputContentTypeIfNeeded(); 5824 mEditor.mInputContentType.imeHintLocales = hintLocales; 5825 } 5826 5827 /** 5828 * @return The current languages list "hint". {@code null} when no "hint" is available. 5829 * @see #setImeHintLocales(LocaleList) 5830 * @see android.view.inputmethod.EditorInfo#hintLocales 5831 */ 5832 @Nullable 5833 public LocaleList getImeHintLocales() { 5834 if (mEditor == null) { 5835 return null; 5836 } 5837 if (mEditor.mInputContentType == null) { 5838 return null; 5839 } 5840 return mEditor.mInputContentType.imeHintLocales; 5841 } 5842 5843 /** 5844 * Returns the error message that was set to be displayed with 5845 * {@link #setError}, or <code>null</code> if no error was set 5846 * or if it the error was cleared by the widget after user input. 5847 */ 5848 public CharSequence getError() { 5849 return mEditor == null ? null : mEditor.mError; 5850 } 5851 5852 /** 5853 * Sets the right-hand compound drawable of the TextView to the "error" 5854 * icon and sets an error message that will be displayed in a popup when 5855 * the TextView has focus. The icon and error message will be reset to 5856 * null when any key events cause changes to the TextView's text. If the 5857 * <code>error</code> is <code>null</code>, the error message and icon 5858 * will be cleared. 5859 */ 5860 @android.view.RemotableViewMethod 5861 public void setError(CharSequence error) { 5862 if (error == null) { 5863 setError(null, null); 5864 } else { 5865 Drawable dr = getContext().getDrawable( 5866 com.android.internal.R.drawable.indicator_input_error); 5867 5868 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 5869 setError(error, dr); 5870 } 5871 } 5872 5873 /** 5874 * Sets the right-hand compound drawable of the TextView to the specified 5875 * icon and sets an error message that will be displayed in a popup when 5876 * the TextView has focus. The icon and error message will be reset to 5877 * null when any key events cause changes to the TextView's text. The 5878 * drawable must already have had {@link Drawable#setBounds} set on it. 5879 * If the <code>error</code> is <code>null</code>, the error message will 5880 * be cleared (and you should provide a <code>null</code> icon as well). 5881 */ 5882 public void setError(CharSequence error, Drawable icon) { 5883 createEditorIfNeeded(); 5884 mEditor.setError(error, icon); 5885 notifyViewAccessibilityStateChangedIfNeeded( 5886 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 5887 } 5888 5889 @Override 5890 protected boolean setFrame(int l, int t, int r, int b) { 5891 boolean result = super.setFrame(l, t, r, b); 5892 5893 if (mEditor != null) mEditor.setFrame(); 5894 5895 restartMarqueeIfNeeded(); 5896 5897 return result; 5898 } 5899 5900 private void restartMarqueeIfNeeded() { 5901 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5902 mRestartMarquee = false; 5903 startMarquee(); 5904 } 5905 } 5906 5907 /** 5908 * Sets the list of input filters that will be used if the buffer is 5909 * Editable. Has no effect otherwise. 5910 * 5911 * @attr ref android.R.styleable#TextView_maxLength 5912 */ 5913 public void setFilters(InputFilter[] filters) { 5914 if (filters == null) { 5915 throw new IllegalArgumentException(); 5916 } 5917 5918 mFilters = filters; 5919 5920 if (mText instanceof Editable) { 5921 setFilters((Editable) mText, filters); 5922 } 5923 } 5924 5925 /** 5926 * Sets the list of input filters on the specified Editable, 5927 * and includes mInput in the list if it is an InputFilter. 5928 */ 5929 private void setFilters(Editable e, InputFilter[] filters) { 5930 if (mEditor != null) { 5931 final boolean undoFilter = mEditor.mUndoInputFilter != null; 5932 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 5933 int num = 0; 5934 if (undoFilter) num++; 5935 if (keyFilter) num++; 5936 if (num > 0) { 5937 InputFilter[] nf = new InputFilter[filters.length + num]; 5938 5939 System.arraycopy(filters, 0, nf, 0, filters.length); 5940 num = 0; 5941 if (undoFilter) { 5942 nf[filters.length] = mEditor.mUndoInputFilter; 5943 num++; 5944 } 5945 if (keyFilter) { 5946 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 5947 } 5948 5949 e.setFilters(nf); 5950 return; 5951 } 5952 } 5953 e.setFilters(filters); 5954 } 5955 5956 /** 5957 * Returns the current list of input filters. 5958 * 5959 * @attr ref android.R.styleable#TextView_maxLength 5960 */ 5961 public InputFilter[] getFilters() { 5962 return mFilters; 5963 } 5964 5965 ///////////////////////////////////////////////////////////////////////// 5966 5967 private int getBoxHeight(Layout l) { 5968 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 5969 int padding = (l == mHintLayout) 5970 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 5971 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 5972 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 5973 } 5974 5975 int getVerticalOffset(boolean forceNormal) { 5976 int voffset = 0; 5977 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 5978 5979 Layout l = mLayout; 5980 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 5981 l = mHintLayout; 5982 } 5983 5984 if (gravity != Gravity.TOP) { 5985 int boxht = getBoxHeight(l); 5986 int textht = l.getHeight(); 5987 5988 if (textht < boxht) { 5989 if (gravity == Gravity.BOTTOM) { 5990 voffset = boxht - textht; 5991 } else { // (gravity == Gravity.CENTER_VERTICAL) 5992 voffset = (boxht - textht) >> 1; 5993 } 5994 } 5995 } 5996 return voffset; 5997 } 5998 5999 private int getBottomVerticalOffset(boolean forceNormal) { 6000 int voffset = 0; 6001 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 6002 6003 Layout l = mLayout; 6004 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 6005 l = mHintLayout; 6006 } 6007 6008 if (gravity != Gravity.BOTTOM) { 6009 int boxht = getBoxHeight(l); 6010 int textht = l.getHeight(); 6011 6012 if (textht < boxht) { 6013 if (gravity == Gravity.TOP) { 6014 voffset = boxht - textht; 6015 } else { // (gravity == Gravity.CENTER_VERTICAL) 6016 voffset = (boxht - textht) >> 1; 6017 } 6018 } 6019 } 6020 return voffset; 6021 } 6022 6023 void invalidateCursorPath() { 6024 if (mHighlightPathBogus) { 6025 invalidateCursor(); 6026 } else { 6027 final int horizontalPadding = getCompoundPaddingLeft(); 6028 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 6029 6030 if (mEditor.mCursorCount == 0) { 6031 synchronized (TEMP_RECTF) { 6032 /* 6033 * The reason for this concern about the thickness of the 6034 * cursor and doing the floor/ceil on the coordinates is that 6035 * some EditTexts (notably textfields in the Browser) have 6036 * anti-aliased text where not all the characters are 6037 * necessarily at integer-multiple locations. This should 6038 * make sure the entire cursor gets invalidated instead of 6039 * sometimes missing half a pixel. 6040 */ 6041 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 6042 if (thick < 1.0f) { 6043 thick = 1.0f; 6044 } 6045 6046 thick /= 2.0f; 6047 6048 // mHighlightPath is guaranteed to be non null at that point. 6049 mHighlightPath.computeBounds(TEMP_RECTF, false); 6050 6051 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 6052 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 6053 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 6054 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 6055 } 6056 } else { 6057 for (int i = 0; i < mEditor.mCursorCount; i++) { 6058 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 6059 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 6060 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 6061 } 6062 } 6063 } 6064 } 6065 6066 void invalidateCursor() { 6067 int where = getSelectionEnd(); 6068 6069 invalidateCursor(where, where, where); 6070 } 6071 6072 private void invalidateCursor(int a, int b, int c) { 6073 if (a >= 0 || b >= 0 || c >= 0) { 6074 int start = Math.min(Math.min(a, b), c); 6075 int end = Math.max(Math.max(a, b), c); 6076 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 6077 } 6078 } 6079 6080 /** 6081 * Invalidates the region of text enclosed between the start and end text offsets. 6082 */ 6083 void invalidateRegion(int start, int end, boolean invalidateCursor) { 6084 if (mLayout == null) { 6085 invalidate(); 6086 } else { 6087 int lineStart = mLayout.getLineForOffset(start); 6088 int top = mLayout.getLineTop(lineStart); 6089 6090 // This is ridiculous, but the descent from the line above 6091 // can hang down into the line we really want to redraw, 6092 // so we have to invalidate part of the line above to make 6093 // sure everything that needs to be redrawn really is. 6094 // (But not the whole line above, because that would cause 6095 // the same problem with the descenders on the line above it!) 6096 if (lineStart > 0) { 6097 top -= mLayout.getLineDescent(lineStart - 1); 6098 } 6099 6100 int lineEnd; 6101 6102 if (start == end) { 6103 lineEnd = lineStart; 6104 } else { 6105 lineEnd = mLayout.getLineForOffset(end); 6106 } 6107 6108 int bottom = mLayout.getLineBottom(lineEnd); 6109 6110 // mEditor can be null in case selection is set programmatically. 6111 if (invalidateCursor && mEditor != null) { 6112 for (int i = 0; i < mEditor.mCursorCount; i++) { 6113 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 6114 top = Math.min(top, bounds.top); 6115 bottom = Math.max(bottom, bounds.bottom); 6116 } 6117 } 6118 6119 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6120 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 6121 6122 int left, right; 6123 if (lineStart == lineEnd && !invalidateCursor) { 6124 left = (int) mLayout.getPrimaryHorizontal(start); 6125 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 6126 left += compoundPaddingLeft; 6127 right += compoundPaddingLeft; 6128 } else { 6129 // Rectangle bounding box when the region spans several lines 6130 left = compoundPaddingLeft; 6131 right = getWidth() - getCompoundPaddingRight(); 6132 } 6133 6134 invalidate(mScrollX + left, verticalPadding + top, 6135 mScrollX + right, verticalPadding + bottom); 6136 } 6137 } 6138 6139 private void registerForPreDraw() { 6140 if (!mPreDrawRegistered) { 6141 getViewTreeObserver().addOnPreDrawListener(this); 6142 mPreDrawRegistered = true; 6143 } 6144 } 6145 6146 private void unregisterForPreDraw() { 6147 getViewTreeObserver().removeOnPreDrawListener(this); 6148 mPreDrawRegistered = false; 6149 mPreDrawListenerDetached = false; 6150 } 6151 6152 /** 6153 * {@inheritDoc} 6154 */ 6155 @Override 6156 public boolean onPreDraw() { 6157 if (mLayout == null) { 6158 assumeLayout(); 6159 } 6160 6161 if (mMovement != null) { 6162 /* This code also provides auto-scrolling when a cursor is moved using a 6163 * CursorController (insertion point or selection limits). 6164 * For selection, ensure start or end is visible depending on controller's state. 6165 */ 6166 int curs = getSelectionEnd(); 6167 // Do not create the controller if it is not already created. 6168 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 6169 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 6170 curs = getSelectionStart(); 6171 } 6172 6173 /* 6174 * TODO: This should really only keep the end in view if 6175 * it already was before the text changed. I'm not sure 6176 * of a good way to tell from here if it was. 6177 */ 6178 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6179 curs = mText.length(); 6180 } 6181 6182 if (curs >= 0) { 6183 bringPointIntoView(curs); 6184 } 6185 } else { 6186 bringTextIntoView(); 6187 } 6188 6189 // This has to be checked here since: 6190 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 6191 // a screen rotation) since layout is not yet initialized at that point. 6192 if (mEditor != null && mEditor.mCreatedWithASelection) { 6193 mEditor.refreshTextActionMode(); 6194 mEditor.mCreatedWithASelection = false; 6195 } 6196 6197 unregisterForPreDraw(); 6198 6199 return true; 6200 } 6201 6202 @Override 6203 protected void onAttachedToWindow() { 6204 super.onAttachedToWindow(); 6205 6206 if (mEditor != null) mEditor.onAttachedToWindow(); 6207 6208 if (mPreDrawListenerDetached) { 6209 getViewTreeObserver().addOnPreDrawListener(this); 6210 mPreDrawListenerDetached = false; 6211 } 6212 } 6213 6214 /** @hide */ 6215 @Override 6216 protected void onDetachedFromWindowInternal() { 6217 if (mPreDrawRegistered) { 6218 getViewTreeObserver().removeOnPreDrawListener(this); 6219 mPreDrawListenerDetached = true; 6220 } 6221 6222 resetResolvedDrawables(); 6223 6224 if (mEditor != null) mEditor.onDetachedFromWindow(); 6225 6226 super.onDetachedFromWindowInternal(); 6227 } 6228 6229 @Override 6230 public void onScreenStateChanged(int screenState) { 6231 super.onScreenStateChanged(screenState); 6232 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 6233 } 6234 6235 @Override 6236 protected boolean isPaddingOffsetRequired() { 6237 return mShadowRadius != 0 || mDrawables != null; 6238 } 6239 6240 @Override 6241 protected int getLeftPaddingOffset() { 6242 return getCompoundPaddingLeft() - mPaddingLeft 6243 + (int) Math.min(0, mShadowDx - mShadowRadius); 6244 } 6245 6246 @Override 6247 protected int getTopPaddingOffset() { 6248 return (int) Math.min(0, mShadowDy - mShadowRadius); 6249 } 6250 6251 @Override 6252 protected int getBottomPaddingOffset() { 6253 return (int) Math.max(0, mShadowDy + mShadowRadius); 6254 } 6255 6256 @Override 6257 protected int getRightPaddingOffset() { 6258 return -(getCompoundPaddingRight() - mPaddingRight) 6259 + (int) Math.max(0, mShadowDx + mShadowRadius); 6260 } 6261 6262 @Override 6263 protected boolean verifyDrawable(@NonNull Drawable who) { 6264 final boolean verified = super.verifyDrawable(who); 6265 if (!verified && mDrawables != null) { 6266 for (Drawable dr : mDrawables.mShowing) { 6267 if (who == dr) { 6268 return true; 6269 } 6270 } 6271 } 6272 return verified; 6273 } 6274 6275 @Override 6276 public void jumpDrawablesToCurrentState() { 6277 super.jumpDrawablesToCurrentState(); 6278 if (mDrawables != null) { 6279 for (Drawable dr : mDrawables.mShowing) { 6280 if (dr != null) { 6281 dr.jumpToCurrentState(); 6282 } 6283 } 6284 } 6285 } 6286 6287 @Override 6288 public void invalidateDrawable(@NonNull Drawable drawable) { 6289 boolean handled = false; 6290 6291 if (verifyDrawable(drawable)) { 6292 final Rect dirty = drawable.getBounds(); 6293 int scrollX = mScrollX; 6294 int scrollY = mScrollY; 6295 6296 // IMPORTANT: The coordinates below are based on the coordinates computed 6297 // for each compound drawable in onDraw(). Make sure to update each section 6298 // accordingly. 6299 final TextView.Drawables drawables = mDrawables; 6300 if (drawables != null) { 6301 if (drawable == drawables.mShowing[Drawables.LEFT]) { 6302 final int compoundPaddingTop = getCompoundPaddingTop(); 6303 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6304 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6305 6306 scrollX += mPaddingLeft; 6307 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 6308 handled = true; 6309 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 6310 final int compoundPaddingTop = getCompoundPaddingTop(); 6311 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6312 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6313 6314 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 6315 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 6316 handled = true; 6317 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 6318 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6319 final int compoundPaddingRight = getCompoundPaddingRight(); 6320 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 6321 6322 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 6323 scrollY += mPaddingTop; 6324 handled = true; 6325 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 6326 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6327 final int compoundPaddingRight = getCompoundPaddingRight(); 6328 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 6329 6330 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 6331 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 6332 handled = true; 6333 } 6334 } 6335 6336 if (handled) { 6337 invalidate(dirty.left + scrollX, dirty.top + scrollY, 6338 dirty.right + scrollX, dirty.bottom + scrollY); 6339 } 6340 } 6341 6342 if (!handled) { 6343 super.invalidateDrawable(drawable); 6344 } 6345 } 6346 6347 @Override 6348 public boolean hasOverlappingRendering() { 6349 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 6350 return ((getBackground() != null && getBackground().getCurrent() != null) 6351 || mText instanceof Spannable || hasSelection() 6352 || isHorizontalFadingEdgeEnabled()); 6353 } 6354 6355 /** 6356 * 6357 * Returns the state of the {@code textIsSelectable} flag (See 6358 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 6359 * to allow users to select and copy text in a non-editable TextView, the content of an 6360 * {@link EditText} can always be selected, independently of the value of this flag. 6361 * <p> 6362 * 6363 * @return True if the text displayed in this TextView can be selected by the user. 6364 * 6365 * @attr ref android.R.styleable#TextView_textIsSelectable 6366 */ 6367 public boolean isTextSelectable() { 6368 return mEditor == null ? false : mEditor.mTextIsSelectable; 6369 } 6370 6371 /** 6372 * Sets whether the content of this view is selectable by the user. The default is 6373 * {@code false}, meaning that the content is not selectable. 6374 * <p> 6375 * When you use a TextView to display a useful piece of information to the user (such as a 6376 * contact's address), make it selectable, so that the user can select and copy its 6377 * content. You can also use set the XML attribute 6378 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 6379 * <p> 6380 * When you call this method to set the value of {@code textIsSelectable}, it sets 6381 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 6382 * and {@code longClickable} to the same value. These flags correspond to the attributes 6383 * {@link android.R.styleable#View_focusable android:focusable}, 6384 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 6385 * {@link android.R.styleable#View_clickable android:clickable}, and 6386 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 6387 * flags to a state you had set previously, call one or more of the following methods: 6388 * {@link #setFocusable(boolean) setFocusable()}, 6389 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 6390 * {@link #setClickable(boolean) setClickable()} or 6391 * {@link #setLongClickable(boolean) setLongClickable()}. 6392 * 6393 * @param selectable Whether the content of this TextView should be selectable. 6394 */ 6395 public void setTextIsSelectable(boolean selectable) { 6396 if (!selectable && mEditor == null) return; // false is default value with no edit data 6397 6398 createEditorIfNeeded(); 6399 if (mEditor.mTextIsSelectable == selectable) return; 6400 6401 mEditor.mTextIsSelectable = selectable; 6402 setFocusableInTouchMode(selectable); 6403 setFocusable(FOCUSABLE_AUTO); 6404 setClickable(selectable); 6405 setLongClickable(selectable); 6406 6407 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 6408 6409 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 6410 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 6411 6412 // Called by setText above, but safer in case of future code changes 6413 mEditor.prepareCursorControllers(); 6414 } 6415 6416 @Override 6417 protected int[] onCreateDrawableState(int extraSpace) { 6418 final int[] drawableState; 6419 6420 if (mSingleLine) { 6421 drawableState = super.onCreateDrawableState(extraSpace); 6422 } else { 6423 drawableState = super.onCreateDrawableState(extraSpace + 1); 6424 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 6425 } 6426 6427 if (isTextSelectable()) { 6428 // Disable pressed state, which was introduced when TextView was made clickable. 6429 // Prevents text color change. 6430 // setClickable(false) would have a similar effect, but it also disables focus changes 6431 // and long press actions, which are both needed by text selection. 6432 final int length = drawableState.length; 6433 for (int i = 0; i < length; i++) { 6434 if (drawableState[i] == R.attr.state_pressed) { 6435 final int[] nonPressedState = new int[length - 1]; 6436 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 6437 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 6438 return nonPressedState; 6439 } 6440 } 6441 } 6442 6443 return drawableState; 6444 } 6445 6446 private Path getUpdatedHighlightPath() { 6447 Path highlight = null; 6448 Paint highlightPaint = mHighlightPaint; 6449 6450 final int selStart = getSelectionStart(); 6451 final int selEnd = getSelectionEnd(); 6452 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 6453 if (selStart == selEnd) { 6454 if (mEditor != null && mEditor.isCursorVisible() 6455 && (SystemClock.uptimeMillis() - mEditor.mShowCursor) 6456 % (2 * Editor.BLINK) < Editor.BLINK) { 6457 if (mHighlightPathBogus) { 6458 if (mHighlightPath == null) mHighlightPath = new Path(); 6459 mHighlightPath.reset(); 6460 mLayout.getCursorPath(selStart, mHighlightPath, mText); 6461 mEditor.updateCursorsPositions(); 6462 mHighlightPathBogus = false; 6463 } 6464 6465 // XXX should pass to skin instead of drawing directly 6466 highlightPaint.setColor(mCurTextColor); 6467 highlightPaint.setStyle(Paint.Style.STROKE); 6468 highlight = mHighlightPath; 6469 } 6470 } else { 6471 if (mHighlightPathBogus) { 6472 if (mHighlightPath == null) mHighlightPath = new Path(); 6473 mHighlightPath.reset(); 6474 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 6475 mHighlightPathBogus = false; 6476 } 6477 6478 // XXX should pass to skin instead of drawing directly 6479 highlightPaint.setColor(mHighlightColor); 6480 highlightPaint.setStyle(Paint.Style.FILL); 6481 6482 highlight = mHighlightPath; 6483 } 6484 } 6485 return highlight; 6486 } 6487 6488 /** 6489 * @hide 6490 */ 6491 public int getHorizontalOffsetForDrawables() { 6492 return 0; 6493 } 6494 6495 @Override 6496 protected void onDraw(Canvas canvas) { 6497 restartMarqueeIfNeeded(); 6498 6499 // Draw the background for this view 6500 super.onDraw(canvas); 6501 6502 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6503 final int compoundPaddingTop = getCompoundPaddingTop(); 6504 final int compoundPaddingRight = getCompoundPaddingRight(); 6505 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6506 final int scrollX = mScrollX; 6507 final int scrollY = mScrollY; 6508 final int right = mRight; 6509 final int left = mLeft; 6510 final int bottom = mBottom; 6511 final int top = mTop; 6512 final boolean isLayoutRtl = isLayoutRtl(); 6513 final int offset = getHorizontalOffsetForDrawables(); 6514 final int leftOffset = isLayoutRtl ? 0 : offset; 6515 final int rightOffset = isLayoutRtl ? offset : 0; 6516 6517 final Drawables dr = mDrawables; 6518 if (dr != null) { 6519 /* 6520 * Compound, not extended, because the icon is not clipped 6521 * if the text height is smaller. 6522 */ 6523 6524 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 6525 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 6526 6527 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6528 // Make sure to update invalidateDrawable() when changing this code. 6529 if (dr.mShowing[Drawables.LEFT] != null) { 6530 canvas.save(); 6531 canvas.translate(scrollX + mPaddingLeft + leftOffset, 6532 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 6533 dr.mShowing[Drawables.LEFT].draw(canvas); 6534 canvas.restore(); 6535 } 6536 6537 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6538 // Make sure to update invalidateDrawable() when changing this code. 6539 if (dr.mShowing[Drawables.RIGHT] != null) { 6540 canvas.save(); 6541 canvas.translate(scrollX + right - left - mPaddingRight 6542 - dr.mDrawableSizeRight - rightOffset, 6543 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 6544 dr.mShowing[Drawables.RIGHT].draw(canvas); 6545 canvas.restore(); 6546 } 6547 6548 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6549 // Make sure to update invalidateDrawable() when changing this code. 6550 if (dr.mShowing[Drawables.TOP] != null) { 6551 canvas.save(); 6552 canvas.translate(scrollX + compoundPaddingLeft 6553 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 6554 dr.mShowing[Drawables.TOP].draw(canvas); 6555 canvas.restore(); 6556 } 6557 6558 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6559 // Make sure to update invalidateDrawable() when changing this code. 6560 if (dr.mShowing[Drawables.BOTTOM] != null) { 6561 canvas.save(); 6562 canvas.translate(scrollX + compoundPaddingLeft 6563 + (hspace - dr.mDrawableWidthBottom) / 2, 6564 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 6565 dr.mShowing[Drawables.BOTTOM].draw(canvas); 6566 canvas.restore(); 6567 } 6568 } 6569 6570 int color = mCurTextColor; 6571 6572 if (mLayout == null) { 6573 assumeLayout(); 6574 } 6575 6576 Layout layout = mLayout; 6577 6578 if (mHint != null && mText.length() == 0) { 6579 if (mHintTextColor != null) { 6580 color = mCurHintTextColor; 6581 } 6582 6583 layout = mHintLayout; 6584 } 6585 6586 mTextPaint.setColor(color); 6587 mTextPaint.drawableState = getDrawableState(); 6588 6589 canvas.save(); 6590 /* Would be faster if we didn't have to do this. Can we chop the 6591 (displayable) text so that we don't need to do this ever? 6592 */ 6593 6594 int extendedPaddingTop = getExtendedPaddingTop(); 6595 int extendedPaddingBottom = getExtendedPaddingBottom(); 6596 6597 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6598 final int maxScrollY = mLayout.getHeight() - vspace; 6599 6600 float clipLeft = compoundPaddingLeft + scrollX; 6601 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 6602 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 6603 float clipBottom = bottom - top + scrollY 6604 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 6605 6606 if (mShadowRadius != 0) { 6607 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 6608 clipRight += Math.max(0, mShadowDx + mShadowRadius); 6609 6610 clipTop += Math.min(0, mShadowDy - mShadowRadius); 6611 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 6612 } 6613 6614 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 6615 6616 int voffsetText = 0; 6617 int voffsetCursor = 0; 6618 6619 // translate in by our padding 6620 /* shortcircuit calling getVerticaOffset() */ 6621 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6622 voffsetText = getVerticalOffset(false); 6623 voffsetCursor = getVerticalOffset(true); 6624 } 6625 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 6626 6627 final int layoutDirection = getLayoutDirection(); 6628 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 6629 if (isMarqueeFadeEnabled()) { 6630 if (!mSingleLine && getLineCount() == 1 && canMarquee() 6631 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 6632 final int width = mRight - mLeft; 6633 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 6634 final float dx = mLayout.getLineRight(0) - (width - padding); 6635 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6636 } 6637 6638 if (mMarquee != null && mMarquee.isRunning()) { 6639 final float dx = -mMarquee.getScroll(); 6640 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6641 } 6642 } 6643 6644 final int cursorOffsetVertical = voffsetCursor - voffsetText; 6645 6646 Path highlight = getUpdatedHighlightPath(); 6647 if (mEditor != null) { 6648 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 6649 } else { 6650 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 6651 } 6652 6653 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 6654 final float dx = mMarquee.getGhostOffset(); 6655 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6656 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 6657 } 6658 6659 canvas.restore(); 6660 } 6661 6662 @Override 6663 public void getFocusedRect(Rect r) { 6664 if (mLayout == null) { 6665 super.getFocusedRect(r); 6666 return; 6667 } 6668 6669 int selEnd = getSelectionEnd(); 6670 if (selEnd < 0) { 6671 super.getFocusedRect(r); 6672 return; 6673 } 6674 6675 int selStart = getSelectionStart(); 6676 if (selStart < 0 || selStart >= selEnd) { 6677 int line = mLayout.getLineForOffset(selEnd); 6678 r.top = mLayout.getLineTop(line); 6679 r.bottom = mLayout.getLineBottom(line); 6680 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 6681 r.right = r.left + 4; 6682 } else { 6683 int lineStart = mLayout.getLineForOffset(selStart); 6684 int lineEnd = mLayout.getLineForOffset(selEnd); 6685 r.top = mLayout.getLineTop(lineStart); 6686 r.bottom = mLayout.getLineBottom(lineEnd); 6687 if (lineStart == lineEnd) { 6688 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 6689 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 6690 } else { 6691 // Selection extends across multiple lines -- make the focused 6692 // rect cover the entire width. 6693 if (mHighlightPathBogus) { 6694 if (mHighlightPath == null) mHighlightPath = new Path(); 6695 mHighlightPath.reset(); 6696 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 6697 mHighlightPathBogus = false; 6698 } 6699 synchronized (TEMP_RECTF) { 6700 mHighlightPath.computeBounds(TEMP_RECTF, true); 6701 r.left = (int) TEMP_RECTF.left - 1; 6702 r.right = (int) TEMP_RECTF.right + 1; 6703 } 6704 } 6705 } 6706 6707 // Adjust for padding and gravity. 6708 int paddingLeft = getCompoundPaddingLeft(); 6709 int paddingTop = getExtendedPaddingTop(); 6710 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6711 paddingTop += getVerticalOffset(false); 6712 } 6713 r.offset(paddingLeft, paddingTop); 6714 int paddingBottom = getExtendedPaddingBottom(); 6715 r.bottom += paddingBottom; 6716 } 6717 6718 /** 6719 * Return the number of lines of text, or 0 if the internal Layout has not 6720 * been built. 6721 */ 6722 public int getLineCount() { 6723 return mLayout != null ? mLayout.getLineCount() : 0; 6724 } 6725 6726 /** 6727 * Return the baseline for the specified line (0...getLineCount() - 1) 6728 * If bounds is not null, return the top, left, right, bottom extents 6729 * of the specified line in it. If the internal Layout has not been built, 6730 * return 0 and set bounds to (0, 0, 0, 0) 6731 * @param line which line to examine (0..getLineCount() - 1) 6732 * @param bounds Optional. If not null, it returns the extent of the line 6733 * @return the Y-coordinate of the baseline 6734 */ 6735 public int getLineBounds(int line, Rect bounds) { 6736 if (mLayout == null) { 6737 if (bounds != null) { 6738 bounds.set(0, 0, 0, 0); 6739 } 6740 return 0; 6741 } else { 6742 int baseline = mLayout.getLineBounds(line, bounds); 6743 6744 int voffset = getExtendedPaddingTop(); 6745 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6746 voffset += getVerticalOffset(true); 6747 } 6748 if (bounds != null) { 6749 bounds.offset(getCompoundPaddingLeft(), voffset); 6750 } 6751 return baseline + voffset; 6752 } 6753 } 6754 6755 @Override 6756 public int getBaseline() { 6757 if (mLayout == null) { 6758 return super.getBaseline(); 6759 } 6760 6761 return getBaselineOffset() + mLayout.getLineBaseline(0); 6762 } 6763 6764 int getBaselineOffset() { 6765 int voffset = 0; 6766 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6767 voffset = getVerticalOffset(true); 6768 } 6769 6770 if (isLayoutModeOptical(mParent)) { 6771 voffset -= getOpticalInsets().top; 6772 } 6773 6774 return getExtendedPaddingTop() + voffset; 6775 } 6776 6777 /** 6778 * @hide 6779 */ 6780 @Override 6781 protected int getFadeTop(boolean offsetRequired) { 6782 if (mLayout == null) return 0; 6783 6784 int voffset = 0; 6785 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6786 voffset = getVerticalOffset(true); 6787 } 6788 6789 if (offsetRequired) voffset += getTopPaddingOffset(); 6790 6791 return getExtendedPaddingTop() + voffset; 6792 } 6793 6794 /** 6795 * @hide 6796 */ 6797 @Override 6798 protected int getFadeHeight(boolean offsetRequired) { 6799 return mLayout != null ? mLayout.getHeight() : 0; 6800 } 6801 6802 @Override 6803 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 6804 if (mText instanceof Spannable && mLinksClickable) { 6805 final float x = event.getX(pointerIndex); 6806 final float y = event.getY(pointerIndex); 6807 final int offset = getOffsetForPosition(x, y); 6808 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset, 6809 ClickableSpan.class); 6810 if (clickables.length > 0) { 6811 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 6812 } 6813 } 6814 if (isTextSelectable() || isTextEditable()) { 6815 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 6816 } 6817 return super.onResolvePointerIcon(event, pointerIndex); 6818 } 6819 6820 @Override 6821 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 6822 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 6823 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 6824 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 6825 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 6826 return true; 6827 } 6828 return super.onKeyPreIme(keyCode, event); 6829 } 6830 6831 /** 6832 * @hide 6833 */ 6834 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 6835 // Do nothing unless mEditor is in text action mode. 6836 if (mEditor == null || mEditor.getTextActionMode() == null) { 6837 return false; 6838 } 6839 6840 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 6841 KeyEvent.DispatcherState state = getKeyDispatcherState(); 6842 if (state != null) { 6843 state.startTracking(event, this); 6844 } 6845 return true; 6846 } else if (event.getAction() == KeyEvent.ACTION_UP) { 6847 KeyEvent.DispatcherState state = getKeyDispatcherState(); 6848 if (state != null) { 6849 state.handleUpEvent(event); 6850 } 6851 if (event.isTracking() && !event.isCanceled()) { 6852 stopTextActionMode(); 6853 return true; 6854 } 6855 } 6856 return false; 6857 } 6858 6859 @Override 6860 public boolean onKeyDown(int keyCode, KeyEvent event) { 6861 final int which = doKeyDown(keyCode, event, null); 6862 if (which == KEY_EVENT_NOT_HANDLED) { 6863 return super.onKeyDown(keyCode, event); 6864 } 6865 6866 return true; 6867 } 6868 6869 @Override 6870 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 6871 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 6872 final int which = doKeyDown(keyCode, down, event); 6873 if (which == KEY_EVENT_NOT_HANDLED) { 6874 // Go through default dispatching. 6875 return super.onKeyMultiple(keyCode, repeatCount, event); 6876 } 6877 if (which == KEY_EVENT_HANDLED) { 6878 // Consumed the whole thing. 6879 return true; 6880 } 6881 6882 repeatCount--; 6883 6884 // We are going to dispatch the remaining events to either the input 6885 // or movement method. To do this, we will just send a repeated stream 6886 // of down and up events until we have done the complete repeatCount. 6887 // It would be nice if those interfaces had an onKeyMultiple() method, 6888 // but adding that is a more complicated change. 6889 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 6890 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 6891 // mEditor and mEditor.mInput are not null from doKeyDown 6892 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 6893 while (--repeatCount > 0) { 6894 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 6895 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 6896 } 6897 hideErrorIfUnchanged(); 6898 6899 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 6900 // mMovement is not null from doKeyDown 6901 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up); 6902 while (--repeatCount > 0) { 6903 mMovement.onKeyDown(this, (Spannable) mText, keyCode, down); 6904 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up); 6905 } 6906 } 6907 6908 return true; 6909 } 6910 6911 /** 6912 * Returns true if pressing ENTER in this field advances focus instead 6913 * of inserting the character. This is true mostly in single-line fields, 6914 * but also in mail addresses and subjects which will display on multiple 6915 * lines but where it doesn't make sense to insert newlines. 6916 */ 6917 private boolean shouldAdvanceFocusOnEnter() { 6918 if (getKeyListener() == null) { 6919 return false; 6920 } 6921 6922 if (mSingleLine) { 6923 return true; 6924 } 6925 6926 if (mEditor != null 6927 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 6928 == EditorInfo.TYPE_CLASS_TEXT) { 6929 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 6930 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 6931 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 6932 return true; 6933 } 6934 } 6935 6936 return false; 6937 } 6938 6939 /** 6940 * Returns true if pressing TAB in this field advances focus instead 6941 * of inserting the character. Insert tabs only in multi-line editors. 6942 */ 6943 private boolean shouldAdvanceFocusOnTab() { 6944 if (getKeyListener() != null && !mSingleLine && mEditor != null 6945 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 6946 == EditorInfo.TYPE_CLASS_TEXT) { 6947 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 6948 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 6949 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 6950 return false; 6951 } 6952 } 6953 return true; 6954 } 6955 6956 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 6957 if (!isEnabled()) { 6958 return KEY_EVENT_NOT_HANDLED; 6959 } 6960 6961 // If this is the initial keydown, we don't want to prevent a movement away from this view. 6962 // While this shouldn't be necessary because any time we're preventing default movement we 6963 // should be restricting the focus to remain within this view, thus we'll also receive 6964 // the key up event, occasionally key up events will get dropped and we don't want to 6965 // prevent the user from traversing out of this on the next key down. 6966 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 6967 mPreventDefaultMovement = false; 6968 } 6969 6970 switch (keyCode) { 6971 case KeyEvent.KEYCODE_ENTER: 6972 if (event.hasNoModifiers()) { 6973 // When mInputContentType is set, we know that we are 6974 // running in a "modern" cupcake environment, so don't need 6975 // to worry about the application trying to capture 6976 // enter key events. 6977 if (mEditor != null && mEditor.mInputContentType != null) { 6978 // If there is an action listener, given them a 6979 // chance to consume the event. 6980 if (mEditor.mInputContentType.onEditorActionListener != null 6981 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 6982 this, EditorInfo.IME_NULL, event)) { 6983 mEditor.mInputContentType.enterDown = true; 6984 // We are consuming the enter key for them. 6985 return KEY_EVENT_HANDLED; 6986 } 6987 } 6988 6989 // If our editor should move focus when enter is pressed, or 6990 // this is a generated event from an IME action button, then 6991 // don't let it be inserted into the text. 6992 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 6993 || shouldAdvanceFocusOnEnter()) { 6994 if (hasOnClickListeners()) { 6995 return KEY_EVENT_NOT_HANDLED; 6996 } 6997 return KEY_EVENT_HANDLED; 6998 } 6999 } 7000 break; 7001 7002 case KeyEvent.KEYCODE_DPAD_CENTER: 7003 if (event.hasNoModifiers()) { 7004 if (shouldAdvanceFocusOnEnter()) { 7005 return KEY_EVENT_NOT_HANDLED; 7006 } 7007 } 7008 break; 7009 7010 case KeyEvent.KEYCODE_TAB: 7011 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 7012 if (shouldAdvanceFocusOnTab()) { 7013 return KEY_EVENT_NOT_HANDLED; 7014 } 7015 } 7016 break; 7017 7018 // Has to be done on key down (and not on key up) to correctly be intercepted. 7019 case KeyEvent.KEYCODE_BACK: 7020 if (mEditor != null && mEditor.getTextActionMode() != null) { 7021 stopTextActionMode(); 7022 return KEY_EVENT_HANDLED; 7023 } 7024 break; 7025 7026 case KeyEvent.KEYCODE_CUT: 7027 if (event.hasNoModifiers() && canCut()) { 7028 if (onTextContextMenuItem(ID_CUT)) { 7029 return KEY_EVENT_HANDLED; 7030 } 7031 } 7032 break; 7033 7034 case KeyEvent.KEYCODE_COPY: 7035 if (event.hasNoModifiers() && canCopy()) { 7036 if (onTextContextMenuItem(ID_COPY)) { 7037 return KEY_EVENT_HANDLED; 7038 } 7039 } 7040 break; 7041 7042 case KeyEvent.KEYCODE_PASTE: 7043 if (event.hasNoModifiers() && canPaste()) { 7044 if (onTextContextMenuItem(ID_PASTE)) { 7045 return KEY_EVENT_HANDLED; 7046 } 7047 } 7048 break; 7049 } 7050 7051 if (mEditor != null && mEditor.mKeyListener != null) { 7052 boolean doDown = true; 7053 if (otherEvent != null) { 7054 try { 7055 beginBatchEdit(); 7056 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 7057 otherEvent); 7058 hideErrorIfUnchanged(); 7059 doDown = false; 7060 if (handled) { 7061 return KEY_EVENT_HANDLED; 7062 } 7063 } catch (AbstractMethodError e) { 7064 // onKeyOther was added after 1.0, so if it isn't 7065 // implemented we need to try to dispatch as a regular down. 7066 } finally { 7067 endBatchEdit(); 7068 } 7069 } 7070 7071 if (doDown) { 7072 beginBatchEdit(); 7073 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 7074 keyCode, event); 7075 endBatchEdit(); 7076 hideErrorIfUnchanged(); 7077 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 7078 } 7079 } 7080 7081 // bug 650865: sometimes we get a key event before a layout. 7082 // don't try to move around if we don't know the layout. 7083 7084 if (mMovement != null && mLayout != null) { 7085 boolean doDown = true; 7086 if (otherEvent != null) { 7087 try { 7088 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 7089 otherEvent); 7090 doDown = false; 7091 if (handled) { 7092 return KEY_EVENT_HANDLED; 7093 } 7094 } catch (AbstractMethodError e) { 7095 // onKeyOther was added after 1.0, so if it isn't 7096 // implemented we need to try to dispatch as a regular down. 7097 } 7098 } 7099 if (doDown) { 7100 if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) { 7101 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 7102 mPreventDefaultMovement = true; 7103 } 7104 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 7105 } 7106 } 7107 } 7108 7109 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 7110 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 7111 } 7112 7113 /** 7114 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 7115 * can be recorded. 7116 * @hide 7117 */ 7118 public void resetErrorChangedFlag() { 7119 /* 7120 * Keep track of what the error was before doing the input 7121 * so that if an input filter changed the error, we leave 7122 * that error showing. Otherwise, we take down whatever 7123 * error was showing when the user types something. 7124 */ 7125 if (mEditor != null) mEditor.mErrorWasChanged = false; 7126 } 7127 7128 /** 7129 * @hide 7130 */ 7131 public void hideErrorIfUnchanged() { 7132 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 7133 setError(null, null); 7134 } 7135 } 7136 7137 @Override 7138 public boolean onKeyUp(int keyCode, KeyEvent event) { 7139 if (!isEnabled()) { 7140 return super.onKeyUp(keyCode, event); 7141 } 7142 7143 if (!KeyEvent.isModifierKey(keyCode)) { 7144 mPreventDefaultMovement = false; 7145 } 7146 7147 switch (keyCode) { 7148 case KeyEvent.KEYCODE_DPAD_CENTER: 7149 if (event.hasNoModifiers()) { 7150 /* 7151 * If there is a click listener, just call through to 7152 * super, which will invoke it. 7153 * 7154 * If there isn't a click listener, try to show the soft 7155 * input method. (It will also 7156 * call performClick(), but that won't do anything in 7157 * this case.) 7158 */ 7159 if (!hasOnClickListeners()) { 7160 if (mMovement != null && mText instanceof Editable 7161 && mLayout != null && onCheckIsTextEditor()) { 7162 InputMethodManager imm = InputMethodManager.peekInstance(); 7163 viewClicked(imm); 7164 if (imm != null && getShowSoftInputOnFocus()) { 7165 imm.showSoftInput(this, 0); 7166 } 7167 } 7168 } 7169 } 7170 return super.onKeyUp(keyCode, event); 7171 7172 case KeyEvent.KEYCODE_ENTER: 7173 if (event.hasNoModifiers()) { 7174 if (mEditor != null && mEditor.mInputContentType != null 7175 && mEditor.mInputContentType.onEditorActionListener != null 7176 && mEditor.mInputContentType.enterDown) { 7177 mEditor.mInputContentType.enterDown = false; 7178 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 7179 this, EditorInfo.IME_NULL, event)) { 7180 return true; 7181 } 7182 } 7183 7184 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 7185 || shouldAdvanceFocusOnEnter()) { 7186 /* 7187 * If there is a click listener, just call through to 7188 * super, which will invoke it. 7189 * 7190 * If there isn't a click listener, try to advance focus, 7191 * but still call through to super, which will reset the 7192 * pressed state and longpress state. (It will also 7193 * call performClick(), but that won't do anything in 7194 * this case.) 7195 */ 7196 if (!hasOnClickListeners()) { 7197 View v = focusSearch(FOCUS_DOWN); 7198 7199 if (v != null) { 7200 if (!v.requestFocus(FOCUS_DOWN)) { 7201 throw new IllegalStateException("focus search returned a view " 7202 + "that wasn't able to take focus!"); 7203 } 7204 7205 /* 7206 * Return true because we handled the key; super 7207 * will return false because there was no click 7208 * listener. 7209 */ 7210 super.onKeyUp(keyCode, event); 7211 return true; 7212 } else if ((event.getFlags() 7213 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 7214 // No target for next focus, but make sure the IME 7215 // if this came from it. 7216 InputMethodManager imm = InputMethodManager.peekInstance(); 7217 if (imm != null && imm.isActive(this)) { 7218 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7219 } 7220 } 7221 } 7222 } 7223 return super.onKeyUp(keyCode, event); 7224 } 7225 break; 7226 } 7227 7228 if (mEditor != null && mEditor.mKeyListener != null) { 7229 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 7230 return true; 7231 } 7232 } 7233 7234 if (mMovement != null && mLayout != null) { 7235 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) { 7236 return true; 7237 } 7238 } 7239 7240 return super.onKeyUp(keyCode, event); 7241 } 7242 7243 @Override 7244 public boolean onCheckIsTextEditor() { 7245 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 7246 } 7247 7248 @Override 7249 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 7250 if (onCheckIsTextEditor() && isEnabled()) { 7251 mEditor.createInputMethodStateIfNeeded(); 7252 outAttrs.inputType = getInputType(); 7253 if (mEditor.mInputContentType != null) { 7254 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 7255 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 7256 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 7257 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 7258 outAttrs.extras = mEditor.mInputContentType.extras; 7259 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 7260 } else { 7261 outAttrs.imeOptions = EditorInfo.IME_NULL; 7262 outAttrs.hintLocales = null; 7263 } 7264 if (focusSearch(FOCUS_DOWN) != null) { 7265 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 7266 } 7267 if (focusSearch(FOCUS_UP) != null) { 7268 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 7269 } 7270 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 7271 == EditorInfo.IME_ACTION_UNSPECIFIED) { 7272 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 7273 // An action has not been set, but the enter key will move to 7274 // the next focus, so set the action to that. 7275 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 7276 } else { 7277 // An action has not been set, and there is no focus to move 7278 // to, so let's just supply a "done" action. 7279 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 7280 } 7281 if (!shouldAdvanceFocusOnEnter()) { 7282 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 7283 } 7284 } 7285 if (isMultilineInputType(outAttrs.inputType)) { 7286 // Multi-line text editors should always show an enter key. 7287 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 7288 } 7289 outAttrs.hintText = mHint; 7290 if (mText instanceof Editable) { 7291 InputConnection ic = new EditableInputConnection(this); 7292 outAttrs.initialSelStart = getSelectionStart(); 7293 outAttrs.initialSelEnd = getSelectionEnd(); 7294 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 7295 return ic; 7296 } 7297 } 7298 return null; 7299 } 7300 7301 /** 7302 * If this TextView contains editable content, extract a portion of it 7303 * based on the information in <var>request</var> in to <var>outText</var>. 7304 * @return Returns true if the text was successfully extracted, else false. 7305 */ 7306 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 7307 createEditorIfNeeded(); 7308 return mEditor.extractText(request, outText); 7309 } 7310 7311 /** 7312 * This is used to remove all style-impacting spans from text before new 7313 * extracted text is being replaced into it, so that we don't have any 7314 * lingering spans applied during the replace. 7315 */ 7316 static void removeParcelableSpans(Spannable spannable, int start, int end) { 7317 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 7318 int i = spans.length; 7319 while (i > 0) { 7320 i--; 7321 spannable.removeSpan(spans[i]); 7322 } 7323 } 7324 7325 /** 7326 * Apply to this text view the given extracted text, as previously 7327 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 7328 */ 7329 public void setExtractedText(ExtractedText text) { 7330 Editable content = getEditableText(); 7331 if (text.text != null) { 7332 if (content == null) { 7333 setText(text.text, TextView.BufferType.EDITABLE); 7334 } else { 7335 int start = 0; 7336 int end = content.length(); 7337 7338 if (text.partialStartOffset >= 0) { 7339 final int N = content.length(); 7340 start = text.partialStartOffset; 7341 if (start > N) start = N; 7342 end = text.partialEndOffset; 7343 if (end > N) end = N; 7344 } 7345 7346 removeParcelableSpans(content, start, end); 7347 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 7348 if (text.text instanceof Spanned) { 7349 // OK to copy spans only. 7350 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 7351 Object.class, content, start); 7352 } 7353 } else { 7354 content.replace(start, end, text.text); 7355 } 7356 } 7357 } 7358 7359 // Now set the selection position... make sure it is in range, to 7360 // avoid crashes. If this is a partial update, it is possible that 7361 // the underlying text may have changed, causing us problems here. 7362 // Also we just don't want to trust clients to do the right thing. 7363 Spannable sp = (Spannable) getText(); 7364 final int N = sp.length(); 7365 int start = text.selectionStart; 7366 if (start < 0) { 7367 start = 0; 7368 } else if (start > N) { 7369 start = N; 7370 } 7371 int end = text.selectionEnd; 7372 if (end < 0) { 7373 end = 0; 7374 } else if (end > N) { 7375 end = N; 7376 } 7377 Selection.setSelection(sp, start, end); 7378 7379 // Finally, update the selection mode. 7380 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 7381 MetaKeyKeyListener.startSelecting(this, sp); 7382 } else { 7383 MetaKeyKeyListener.stopSelecting(this, sp); 7384 } 7385 } 7386 7387 /** 7388 * @hide 7389 */ 7390 public void setExtracting(ExtractedTextRequest req) { 7391 if (mEditor.mInputMethodState != null) { 7392 mEditor.mInputMethodState.mExtractedTextRequest = req; 7393 } 7394 // This would stop a possible selection mode, but no such mode is started in case 7395 // extracted mode will start. Some text is selected though, and will trigger an action mode 7396 // in the extracted view. 7397 mEditor.hideCursorAndSpanControllers(); 7398 stopTextActionMode(); 7399 if (mEditor.mSelectionModifierCursorController != null) { 7400 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 7401 } 7402 } 7403 7404 /** 7405 * Called by the framework in response to a text completion from 7406 * the current input method, provided by it calling 7407 * {@link InputConnection#commitCompletion 7408 * InputConnection.commitCompletion()}. The default implementation does 7409 * nothing; text views that are supporting auto-completion should override 7410 * this to do their desired behavior. 7411 * 7412 * @param text The auto complete text the user has selected. 7413 */ 7414 public void onCommitCompletion(CompletionInfo text) { 7415 // intentionally empty 7416 } 7417 7418 /** 7419 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 7420 * a dictionary) from the current input method, provided by it calling 7421 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 7422 * The default implementation flashes the background of the corrected word to provide 7423 * feedback to the user. 7424 * 7425 * @param info The auto correct info about the text that was corrected. 7426 */ 7427 public void onCommitCorrection(CorrectionInfo info) { 7428 if (mEditor != null) mEditor.onCommitCorrection(info); 7429 } 7430 7431 public void beginBatchEdit() { 7432 if (mEditor != null) mEditor.beginBatchEdit(); 7433 } 7434 7435 public void endBatchEdit() { 7436 if (mEditor != null) mEditor.endBatchEdit(); 7437 } 7438 7439 /** 7440 * Called by the framework in response to a request to begin a batch 7441 * of edit operations through a call to link {@link #beginBatchEdit()}. 7442 */ 7443 public void onBeginBatchEdit() { 7444 // intentionally empty 7445 } 7446 7447 /** 7448 * Called by the framework in response to a request to end a batch 7449 * of edit operations through a call to link {@link #endBatchEdit}. 7450 */ 7451 public void onEndBatchEdit() { 7452 // intentionally empty 7453 } 7454 7455 /** 7456 * Called by the framework in response to a private command from the 7457 * current method, provided by it calling 7458 * {@link InputConnection#performPrivateCommand 7459 * InputConnection.performPrivateCommand()}. 7460 * 7461 * @param action The action name of the command. 7462 * @param data Any additional data for the command. This may be null. 7463 * @return Return true if you handled the command, else false. 7464 */ 7465 public boolean onPrivateIMECommand(String action, Bundle data) { 7466 return false; 7467 } 7468 7469 private void nullLayouts() { 7470 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 7471 mSavedLayout = (BoringLayout) mLayout; 7472 } 7473 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 7474 mSavedHintLayout = (BoringLayout) mHintLayout; 7475 } 7476 7477 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 7478 7479 mBoring = mHintBoring = null; 7480 7481 // Since it depends on the value of mLayout 7482 if (mEditor != null) mEditor.prepareCursorControllers(); 7483 } 7484 7485 /** 7486 * Make a new Layout based on the already-measured size of the view, 7487 * on the assumption that it was measured correctly at some point. 7488 */ 7489 private void assumeLayout() { 7490 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7491 7492 if (width < 1) { 7493 width = 0; 7494 } 7495 7496 int physicalWidth = width; 7497 7498 if (mHorizontallyScrolling) { 7499 width = VERY_WIDE; 7500 } 7501 7502 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 7503 physicalWidth, false); 7504 } 7505 7506 private Layout.Alignment getLayoutAlignment() { 7507 Layout.Alignment alignment; 7508 switch (getTextAlignment()) { 7509 case TEXT_ALIGNMENT_GRAVITY: 7510 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 7511 case Gravity.START: 7512 alignment = Layout.Alignment.ALIGN_NORMAL; 7513 break; 7514 case Gravity.END: 7515 alignment = Layout.Alignment.ALIGN_OPPOSITE; 7516 break; 7517 case Gravity.LEFT: 7518 alignment = Layout.Alignment.ALIGN_LEFT; 7519 break; 7520 case Gravity.RIGHT: 7521 alignment = Layout.Alignment.ALIGN_RIGHT; 7522 break; 7523 case Gravity.CENTER_HORIZONTAL: 7524 alignment = Layout.Alignment.ALIGN_CENTER; 7525 break; 7526 default: 7527 alignment = Layout.Alignment.ALIGN_NORMAL; 7528 break; 7529 } 7530 break; 7531 case TEXT_ALIGNMENT_TEXT_START: 7532 alignment = Layout.Alignment.ALIGN_NORMAL; 7533 break; 7534 case TEXT_ALIGNMENT_TEXT_END: 7535 alignment = Layout.Alignment.ALIGN_OPPOSITE; 7536 break; 7537 case TEXT_ALIGNMENT_CENTER: 7538 alignment = Layout.Alignment.ALIGN_CENTER; 7539 break; 7540 case TEXT_ALIGNMENT_VIEW_START: 7541 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 7542 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 7543 break; 7544 case TEXT_ALIGNMENT_VIEW_END: 7545 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 7546 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 7547 break; 7548 case TEXT_ALIGNMENT_INHERIT: 7549 // This should never happen as we have already resolved the text alignment 7550 // but better safe than sorry so we just fall through 7551 default: 7552 alignment = Layout.Alignment.ALIGN_NORMAL; 7553 break; 7554 } 7555 return alignment; 7556 } 7557 7558 /** 7559 * The width passed in is now the desired layout width, 7560 * not the full view width with padding. 7561 * {@hide} 7562 */ 7563 protected void makeNewLayout(int wantWidth, int hintWidth, 7564 BoringLayout.Metrics boring, 7565 BoringLayout.Metrics hintBoring, 7566 int ellipsisWidth, boolean bringIntoView) { 7567 stopMarquee(); 7568 7569 // Update "old" cached values 7570 mOldMaximum = mMaximum; 7571 mOldMaxMode = mMaxMode; 7572 7573 mHighlightPathBogus = true; 7574 7575 if (wantWidth < 0) { 7576 wantWidth = 0; 7577 } 7578 if (hintWidth < 0) { 7579 hintWidth = 0; 7580 } 7581 7582 Layout.Alignment alignment = getLayoutAlignment(); 7583 final boolean testDirChange = mSingleLine && mLayout != null 7584 && (alignment == Layout.Alignment.ALIGN_NORMAL 7585 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 7586 int oldDir = 0; 7587 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 7588 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 7589 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 7590 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 7591 TruncateAt effectiveEllipsize = mEllipsize; 7592 if (mEllipsize == TruncateAt.MARQUEE 7593 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7594 effectiveEllipsize = TruncateAt.END_SMALL; 7595 } 7596 7597 if (mTextDir == null) { 7598 mTextDir = getTextDirectionHeuristic(); 7599 } 7600 7601 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 7602 effectiveEllipsize, effectiveEllipsize == mEllipsize); 7603 if (switchEllipsize) { 7604 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 7605 ? TruncateAt.END : TruncateAt.MARQUEE; 7606 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 7607 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 7608 } 7609 7610 shouldEllipsize = mEllipsize != null; 7611 mHintLayout = null; 7612 7613 if (mHint != null) { 7614 if (shouldEllipsize) hintWidth = wantWidth; 7615 7616 if (hintBoring == UNKNOWN_BORING) { 7617 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 7618 mHintBoring); 7619 if (hintBoring != null) { 7620 mHintBoring = hintBoring; 7621 } 7622 } 7623 7624 if (hintBoring != null) { 7625 if (hintBoring.width <= hintWidth 7626 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 7627 if (mSavedHintLayout != null) { 7628 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 7629 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7630 hintBoring, mIncludePad); 7631 } else { 7632 mHintLayout = BoringLayout.make(mHint, mTextPaint, 7633 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7634 hintBoring, mIncludePad); 7635 } 7636 7637 mSavedHintLayout = (BoringLayout) mHintLayout; 7638 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 7639 if (mSavedHintLayout != null) { 7640 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 7641 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7642 hintBoring, mIncludePad, mEllipsize, 7643 ellipsisWidth); 7644 } else { 7645 mHintLayout = BoringLayout.make(mHint, mTextPaint, 7646 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7647 hintBoring, mIncludePad, mEllipsize, 7648 ellipsisWidth); 7649 } 7650 } 7651 } 7652 // TODO: code duplication with makeSingleLayout() 7653 if (mHintLayout == null) { 7654 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 7655 mHint.length(), mTextPaint, hintWidth) 7656 .setAlignment(alignment) 7657 .setTextDirection(mTextDir) 7658 .setLineSpacing(mSpacingAdd, mSpacingMult) 7659 .setIncludePad(mIncludePad) 7660 .setBreakStrategy(mBreakStrategy) 7661 .setHyphenationFrequency(mHyphenationFrequency) 7662 .setJustify(mJustify) 7663 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 7664 if (shouldEllipsize) { 7665 builder.setEllipsize(mEllipsize) 7666 .setEllipsizedWidth(ellipsisWidth); 7667 } 7668 mHintLayout = builder.build(); 7669 } 7670 } 7671 7672 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 7673 registerForPreDraw(); 7674 } 7675 7676 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7677 if (!compressText(ellipsisWidth)) { 7678 final int height = mLayoutParams.height; 7679 // If the size of the view does not depend on the size of the text, try to 7680 // start the marquee immediately 7681 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 7682 startMarquee(); 7683 } else { 7684 // Defer the start of the marquee until we know our width (see setFrame()) 7685 mRestartMarquee = true; 7686 } 7687 } 7688 } 7689 7690 // CursorControllers need a non-null mLayout 7691 if (mEditor != null) mEditor.prepareCursorControllers(); 7692 } 7693 7694 /** 7695 * @hide 7696 */ 7697 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 7698 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 7699 boolean useSaved) { 7700 Layout result = null; 7701 if (mText instanceof Spannable) { 7702 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 7703 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, 7704 mBreakStrategy, mHyphenationFrequency, mJustify, 7705 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); 7706 } else { 7707 if (boring == UNKNOWN_BORING) { 7708 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 7709 if (boring != null) { 7710 mBoring = boring; 7711 } 7712 } 7713 7714 if (boring != null) { 7715 if (boring.width <= wantWidth 7716 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 7717 if (useSaved && mSavedLayout != null) { 7718 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 7719 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7720 boring, mIncludePad); 7721 } else { 7722 result = BoringLayout.make(mTransformed, mTextPaint, 7723 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7724 boring, mIncludePad); 7725 } 7726 7727 if (useSaved) { 7728 mSavedLayout = (BoringLayout) result; 7729 } 7730 } else if (shouldEllipsize && boring.width <= wantWidth) { 7731 if (useSaved && mSavedLayout != null) { 7732 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 7733 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7734 boring, mIncludePad, effectiveEllipsize, 7735 ellipsisWidth); 7736 } else { 7737 result = BoringLayout.make(mTransformed, mTextPaint, 7738 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7739 boring, mIncludePad, effectiveEllipsize, 7740 ellipsisWidth); 7741 } 7742 } 7743 } 7744 } 7745 if (result == null) { 7746 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 7747 0, mTransformed.length(), mTextPaint, wantWidth) 7748 .setAlignment(alignment) 7749 .setTextDirection(mTextDir) 7750 .setLineSpacing(mSpacingAdd, mSpacingMult) 7751 .setIncludePad(mIncludePad) 7752 .setBreakStrategy(mBreakStrategy) 7753 .setHyphenationFrequency(mHyphenationFrequency) 7754 .setJustify(mJustify) 7755 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 7756 if (shouldEllipsize) { 7757 builder.setEllipsize(effectiveEllipsize) 7758 .setEllipsizedWidth(ellipsisWidth); 7759 } 7760 // TODO: explore always setting maxLines 7761 result = builder.build(); 7762 } 7763 return result; 7764 } 7765 7766 private boolean compressText(float width) { 7767 if (isHardwareAccelerated()) return false; 7768 7769 // Only compress the text if it hasn't been compressed by the previous pass 7770 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 7771 && mTextPaint.getTextScaleX() == 1.0f) { 7772 final float textWidth = mLayout.getLineWidth(0); 7773 final float overflow = (textWidth + 1.0f - width) / width; 7774 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 7775 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 7776 post(new Runnable() { 7777 public void run() { 7778 requestLayout(); 7779 } 7780 }); 7781 return true; 7782 } 7783 } 7784 7785 return false; 7786 } 7787 7788 private static int desired(Layout layout) { 7789 int n = layout.getLineCount(); 7790 CharSequence text = layout.getText(); 7791 float max = 0; 7792 7793 // if any line was wrapped, we can't use it. 7794 // but it's ok for the last line not to have a newline 7795 7796 for (int i = 0; i < n - 1; i++) { 7797 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 7798 return -1; 7799 } 7800 } 7801 7802 for (int i = 0; i < n; i++) { 7803 max = Math.max(max, layout.getLineWidth(i)); 7804 } 7805 7806 return (int) Math.ceil(max); 7807 } 7808 7809 /** 7810 * Set whether the TextView includes extra top and bottom padding to make 7811 * room for accents that go above the normal ascent and descent. 7812 * The default is true. 7813 * 7814 * @see #getIncludeFontPadding() 7815 * 7816 * @attr ref android.R.styleable#TextView_includeFontPadding 7817 */ 7818 public void setIncludeFontPadding(boolean includepad) { 7819 if (mIncludePad != includepad) { 7820 mIncludePad = includepad; 7821 7822 if (mLayout != null) { 7823 nullLayouts(); 7824 requestLayout(); 7825 invalidate(); 7826 } 7827 } 7828 } 7829 7830 /** 7831 * Gets whether the TextView includes extra top and bottom padding to make 7832 * room for accents that go above the normal ascent and descent. 7833 * 7834 * @see #setIncludeFontPadding(boolean) 7835 * 7836 * @attr ref android.R.styleable#TextView_includeFontPadding 7837 */ 7838 public boolean getIncludeFontPadding() { 7839 return mIncludePad; 7840 } 7841 7842 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 7843 7844 @Override 7845 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 7846 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 7847 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 7848 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 7849 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 7850 7851 int width; 7852 int height; 7853 7854 BoringLayout.Metrics boring = UNKNOWN_BORING; 7855 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 7856 7857 if (mTextDir == null) { 7858 mTextDir = getTextDirectionHeuristic(); 7859 } 7860 7861 int des = -1; 7862 boolean fromexisting = false; 7863 7864 if (widthMode == MeasureSpec.EXACTLY) { 7865 // Parent has told us how big to be. So be it. 7866 width = widthSize; 7867 } else { 7868 if (mLayout != null && mEllipsize == null) { 7869 des = desired(mLayout); 7870 } 7871 7872 if (des < 0) { 7873 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 7874 if (boring != null) { 7875 mBoring = boring; 7876 } 7877 } else { 7878 fromexisting = true; 7879 } 7880 7881 if (boring == null || boring == UNKNOWN_BORING) { 7882 if (des < 0) { 7883 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0, 7884 mTransformed.length(), mTextPaint, mTextDir)); 7885 } 7886 width = des; 7887 } else { 7888 width = boring.width; 7889 } 7890 7891 final Drawables dr = mDrawables; 7892 if (dr != null) { 7893 width = Math.max(width, dr.mDrawableWidthTop); 7894 width = Math.max(width, dr.mDrawableWidthBottom); 7895 } 7896 7897 if (mHint != null) { 7898 int hintDes = -1; 7899 int hintWidth; 7900 7901 if (mHintLayout != null && mEllipsize == null) { 7902 hintDes = desired(mHintLayout); 7903 } 7904 7905 if (hintDes < 0) { 7906 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 7907 if (hintBoring != null) { 7908 mHintBoring = hintBoring; 7909 } 7910 } 7911 7912 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 7913 if (hintDes < 0) { 7914 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, 0, mHint.length(), 7915 mTextPaint, mTextDir)); 7916 } 7917 hintWidth = hintDes; 7918 } else { 7919 hintWidth = hintBoring.width; 7920 } 7921 7922 if (hintWidth > width) { 7923 width = hintWidth; 7924 } 7925 } 7926 7927 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 7928 7929 if (mMaxWidthMode == EMS) { 7930 width = Math.min(width, mMaxWidth * getLineHeight()); 7931 } else { 7932 width = Math.min(width, mMaxWidth); 7933 } 7934 7935 if (mMinWidthMode == EMS) { 7936 width = Math.max(width, mMinWidth * getLineHeight()); 7937 } else { 7938 width = Math.max(width, mMinWidth); 7939 } 7940 7941 // Check against our minimum width 7942 width = Math.max(width, getSuggestedMinimumWidth()); 7943 7944 if (widthMode == MeasureSpec.AT_MOST) { 7945 width = Math.min(widthSize, width); 7946 } 7947 } 7948 7949 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7950 int unpaddedWidth = want; 7951 7952 if (mHorizontallyScrolling) want = VERY_WIDE; 7953 7954 int hintWant = want; 7955 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 7956 7957 if (mLayout == null) { 7958 makeNewLayout(want, hintWant, boring, hintBoring, 7959 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 7960 } else { 7961 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 7962 || (mLayout.getEllipsizedWidth() 7963 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 7964 7965 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 7966 && (want > mLayout.getWidth()) 7967 && (mLayout instanceof BoringLayout 7968 || (fromexisting && des >= 0 && des <= want)); 7969 7970 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 7971 7972 if (layoutChanged || maximumChanged) { 7973 if (!maximumChanged && widthChanged) { 7974 mLayout.increaseWidthTo(want); 7975 } else { 7976 makeNewLayout(want, hintWant, boring, hintBoring, 7977 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 7978 } 7979 } else { 7980 // Nothing has changed 7981 } 7982 } 7983 7984 if (heightMode == MeasureSpec.EXACTLY) { 7985 // Parent has told us how big to be. So be it. 7986 height = heightSize; 7987 mDesiredHeightAtMeasure = -1; 7988 } else { 7989 int desired = getDesiredHeight(); 7990 7991 height = desired; 7992 mDesiredHeightAtMeasure = desired; 7993 7994 if (heightMode == MeasureSpec.AT_MOST) { 7995 height = Math.min(desired, heightSize); 7996 } 7997 } 7998 7999 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 8000 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 8001 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 8002 } 8003 8004 /* 8005 * We didn't let makeNewLayout() register to bring the cursor into view, 8006 * so do it here if there is any possibility that it is needed. 8007 */ 8008 if (mMovement != null 8009 || mLayout.getWidth() > unpaddedWidth 8010 || mLayout.getHeight() > unpaddedHeight) { 8011 registerForPreDraw(); 8012 } else { 8013 scrollTo(0, 0); 8014 } 8015 8016 setMeasuredDimension(width, height); 8017 } 8018 8019 /** 8020 * Automatically computes and sets the text size. 8021 */ 8022 private void autoSizeText() { 8023 final int maxWidth = getWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 8024 final int maxHeight = getHeight() - getExtendedPaddingBottom() - getExtendedPaddingTop(); 8025 8026 if (maxWidth <= 0 || maxHeight <= 0) { 8027 return; 8028 } 8029 8030 synchronized (TEMP_RECTF) { 8031 TEMP_RECTF.setEmpty(); 8032 TEMP_RECTF.right = maxWidth; 8033 TEMP_RECTF.bottom = maxHeight; 8034 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 8035 if (optimalTextSize != getTextSize()) { 8036 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize); 8037 } 8038 } 8039 } 8040 8041 /** 8042 * Performs a binary search to find the largest text size that will still fit within the size 8043 * available to this view. 8044 */ 8045 private int findLargestTextSizeWhichFits(RectF availableSpace) { 8046 final int sizesCount = mAutoSizeTextSizesInPx.length; 8047 if (sizesCount == 0) { 8048 throw new IllegalStateException("No available text sizes to choose from."); 8049 } 8050 8051 int bestSizeIndex = 0; 8052 int lowIndex = bestSizeIndex + 1; 8053 int highIndex = sizesCount - 1; 8054 int sizeToTryIndex; 8055 while (lowIndex <= highIndex) { 8056 sizeToTryIndex = (lowIndex + highIndex) / 2; 8057 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 8058 bestSizeIndex = lowIndex; 8059 lowIndex = sizeToTryIndex + 1; 8060 } else { 8061 highIndex = sizeToTryIndex - 1; 8062 bestSizeIndex = highIndex; 8063 } 8064 } 8065 8066 return mAutoSizeTextSizesInPx[bestSizeIndex]; 8067 } 8068 8069 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 8070 final CharSequence text = getText(); 8071 final int maxLines = getMaxLines(); 8072 if (mTempTextPaint == null) { 8073 mTempTextPaint = new TextPaint(); 8074 } else { 8075 mTempTextPaint.reset(); 8076 } 8077 mTempTextPaint.set(getPaint()); 8078 mTempTextPaint.setTextSize(suggestedSizeInPx); 8079 8080 if ((mLayout instanceof BoringLayout) && BoringLayout.isBoring( 8081 text, mTempTextPaint, getTextDirectionHeuristic(), mBoring) != null) { 8082 return mTempTextPaint.getFontSpacing() <= availableSpace.bottom 8083 && mTempTextPaint.measureText(text, 0, text.length()) <= availableSpace.right; 8084 } else { 8085 StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(), 8086 mTempTextPaint, 8087 getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight()); 8088 layoutBuilder.setAlignment(getLayoutAlignment()); 8089 layoutBuilder.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()); 8090 layoutBuilder.setIncludePad(true); 8091 StaticLayout layout = layoutBuilder.build(); 8092 8093 // Lines overflow. 8094 if (maxLines != -1 && layout.getLineCount() > maxLines) { 8095 return false; 8096 } 8097 8098 // Height overflow. 8099 if (layout.getHeight() > availableSpace.bottom) { 8100 return false; 8101 } 8102 } 8103 8104 return true; 8105 } 8106 8107 private int getDesiredHeight() { 8108 return Math.max( 8109 getDesiredHeight(mLayout, true), 8110 getDesiredHeight(mHintLayout, mEllipsize != null)); 8111 } 8112 8113 private int getDesiredHeight(Layout layout, boolean cap) { 8114 if (layout == null) { 8115 return 0; 8116 } 8117 8118 /* 8119 * Don't cap the hint to a certain number of lines. 8120 * (Do cap it, though, if we have a maximum pixel height.) 8121 */ 8122 int desired = layout.getHeight(cap); 8123 8124 final Drawables dr = mDrawables; 8125 if (dr != null) { 8126 desired = Math.max(desired, dr.mDrawableHeightLeft); 8127 desired = Math.max(desired, dr.mDrawableHeightRight); 8128 } 8129 8130 int linecount = layout.getLineCount(); 8131 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 8132 desired += padding; 8133 8134 if (mMaxMode != LINES) { 8135 desired = Math.min(desired, mMaximum); 8136 } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) { 8137 desired = layout.getLineTop(mMaximum); 8138 8139 if (dr != null) { 8140 desired = Math.max(desired, dr.mDrawableHeightLeft); 8141 desired = Math.max(desired, dr.mDrawableHeightRight); 8142 } 8143 8144 desired += padding; 8145 linecount = mMaximum; 8146 } 8147 8148 if (mMinMode == LINES) { 8149 if (linecount < mMinimum) { 8150 desired += getLineHeight() * (mMinimum - linecount); 8151 } 8152 } else { 8153 desired = Math.max(desired, mMinimum); 8154 } 8155 8156 // Check against our minimum height 8157 desired = Math.max(desired, getSuggestedMinimumHeight()); 8158 8159 return desired; 8160 } 8161 8162 /** 8163 * Check whether a change to the existing text layout requires a 8164 * new view layout. 8165 */ 8166 private void checkForResize() { 8167 boolean sizeChanged = false; 8168 8169 if (mLayout != null) { 8170 // Check if our width changed 8171 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 8172 sizeChanged = true; 8173 invalidate(); 8174 } 8175 8176 // Check if our height changed 8177 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 8178 int desiredHeight = getDesiredHeight(); 8179 8180 if (desiredHeight != this.getHeight()) { 8181 sizeChanged = true; 8182 } 8183 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 8184 if (mDesiredHeightAtMeasure >= 0) { 8185 int desiredHeight = getDesiredHeight(); 8186 8187 if (desiredHeight != mDesiredHeightAtMeasure) { 8188 sizeChanged = true; 8189 } 8190 } 8191 } 8192 } 8193 8194 if (sizeChanged) { 8195 requestLayout(); 8196 // caller will have already invalidated 8197 } 8198 } 8199 8200 /** 8201 * Check whether entirely new text requires a new view layout 8202 * or merely a new text layout. 8203 */ 8204 private void checkForRelayout() { 8205 // If we have a fixed width, we can just swap in a new text layout 8206 // if the text height stays the same or if the view height is fixed. 8207 8208 if (((mLayoutParams.width != LayoutParams.WRAP_CONTENT && mLayoutParams.width != 0) 8209 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 8210 && (mHint == null || mHintLayout != null) 8211 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 8212 // Static width, so try making a new text layout. 8213 8214 int oldht = mLayout.getHeight(); 8215 int want = mLayout.getWidth(); 8216 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 8217 8218 /* 8219 * No need to bring the text into view, since the size is not 8220 * changing (unless we do the requestLayout(), in which case it 8221 * will happen at measure). 8222 */ 8223 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 8224 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 8225 false); 8226 8227 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 8228 // In a fixed-height view, so use our new text layout. 8229 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 8230 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 8231 invalidate(); 8232 return; 8233 } 8234 8235 // Dynamic height, but height has stayed the same, 8236 // so use our new text layout. 8237 if (mLayout.getHeight() == oldht 8238 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 8239 invalidate(); 8240 return; 8241 } 8242 } 8243 8244 // We lose: the height has changed and we have a dynamic height. 8245 // Request a new view layout using our new text layout. 8246 requestLayout(); 8247 invalidate(); 8248 } else { 8249 // Dynamic width, so we have no choice but to request a new 8250 // view layout with a new text layout. 8251 nullLayouts(); 8252 requestLayout(); 8253 invalidate(); 8254 } 8255 } 8256 8257 @Override 8258 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 8259 super.onLayout(changed, left, top, right, bottom); 8260 if (mDeferScroll >= 0) { 8261 int curs = mDeferScroll; 8262 mDeferScroll = -1; 8263 bringPointIntoView(Math.min(curs, mText.length())); 8264 } 8265 8266 if (isAutoSizeEnabled()) { 8267 if (mNeedsAutoSizeText) { 8268 // Call auto-size after the width and height have been calculated. 8269 autoSizeText(); 8270 } 8271 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 8272 // after the next layout round should set this to false. 8273 mNeedsAutoSizeText = true; 8274 } 8275 } 8276 8277 private boolean isShowingHint() { 8278 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 8279 } 8280 8281 /** 8282 * Returns true if anything changed. 8283 */ 8284 private boolean bringTextIntoView() { 8285 Layout layout = isShowingHint() ? mHintLayout : mLayout; 8286 int line = 0; 8287 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8288 line = layout.getLineCount() - 1; 8289 } 8290 8291 Layout.Alignment a = layout.getParagraphAlignment(line); 8292 int dir = layout.getParagraphDirection(line); 8293 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8294 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8295 int ht = layout.getHeight(); 8296 8297 int scrollx, scrolly; 8298 8299 // Convert to left, center, or right alignment. 8300 if (a == Layout.Alignment.ALIGN_NORMAL) { 8301 a = dir == Layout.DIR_LEFT_TO_RIGHT 8302 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8303 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 8304 a = dir == Layout.DIR_LEFT_TO_RIGHT 8305 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8306 } 8307 8308 if (a == Layout.Alignment.ALIGN_CENTER) { 8309 /* 8310 * Keep centered if possible, or, if it is too wide to fit, 8311 * keep leading edge in view. 8312 */ 8313 8314 int left = (int) Math.floor(layout.getLineLeft(line)); 8315 int right = (int) Math.ceil(layout.getLineRight(line)); 8316 8317 if (right - left < hspace) { 8318 scrollx = (right + left) / 2 - hspace / 2; 8319 } else { 8320 if (dir < 0) { 8321 scrollx = right - hspace; 8322 } else { 8323 scrollx = left; 8324 } 8325 } 8326 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 8327 int right = (int) Math.ceil(layout.getLineRight(line)); 8328 scrollx = right - hspace; 8329 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 8330 scrollx = (int) Math.floor(layout.getLineLeft(line)); 8331 } 8332 8333 if (ht < vspace) { 8334 scrolly = 0; 8335 } else { 8336 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8337 scrolly = ht - vspace; 8338 } else { 8339 scrolly = 0; 8340 } 8341 } 8342 8343 if (scrollx != mScrollX || scrolly != mScrollY) { 8344 scrollTo(scrollx, scrolly); 8345 return true; 8346 } else { 8347 return false; 8348 } 8349 } 8350 8351 /** 8352 * Move the point, specified by the offset, into the view if it is needed. 8353 * This has to be called after layout. Returns true if anything changed. 8354 */ 8355 public boolean bringPointIntoView(int offset) { 8356 if (isLayoutRequested()) { 8357 mDeferScroll = offset; 8358 return false; 8359 } 8360 boolean changed = false; 8361 8362 Layout layout = isShowingHint() ? mHintLayout : mLayout; 8363 8364 if (layout == null) return changed; 8365 8366 int line = layout.getLineForOffset(offset); 8367 8368 int grav; 8369 8370 switch (layout.getParagraphAlignment(line)) { 8371 case ALIGN_LEFT: 8372 grav = 1; 8373 break; 8374 case ALIGN_RIGHT: 8375 grav = -1; 8376 break; 8377 case ALIGN_NORMAL: 8378 grav = layout.getParagraphDirection(line); 8379 break; 8380 case ALIGN_OPPOSITE: 8381 grav = -layout.getParagraphDirection(line); 8382 break; 8383 case ALIGN_CENTER: 8384 default: 8385 grav = 0; 8386 break; 8387 } 8388 8389 // We only want to clamp the cursor to fit within the layout width 8390 // in left-to-right modes, because in a right to left alignment, 8391 // we want to scroll to keep the line-right on the screen, as other 8392 // lines are likely to have text flush with the right margin, which 8393 // we want to keep visible. 8394 // A better long-term solution would probably be to measure both 8395 // the full line and a blank-trimmed version, and, for example, use 8396 // the latter measurement for centering and right alignment, but for 8397 // the time being we only implement the cursor clamping in left to 8398 // right where it is most likely to be annoying. 8399 final boolean clamped = grav > 0; 8400 // FIXME: Is it okay to truncate this, or should we round? 8401 final int x = (int) layout.getPrimaryHorizontal(offset, clamped, true); 8402 final int top = layout.getLineTop(line); 8403 final int bottom = layout.getLineTop(line + 1); 8404 8405 int left = (int) Math.floor(layout.getLineLeft(line)); 8406 int right = (int) Math.ceil(layout.getLineRight(line)); 8407 int ht = layout.getHeight(); 8408 8409 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8410 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8411 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 8412 // If cursor has been clamped, make sure we don't scroll. 8413 right = Math.max(x, left + hspace); 8414 } 8415 8416 int hslack = (bottom - top) / 2; 8417 int vslack = hslack; 8418 8419 if (vslack > vspace / 4) { 8420 vslack = vspace / 4; 8421 } 8422 if (hslack > hspace / 4) { 8423 hslack = hspace / 4; 8424 } 8425 8426 int hs = mScrollX; 8427 int vs = mScrollY; 8428 8429 if (top - vs < vslack) { 8430 vs = top - vslack; 8431 } 8432 if (bottom - vs > vspace - vslack) { 8433 vs = bottom - (vspace - vslack); 8434 } 8435 if (ht - vs < vspace) { 8436 vs = ht - vspace; 8437 } 8438 if (0 - vs > 0) { 8439 vs = 0; 8440 } 8441 8442 if (grav != 0) { 8443 if (x - hs < hslack) { 8444 hs = x - hslack; 8445 } 8446 if (x - hs > hspace - hslack) { 8447 hs = x - (hspace - hslack); 8448 } 8449 } 8450 8451 if (grav < 0) { 8452 if (left - hs > 0) { 8453 hs = left; 8454 } 8455 if (right - hs < hspace) { 8456 hs = right - hspace; 8457 } 8458 } else if (grav > 0) { 8459 if (right - hs < hspace) { 8460 hs = right - hspace; 8461 } 8462 if (left - hs > 0) { 8463 hs = left; 8464 } 8465 } else /* grav == 0 */ { 8466 if (right - left <= hspace) { 8467 /* 8468 * If the entire text fits, center it exactly. 8469 */ 8470 hs = left - (hspace - (right - left)) / 2; 8471 } else if (x > right - hslack) { 8472 /* 8473 * If we are near the right edge, keep the right edge 8474 * at the edge of the view. 8475 */ 8476 hs = right - hspace; 8477 } else if (x < left + hslack) { 8478 /* 8479 * If we are near the left edge, keep the left edge 8480 * at the edge of the view. 8481 */ 8482 hs = left; 8483 } else if (left > hs) { 8484 /* 8485 * Is there whitespace visible at the left? Fix it if so. 8486 */ 8487 hs = left; 8488 } else if (right < hs + hspace) { 8489 /* 8490 * Is there whitespace visible at the right? Fix it if so. 8491 */ 8492 hs = right - hspace; 8493 } else { 8494 /* 8495 * Otherwise, float as needed. 8496 */ 8497 if (x - hs < hslack) { 8498 hs = x - hslack; 8499 } 8500 if (x - hs > hspace - hslack) { 8501 hs = x - (hspace - hslack); 8502 } 8503 } 8504 } 8505 8506 if (hs != mScrollX || vs != mScrollY) { 8507 if (mScroller == null) { 8508 scrollTo(hs, vs); 8509 } else { 8510 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 8511 int dx = hs - mScrollX; 8512 int dy = vs - mScrollY; 8513 8514 if (duration > ANIMATED_SCROLL_GAP) { 8515 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 8516 awakenScrollBars(mScroller.getDuration()); 8517 invalidate(); 8518 } else { 8519 if (!mScroller.isFinished()) { 8520 mScroller.abortAnimation(); 8521 } 8522 8523 scrollBy(dx, dy); 8524 } 8525 8526 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 8527 } 8528 8529 changed = true; 8530 } 8531 8532 if (isFocused()) { 8533 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 8534 // requestRectangleOnScreen() is in terms of content coordinates. 8535 8536 // The offsets here are to ensure the rectangle we are using is 8537 // within our view bounds, in case the cursor is on the far left 8538 // or right. If it isn't withing the bounds, then this request 8539 // will be ignored. 8540 if (mTempRect == null) mTempRect = new Rect(); 8541 mTempRect.set(x - 2, top, x + 2, bottom); 8542 getInterestingRect(mTempRect, line); 8543 mTempRect.offset(mScrollX, mScrollY); 8544 8545 if (requestRectangleOnScreen(mTempRect)) { 8546 changed = true; 8547 } 8548 } 8549 8550 return changed; 8551 } 8552 8553 /** 8554 * Move the cursor, if needed, so that it is at an offset that is visible 8555 * to the user. This will not move the cursor if it represents more than 8556 * one character (a selection range). This will only work if the 8557 * TextView contains spannable text; otherwise it will do nothing. 8558 * 8559 * @return True if the cursor was actually moved, false otherwise. 8560 */ 8561 public boolean moveCursorToVisibleOffset() { 8562 if (!(mText instanceof Spannable)) { 8563 return false; 8564 } 8565 int start = getSelectionStart(); 8566 int end = getSelectionEnd(); 8567 if (start != end) { 8568 return false; 8569 } 8570 8571 // First: make sure the line is visible on screen: 8572 8573 int line = mLayout.getLineForOffset(start); 8574 8575 final int top = mLayout.getLineTop(line); 8576 final int bottom = mLayout.getLineTop(line + 1); 8577 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8578 int vslack = (bottom - top) / 2; 8579 if (vslack > vspace / 4) { 8580 vslack = vspace / 4; 8581 } 8582 final int vs = mScrollY; 8583 8584 if (top < (vs + vslack)) { 8585 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 8586 } else if (bottom > (vspace + vs - vslack)) { 8587 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 8588 } 8589 8590 // Next: make sure the character is visible on screen: 8591 8592 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8593 final int hs = mScrollX; 8594 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 8595 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 8596 8597 // line might contain bidirectional text 8598 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 8599 final int highChar = leftChar > rightChar ? leftChar : rightChar; 8600 8601 int newStart = start; 8602 if (newStart < lowChar) { 8603 newStart = lowChar; 8604 } else if (newStart > highChar) { 8605 newStart = highChar; 8606 } 8607 8608 if (newStart != start) { 8609 Selection.setSelection((Spannable) mText, newStart); 8610 return true; 8611 } 8612 8613 return false; 8614 } 8615 8616 @Override 8617 public void computeScroll() { 8618 if (mScroller != null) { 8619 if (mScroller.computeScrollOffset()) { 8620 mScrollX = mScroller.getCurrX(); 8621 mScrollY = mScroller.getCurrY(); 8622 invalidateParentCaches(); 8623 postInvalidate(); // So we draw again 8624 } 8625 } 8626 } 8627 8628 private void getInterestingRect(Rect r, int line) { 8629 convertFromViewportToContentCoordinates(r); 8630 8631 // Rectangle can can be expanded on first and last line to take 8632 // padding into account. 8633 // TODO Take left/right padding into account too? 8634 if (line == 0) r.top -= getExtendedPaddingTop(); 8635 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 8636 } 8637 8638 private void convertFromViewportToContentCoordinates(Rect r) { 8639 final int horizontalOffset = viewportToContentHorizontalOffset(); 8640 r.left += horizontalOffset; 8641 r.right += horizontalOffset; 8642 8643 final int verticalOffset = viewportToContentVerticalOffset(); 8644 r.top += verticalOffset; 8645 r.bottom += verticalOffset; 8646 } 8647 8648 int viewportToContentHorizontalOffset() { 8649 return getCompoundPaddingLeft() - mScrollX; 8650 } 8651 8652 int viewportToContentVerticalOffset() { 8653 int offset = getExtendedPaddingTop() - mScrollY; 8654 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8655 offset += getVerticalOffset(false); 8656 } 8657 return offset; 8658 } 8659 8660 @Override 8661 public void debug(int depth) { 8662 super.debug(depth); 8663 8664 String output = debugIndent(depth); 8665 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 8666 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 8667 + "} "; 8668 8669 if (mText != null) { 8670 8671 output += "mText=\"" + mText + "\" "; 8672 if (mLayout != null) { 8673 output += "mLayout width=" + mLayout.getWidth() 8674 + " height=" + mLayout.getHeight(); 8675 } 8676 } else { 8677 output += "mText=NULL"; 8678 } 8679 Log.d(VIEW_LOG_TAG, output); 8680 } 8681 8682 /** 8683 * Convenience for {@link Selection#getSelectionStart}. 8684 */ 8685 @ViewDebug.ExportedProperty(category = "text") 8686 public int getSelectionStart() { 8687 return Selection.getSelectionStart(getText()); 8688 } 8689 8690 /** 8691 * Convenience for {@link Selection#getSelectionEnd}. 8692 */ 8693 @ViewDebug.ExportedProperty(category = "text") 8694 public int getSelectionEnd() { 8695 return Selection.getSelectionEnd(getText()); 8696 } 8697 8698 /** 8699 * Return true iff there is a selection inside this text view. 8700 */ 8701 public boolean hasSelection() { 8702 final int selectionStart = getSelectionStart(); 8703 final int selectionEnd = getSelectionEnd(); 8704 8705 return selectionStart >= 0 && selectionStart != selectionEnd; 8706 } 8707 8708 String getSelectedText() { 8709 if (!hasSelection()) { 8710 return null; 8711 } 8712 8713 final int start = getSelectionStart(); 8714 final int end = getSelectionEnd(); 8715 return String.valueOf( 8716 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 8717 } 8718 8719 /** 8720 * Sets the properties of this field (lines, horizontally scrolling, 8721 * transformation method) to be for a single-line input. 8722 * 8723 * @attr ref android.R.styleable#TextView_singleLine 8724 */ 8725 public void setSingleLine() { 8726 setSingleLine(true); 8727 } 8728 8729 /** 8730 * Sets the properties of this field to transform input to ALL CAPS 8731 * display. This may use a "small caps" formatting if available. 8732 * This setting will be ignored if this field is editable or selectable. 8733 * 8734 * This call replaces the current transformation method. Disabling this 8735 * will not necessarily restore the previous behavior from before this 8736 * was enabled. 8737 * 8738 * @see #setTransformationMethod(TransformationMethod) 8739 * @attr ref android.R.styleable#TextView_textAllCaps 8740 */ 8741 public void setAllCaps(boolean allCaps) { 8742 if (allCaps) { 8743 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 8744 } else { 8745 setTransformationMethod(null); 8746 } 8747 } 8748 8749 /** 8750 * If true, sets the properties of this field (number of lines, horizontally scrolling, 8751 * transformation method) to be for a single-line input; if false, restores these to the default 8752 * conditions. 8753 * 8754 * Note that the default conditions are not necessarily those that were in effect prior this 8755 * method, and you may want to reset these properties to your custom values. 8756 * 8757 * @attr ref android.R.styleable#TextView_singleLine 8758 */ 8759 @android.view.RemotableViewMethod 8760 public void setSingleLine(boolean singleLine) { 8761 // Could be used, but may break backward compatibility. 8762 // if (mSingleLine == singleLine) return; 8763 setInputTypeSingleLine(singleLine); 8764 applySingleLine(singleLine, true, true); 8765 } 8766 8767 /** 8768 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 8769 * @param singleLine 8770 */ 8771 private void setInputTypeSingleLine(boolean singleLine) { 8772 if (mEditor != null 8773 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 8774 == EditorInfo.TYPE_CLASS_TEXT) { 8775 if (singleLine) { 8776 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 8777 } else { 8778 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 8779 } 8780 } 8781 } 8782 8783 private void applySingleLine(boolean singleLine, boolean applyTransformation, 8784 boolean changeMaxLines) { 8785 mSingleLine = singleLine; 8786 if (singleLine) { 8787 setLines(1); 8788 setHorizontallyScrolling(true); 8789 if (applyTransformation) { 8790 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 8791 } 8792 } else { 8793 if (changeMaxLines) { 8794 setMaxLines(Integer.MAX_VALUE); 8795 } 8796 setHorizontallyScrolling(false); 8797 if (applyTransformation) { 8798 setTransformationMethod(null); 8799 } 8800 } 8801 } 8802 8803 /** 8804 * Causes words in the text that are longer than the view's width 8805 * to be ellipsized instead of broken in the middle. You may also 8806 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 8807 * to constrain the text to a single line. Use <code>null</code> 8808 * to turn off ellipsizing. 8809 * 8810 * If {@link #setMaxLines} has been used to set two or more lines, 8811 * only {@link android.text.TextUtils.TruncateAt#END} and 8812 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 8813 * (other ellipsizing types will not do anything). 8814 * 8815 * @attr ref android.R.styleable#TextView_ellipsize 8816 */ 8817 public void setEllipsize(TextUtils.TruncateAt where) { 8818 // TruncateAt is an enum. != comparison is ok between these singleton objects. 8819 if (mEllipsize != where) { 8820 mEllipsize = where; 8821 8822 if (mLayout != null) { 8823 nullLayouts(); 8824 requestLayout(); 8825 invalidate(); 8826 } 8827 } 8828 } 8829 8830 /** 8831 * Sets how many times to repeat the marquee animation. Only applied if the 8832 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 8833 * 8834 * @see #getMarqueeRepeatLimit() 8835 * 8836 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 8837 */ 8838 public void setMarqueeRepeatLimit(int marqueeLimit) { 8839 mMarqueeRepeatLimit = marqueeLimit; 8840 } 8841 8842 /** 8843 * Gets the number of times the marquee animation is repeated. Only meaningful if the 8844 * TextView has marquee enabled. 8845 * 8846 * @return the number of times the marquee animation is repeated. -1 if the animation 8847 * repeats indefinitely 8848 * 8849 * @see #setMarqueeRepeatLimit(int) 8850 * 8851 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 8852 */ 8853 public int getMarqueeRepeatLimit() { 8854 return mMarqueeRepeatLimit; 8855 } 8856 8857 /** 8858 * Returns where, if anywhere, words that are longer than the view 8859 * is wide should be ellipsized. 8860 */ 8861 @ViewDebug.ExportedProperty 8862 public TextUtils.TruncateAt getEllipsize() { 8863 return mEllipsize; 8864 } 8865 8866 /** 8867 * Set the TextView so that when it takes focus, all the text is 8868 * selected. 8869 * 8870 * @attr ref android.R.styleable#TextView_selectAllOnFocus 8871 */ 8872 @android.view.RemotableViewMethod 8873 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 8874 createEditorIfNeeded(); 8875 mEditor.mSelectAllOnFocus = selectAllOnFocus; 8876 8877 if (selectAllOnFocus && !(mText instanceof Spannable)) { 8878 setText(mText, BufferType.SPANNABLE); 8879 } 8880 } 8881 8882 /** 8883 * Set whether the cursor is visible. The default is true. Note that this property only 8884 * makes sense for editable TextView. 8885 * 8886 * @see #isCursorVisible() 8887 * 8888 * @attr ref android.R.styleable#TextView_cursorVisible 8889 */ 8890 @android.view.RemotableViewMethod 8891 public void setCursorVisible(boolean visible) { 8892 if (visible && mEditor == null) return; // visible is the default value with no edit data 8893 createEditorIfNeeded(); 8894 if (mEditor.mCursorVisible != visible) { 8895 mEditor.mCursorVisible = visible; 8896 invalidate(); 8897 8898 mEditor.makeBlink(); 8899 8900 // InsertionPointCursorController depends on mCursorVisible 8901 mEditor.prepareCursorControllers(); 8902 } 8903 } 8904 8905 /** 8906 * @return whether or not the cursor is visible (assuming this TextView is editable) 8907 * 8908 * @see #setCursorVisible(boolean) 8909 * 8910 * @attr ref android.R.styleable#TextView_cursorVisible 8911 */ 8912 public boolean isCursorVisible() { 8913 // true is the default value 8914 return mEditor == null ? true : mEditor.mCursorVisible; 8915 } 8916 8917 private boolean canMarquee() { 8918 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8919 return width > 0 && (mLayout.getLineWidth(0) > width 8920 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 8921 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 8922 } 8923 8924 private void startMarquee() { 8925 // Do not ellipsize EditText 8926 if (getKeyListener() != null) return; 8927 8928 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 8929 return; 8930 } 8931 8932 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 8933 && getLineCount() == 1 && canMarquee()) { 8934 8935 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8936 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 8937 final Layout tmp = mLayout; 8938 mLayout = mSavedMarqueeModeLayout; 8939 mSavedMarqueeModeLayout = tmp; 8940 setHorizontalFadingEdgeEnabled(true); 8941 requestLayout(); 8942 invalidate(); 8943 } 8944 8945 if (mMarquee == null) mMarquee = new Marquee(this); 8946 mMarquee.start(mMarqueeRepeatLimit); 8947 } 8948 } 8949 8950 private void stopMarquee() { 8951 if (mMarquee != null && !mMarquee.isStopped()) { 8952 mMarquee.stop(); 8953 } 8954 8955 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 8956 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 8957 final Layout tmp = mSavedMarqueeModeLayout; 8958 mSavedMarqueeModeLayout = mLayout; 8959 mLayout = tmp; 8960 setHorizontalFadingEdgeEnabled(false); 8961 requestLayout(); 8962 invalidate(); 8963 } 8964 } 8965 8966 private void startStopMarquee(boolean start) { 8967 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8968 if (start) { 8969 startMarquee(); 8970 } else { 8971 stopMarquee(); 8972 } 8973 } 8974 } 8975 8976 /** 8977 * This method is called when the text is changed, in case any subclasses 8978 * would like to know. 8979 * 8980 * Within <code>text</code>, the <code>lengthAfter</code> characters 8981 * beginning at <code>start</code> have just replaced old text that had 8982 * length <code>lengthBefore</code>. It is an error to attempt to make 8983 * changes to <code>text</code> from this callback. 8984 * 8985 * @param text The text the TextView is displaying 8986 * @param start The offset of the start of the range of the text that was 8987 * modified 8988 * @param lengthBefore The length of the former text that has been replaced 8989 * @param lengthAfter The length of the replacement modified text 8990 */ 8991 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 8992 // intentionally empty, template pattern method can be overridden by subclasses 8993 } 8994 8995 /** 8996 * This method is called when the selection has changed, in case any 8997 * subclasses would like to know. 8998 * 8999 * @param selStart The new selection start location. 9000 * @param selEnd The new selection end location. 9001 */ 9002 protected void onSelectionChanged(int selStart, int selEnd) { 9003 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 9004 } 9005 9006 /** 9007 * Adds a TextWatcher to the list of those whose methods are called 9008 * whenever this TextView's text changes. 9009 * <p> 9010 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 9011 * not called after {@link #setText} calls. Now, doing {@link #setText} 9012 * if there are any text changed listeners forces the buffer type to 9013 * Editable if it would not otherwise be and does call this method. 9014 */ 9015 public void addTextChangedListener(TextWatcher watcher) { 9016 if (mListeners == null) { 9017 mListeners = new ArrayList<TextWatcher>(); 9018 } 9019 9020 mListeners.add(watcher); 9021 } 9022 9023 /** 9024 * Removes the specified TextWatcher from the list of those whose 9025 * methods are called 9026 * whenever this TextView's text changes. 9027 */ 9028 public void removeTextChangedListener(TextWatcher watcher) { 9029 if (mListeners != null) { 9030 int i = mListeners.indexOf(watcher); 9031 9032 if (i >= 0) { 9033 mListeners.remove(i); 9034 } 9035 } 9036 } 9037 9038 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 9039 if (mListeners != null) { 9040 final ArrayList<TextWatcher> list = mListeners; 9041 final int count = list.size(); 9042 for (int i = 0; i < count; i++) { 9043 list.get(i).beforeTextChanged(text, start, before, after); 9044 } 9045 } 9046 9047 // The spans that are inside or intersect the modified region no longer make sense 9048 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 9049 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 9050 } 9051 9052 // Removes all spans that are inside or actually overlap the start..end range 9053 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 9054 if (!(mText instanceof Editable)) return; 9055 Editable text = (Editable) mText; 9056 9057 T[] spans = text.getSpans(start, end, type); 9058 final int length = spans.length; 9059 for (int i = 0; i < length; i++) { 9060 final int spanStart = text.getSpanStart(spans[i]); 9061 final int spanEnd = text.getSpanEnd(spans[i]); 9062 if (spanEnd == start || spanStart == end) break; 9063 text.removeSpan(spans[i]); 9064 } 9065 } 9066 9067 void removeAdjacentSuggestionSpans(final int pos) { 9068 if (!(mText instanceof Editable)) return; 9069 final Editable text = (Editable) mText; 9070 9071 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 9072 final int length = spans.length; 9073 for (int i = 0; i < length; i++) { 9074 final int spanStart = text.getSpanStart(spans[i]); 9075 final int spanEnd = text.getSpanEnd(spans[i]); 9076 if (spanEnd == pos || spanStart == pos) { 9077 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 9078 text.removeSpan(spans[i]); 9079 } 9080 } 9081 } 9082 } 9083 9084 /** 9085 * Not private so it can be called from an inner class without going 9086 * through a thunk. 9087 */ 9088 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 9089 if (mListeners != null) { 9090 final ArrayList<TextWatcher> list = mListeners; 9091 final int count = list.size(); 9092 for (int i = 0; i < count; i++) { 9093 list.get(i).onTextChanged(text, start, before, after); 9094 } 9095 } 9096 9097 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 9098 } 9099 9100 /** 9101 * Not private so it can be called from an inner class without going 9102 * through a thunk. 9103 */ 9104 void sendAfterTextChanged(Editable text) { 9105 if (mListeners != null) { 9106 final ArrayList<TextWatcher> list = mListeners; 9107 final int count = list.size(); 9108 for (int i = 0; i < count; i++) { 9109 list.get(i).afterTextChanged(text); 9110 } 9111 } 9112 9113 // Always notify AutoFillManager - it will return right away if auto-fill is disabled. 9114 final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class); 9115 if (afm != null) { 9116 if (DEBUG_AUTOFILL) { 9117 Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + text); 9118 } 9119 afm.valueChanged(TextView.this); 9120 } 9121 9122 hideErrorIfUnchanged(); 9123 } 9124 9125 void updateAfterEdit() { 9126 invalidate(); 9127 int curs = getSelectionStart(); 9128 9129 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9130 registerForPreDraw(); 9131 } 9132 9133 checkForResize(); 9134 9135 if (curs >= 0) { 9136 mHighlightPathBogus = true; 9137 if (mEditor != null) mEditor.makeBlink(); 9138 bringPointIntoView(curs); 9139 } 9140 } 9141 9142 /** 9143 * Not private so it can be called from an inner class without going 9144 * through a thunk. 9145 */ 9146 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 9147 sLastCutCopyOrTextChangedTime = 0; 9148 9149 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 9150 if (ims == null || ims.mBatchEditNesting == 0) { 9151 updateAfterEdit(); 9152 } 9153 if (ims != null) { 9154 ims.mContentChanged = true; 9155 if (ims.mChangedStart < 0) { 9156 ims.mChangedStart = start; 9157 ims.mChangedEnd = start + before; 9158 } else { 9159 ims.mChangedStart = Math.min(ims.mChangedStart, start); 9160 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 9161 } 9162 ims.mChangedDelta += after - before; 9163 } 9164 resetErrorChangedFlag(); 9165 sendOnTextChanged(buffer, start, before, after); 9166 onTextChanged(buffer, start, before, after); 9167 } 9168 9169 /** 9170 * Not private so it can be called from an inner class without going 9171 * through a thunk. 9172 */ 9173 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 9174 // XXX Make the start and end move together if this ends up 9175 // spending too much time invalidating. 9176 9177 boolean selChanged = false; 9178 int newSelStart = -1, newSelEnd = -1; 9179 9180 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 9181 9182 if (what == Selection.SELECTION_END) { 9183 selChanged = true; 9184 newSelEnd = newStart; 9185 9186 if (oldStart >= 0 || newStart >= 0) { 9187 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 9188 checkForResize(); 9189 registerForPreDraw(); 9190 if (mEditor != null) mEditor.makeBlink(); 9191 } 9192 } 9193 9194 if (what == Selection.SELECTION_START) { 9195 selChanged = true; 9196 newSelStart = newStart; 9197 9198 if (oldStart >= 0 || newStart >= 0) { 9199 int end = Selection.getSelectionEnd(buf); 9200 invalidateCursor(end, oldStart, newStart); 9201 } 9202 } 9203 9204 if (selChanged) { 9205 mHighlightPathBogus = true; 9206 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 9207 9208 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 9209 if (newSelStart < 0) { 9210 newSelStart = Selection.getSelectionStart(buf); 9211 } 9212 if (newSelEnd < 0) { 9213 newSelEnd = Selection.getSelectionEnd(buf); 9214 } 9215 9216 if (mEditor != null) { 9217 mEditor.refreshTextActionMode(); 9218 if (!hasSelection() 9219 && mEditor.getTextActionMode() == null && hasTransientState()) { 9220 // User generated selection has been removed. 9221 setHasTransientState(false); 9222 } 9223 } 9224 onSelectionChanged(newSelStart, newSelEnd); 9225 } 9226 } 9227 9228 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 9229 || what instanceof CharacterStyle) { 9230 if (ims == null || ims.mBatchEditNesting == 0) { 9231 invalidate(); 9232 mHighlightPathBogus = true; 9233 checkForResize(); 9234 } else { 9235 ims.mContentChanged = true; 9236 } 9237 if (mEditor != null) { 9238 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 9239 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 9240 mEditor.invalidateHandlesAndActionMode(); 9241 } 9242 } 9243 9244 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 9245 mHighlightPathBogus = true; 9246 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 9247 ims.mSelectionModeChanged = true; 9248 } 9249 9250 if (Selection.getSelectionStart(buf) >= 0) { 9251 if (ims == null || ims.mBatchEditNesting == 0) { 9252 invalidateCursor(); 9253 } else { 9254 ims.mCursorChanged = true; 9255 } 9256 } 9257 } 9258 9259 if (what instanceof ParcelableSpan) { 9260 // If this is a span that can be sent to a remote process, 9261 // the current extract editor would be interested in it. 9262 if (ims != null && ims.mExtractedTextRequest != null) { 9263 if (ims.mBatchEditNesting != 0) { 9264 if (oldStart >= 0) { 9265 if (ims.mChangedStart > oldStart) { 9266 ims.mChangedStart = oldStart; 9267 } 9268 if (ims.mChangedStart > oldEnd) { 9269 ims.mChangedStart = oldEnd; 9270 } 9271 } 9272 if (newStart >= 0) { 9273 if (ims.mChangedStart > newStart) { 9274 ims.mChangedStart = newStart; 9275 } 9276 if (ims.mChangedStart > newEnd) { 9277 ims.mChangedStart = newEnd; 9278 } 9279 } 9280 } else { 9281 if (DEBUG_EXTRACT) { 9282 Log.v(LOG_TAG, "Span change outside of batch: " 9283 + oldStart + "-" + oldEnd + "," 9284 + newStart + "-" + newEnd + " " + what); 9285 } 9286 ims.mContentChanged = true; 9287 } 9288 } 9289 } 9290 9291 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 9292 && what instanceof SpellCheckSpan) { 9293 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 9294 } 9295 } 9296 9297 @Override 9298 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 9299 if (isTemporarilyDetached()) { 9300 // If we are temporarily in the detach state, then do nothing. 9301 super.onFocusChanged(focused, direction, previouslyFocusedRect); 9302 return; 9303 } 9304 9305 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 9306 9307 if (focused) { 9308 if (mText instanceof Spannable) { 9309 Spannable sp = (Spannable) mText; 9310 MetaKeyKeyListener.resetMetaState(sp); 9311 } 9312 } 9313 9314 startStopMarquee(focused); 9315 9316 if (mTransformation != null) { 9317 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 9318 } 9319 9320 super.onFocusChanged(focused, direction, previouslyFocusedRect); 9321 } 9322 9323 @Override 9324 public void onWindowFocusChanged(boolean hasWindowFocus) { 9325 super.onWindowFocusChanged(hasWindowFocus); 9326 9327 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 9328 9329 startStopMarquee(hasWindowFocus); 9330 } 9331 9332 @Override 9333 protected void onVisibilityChanged(View changedView, int visibility) { 9334 super.onVisibilityChanged(changedView, visibility); 9335 if (mEditor != null && visibility != VISIBLE) { 9336 mEditor.hideCursorAndSpanControllers(); 9337 stopTextActionMode(); 9338 } 9339 } 9340 9341 /** 9342 * Use {@link BaseInputConnection#removeComposingSpans 9343 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 9344 * state from this text view. 9345 */ 9346 public void clearComposingText() { 9347 if (mText instanceof Spannable) { 9348 BaseInputConnection.removeComposingSpans((Spannable) mText); 9349 } 9350 } 9351 9352 @Override 9353 public void setSelected(boolean selected) { 9354 boolean wasSelected = isSelected(); 9355 9356 super.setSelected(selected); 9357 9358 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9359 if (selected) { 9360 startMarquee(); 9361 } else { 9362 stopMarquee(); 9363 } 9364 } 9365 } 9366 9367 @Override 9368 public boolean onTouchEvent(MotionEvent event) { 9369 final int action = event.getActionMasked(); 9370 if (mEditor != null) { 9371 mEditor.onTouchEvent(event); 9372 9373 if (mEditor.mSelectionModifierCursorController != null 9374 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 9375 return true; 9376 } 9377 } 9378 9379 final boolean superResult = super.onTouchEvent(event); 9380 9381 /* 9382 * Don't handle the release after a long press, because it will move the selection away from 9383 * whatever the menu action was trying to affect. If the long press should have triggered an 9384 * insertion action mode, we can now actually show it. 9385 */ 9386 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 9387 mEditor.mDiscardNextActionUp = false; 9388 9389 if (mEditor.mIsInsertionActionModeStartPending) { 9390 mEditor.startInsertionActionMode(); 9391 mEditor.mIsInsertionActionModeStartPending = false; 9392 } 9393 return superResult; 9394 } 9395 9396 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 9397 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 9398 9399 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 9400 && mText instanceof Spannable && mLayout != null) { 9401 boolean handled = false; 9402 9403 if (mMovement != null) { 9404 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 9405 } 9406 9407 final boolean textIsSelectable = isTextSelectable(); 9408 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 9409 // The LinkMovementMethod which should handle taps on links has not been installed 9410 // on non editable text that support text selection. 9411 // We reproduce its behavior here to open links for these. 9412 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 9413 getSelectionEnd(), ClickableSpan.class); 9414 9415 if (links.length > 0) { 9416 links[0].onClick(this); 9417 handled = true; 9418 } 9419 } 9420 9421 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 9422 // Show the IME, except when selecting in read-only text. 9423 final InputMethodManager imm = InputMethodManager.peekInstance(); 9424 viewClicked(imm); 9425 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 9426 imm.showSoftInput(this, 0); 9427 } 9428 9429 // The above condition ensures that the mEditor is not null 9430 mEditor.onTouchUpEvent(event); 9431 9432 handled = true; 9433 } 9434 9435 if (handled) { 9436 return true; 9437 } 9438 } 9439 9440 return superResult; 9441 } 9442 9443 @Override 9444 public boolean onGenericMotionEvent(MotionEvent event) { 9445 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 9446 try { 9447 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 9448 return true; 9449 } 9450 } catch (AbstractMethodError ex) { 9451 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 9452 // Ignore its absence in case third party applications implemented the 9453 // interface directly. 9454 } 9455 } 9456 return super.onGenericMotionEvent(event); 9457 } 9458 9459 @Override 9460 protected void onCreateContextMenu(ContextMenu menu) { 9461 if (mEditor != null) { 9462 mEditor.onCreateContextMenu(menu); 9463 } 9464 } 9465 9466 @Override 9467 public boolean showContextMenu() { 9468 if (mEditor != null) { 9469 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 9470 } 9471 return super.showContextMenu(); 9472 } 9473 9474 @Override 9475 public boolean showContextMenu(float x, float y) { 9476 if (mEditor != null) { 9477 mEditor.setContextMenuAnchor(x, y); 9478 } 9479 return super.showContextMenu(x, y); 9480 } 9481 9482 /** 9483 * @return True iff this TextView contains a text that can be edited, or if this is 9484 * a selectable TextView. 9485 */ 9486 boolean isTextEditable() { 9487 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 9488 } 9489 9490 /** 9491 * Returns true, only while processing a touch gesture, if the initial 9492 * touch down event caused focus to move to the text view and as a result 9493 * its selection changed. Only valid while processing the touch gesture 9494 * of interest, in an editable text view. 9495 */ 9496 public boolean didTouchFocusSelect() { 9497 return mEditor != null && mEditor.mTouchFocusSelected; 9498 } 9499 9500 @Override 9501 public void cancelLongPress() { 9502 super.cancelLongPress(); 9503 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 9504 } 9505 9506 @Override 9507 public boolean onTrackballEvent(MotionEvent event) { 9508 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 9509 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 9510 return true; 9511 } 9512 } 9513 9514 return super.onTrackballEvent(event); 9515 } 9516 9517 public void setScroller(Scroller s) { 9518 mScroller = s; 9519 } 9520 9521 @Override 9522 protected float getLeftFadingEdgeStrength() { 9523 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 9524 final Marquee marquee = mMarquee; 9525 if (marquee.shouldDrawLeftFade()) { 9526 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 9527 } else { 9528 return 0.0f; 9529 } 9530 } else if (getLineCount() == 1) { 9531 final float lineLeft = getLayout().getLineLeft(0); 9532 if (lineLeft > mScrollX) return 0.0f; 9533 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 9534 } 9535 return super.getLeftFadingEdgeStrength(); 9536 } 9537 9538 @Override 9539 protected float getRightFadingEdgeStrength() { 9540 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 9541 final Marquee marquee = mMarquee; 9542 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 9543 } else if (getLineCount() == 1) { 9544 final float rightEdge = mScrollX + 9545 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9546 final float lineRight = getLayout().getLineRight(0); 9547 if (lineRight < rightEdge) return 0.0f; 9548 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 9549 } 9550 return super.getRightFadingEdgeStrength(); 9551 } 9552 9553 /** 9554 * Calculates the fading edge strength as the ratio of the distance between two 9555 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 9556 * value for the distance calculation. 9557 * 9558 * @param position1 A horizontal position. 9559 * @param position2 A horizontal position. 9560 * @return Fading edge strength between [0.0f, 1.0f]. 9561 */ 9562 @FloatRange(from = 0.0, to = 1.0) 9563 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 9564 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 9565 if (horizontalFadingEdgeLength == 0) return 0.0f; 9566 final float diff = Math.abs(position1 - position2); 9567 if (diff > horizontalFadingEdgeLength) return 1.0f; 9568 return diff / horizontalFadingEdgeLength; 9569 } 9570 9571 private boolean isMarqueeFadeEnabled() { 9572 return mEllipsize == TextUtils.TruncateAt.MARQUEE 9573 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 9574 } 9575 9576 @Override 9577 protected int computeHorizontalScrollRange() { 9578 if (mLayout != null) { 9579 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 9580 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 9581 } 9582 9583 return super.computeHorizontalScrollRange(); 9584 } 9585 9586 @Override 9587 protected int computeVerticalScrollRange() { 9588 if (mLayout != null) { 9589 return mLayout.getHeight(); 9590 } 9591 return super.computeVerticalScrollRange(); 9592 } 9593 9594 @Override 9595 protected int computeVerticalScrollExtent() { 9596 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9597 } 9598 9599 @Override 9600 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 9601 super.findViewsWithText(outViews, searched, flags); 9602 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 9603 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 9604 String searchedLowerCase = searched.toString().toLowerCase(); 9605 String textLowerCase = mText.toString().toLowerCase(); 9606 if (textLowerCase.contains(searchedLowerCase)) { 9607 outViews.add(this); 9608 } 9609 } 9610 } 9611 9612 /** 9613 * Type of the text buffer that defines the characteristics of the text such as static, 9614 * styleable, or editable. 9615 */ 9616 public enum BufferType { 9617 NORMAL, SPANNABLE, EDITABLE 9618 } 9619 9620 /** 9621 * Returns the TextView_textColor attribute from the TypedArray, if set, or 9622 * the TextAppearance_textColor from the TextView_textAppearance attribute, 9623 * if TextView_textColor was not set directly. 9624 * 9625 * @removed 9626 */ 9627 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 9628 if (attrs == null) { 9629 // Preserve behavior prior to removal of this API. 9630 throw new NullPointerException(); 9631 } 9632 9633 // It's not safe to use this method from apps. The parameter 'attrs' 9634 // must have been obtained using the TextView filter array which is not 9635 // available to the SDK. As such, we grab a default TypedArray with the 9636 // right filter instead here. 9637 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 9638 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 9639 if (colors == null) { 9640 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 9641 if (ap != 0) { 9642 final TypedArray appearance = context.obtainStyledAttributes( 9643 ap, R.styleable.TextAppearance); 9644 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 9645 appearance.recycle(); 9646 } 9647 } 9648 a.recycle(); 9649 9650 return colors; 9651 } 9652 9653 /** 9654 * Returns the default color from the TextView_textColor attribute from the 9655 * AttributeSet, if set, or the default color from the 9656 * TextAppearance_textColor from the TextView_textAppearance attribute, if 9657 * TextView_textColor was not set directly. 9658 * 9659 * @removed 9660 */ 9661 public static int getTextColor(Context context, TypedArray attrs, int def) { 9662 final ColorStateList colors = getTextColors(context, attrs); 9663 if (colors == null) { 9664 return def; 9665 } else { 9666 return colors.getDefaultColor(); 9667 } 9668 } 9669 9670 @Override 9671 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 9672 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 9673 // Handle Ctrl-only shortcuts. 9674 switch (keyCode) { 9675 case KeyEvent.KEYCODE_A: 9676 if (canSelectText()) { 9677 return onTextContextMenuItem(ID_SELECT_ALL); 9678 } 9679 break; 9680 case KeyEvent.KEYCODE_Z: 9681 if (canUndo()) { 9682 return onTextContextMenuItem(ID_UNDO); 9683 } 9684 break; 9685 case KeyEvent.KEYCODE_X: 9686 if (canCut()) { 9687 return onTextContextMenuItem(ID_CUT); 9688 } 9689 break; 9690 case KeyEvent.KEYCODE_C: 9691 if (canCopy()) { 9692 return onTextContextMenuItem(ID_COPY); 9693 } 9694 break; 9695 case KeyEvent.KEYCODE_V: 9696 if (canPaste()) { 9697 return onTextContextMenuItem(ID_PASTE); 9698 } 9699 break; 9700 } 9701 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 9702 // Handle Ctrl-Shift shortcuts. 9703 switch (keyCode) { 9704 case KeyEvent.KEYCODE_Z: 9705 if (canRedo()) { 9706 return onTextContextMenuItem(ID_REDO); 9707 } 9708 break; 9709 case KeyEvent.KEYCODE_V: 9710 if (canPaste()) { 9711 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 9712 } 9713 } 9714 } 9715 return super.onKeyShortcut(keyCode, event); 9716 } 9717 9718 /** 9719 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 9720 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 9721 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 9722 * sufficient. 9723 */ 9724 boolean canSelectText() { 9725 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 9726 } 9727 9728 /** 9729 * Test based on the <i>intrinsic</i> charateristics of the TextView. 9730 * The text must be spannable and the movement method must allow for arbitary selection. 9731 * 9732 * See also {@link #canSelectText()}. 9733 */ 9734 boolean textCanBeSelected() { 9735 // prepareCursorController() relies on this method. 9736 // If you change this condition, make sure prepareCursorController is called anywhere 9737 // the value of this condition might be changed. 9738 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 9739 return isTextEditable() 9740 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 9741 } 9742 9743 private Locale getTextServicesLocale(boolean allowNullLocale) { 9744 // Start fetching the text services locale asynchronously. 9745 updateTextServicesLocaleAsync(); 9746 // If !allowNullLocale and there is no cached text services locale, just return the default 9747 // locale. 9748 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 9749 : mCurrentSpellCheckerLocaleCache; 9750 } 9751 9752 /** 9753 * This is a temporary method. Future versions may support multi-locale text. 9754 * Caveat: This method may not return the latest text services locale, but this should be 9755 * acceptable and it's more important to make this method asynchronous. 9756 * 9757 * @return The locale that should be used for a word iterator 9758 * in this TextView, based on the current spell checker settings, 9759 * the current IME's locale, or the system default locale. 9760 * Please note that a word iterator in this TextView is different from another word iterator 9761 * used by SpellChecker.java of TextView. This method should be used for the former. 9762 * @hide 9763 */ 9764 // TODO: Support multi-locale 9765 // TODO: Update the text services locale immediately after the keyboard locale is switched 9766 // by catching intent of keyboard switch event 9767 public Locale getTextServicesLocale() { 9768 return getTextServicesLocale(false /* allowNullLocale */); 9769 } 9770 9771 /** 9772 * @return {@code true} if this TextView is specialized for showing and interacting with the 9773 * extracted text in a full-screen input method. 9774 * @hide 9775 */ 9776 public boolean isInExtractedMode() { 9777 return false; 9778 } 9779 9780 /** 9781 * @return {@code true} if this widget supports auto-sizing text and has been configured to 9782 * auto-size. 9783 */ 9784 private boolean isAutoSizeEnabled() { 9785 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 9786 } 9787 9788 /** 9789 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 9790 * @hide 9791 */ 9792 protected boolean supportsAutoSizeText() { 9793 return true; 9794 } 9795 9796 /** 9797 * This is a temporary method. Future versions may support multi-locale text. 9798 * Caveat: This method may not return the latest spell checker locale, but this should be 9799 * acceptable and it's more important to make this method asynchronous. 9800 * 9801 * @return The locale that should be used for a spell checker in this TextView, 9802 * based on the current spell checker settings, the current IME's locale, or the system default 9803 * locale. 9804 * @hide 9805 */ 9806 public Locale getSpellCheckerLocale() { 9807 return getTextServicesLocale(true /* allowNullLocale */); 9808 } 9809 9810 private void updateTextServicesLocaleAsync() { 9811 // AsyncTask.execute() uses a serial executor which means we don't have 9812 // to lock around updateTextServicesLocaleLocked() to prevent it from 9813 // being executed n times in parallel. 9814 AsyncTask.execute(new Runnable() { 9815 @Override 9816 public void run() { 9817 updateTextServicesLocaleLocked(); 9818 } 9819 }); 9820 } 9821 9822 private void updateTextServicesLocaleLocked() { 9823 final TextServicesManager textServicesManager = (TextServicesManager) 9824 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 9825 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 9826 final Locale locale; 9827 if (subtype != null) { 9828 locale = subtype.getLocaleObject(); 9829 } else { 9830 locale = null; 9831 } 9832 mCurrentSpellCheckerLocaleCache = locale; 9833 } 9834 9835 void onLocaleChanged() { 9836 mEditor.onLocaleChanged(); 9837 } 9838 9839 /** 9840 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 9841 * Made available to achieve a consistent behavior. 9842 * @hide 9843 */ 9844 public WordIterator getWordIterator() { 9845 if (mEditor != null) { 9846 return mEditor.getWordIterator(); 9847 } else { 9848 return null; 9849 } 9850 } 9851 9852 /** @hide */ 9853 @Override 9854 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 9855 super.onPopulateAccessibilityEventInternal(event); 9856 9857 final CharSequence text = getTextForAccessibility(); 9858 if (!TextUtils.isEmpty(text)) { 9859 event.getText().add(text); 9860 } 9861 } 9862 9863 @Override 9864 public CharSequence getAccessibilityClassName() { 9865 return TextView.class.getName(); 9866 } 9867 9868 @Override 9869 public void onProvideStructure(ViewStructure structure) { 9870 super.onProvideStructure(structure); 9871 onProvideAutoStructureForAssistOrAutoFill(structure, false); 9872 } 9873 9874 @Override 9875 public void onProvideAutoFillStructure(ViewStructure structure, int flags) { 9876 super.onProvideAutoFillStructure(structure, flags); 9877 onProvideAutoStructureForAssistOrAutoFill(structure, true); 9878 } 9879 9880 private void onProvideAutoStructureForAssistOrAutoFill(ViewStructure structure, 9881 boolean forAutoFill) { 9882 final boolean isPassword = hasPasswordTransformationMethod() 9883 || isPasswordInputType(getInputType()); 9884 if (forAutoFill) { 9885 structure.setSanitized(mTextFromResource); 9886 } 9887 9888 if (!isPassword || forAutoFill) { 9889 if (mLayout == null) { 9890 assumeLayout(); 9891 } 9892 Layout layout = mLayout; 9893 final int lineCount = layout.getLineCount(); 9894 if (lineCount <= 1) { 9895 // Simple case: this is a single line. 9896 final CharSequence text = getText(); 9897 structure.setText(text, getSelectionStart(), getSelectionEnd()); 9898 } else { 9899 // Complex case: multi-line, could be scrolled or within a scroll container 9900 // so some lines are not visible. 9901 final int[] tmpCords = new int[2]; 9902 getLocationInWindow(tmpCords); 9903 final int topWindowLocation = tmpCords[1]; 9904 View root = this; 9905 ViewParent viewParent = getParent(); 9906 while (viewParent instanceof View) { 9907 root = (View) viewParent; 9908 viewParent = root.getParent(); 9909 } 9910 final int windowHeight = root.getHeight(); 9911 final int topLine; 9912 final int bottomLine; 9913 if (topWindowLocation >= 0) { 9914 // The top of the view is fully within its window; start text at line 0. 9915 topLine = getLineAtCoordinateUnclamped(0); 9916 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 9917 } else { 9918 // The top of hte window has scrolled off the top of the window; figure out 9919 // the starting line for this. 9920 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 9921 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 9922 } 9923 // We want to return some contextual lines above/below the lines that are 9924 // actually visible. 9925 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 9926 if (expandedTopLine < 0) { 9927 expandedTopLine = 0; 9928 } 9929 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 9930 if (expandedBottomLine >= lineCount) { 9931 expandedBottomLine = lineCount - 1; 9932 } 9933 // Convert lines into character offsets. 9934 int expandedTopChar = layout.getLineStart(expandedTopLine); 9935 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 9936 // Take into account selection -- if there is a selection, we need to expand 9937 // the text we are returning to include that selection. 9938 final int selStart = getSelectionStart(); 9939 final int selEnd = getSelectionEnd(); 9940 if (selStart < selEnd) { 9941 if (selStart < expandedTopChar) { 9942 expandedTopChar = selStart; 9943 } 9944 if (selEnd > expandedBottomChar) { 9945 expandedBottomChar = selEnd; 9946 } 9947 } 9948 // Get the text and trim it to the range we are reporting. 9949 CharSequence text = getText(); 9950 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 9951 text = text.subSequence(expandedTopChar, expandedBottomChar); 9952 } 9953 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 9954 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 9955 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 9956 final int baselineOffset = getBaselineOffset(); 9957 for (int i = topLine; i <= bottomLine; i++) { 9958 lineOffsets[i - topLine] = layout.getLineStart(i); 9959 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 9960 } 9961 structure.setTextLines(lineOffsets, lineBaselines); 9962 } 9963 9964 // Extract style information that applies to the TextView as a whole. 9965 int style = 0; 9966 int typefaceStyle = getTypefaceStyle(); 9967 if ((typefaceStyle & Typeface.BOLD) != 0) { 9968 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 9969 } 9970 if ((typefaceStyle & Typeface.ITALIC) != 0) { 9971 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 9972 } 9973 9974 // Global styles can also be set via TextView.setPaintFlags(). 9975 int paintFlags = mTextPaint.getFlags(); 9976 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 9977 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 9978 } 9979 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 9980 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 9981 } 9982 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 9983 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 9984 } 9985 9986 // TextView does not have its own text background color. A background is either part 9987 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 9988 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 9989 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 9990 } 9991 structure.setHint(getHint()); 9992 } 9993 9994 // TODO(b/33197203): add unit/CTS tests for auto-fill methods 9995 9996 @Override 9997 public void autoFill(AutoFillValue value) { 9998 final CharSequence text = value.getTextValue(); 9999 10000 if (text != null && isTextEditable()) { 10001 setText(text, mBufferType, true, 0); 10002 } 10003 } 10004 10005 @Override 10006 @Nullable 10007 public AutoFillType getAutoFillType() { 10008 return isTextEditable() ? AutoFillType.forText(getInputType()) : null; 10009 } 10010 10011 @Override 10012 @Nullable 10013 public AutoFillValue getAutoFillValue() { 10014 return isTextEditable() ? AutoFillValue.forText(getText()) : null; 10015 } 10016 10017 /** @hide */ 10018 @Override 10019 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 10020 super.onInitializeAccessibilityEventInternal(event); 10021 10022 final boolean isPassword = hasPasswordTransformationMethod(); 10023 event.setPassword(isPassword); 10024 10025 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 10026 event.setFromIndex(Selection.getSelectionStart(mText)); 10027 event.setToIndex(Selection.getSelectionEnd(mText)); 10028 event.setItemCount(mText.length()); 10029 } 10030 } 10031 10032 /** @hide */ 10033 @Override 10034 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 10035 super.onInitializeAccessibilityNodeInfoInternal(info); 10036 10037 final boolean isPassword = hasPasswordTransformationMethod(); 10038 info.setPassword(isPassword); 10039 info.setText(getTextForAccessibility()); 10040 info.setHintText(mHint); 10041 info.setShowingHintText(isShowingHint()); 10042 10043 if (mBufferType == BufferType.EDITABLE) { 10044 info.setEditable(true); 10045 if (isEnabled()) { 10046 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 10047 } 10048 } 10049 10050 if (mEditor != null) { 10051 info.setInputType(mEditor.mInputType); 10052 10053 if (mEditor.mError != null) { 10054 info.setContentInvalid(true); 10055 info.setError(mEditor.mError); 10056 } 10057 } 10058 10059 if (!TextUtils.isEmpty(mText)) { 10060 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 10061 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 10062 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 10063 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 10064 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 10065 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 10066 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 10067 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 10068 info.setAvailableExtraData( 10069 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); 10070 } 10071 10072 if (isFocused()) { 10073 if (canCopy()) { 10074 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 10075 } 10076 if (canPaste()) { 10077 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 10078 } 10079 if (canCut()) { 10080 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 10081 } 10082 if (canShare()) { 10083 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 10084 ACCESSIBILITY_ACTION_SHARE, 10085 getResources().getString(com.android.internal.R.string.share))); 10086 } 10087 if (canProcessText()) { // also implies mEditor is not null. 10088 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 10089 } 10090 } 10091 10092 // Check for known input filter types. 10093 final int numFilters = mFilters.length; 10094 for (int i = 0; i < numFilters; i++) { 10095 final InputFilter filter = mFilters[i]; 10096 if (filter instanceof InputFilter.LengthFilter) { 10097 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 10098 } 10099 } 10100 10101 if (!isSingleLine()) { 10102 info.setMultiLine(true); 10103 } 10104 } 10105 10106 @Override 10107 public void addExtraDataToAccessibilityNodeInfo( 10108 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 10109 // The only extra data we support requires arguments. 10110 if (arguments == null) { 10111 return; 10112 } 10113 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 10114 int positionInfoStartIndex = arguments.getInt( 10115 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 10116 int positionInfoLength = arguments.getInt( 10117 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 10118 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 10119 || (positionInfoStartIndex >= mText.length())) { 10120 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 10121 return; 10122 } 10123 RectF[] boundingRects = new RectF[positionInfoLength]; 10124 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 10125 populateCharacterBounds(builder, positionInfoStartIndex, 10126 positionInfoStartIndex + positionInfoLength, 10127 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 10128 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 10129 if (mTempRect == null) mTempRect = new Rect(); 10130 Rect viewBoundsInScreen = mTempRect; 10131 info.getBoundsInScreen(viewBoundsInScreen); 10132 for (int i = 0; i < positionInfoLength; i++) { 10133 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 10134 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 10135 RectF bounds = cursorAnchorInfo 10136 .getCharacterBounds(positionInfoStartIndex + i); 10137 if (bounds != null) { 10138 bounds.offset(viewBoundsInScreen.left, viewBoundsInScreen.top); 10139 boundingRects[i] = bounds; 10140 } 10141 } 10142 } 10143 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 10144 } 10145 } 10146 10147 /** 10148 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 10149 * 10150 * @param builder The builder to populate 10151 * @param startIndex The starting character index to populate 10152 * @param endIndex The ending character index to populate 10153 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 10154 * content 10155 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 10156 * @hide 10157 */ 10158 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 10159 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 10160 float viewportToContentVerticalOffset) { 10161 final int minLine = mLayout.getLineForOffset(startIndex); 10162 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 10163 for (int line = minLine; line <= maxLine; ++line) { 10164 final int lineStart = mLayout.getLineStart(line); 10165 final int lineEnd = mLayout.getLineEnd(line); 10166 final int offsetStart = Math.max(lineStart, startIndex); 10167 final int offsetEnd = Math.min(lineEnd, endIndex); 10168 final boolean ltrLine = 10169 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 10170 final float[] widths = new float[offsetEnd - offsetStart]; 10171 mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths); 10172 final float top = mLayout.getLineTop(line); 10173 final float bottom = mLayout.getLineBottom(line); 10174 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 10175 final float charWidth = widths[offset - offsetStart]; 10176 final boolean isRtl = mLayout.isRtlCharAt(offset); 10177 final float primary = mLayout.getPrimaryHorizontal(offset); 10178 final float secondary = mLayout.getSecondaryHorizontal(offset); 10179 // TODO: This doesn't work perfectly for text with custom styles and 10180 // TAB chars. 10181 final float left; 10182 final float right; 10183 if (ltrLine) { 10184 if (isRtl) { 10185 left = secondary - charWidth; 10186 right = secondary; 10187 } else { 10188 left = primary; 10189 right = primary + charWidth; 10190 } 10191 } else { 10192 if (!isRtl) { 10193 left = secondary; 10194 right = secondary + charWidth; 10195 } else { 10196 left = primary - charWidth; 10197 right = primary; 10198 } 10199 } 10200 // TODO: Check top-right and bottom-left as well. 10201 final float localLeft = left + viewportToContentHorizontalOffset; 10202 final float localRight = right + viewportToContentHorizontalOffset; 10203 final float localTop = top + viewportToContentVerticalOffset; 10204 final float localBottom = bottom + viewportToContentVerticalOffset; 10205 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 10206 final boolean isBottomRightVisible = 10207 isPositionVisible(localRight, localBottom); 10208 int characterBoundsFlags = 0; 10209 if (isTopLeftVisible || isBottomRightVisible) { 10210 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 10211 } 10212 if (!isTopLeftVisible || !isBottomRightVisible) { 10213 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 10214 } 10215 if (isRtl) { 10216 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 10217 } 10218 // Here offset is the index in Java chars. 10219 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 10220 localBottom, characterBoundsFlags); 10221 } 10222 } 10223 } 10224 10225 /** 10226 * @hide 10227 */ 10228 public boolean isPositionVisible(final float positionX, final float positionY) { 10229 synchronized (TEMP_POSITION) { 10230 final float[] position = TEMP_POSITION; 10231 position[0] = positionX; 10232 position[1] = positionY; 10233 View view = this; 10234 10235 while (view != null) { 10236 if (view != this) { 10237 // Local scroll is already taken into account in positionX/Y 10238 position[0] -= view.getScrollX(); 10239 position[1] -= view.getScrollY(); 10240 } 10241 10242 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 10243 || position[1] > view.getHeight()) { 10244 return false; 10245 } 10246 10247 if (!view.getMatrix().isIdentity()) { 10248 view.getMatrix().mapPoints(position); 10249 } 10250 10251 position[0] += view.getLeft(); 10252 position[1] += view.getTop(); 10253 10254 final ViewParent parent = view.getParent(); 10255 if (parent instanceof View) { 10256 view = (View) parent; 10257 } else { 10258 // We've reached the ViewRoot, stop iterating 10259 view = null; 10260 } 10261 } 10262 } 10263 10264 // We've been able to walk up the view hierarchy and the position was never clipped 10265 return true; 10266 } 10267 10268 /** 10269 * Performs an accessibility action after it has been offered to the 10270 * delegate. 10271 * 10272 * @hide 10273 */ 10274 @Override 10275 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 10276 if (mEditor != null 10277 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 10278 return true; 10279 } 10280 switch (action) { 10281 case AccessibilityNodeInfo.ACTION_CLICK: { 10282 return performAccessibilityActionClick(arguments); 10283 } 10284 case AccessibilityNodeInfo.ACTION_COPY: { 10285 if (isFocused() && canCopy()) { 10286 if (onTextContextMenuItem(ID_COPY)) { 10287 return true; 10288 } 10289 } 10290 } return false; 10291 case AccessibilityNodeInfo.ACTION_PASTE: { 10292 if (isFocused() && canPaste()) { 10293 if (onTextContextMenuItem(ID_PASTE)) { 10294 return true; 10295 } 10296 } 10297 } return false; 10298 case AccessibilityNodeInfo.ACTION_CUT: { 10299 if (isFocused() && canCut()) { 10300 if (onTextContextMenuItem(ID_CUT)) { 10301 return true; 10302 } 10303 } 10304 } return false; 10305 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 10306 ensureIterableTextForAccessibilitySelectable(); 10307 CharSequence text = getIterableTextForAccessibility(); 10308 if (text == null) { 10309 return false; 10310 } 10311 final int start = (arguments != null) ? arguments.getInt( 10312 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 10313 final int end = (arguments != null) ? arguments.getInt( 10314 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 10315 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 10316 // No arguments clears the selection. 10317 if (start == end && end == -1) { 10318 Selection.removeSelection((Spannable) text); 10319 return true; 10320 } 10321 if (start >= 0 && start <= end && end <= text.length()) { 10322 Selection.setSelection((Spannable) text, start, end); 10323 // Make sure selection mode is engaged. 10324 if (mEditor != null) { 10325 mEditor.startSelectionActionModeAsync(); 10326 } 10327 return true; 10328 } 10329 } 10330 } return false; 10331 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 10332 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 10333 ensureIterableTextForAccessibilitySelectable(); 10334 return super.performAccessibilityActionInternal(action, arguments); 10335 } 10336 case ACCESSIBILITY_ACTION_SHARE: { 10337 if (isFocused() && canShare()) { 10338 if (onTextContextMenuItem(ID_SHARE)) { 10339 return true; 10340 } 10341 } 10342 } return false; 10343 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 10344 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 10345 return false; 10346 } 10347 CharSequence text = (arguments != null) ? arguments.getCharSequence( 10348 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 10349 setText(text); 10350 if (mText != null) { 10351 int updatedTextLength = mText.length(); 10352 if (updatedTextLength > 0) { 10353 Selection.setSelection((Spannable) mText, updatedTextLength); 10354 } 10355 } 10356 } return true; 10357 default: { 10358 return super.performAccessibilityActionInternal(action, arguments); 10359 } 10360 } 10361 } 10362 10363 private boolean performAccessibilityActionClick(Bundle arguments) { 10364 boolean handled = false; 10365 10366 if (!isEnabled()) { 10367 return false; 10368 } 10369 10370 if (isClickable() || isLongClickable()) { 10371 // Simulate View.onTouchEvent for an ACTION_UP event 10372 if (isFocusable() && !isFocused()) { 10373 requestFocus(); 10374 } 10375 10376 performClick(); 10377 handled = true; 10378 } 10379 10380 // Show the IME, except when selecting in read-only text. 10381 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 10382 && (isTextEditable() || isTextSelectable()) && isFocused()) { 10383 final InputMethodManager imm = InputMethodManager.peekInstance(); 10384 viewClicked(imm); 10385 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10386 handled |= imm.showSoftInput(this, 0); 10387 } 10388 } 10389 10390 return handled; 10391 } 10392 10393 private boolean hasSpannableText() { 10394 return mText != null && mText instanceof Spannable; 10395 } 10396 10397 /** @hide */ 10398 @Override 10399 public void sendAccessibilityEventInternal(int eventType) { 10400 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 10401 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 10402 } 10403 10404 // Do not send scroll events since first they are not interesting for 10405 // accessibility and second such events a generated too frequently. 10406 // For details see the implementation of bringTextIntoView(). 10407 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 10408 return; 10409 } 10410 super.sendAccessibilityEventInternal(eventType); 10411 } 10412 10413 /** 10414 * Returns the text that should be exposed to accessibility services. 10415 * <p> 10416 * This approximates what is displayed visually. If the user has specified 10417 * that accessibility services should speak passwords, this method will 10418 * bypass any password transformation method and return unobscured text. 10419 * 10420 * @return the text that should be exposed to accessibility services, may 10421 * be {@code null} if no text is set 10422 */ 10423 @Nullable 10424 private CharSequence getTextForAccessibility() { 10425 // If the text is empty, we must be showing the hint text. 10426 if (TextUtils.isEmpty(mText)) { 10427 return mHint; 10428 } 10429 10430 // Otherwise, return whatever text is being displayed. 10431 return mTransformed; 10432 } 10433 10434 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 10435 int fromIndex, int removedCount, int addedCount) { 10436 AccessibilityEvent event = 10437 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 10438 event.setFromIndex(fromIndex); 10439 event.setRemovedCount(removedCount); 10440 event.setAddedCount(addedCount); 10441 event.setBeforeText(beforeText); 10442 sendAccessibilityEventUnchecked(event); 10443 } 10444 10445 /** 10446 * Returns whether this text view is a current input method target. The 10447 * default implementation just checks with {@link InputMethodManager}. 10448 */ 10449 public boolean isInputMethodTarget() { 10450 InputMethodManager imm = InputMethodManager.peekInstance(); 10451 return imm != null && imm.isActive(this); 10452 } 10453 10454 static final int ID_SELECT_ALL = android.R.id.selectAll; 10455 static final int ID_UNDO = android.R.id.undo; 10456 static final int ID_REDO = android.R.id.redo; 10457 static final int ID_CUT = android.R.id.cut; 10458 static final int ID_COPY = android.R.id.copy; 10459 static final int ID_PASTE = android.R.id.paste; 10460 static final int ID_SHARE = android.R.id.shareText; 10461 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 10462 static final int ID_REPLACE = android.R.id.replaceText; 10463 static final int ID_ASSIST = android.R.id.textAssist; 10464 10465 /** 10466 * Called when a context menu option for the text view is selected. Currently 10467 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 10468 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 10469 * 10470 * @return true if the context menu item action was performed. 10471 */ 10472 public boolean onTextContextMenuItem(int id) { 10473 int min = 0; 10474 int max = mText.length(); 10475 10476 if (isFocused()) { 10477 final int selStart = getSelectionStart(); 10478 final int selEnd = getSelectionEnd(); 10479 10480 min = Math.max(0, Math.min(selStart, selEnd)); 10481 max = Math.max(0, Math.max(selStart, selEnd)); 10482 } 10483 10484 switch (id) { 10485 case ID_SELECT_ALL: 10486 selectAllText(); 10487 return true; 10488 10489 case ID_UNDO: 10490 if (mEditor != null) { 10491 mEditor.undo(); 10492 } 10493 return true; // Returns true even if nothing was undone. 10494 10495 case ID_REDO: 10496 if (mEditor != null) { 10497 mEditor.redo(); 10498 } 10499 return true; // Returns true even if nothing was undone. 10500 10501 case ID_PASTE: 10502 paste(min, max, true /* withFormatting */); 10503 return true; 10504 10505 case ID_PASTE_AS_PLAIN_TEXT: 10506 paste(min, max, false /* withFormatting */); 10507 return true; 10508 10509 case ID_CUT: 10510 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 10511 deleteText_internal(min, max); 10512 return true; 10513 10514 case ID_COPY: 10515 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 10516 stopTextActionMode(); 10517 return true; 10518 10519 case ID_REPLACE: 10520 if (mEditor != null) { 10521 mEditor.replace(); 10522 } 10523 return true; 10524 10525 case ID_SHARE: 10526 shareSelectedText(); 10527 return true; 10528 } 10529 return false; 10530 } 10531 10532 CharSequence getTransformedText(int start, int end) { 10533 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 10534 } 10535 10536 @Override 10537 public boolean performLongClick() { 10538 boolean handled = false; 10539 10540 if (mEditor != null) { 10541 mEditor.mIsBeingLongClicked = true; 10542 } 10543 10544 if (super.performLongClick()) { 10545 handled = true; 10546 } 10547 10548 if (mEditor != null) { 10549 handled |= mEditor.performLongClick(handled); 10550 mEditor.mIsBeingLongClicked = false; 10551 } 10552 10553 if (handled) { 10554 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 10555 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 10556 } else { 10557 MetricsLogger.action( 10558 mContext, 10559 MetricsEvent.TEXT_LONGPRESS, 10560 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 10561 } 10562 10563 return handled; 10564 } 10565 10566 @Override 10567 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 10568 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 10569 if (mEditor != null) { 10570 mEditor.onScrollChanged(); 10571 } 10572 } 10573 10574 /** 10575 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 10576 * by the IME or by the spell checker as the user types. This is done by adding 10577 * {@link SuggestionSpan}s to the text. 10578 * 10579 * When suggestions are enabled (default), this list of suggestions will be displayed when the 10580 * user asks for them on these parts of the text. This value depends on the inputType of this 10581 * TextView. 10582 * 10583 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 10584 * 10585 * In addition, the type variation must be one of 10586 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 10587 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 10588 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 10589 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 10590 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 10591 * 10592 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 10593 * 10594 * @return true if the suggestions popup window is enabled, based on the inputType. 10595 */ 10596 public boolean isSuggestionsEnabled() { 10597 if (mEditor == null) return false; 10598 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 10599 return false; 10600 } 10601 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 10602 10603 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 10604 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 10605 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 10606 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 10607 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 10608 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 10609 } 10610 10611 /** 10612 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 10613 * selection is initiated in this View. 10614 * 10615 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 10616 * Paste, Replace and Share actions, depending on what this View supports. 10617 * 10618 * <p>A custom implementation can add new entries in the default menu in its 10619 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 10620 * method. The default actions can also be removed from the menu using 10621 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 10622 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 10623 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 10624 * 10625 * <p>Returning false from 10626 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 10627 * will prevent the action mode from being started. 10628 * 10629 * <p>Action click events should be handled by the custom implementation of 10630 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 10631 * android.view.MenuItem)}. 10632 * 10633 * <p>Note that text selection mode is not started when a TextView receives focus and the 10634 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 10635 * that case, to allow for quick replacement. 10636 */ 10637 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 10638 createEditorIfNeeded(); 10639 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 10640 } 10641 10642 /** 10643 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 10644 * 10645 * @return The current custom selection callback. 10646 */ 10647 public ActionMode.Callback getCustomSelectionActionModeCallback() { 10648 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 10649 } 10650 10651 /** 10652 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 10653 * insertion is initiated in this View. 10654 * The standard implementation populates the menu with a subset of Select All, 10655 * Paste and Replace actions, depending on what this View supports. 10656 * 10657 * <p>A custom implementation can add new entries in the default menu in its 10658 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 10659 * android.view.Menu)} method. The default actions can also be removed from the menu using 10660 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 10661 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 10662 * 10663 * <p>Returning false from 10664 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 10665 * android.view.Menu)} will prevent the action mode from being started.</p> 10666 * 10667 * <p>Action click events should be handled by the custom implementation of 10668 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 10669 * android.view.MenuItem)}.</p> 10670 * 10671 * <p>Note that text insertion mode is not started when a TextView receives focus and the 10672 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 10673 */ 10674 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 10675 createEditorIfNeeded(); 10676 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 10677 } 10678 10679 /** 10680 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 10681 * 10682 * @return The current custom insertion callback. 10683 */ 10684 public ActionMode.Callback getCustomInsertionActionModeCallback() { 10685 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 10686 } 10687 10688 /** 10689 * Sets the {@link TextClassifier} for this TextView. 10690 */ 10691 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 10692 mTextClassifier = textClassifier; 10693 } 10694 10695 /** 10696 * Returns the {@link TextClassifier} used by this TextView. 10697 * If no TextClassifier has been set, this TextView uses the default set by the 10698 * {@link TextClassificationManager}. 10699 */ 10700 @NonNull 10701 public TextClassifier getTextClassifier() { 10702 if (mTextClassifier == null) { 10703 TextClassificationManager tcm = 10704 mContext.getSystemService(TextClassificationManager.class); 10705 if (tcm != null) { 10706 mTextClassifier = tcm.getDefaultTextClassifier(); 10707 } else { 10708 mTextClassifier = TextClassifier.NO_OP; 10709 } 10710 } 10711 return mTextClassifier; 10712 } 10713 10714 /** 10715 * @hide 10716 */ 10717 protected void stopTextActionMode() { 10718 if (mEditor != null) { 10719 mEditor.stopTextActionMode(); 10720 } 10721 } 10722 10723 boolean canUndo() { 10724 return mEditor != null && mEditor.canUndo(); 10725 } 10726 10727 boolean canRedo() { 10728 return mEditor != null && mEditor.canRedo(); 10729 } 10730 10731 boolean canCut() { 10732 if (hasPasswordTransformationMethod()) { 10733 return false; 10734 } 10735 10736 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 10737 && mEditor.mKeyListener != null) { 10738 return true; 10739 } 10740 10741 return false; 10742 } 10743 10744 boolean canCopy() { 10745 if (hasPasswordTransformationMethod()) { 10746 return false; 10747 } 10748 10749 if (mText.length() > 0 && hasSelection() && mEditor != null) { 10750 return true; 10751 } 10752 10753 return false; 10754 } 10755 10756 boolean canShare() { 10757 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 10758 return false; 10759 } 10760 return canCopy(); 10761 } 10762 10763 boolean isDeviceProvisioned() { 10764 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 10765 mDeviceProvisionedState = Settings.Global.getInt( 10766 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 10767 ? DEVICE_PROVISIONED_YES 10768 : DEVICE_PROVISIONED_NO; 10769 } 10770 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 10771 } 10772 10773 boolean canPaste() { 10774 return (mText instanceof Editable 10775 && mEditor != null && mEditor.mKeyListener != null 10776 && getSelectionStart() >= 0 10777 && getSelectionEnd() >= 0 10778 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) 10779 .hasPrimaryClip()); 10780 } 10781 10782 boolean canProcessText() { 10783 if (getId() == View.NO_ID) { 10784 return false; 10785 } 10786 return canShare(); 10787 } 10788 10789 boolean canSelectAllText() { 10790 return canSelectText() && !hasPasswordTransformationMethod() 10791 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 10792 } 10793 10794 boolean selectAllText() { 10795 final int length = mText.length(); 10796 Selection.setSelection((Spannable) mText, 0, length); 10797 return length > 0; 10798 } 10799 10800 void replaceSelectionWithText(CharSequence text) { 10801 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 10802 } 10803 10804 /** 10805 * Paste clipboard content between min and max positions. 10806 */ 10807 private void paste(int min, int max, boolean withFormatting) { 10808 ClipboardManager clipboard = 10809 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 10810 ClipData clip = clipboard.getPrimaryClip(); 10811 if (clip != null) { 10812 boolean didFirst = false; 10813 for (int i = 0; i < clip.getItemCount(); i++) { 10814 final CharSequence paste; 10815 if (withFormatting) { 10816 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 10817 } else { 10818 // Get an item as text and remove all spans by toString(). 10819 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 10820 paste = (text instanceof Spanned) ? text.toString() : text; 10821 } 10822 if (paste != null) { 10823 if (!didFirst) { 10824 Selection.setSelection((Spannable) mText, max); 10825 ((Editable) mText).replace(min, max, paste); 10826 didFirst = true; 10827 } else { 10828 ((Editable) mText).insert(getSelectionEnd(), "\n"); 10829 ((Editable) mText).insert(getSelectionEnd(), paste); 10830 } 10831 } 10832 } 10833 sLastCutCopyOrTextChangedTime = 0; 10834 } 10835 } 10836 10837 private void shareSelectedText() { 10838 String selectedText = getSelectedText(); 10839 if (selectedText != null && !selectedText.isEmpty()) { 10840 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 10841 sharingIntent.setType("text/plain"); 10842 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 10843 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 10844 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 10845 Selection.setSelection((Spannable) mText, getSelectionEnd()); 10846 } 10847 } 10848 10849 private void setPrimaryClip(ClipData clip) { 10850 ClipboardManager clipboard = 10851 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 10852 clipboard.setPrimaryClip(clip); 10853 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 10854 } 10855 10856 /** 10857 * Get the character offset closest to the specified absolute position. A typical use case is to 10858 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 10859 * 10860 * @param x The horizontal absolute position of a point on screen 10861 * @param y The vertical absolute position of a point on screen 10862 * @return the character offset for the character whose position is closest to the specified 10863 * position. Returns -1 if there is no layout. 10864 */ 10865 public int getOffsetForPosition(float x, float y) { 10866 if (getLayout() == null) return -1; 10867 final int line = getLineAtCoordinate(y); 10868 final int offset = getOffsetAtCoordinate(line, x); 10869 return offset; 10870 } 10871 10872 float convertToLocalHorizontalCoordinate(float x) { 10873 x -= getTotalPaddingLeft(); 10874 // Clamp the position to inside of the view. 10875 x = Math.max(0.0f, x); 10876 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 10877 x += getScrollX(); 10878 return x; 10879 } 10880 10881 int getLineAtCoordinate(float y) { 10882 y -= getTotalPaddingTop(); 10883 // Clamp the position to inside of the view. 10884 y = Math.max(0.0f, y); 10885 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 10886 y += getScrollY(); 10887 return getLayout().getLineForVertical((int) y); 10888 } 10889 10890 int getLineAtCoordinateUnclamped(float y) { 10891 y -= getTotalPaddingTop(); 10892 y += getScrollY(); 10893 return getLayout().getLineForVertical((int) y); 10894 } 10895 10896 int getOffsetAtCoordinate(int line, float x) { 10897 x = convertToLocalHorizontalCoordinate(x); 10898 return getLayout().getOffsetForHorizontal(line, x); 10899 } 10900 10901 @Override 10902 public boolean onDragEvent(DragEvent event) { 10903 switch (event.getAction()) { 10904 case DragEvent.ACTION_DRAG_STARTED: 10905 return mEditor != null && mEditor.hasInsertionController(); 10906 10907 case DragEvent.ACTION_DRAG_ENTERED: 10908 TextView.this.requestFocus(); 10909 return true; 10910 10911 case DragEvent.ACTION_DRAG_LOCATION: 10912 final int offset = getOffsetForPosition(event.getX(), event.getY()); 10913 Selection.setSelection((Spannable) mText, offset); 10914 return true; 10915 10916 case DragEvent.ACTION_DROP: 10917 if (mEditor != null) mEditor.onDrop(event); 10918 return true; 10919 10920 case DragEvent.ACTION_DRAG_ENDED: 10921 case DragEvent.ACTION_DRAG_EXITED: 10922 default: 10923 return true; 10924 } 10925 } 10926 10927 boolean isInBatchEditMode() { 10928 if (mEditor == null) return false; 10929 final Editor.InputMethodState ims = mEditor.mInputMethodState; 10930 if (ims != null) { 10931 return ims.mBatchEditNesting > 0; 10932 } 10933 return mEditor.mInBatchEditControllers; 10934 } 10935 10936 @Override 10937 public void onRtlPropertiesChanged(int layoutDirection) { 10938 super.onRtlPropertiesChanged(layoutDirection); 10939 10940 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 10941 if (mTextDir != newTextDir) { 10942 mTextDir = newTextDir; 10943 if (mLayout != null) { 10944 checkForRelayout(); 10945 } 10946 } 10947 } 10948 10949 /** 10950 * @hide 10951 */ 10952 protected TextDirectionHeuristic getTextDirectionHeuristic() { 10953 if (hasPasswordTransformationMethod()) { 10954 // passwords fields should be LTR 10955 return TextDirectionHeuristics.LTR; 10956 } 10957 10958 // Always need to resolve layout direction first 10959 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 10960 10961 // Now, we can select the heuristic 10962 switch (getTextDirection()) { 10963 default: 10964 case TEXT_DIRECTION_FIRST_STRONG: 10965 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 10966 TextDirectionHeuristics.FIRSTSTRONG_LTR); 10967 case TEXT_DIRECTION_ANY_RTL: 10968 return TextDirectionHeuristics.ANYRTL_LTR; 10969 case TEXT_DIRECTION_LTR: 10970 return TextDirectionHeuristics.LTR; 10971 case TEXT_DIRECTION_RTL: 10972 return TextDirectionHeuristics.RTL; 10973 case TEXT_DIRECTION_LOCALE: 10974 return TextDirectionHeuristics.LOCALE; 10975 case TEXT_DIRECTION_FIRST_STRONG_LTR: 10976 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 10977 case TEXT_DIRECTION_FIRST_STRONG_RTL: 10978 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 10979 } 10980 } 10981 10982 /** 10983 * @hide 10984 */ 10985 @Override 10986 public void onResolveDrawables(int layoutDirection) { 10987 // No need to resolve twice 10988 if (mLastLayoutDirection == layoutDirection) { 10989 return; 10990 } 10991 mLastLayoutDirection = layoutDirection; 10992 10993 // Resolve drawables 10994 if (mDrawables != null) { 10995 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 10996 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 10997 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 10998 applyCompoundDrawableTint(); 10999 } 11000 } 11001 } 11002 11003 /** 11004 * Prepares a drawable for display by propagating layout direction and 11005 * drawable state. 11006 * 11007 * @param dr the drawable to prepare 11008 */ 11009 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 11010 if (dr == null) { 11011 return; 11012 } 11013 11014 dr.setLayoutDirection(getLayoutDirection()); 11015 11016 if (dr.isStateful()) { 11017 dr.setState(getDrawableState()); 11018 dr.jumpToCurrentState(); 11019 } 11020 } 11021 11022 /** 11023 * @hide 11024 */ 11025 protected void resetResolvedDrawables() { 11026 super.resetResolvedDrawables(); 11027 mLastLayoutDirection = -1; 11028 } 11029 11030 /** 11031 * @hide 11032 */ 11033 protected void viewClicked(InputMethodManager imm) { 11034 if (imm != null) { 11035 imm.viewClicked(this); 11036 } 11037 } 11038 11039 /** 11040 * Deletes the range of text [start, end[. 11041 * @hide 11042 */ 11043 protected void deleteText_internal(int start, int end) { 11044 ((Editable) mText).delete(start, end); 11045 } 11046 11047 /** 11048 * Replaces the range of text [start, end[ by replacement text 11049 * @hide 11050 */ 11051 protected void replaceText_internal(int start, int end, CharSequence text) { 11052 ((Editable) mText).replace(start, end, text); 11053 } 11054 11055 /** 11056 * Sets a span on the specified range of text 11057 * @hide 11058 */ 11059 protected void setSpan_internal(Object span, int start, int end, int flags) { 11060 ((Editable) mText).setSpan(span, start, end, flags); 11061 } 11062 11063 /** 11064 * Moves the cursor to the specified offset position in text 11065 * @hide 11066 */ 11067 protected void setCursorPosition_internal(int start, int end) { 11068 Selection.setSelection(((Editable) mText), start, end); 11069 } 11070 11071 /** 11072 * An Editor should be created as soon as any of the editable-specific fields (grouped 11073 * inside the Editor object) is assigned to a non-default value. 11074 * This method will create the Editor if needed. 11075 * 11076 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 11077 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 11078 * Editor for backward compatibility, as soon as one of these fields is assigned. 11079 * 11080 * Also note that for performance reasons, the mEditor is created when needed, but not 11081 * reset when no more edit-specific fields are needed. 11082 */ 11083 private void createEditorIfNeeded() { 11084 if (mEditor == null) { 11085 mEditor = new Editor(this); 11086 } 11087 } 11088 11089 /** 11090 * @hide 11091 */ 11092 @Override 11093 public CharSequence getIterableTextForAccessibility() { 11094 return mText; 11095 } 11096 11097 private void ensureIterableTextForAccessibilitySelectable() { 11098 if (!(mText instanceof Spannable)) { 11099 setText(mText, BufferType.SPANNABLE); 11100 } 11101 } 11102 11103 /** 11104 * @hide 11105 */ 11106 @Override 11107 public TextSegmentIterator getIteratorForGranularity(int granularity) { 11108 switch (granularity) { 11109 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 11110 Spannable text = (Spannable) getIterableTextForAccessibility(); 11111 if (!TextUtils.isEmpty(text) && getLayout() != null) { 11112 AccessibilityIterators.LineTextSegmentIterator iterator = 11113 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 11114 iterator.initialize(text, getLayout()); 11115 return iterator; 11116 } 11117 } break; 11118 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 11119 Spannable text = (Spannable) getIterableTextForAccessibility(); 11120 if (!TextUtils.isEmpty(text) && getLayout() != null) { 11121 AccessibilityIterators.PageTextSegmentIterator iterator = 11122 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 11123 iterator.initialize(this); 11124 return iterator; 11125 } 11126 } break; 11127 } 11128 return super.getIteratorForGranularity(granularity); 11129 } 11130 11131 /** 11132 * @hide 11133 */ 11134 @Override 11135 public int getAccessibilitySelectionStart() { 11136 return getSelectionStart(); 11137 } 11138 11139 /** 11140 * @hide 11141 */ 11142 public boolean isAccessibilitySelectionExtendable() { 11143 return true; 11144 } 11145 11146 /** 11147 * @hide 11148 */ 11149 @Override 11150 public int getAccessibilitySelectionEnd() { 11151 return getSelectionEnd(); 11152 } 11153 11154 /** 11155 * @hide 11156 */ 11157 @Override 11158 public void setAccessibilitySelection(int start, int end) { 11159 if (getAccessibilitySelectionStart() == start 11160 && getAccessibilitySelectionEnd() == end) { 11161 return; 11162 } 11163 CharSequence text = getIterableTextForAccessibility(); 11164 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 11165 Selection.setSelection((Spannable) text, start, end); 11166 } else { 11167 Selection.removeSelection((Spannable) text); 11168 } 11169 // Hide all selection controllers used for adjusting selection 11170 // since we are doing so explicitlty by other means and these 11171 // controllers interact with how selection behaves. 11172 if (mEditor != null) { 11173 mEditor.hideCursorAndSpanControllers(); 11174 mEditor.stopTextActionMode(); 11175 } 11176 } 11177 11178 /** @hide */ 11179 @Override 11180 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 11181 super.encodeProperties(stream); 11182 11183 TruncateAt ellipsize = getEllipsize(); 11184 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 11185 stream.addProperty("text:textSize", getTextSize()); 11186 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 11187 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 11188 stream.addProperty("text:selectionStart", getSelectionStart()); 11189 stream.addProperty("text:selectionEnd", getSelectionEnd()); 11190 stream.addProperty("text:curTextColor", mCurTextColor); 11191 stream.addProperty("text:text", mText == null ? null : mText.toString()); 11192 stream.addProperty("text:gravity", mGravity); 11193 } 11194 11195 /** 11196 * User interface state that is stored by TextView for implementing 11197 * {@link View#onSaveInstanceState}. 11198 */ 11199 public static class SavedState extends BaseSavedState { 11200 int selStart = -1; 11201 int selEnd = -1; 11202 CharSequence text; 11203 boolean frozenWithFocus; 11204 CharSequence error; 11205 ParcelableParcel editorState; // Optional state from Editor. 11206 11207 SavedState(Parcelable superState) { 11208 super(superState); 11209 } 11210 11211 @Override 11212 public void writeToParcel(Parcel out, int flags) { 11213 super.writeToParcel(out, flags); 11214 out.writeInt(selStart); 11215 out.writeInt(selEnd); 11216 out.writeInt(frozenWithFocus ? 1 : 0); 11217 TextUtils.writeToParcel(text, out, flags); 11218 11219 if (error == null) { 11220 out.writeInt(0); 11221 } else { 11222 out.writeInt(1); 11223 TextUtils.writeToParcel(error, out, flags); 11224 } 11225 11226 if (editorState == null) { 11227 out.writeInt(0); 11228 } else { 11229 out.writeInt(1); 11230 editorState.writeToParcel(out, flags); 11231 } 11232 } 11233 11234 @Override 11235 public String toString() { 11236 String str = "TextView.SavedState{" 11237 + Integer.toHexString(System.identityHashCode(this)) 11238 + " start=" + selStart + " end=" + selEnd; 11239 if (text != null) { 11240 str += " text=" + text; 11241 } 11242 return str + "}"; 11243 } 11244 11245 @SuppressWarnings("hiding") 11246 public static final Parcelable.Creator<SavedState> CREATOR = 11247 new Parcelable.Creator<SavedState>() { 11248 public SavedState createFromParcel(Parcel in) { 11249 return new SavedState(in); 11250 } 11251 11252 public SavedState[] newArray(int size) { 11253 return new SavedState[size]; 11254 } 11255 }; 11256 11257 private SavedState(Parcel in) { 11258 super(in); 11259 selStart = in.readInt(); 11260 selEnd = in.readInt(); 11261 frozenWithFocus = (in.readInt() != 0); 11262 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 11263 11264 if (in.readInt() != 0) { 11265 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 11266 } 11267 11268 if (in.readInt() != 0) { 11269 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 11270 } 11271 } 11272 } 11273 11274 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 11275 private char[] mChars; 11276 private int mStart, mLength; 11277 11278 public CharWrapper(char[] chars, int start, int len) { 11279 mChars = chars; 11280 mStart = start; 11281 mLength = len; 11282 } 11283 11284 /* package */ void set(char[] chars, int start, int len) { 11285 mChars = chars; 11286 mStart = start; 11287 mLength = len; 11288 } 11289 11290 public int length() { 11291 return mLength; 11292 } 11293 11294 public char charAt(int off) { 11295 return mChars[off + mStart]; 11296 } 11297 11298 @Override 11299 public String toString() { 11300 return new String(mChars, mStart, mLength); 11301 } 11302 11303 public CharSequence subSequence(int start, int end) { 11304 if (start < 0 || end < 0 || start > mLength || end > mLength) { 11305 throw new IndexOutOfBoundsException(start + ", " + end); 11306 } 11307 11308 return new String(mChars, start + mStart, end - start); 11309 } 11310 11311 public void getChars(int start, int end, char[] buf, int off) { 11312 if (start < 0 || end < 0 || start > mLength || end > mLength) { 11313 throw new IndexOutOfBoundsException(start + ", " + end); 11314 } 11315 11316 System.arraycopy(mChars, start + mStart, buf, off, end - start); 11317 } 11318 11319 @Override 11320 public void drawText(BaseCanvas c, int start, int end, 11321 float x, float y, Paint p) { 11322 c.drawText(mChars, start + mStart, end - start, x, y, p); 11323 } 11324 11325 @Override 11326 public void drawTextRun(BaseCanvas c, int start, int end, 11327 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 11328 int count = end - start; 11329 int contextCount = contextEnd - contextStart; 11330 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 11331 contextCount, x, y, isRtl, p); 11332 } 11333 11334 public float measureText(int start, int end, Paint p) { 11335 return p.measureText(mChars, start + mStart, end - start); 11336 } 11337 11338 public int getTextWidths(int start, int end, float[] widths, Paint p) { 11339 return p.getTextWidths(mChars, start + mStart, end - start, widths); 11340 } 11341 11342 public float getTextRunAdvances(int start, int end, int contextStart, 11343 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 11344 Paint p) { 11345 int count = end - start; 11346 int contextCount = contextEnd - contextStart; 11347 return p.getTextRunAdvances(mChars, start + mStart, count, 11348 contextStart + mStart, contextCount, isRtl, advances, 11349 advancesIndex); 11350 } 11351 11352 public int getTextRunCursor(int contextStart, int contextEnd, int dir, 11353 int offset, int cursorOpt, Paint p) { 11354 int contextCount = contextEnd - contextStart; 11355 return p.getTextRunCursor(mChars, contextStart + mStart, 11356 contextCount, dir, offset + mStart, cursorOpt); 11357 } 11358 } 11359 11360 private static final class Marquee { 11361 // TODO: Add an option to configure this 11362 private static final float MARQUEE_DELTA_MAX = 0.07f; 11363 private static final int MARQUEE_DELAY = 1200; 11364 private static final int MARQUEE_DP_PER_SECOND = 30; 11365 11366 private static final byte MARQUEE_STOPPED = 0x0; 11367 private static final byte MARQUEE_STARTING = 0x1; 11368 private static final byte MARQUEE_RUNNING = 0x2; 11369 11370 private final WeakReference<TextView> mView; 11371 private final Choreographer mChoreographer; 11372 11373 private byte mStatus = MARQUEE_STOPPED; 11374 private final float mPixelsPerSecond; 11375 private float mMaxScroll; 11376 private float mMaxFadeScroll; 11377 private float mGhostStart; 11378 private float mGhostOffset; 11379 private float mFadeStop; 11380 private int mRepeatLimit; 11381 11382 private float mScroll; 11383 private long mLastAnimationMs; 11384 11385 Marquee(TextView v) { 11386 final float density = v.getContext().getResources().getDisplayMetrics().density; 11387 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; 11388 mView = new WeakReference<TextView>(v); 11389 mChoreographer = Choreographer.getInstance(); 11390 } 11391 11392 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 11393 @Override 11394 public void doFrame(long frameTimeNanos) { 11395 tick(); 11396 } 11397 }; 11398 11399 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 11400 @Override 11401 public void doFrame(long frameTimeNanos) { 11402 mStatus = MARQUEE_RUNNING; 11403 mLastAnimationMs = mChoreographer.getFrameTime(); 11404 tick(); 11405 } 11406 }; 11407 11408 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 11409 @Override 11410 public void doFrame(long frameTimeNanos) { 11411 if (mStatus == MARQUEE_RUNNING) { 11412 if (mRepeatLimit >= 0) { 11413 mRepeatLimit--; 11414 } 11415 start(mRepeatLimit); 11416 } 11417 } 11418 }; 11419 11420 void tick() { 11421 if (mStatus != MARQUEE_RUNNING) { 11422 return; 11423 } 11424 11425 mChoreographer.removeFrameCallback(mTickCallback); 11426 11427 final TextView textView = mView.get(); 11428 if (textView != null && (textView.isFocused() || textView.isSelected())) { 11429 long currentMs = mChoreographer.getFrameTime(); 11430 long deltaMs = currentMs - mLastAnimationMs; 11431 mLastAnimationMs = currentMs; 11432 float deltaPx = deltaMs / 1000f * mPixelsPerSecond; 11433 mScroll += deltaPx; 11434 if (mScroll > mMaxScroll) { 11435 mScroll = mMaxScroll; 11436 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 11437 } else { 11438 mChoreographer.postFrameCallback(mTickCallback); 11439 } 11440 textView.invalidate(); 11441 } 11442 } 11443 11444 void stop() { 11445 mStatus = MARQUEE_STOPPED; 11446 mChoreographer.removeFrameCallback(mStartCallback); 11447 mChoreographer.removeFrameCallback(mRestartCallback); 11448 mChoreographer.removeFrameCallback(mTickCallback); 11449 resetScroll(); 11450 } 11451 11452 private void resetScroll() { 11453 mScroll = 0.0f; 11454 final TextView textView = mView.get(); 11455 if (textView != null) textView.invalidate(); 11456 } 11457 11458 void start(int repeatLimit) { 11459 if (repeatLimit == 0) { 11460 stop(); 11461 return; 11462 } 11463 mRepeatLimit = repeatLimit; 11464 final TextView textView = mView.get(); 11465 if (textView != null && textView.mLayout != null) { 11466 mStatus = MARQUEE_STARTING; 11467 mScroll = 0.0f; 11468 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 11469 - textView.getCompoundPaddingRight(); 11470 final float lineWidth = textView.mLayout.getLineWidth(0); 11471 final float gap = textWidth / 3.0f; 11472 mGhostStart = lineWidth - textWidth + gap; 11473 mMaxScroll = mGhostStart + textWidth; 11474 mGhostOffset = lineWidth + gap; 11475 mFadeStop = lineWidth + textWidth / 6.0f; 11476 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 11477 11478 textView.invalidate(); 11479 mChoreographer.postFrameCallback(mStartCallback); 11480 } 11481 } 11482 11483 float getGhostOffset() { 11484 return mGhostOffset; 11485 } 11486 11487 float getScroll() { 11488 return mScroll; 11489 } 11490 11491 float getMaxFadeScroll() { 11492 return mMaxFadeScroll; 11493 } 11494 11495 boolean shouldDrawLeftFade() { 11496 return mScroll <= mFadeStop; 11497 } 11498 11499 boolean shouldDrawGhost() { 11500 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 11501 } 11502 11503 boolean isRunning() { 11504 return mStatus == MARQUEE_RUNNING; 11505 } 11506 11507 boolean isStopped() { 11508 return mStatus == MARQUEE_STOPPED; 11509 } 11510 } 11511 11512 private class ChangeWatcher implements TextWatcher, SpanWatcher { 11513 11514 private CharSequence mBeforeText; 11515 11516 public void beforeTextChanged(CharSequence buffer, int start, 11517 int before, int after) { 11518 if (DEBUG_EXTRACT) { 11519 Log.v(LOG_TAG, "beforeTextChanged start=" + start 11520 + " before=" + before + " after=" + after + ": " + buffer); 11521 } 11522 11523 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 11524 mBeforeText = buffer.toString(); 11525 } 11526 11527 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 11528 } 11529 11530 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 11531 if (DEBUG_EXTRACT) { 11532 Log.v(LOG_TAG, "onTextChanged start=" + start 11533 + " before=" + before + " after=" + after + ": " + buffer); 11534 } 11535 TextView.this.handleTextChanged(buffer, start, before, after); 11536 11537 if (AccessibilityManager.getInstance(mContext).isEnabled() 11538 && (isFocused() || isSelected() && isShown())) { 11539 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 11540 mBeforeText = null; 11541 } 11542 } 11543 11544 public void afterTextChanged(Editable buffer) { 11545 if (DEBUG_EXTRACT) { 11546 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 11547 } 11548 TextView.this.sendAfterTextChanged(buffer); 11549 11550 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 11551 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 11552 } 11553 } 11554 11555 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 11556 if (DEBUG_EXTRACT) { 11557 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 11558 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 11559 } 11560 TextView.this.spanChange(buf, what, s, st, e, en); 11561 } 11562 11563 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 11564 if (DEBUG_EXTRACT) { 11565 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 11566 } 11567 TextView.this.spanChange(buf, what, -1, s, -1, e); 11568 } 11569 11570 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 11571 if (DEBUG_EXTRACT) { 11572 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 11573 } 11574 TextView.this.spanChange(buf, what, s, -1, e, -1); 11575 } 11576 } 11577} 11578