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