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