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