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