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