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