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