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