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