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