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