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 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 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 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 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 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 */ 610 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 611 } 612 613 public TextView(Context context) { 614 this(context, null); 615 } 616 617 public TextView(Context context, AttributeSet attrs) { 618 this(context, attrs, com.android.internal.R.attr.textViewStyle); 619 } 620 621 @SuppressWarnings("deprecation") 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 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 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 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 */ 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 */ 1449 protected boolean getDefaultEditable() { 1450 return false; 1451 } 1452 1453 /** 1454 * Subclasses override this to specify a default movement method. 1455 */ 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 1471 public CharSequence getText() { 1472 return mText; 1473 } 1474 1475 /** 1476 * Returns the length, in characters, of the text managed by this TextView 1477 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 2351 public int getCompoundDrawablePadding() { 2352 final Drawables dr = mDrawables; 2353 return dr != null ? dr.mDrawablePadding : 0; 2354 } 2355 2356 @Override 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 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 */ 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 */ 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 */ 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 */ 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") 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 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 */ 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 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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 */ 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 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 */ 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 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 */ 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 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 */ 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 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 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 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 */ 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 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 */ 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 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 */ 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 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 */ 3611 public boolean getFreezesText() { 3612 return mFreezesText; 3613 } 3614 3615 /////////////////////////////////////////////////////////////////////////// 3616 3617 /** 3618 * Sets the Factory used to create new Editables. 3619 */ 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 */ 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 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 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 */ 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 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 */ 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 */ 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 3887 public final void setText(int resid) { 3888 setText(getContext().getResources().getText(resid)); 3889 } 3890 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 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 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 3938 public CharSequence getHint() { 3939 return mHint; 3940 } 3941 3942 boolean isSingleLine() { 3943 return mSingleLine; 3944 } 3945 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 */ 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 */ 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 */ 4043 private boolean hasPasswordTransformationMethod() { 4044 return mTransformation instanceof PasswordTransformationMethod; 4045 } 4046 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 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 */ 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 */ 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 */ 4481 public InputFilter[] getFilters() { 4482 return mFilters; 4483 } 4484 4485 ///////////////////////////////////////////////////////////////////////// 4486 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 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 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 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 4584 void invalidateCursor() { 4585 int where = getSelectionEnd(); 4586 4587 invalidateCursor(where, where, where); 4588 } 4589 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 */ 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 4656 private void registerForPreDraw() { 4657 if (!mPreDrawRegistered) { 4658 getViewTreeObserver().addOnPreDrawListener(this); 4659 mPreDrawRegistered = true; 4660 } 4661 } 4662 4663 /** 4664 * {@inheritDoc} 4665 */ 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 4721 protected void onAttachedToWindow() { 4722 super.onAttachedToWindow(); 4723 4724 mTemporaryDetach = false; 4725 4726 if (mEditor != null) mEditor.onAttachedToWindow(); 4727 } 4728 4729 @Override 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 4744 public void onScreenStateChanged(int screenState) { 4745 super.onScreenStateChanged(screenState); 4746 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 4747 } 4748 4749 @Override 4750 protected boolean isPaddingOffsetRequired() { 4751 return mShadowRadius != 0 || mDrawables != null; 4752 } 4753 4754 @Override 4755 protected int getLeftPaddingOffset() { 4756 return getCompoundPaddingLeft() - mPaddingLeft + 4757 (int) Math.min(0, mShadowDx - mShadowRadius); 4758 } 4759 4760 @Override 4761 protected int getTopPaddingOffset() { 4762 return (int) Math.min(0, mShadowDy - mShadowRadius); 4763 } 4764 4765 @Override 4766 protected int getBottomPaddingOffset() { 4767 return (int) Math.max(0, mShadowDy + mShadowRadius); 4768 } 4769 4770 @Override 4771 protected int getRightPaddingOffset() { 4772 return -(getCompoundPaddingRight() - mPaddingRight) + 4773 (int) Math.max(0, mShadowDx + mShadowRadius); 4774 } 4775 4776 @Override 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 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 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 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 */ 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 */ 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 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 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 */ 5004 public int getHorizontalOffsetForDrawables() { 5005 return 0; 5006 } 5007 5008 @Override 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 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 */ 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 */ 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 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 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 5310 protected int getFadeHeight(boolean offsetRequired) { 5311 return mLayout != null ? mLayout.getHeight() : 0; 5312 } 5313 5314 @Override 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 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 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 */ 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 */ 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 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 */ 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 */ 5589 public void hideErrorIfUnchanged() { 5590 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 5591 setError(null, null); 5592 } 5593 } 5594 5595 @Override 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 5699 public boolean onCheckIsTextEditor() { 5700 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 5701 } 5702 5703 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 5858 public void onCommitCorrection(CorrectionInfo info) { 5859 if (mEditor != null) mEditor.onCommitCorrection(info); 5860 } 5861 5862 public void beginBatchEdit() { 5863 if (mEditor != null) mEditor.beginBatchEdit(); 5864 } 5865 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 */ 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 */ 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 */ 5896 public boolean onPrivateIMECommand(String action, Bundle data) { 5897 return false; 5898 } 5899 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 */ 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 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 */ 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 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 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 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 */ 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 */ 6271 public boolean getIncludeFontPadding() { 6272 return mIncludePad; 6273 } 6274 6275 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 6276 6277 @Override 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 6451 private int getDesiredHeight() { 6452 return Math.max( 6453 getDesiredHeight(mLayout, true), 6454 getDesiredHeight(mHintLayout, mEllipsize != null)); 6455 } 6456 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 */ 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 */ 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 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 6615 private boolean isShowingHint() { 6616 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 6617 } 6618 6619 /** 6620 * Returns true if anything changed. 6621 */ 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 */ 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 */ 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 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 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 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 6975 int viewportToContentHorizontalOffset() { 6976 return getCompoundPaddingLeft() - mScrollX; 6977 } 6978 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 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") 7013 public int getSelectionStart() { 7014 return Selection.getSelectionStart(getText()); 7015 } 7016 7017 /** 7018 * Convenience for {@link Selection#getSelectionEnd}. 7019 */ 7020 @ViewDebug.ExportedProperty(category = "text") 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 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 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 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 */ 7227 public boolean isCursorVisible() { 7228 // true is the default value 7229 return mEditor == null ? true : mEditor.mCursorVisible; 7230 } 7231 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 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 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 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 */ 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 */ 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 */ 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 */ 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 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 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 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 */ 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 */ 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 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 */ 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 */ 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 7591 public void dispatchFinishTemporaryDetach() { 7592 mDispatchTemporaryDetach = true; 7593 super.dispatchFinishTemporaryDetach(); 7594 mDispatchTemporaryDetach = false; 7595 } 7596 7597 @Override 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 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 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 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 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 */ 7666 public void clearComposingText() { 7667 if (mText instanceof Spannable) { 7668 BaseInputConnection.removeComposingSpans((Spannable)mText); 7669 } 7670 } 7671 7672 @Override 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 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 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 */ 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 */ 7782 public boolean didTouchFocusSelect() { 7783 return mEditor != null && mEditor.mTouchFocusSelected; 7784 } 7785 7786 @Override 7787 public void cancelLongPress() { 7788 super.cancelLongPress(); 7789 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 7790 } 7791 7792 @Override 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 7803 public void setScroller(Scroller s) { 7804 mScroller = s; 7805 } 7806 7807 @Override 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 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 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 7892 protected int computeVerticalScrollRange() { 7893 if (mLayout != null) 7894 return mLayout.getHeight(); 7895 7896 return super.computeVerticalScrollRange(); 7897 } 7898 7899 @Override 7900 protected int computeVerticalScrollExtent() { 7901 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 7902 } 7903 7904 @Override 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 */ 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 */ 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 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 */ 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 */ 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 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 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 */ 8059 public Locale getSpellCheckerLocale() { 8060 return getTextServicesLocale(true /* allowNullLocale */); 8061 } 8062 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 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 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 */ 8098 public WordIterator getWordIterator() { 8099 if (mEditor != null) { 8100 return mEditor.getWordIterator(); 8101 } else { 8102 return null; 8103 } 8104 } 8105 8106 @Override 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 */ 8123 private boolean shouldSpeakPasswordsForAccessibility() { 8124 return (Settings.Secure.getInt(mContext.getContentResolver(), 8125 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); 8126 } 8127 8128 @Override 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 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 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 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 */ 8272 public CharSequence getTextForAccessibility() { 8273 CharSequence text = getText(); 8274 if (TextUtils.isEmpty(text)) { 8275 text = getHint(); 8276 } 8277 return text; 8278 } 8279 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 */ 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 */ 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 8349 CharSequence getTransformedText(int start, int end) { 8350 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 8351 } 8352 8353 @Override 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 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 */ 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 */ 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 */ 8452 public ActionMode.Callback getCustomSelectionActionModeCallback() { 8453 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 8454 } 8455 8456 /** 8457 * @hide 8458 */ 8459 protected void stopSelectionActionMode() { 8460 mEditor.stopSelectionActionMode(); 8461 } 8462 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 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 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 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 */ 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 */ 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 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 */ 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 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 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 8625 private int getOffsetAtCoordinate(int line, float x) { 8626 x = convertToLocalHorizontalCoordinate(x); 8627 return getLayout().getOffsetForHorizontal(line, x); 8628 } 8629 8630 @Override 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 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 8666 public void onRtlPropertiesChanged(int layoutDirection) { 8667 super.onRtlPropertiesChanged(layoutDirection); 8668 8669 mTextDir = getTextDirectionHeuristic(); 8670 8671 if (mLayout != null) { 8672 checkForRelayout(); 8673 } 8674 } 8675 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 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 */ 8722 protected void resetResolvedDrawables() { 8723 super.resetResolvedDrawables(); 8724 mLastLayoutDirection = -1; 8725 } 8726 8727 /** 8728 * @hide 8729 */ 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 */ 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 */ 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 */ 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 */ 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 */ 8780 private void createEditorIfNeeded() { 8781 if (mEditor == null) { 8782 mEditor = new Editor(this); 8783 } 8784 } 8785 8786 /** 8787 * @hide 8788 */ 8789 @Override 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 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 8829 public int getAccessibilitySelectionStart() { 8830 return getSelectionStart(); 8831 } 8832 8833 /** 8834 * @hide 8835 */ 8836 public boolean isAccessibilitySelectionExtendable() { 8837 return true; 8838 } 8839 8840 /** 8841 * @hide 8842 */ 8843 @Override 8844 public int getAccessibilitySelectionEnd() { 8845 return getSelectionEnd(); 8846 } 8847 8848 /** 8849 * @hide 8850 */ 8851 @Override 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 8882 SavedState(Parcelable superState) { 8883 super(superState); 8884 } 8885 8886 @Override 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 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 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 8942 public CharWrapper(char[] chars, int start, int len) { 8943 mChars = chars; 8944 mStart = start; 8945 mLength = len; 8946 } 8947 8948 /* package */ void set(char[] chars, int start, int len) { 8949 mChars = chars; 8950 mStart = start; 8951 mLength = len; 8952 } 8953 8954 public int length() { 8955 return mLength; 8956 } 8957 8958 public char charAt(int off) { 8959 return mChars[off + mStart]; 8960 } 8961 8962 @Override 8963 public String toString() { 8964 return new String(mChars, mStart, mLength); 8965 } 8966 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 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 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 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 8996 public float measureText(int start, int end, Paint p) { 8997 return p.measureText(mChars, start + mStart, end - start); 8998 } 8999 9000 public int getTextWidths(int start, int end, float[] widths, Paint p) { 9001 return p.getTextWidths(mChars, start + mStart, end - start, widths); 9002 } 9003 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 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 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 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 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 9098 void stop() { 9099 mStatus = MARQUEE_STOPPED; 9100 removeMessages(MESSAGE_START); 9101 removeMessages(MESSAGE_RESTART); 9102 removeMessages(MESSAGE_TICK); 9103 resetScroll(); 9104 } 9105 9106 private void resetScroll() { 9107 mScroll = 0.0f; 9108 final TextView textView = mView.get(); 9109 if (textView != null) textView.invalidate(); 9110 } 9111 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 9137 float getGhostOffset() { 9138 return mGhostOffset; 9139 } 9140 9141 float getScroll() { 9142 return mScroll; 9143 } 9144 9145 float getMaxFadeScroll() { 9146 return mMaxFadeScroll; 9147 } 9148 9149 boolean shouldDrawLeftFade() { 9150 return mScroll <= mFadeStop; 9151 } 9152 9153 boolean shouldDrawGhost() { 9154 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 9155 } 9156 9157 boolean isRunning() { 9158 return mStatus == MARQUEE_RUNNING; 9159 } 9160 9161 boolean isStopped() { 9162 return mStatus == MARQUEE_STOPPED; 9163 } 9164 } 9165 9166 private class ChangeWatcher implements TextWatcher, SpanWatcher { 9167 9168 private CharSequence mBeforeText; 9169 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 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 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 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 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 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