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