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