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