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