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