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