TextView.java revision c047ca454012d49820e9bf14f4501156e4479881
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) { 3637 mCurHintTextColor = color; 3638 if (mText.length() == 0) { 3639 inval = true; 3640 } 3641 } 3642 } 3643 if (inval) { 3644 // Text needs to be redrawn with the new color 3645 if (mEditor != null) mEditor.invalidateTextDisplayList(); 3646 invalidate(); 3647 } 3648 } 3649 3650 @Override 3651 protected void drawableStateChanged() { 3652 super.drawableStateChanged(); 3653 if (mTextColor != null && mTextColor.isStateful() 3654 || (mHintTextColor != null && mHintTextColor.isStateful()) 3655 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 3656 updateTextColors(); 3657 } 3658 3659 final Drawables dr = mDrawables; 3660 if (dr != null) { 3661 int[] state = getDrawableState(); 3662 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 3663 dr.mDrawableTop.setState(state); 3664 } 3665 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 3666 dr.mDrawableBottom.setState(state); 3667 } 3668 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 3669 dr.mDrawableLeft.setState(state); 3670 } 3671 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 3672 dr.mDrawableRight.setState(state); 3673 } 3674 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) { 3675 dr.mDrawableStart.setState(state); 3676 } 3677 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) { 3678 dr.mDrawableEnd.setState(state); 3679 } 3680 } 3681 } 3682 3683 @Override 3684 public void drawableHotspotChanged(float x, float y) { 3685 super.drawableHotspotChanged(x, y); 3686 3687 final Drawables dr = mDrawables; 3688 if (dr != null) { 3689 if (dr.mDrawableTop != null) { 3690 dr.mDrawableTop.setHotspot(x, y); 3691 } 3692 if (dr.mDrawableBottom != null) { 3693 dr.mDrawableBottom.setHotspot(x, y); 3694 } 3695 if (dr.mDrawableLeft != null) { 3696 dr.mDrawableLeft.setHotspot(x, y); 3697 } 3698 if (dr.mDrawableRight != null) { 3699 dr.mDrawableRight.setHotspot(x, y); 3700 } 3701 if (dr.mDrawableStart != null) { 3702 dr.mDrawableStart.setHotspot(x, y); 3703 } 3704 if (dr.mDrawableEnd != null) { 3705 dr.mDrawableEnd.setHotspot(x, y); 3706 } 3707 } 3708 } 3709 3710 @Override 3711 public Parcelable onSaveInstanceState() { 3712 Parcelable superState = super.onSaveInstanceState(); 3713 3714 // Save state if we are forced to 3715 boolean save = mFreezesText; 3716 int start = 0; 3717 int end = 0; 3718 3719 if (mText != null) { 3720 start = getSelectionStart(); 3721 end = getSelectionEnd(); 3722 if (start >= 0 || end >= 0) { 3723 // Or save state if there is a selection 3724 save = true; 3725 } 3726 } 3727 3728 if (save) { 3729 SavedState ss = new SavedState(superState); 3730 // XXX Should also save the current scroll position! 3731 ss.selStart = start; 3732 ss.selEnd = end; 3733 3734 if (mText instanceof Spanned) { 3735 Spannable sp = new SpannableStringBuilder(mText); 3736 3737 if (mEditor != null) { 3738 removeMisspelledSpans(sp); 3739 sp.removeSpan(mEditor.mSuggestionRangeSpan); 3740 } 3741 3742 ss.text = sp; 3743 } else { 3744 ss.text = mText.toString(); 3745 } 3746 3747 if (isFocused() && start >= 0 && end >= 0) { 3748 ss.frozenWithFocus = true; 3749 } 3750 3751 ss.error = getError(); 3752 3753 return ss; 3754 } 3755 3756 return superState; 3757 } 3758 3759 void removeMisspelledSpans(Spannable spannable) { 3760 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 3761 SuggestionSpan.class); 3762 for (int i = 0; i < suggestionSpans.length; i++) { 3763 int flags = suggestionSpans[i].getFlags(); 3764 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 3765 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 3766 spannable.removeSpan(suggestionSpans[i]); 3767 } 3768 } 3769 } 3770 3771 @Override 3772 public void onRestoreInstanceState(Parcelable state) { 3773 if (!(state instanceof SavedState)) { 3774 super.onRestoreInstanceState(state); 3775 return; 3776 } 3777 3778 SavedState ss = (SavedState)state; 3779 super.onRestoreInstanceState(ss.getSuperState()); 3780 3781 // XXX restore buffer type too, as well as lots of other stuff 3782 if (ss.text != null) { 3783 setText(ss.text); 3784 } 3785 3786 if (ss.selStart >= 0 && ss.selEnd >= 0) { 3787 if (mText instanceof Spannable) { 3788 int len = mText.length(); 3789 3790 if (ss.selStart > len || ss.selEnd > len) { 3791 String restored = ""; 3792 3793 if (ss.text != null) { 3794 restored = "(restored) "; 3795 } 3796 3797 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + 3798 "/" + ss.selEnd + " out of range for " + restored + 3799 "text " + mText); 3800 } else { 3801 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); 3802 3803 if (ss.frozenWithFocus) { 3804 createEditorIfNeeded(); 3805 mEditor.mFrozenWithFocus = true; 3806 } 3807 } 3808 } 3809 } 3810 3811 if (ss.error != null) { 3812 final CharSequence error = ss.error; 3813 // Display the error later, after the first layout pass 3814 post(new Runnable() { 3815 public void run() { 3816 setError(error); 3817 } 3818 }); 3819 } 3820 } 3821 3822 /** 3823 * Control whether this text view saves its entire text contents when 3824 * freezing to an icicle, in addition to dynamic state such as cursor 3825 * position. By default this is false, not saving the text. Set to true 3826 * if the text in the text view is not being saved somewhere else in 3827 * persistent storage (such as in a content provider) so that if the 3828 * view is later thawed the user will not lose their data. 3829 * 3830 * @param freezesText Controls whether a frozen icicle should include the 3831 * entire text data: true to include it, false to not. 3832 * 3833 * @attr ref android.R.styleable#TextView_freezesText 3834 */ 3835 @android.view.RemotableViewMethod 3836 public void setFreezesText(boolean freezesText) { 3837 mFreezesText = freezesText; 3838 } 3839 3840 /** 3841 * Return whether this text view is including its entire text contents 3842 * in frozen icicles. 3843 * 3844 * @return Returns true if text is included, false if it isn't. 3845 * 3846 * @see #setFreezesText 3847 */ 3848 public boolean getFreezesText() { 3849 return mFreezesText; 3850 } 3851 3852 /////////////////////////////////////////////////////////////////////////// 3853 3854 /** 3855 * Sets the Factory used to create new Editables. 3856 */ 3857 public final void setEditableFactory(Editable.Factory factory) { 3858 mEditableFactory = factory; 3859 setText(mText); 3860 } 3861 3862 /** 3863 * Sets the Factory used to create new Spannables. 3864 */ 3865 public final void setSpannableFactory(Spannable.Factory factory) { 3866 mSpannableFactory = factory; 3867 setText(mText); 3868 } 3869 3870 /** 3871 * Sets the string value of the TextView. TextView <em>does not</em> accept 3872 * HTML-like formatting, which you can do with text strings in XML resource files. 3873 * To style your strings, attach android.text.style.* objects to a 3874 * {@link android.text.SpannableString SpannableString}, or see the 3875 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 3876 * Available Resource Types</a> documentation for an example of setting 3877 * formatted text in the XML resource file. 3878 * 3879 * @attr ref android.R.styleable#TextView_text 3880 */ 3881 @android.view.RemotableViewMethod 3882 public final void setText(CharSequence text) { 3883 setText(text, mBufferType); 3884 } 3885 3886 /** 3887 * Like {@link #setText(CharSequence)}, 3888 * except that the cursor position (if any) is retained in the new text. 3889 * 3890 * @param text The new text to place in the text view. 3891 * 3892 * @see #setText(CharSequence) 3893 */ 3894 @android.view.RemotableViewMethod 3895 public final void setTextKeepState(CharSequence text) { 3896 setTextKeepState(text, mBufferType); 3897 } 3898 3899 /** 3900 * Sets the text that this TextView is to display (see 3901 * {@link #setText(CharSequence)}) and also sets whether it is stored 3902 * in a styleable/spannable buffer and whether it is editable. 3903 * 3904 * @attr ref android.R.styleable#TextView_text 3905 * @attr ref android.R.styleable#TextView_bufferType 3906 */ 3907 public void setText(CharSequence text, BufferType type) { 3908 setText(text, type, true, 0); 3909 3910 if (mCharWrapper != null) { 3911 mCharWrapper.mChars = null; 3912 } 3913 } 3914 3915 private void setText(CharSequence text, BufferType type, 3916 boolean notifyBefore, int oldlen) { 3917 if (text == null) { 3918 text = ""; 3919 } 3920 3921 // If suggestions are not enabled, remove the suggestion spans from the text 3922 if (!isSuggestionsEnabled()) { 3923 text = removeSuggestionSpans(text); 3924 } 3925 3926 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 3927 3928 if (text instanceof Spanned && 3929 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 3930 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 3931 setHorizontalFadingEdgeEnabled(true); 3932 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 3933 } else { 3934 setHorizontalFadingEdgeEnabled(false); 3935 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 3936 } 3937 setEllipsize(TextUtils.TruncateAt.MARQUEE); 3938 } 3939 3940 int n = mFilters.length; 3941 for (int i = 0; i < n; i++) { 3942 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 3943 if (out != null) { 3944 text = out; 3945 } 3946 } 3947 3948 if (notifyBefore) { 3949 if (mText != null) { 3950 oldlen = mText.length(); 3951 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 3952 } else { 3953 sendBeforeTextChanged("", 0, 0, text.length()); 3954 } 3955 } 3956 3957 boolean needEditableForNotification = false; 3958 3959 if (mListeners != null && mListeners.size() != 0) { 3960 needEditableForNotification = true; 3961 } 3962 3963 if (type == BufferType.EDITABLE || getKeyListener() != null || 3964 needEditableForNotification) { 3965 createEditorIfNeeded(); 3966 Editable t = mEditableFactory.newEditable(text); 3967 text = t; 3968 setFilters(t, mFilters); 3969 InputMethodManager imm = InputMethodManager.peekInstance(); 3970 if (imm != null) imm.restartInput(this); 3971 } else if (type == BufferType.SPANNABLE || mMovement != null) { 3972 text = mSpannableFactory.newSpannable(text); 3973 } else if (!(text instanceof CharWrapper)) { 3974 text = TextUtils.stringOrSpannedString(text); 3975 } 3976 3977 if (mAutoLinkMask != 0) { 3978 Spannable s2; 3979 3980 if (type == BufferType.EDITABLE || text instanceof Spannable) { 3981 s2 = (Spannable) text; 3982 } else { 3983 s2 = mSpannableFactory.newSpannable(text); 3984 } 3985 3986 if (Linkify.addLinks(s2, mAutoLinkMask)) { 3987 text = s2; 3988 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 3989 3990 /* 3991 * We must go ahead and set the text before changing the 3992 * movement method, because setMovementMethod() may call 3993 * setText() again to try to upgrade the buffer type. 3994 */ 3995 mText = text; 3996 3997 // Do not change the movement method for text that support text selection as it 3998 // would prevent an arbitrary cursor displacement. 3999 if (mLinksClickable && !textCanBeSelected()) { 4000 setMovementMethod(LinkMovementMethod.getInstance()); 4001 } 4002 } 4003 } 4004 4005 mBufferType = type; 4006 mText = text; 4007 4008 if (mTransformation == null) { 4009 mTransformed = text; 4010 } else { 4011 mTransformed = mTransformation.getTransformation(text, this); 4012 } 4013 4014 final int textLength = text.length(); 4015 4016 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 4017 Spannable sp = (Spannable) text; 4018 4019 // Remove any ChangeWatchers that might have come from other TextViews. 4020 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 4021 final int count = watchers.length; 4022 for (int i = 0; i < count; i++) { 4023 sp.removeSpan(watchers[i]); 4024 } 4025 4026 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 4027 4028 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 4029 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 4030 4031 if (mEditor != null) mEditor.addSpanWatchers(sp); 4032 4033 if (mTransformation != null) { 4034 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 4035 } 4036 4037 if (mMovement != null) { 4038 mMovement.initialize(this, (Spannable) text); 4039 4040 /* 4041 * Initializing the movement method will have set the 4042 * selection, so reset mSelectionMoved to keep that from 4043 * interfering with the normal on-focus selection-setting. 4044 */ 4045 if (mEditor != null) mEditor.mSelectionMoved = false; 4046 } 4047 } 4048 4049 if (mLayout != null) { 4050 checkForRelayout(); 4051 } 4052 4053 sendOnTextChanged(text, 0, oldlen, textLength); 4054 onTextChanged(text, 0, oldlen, textLength); 4055 4056 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 4057 4058 if (needEditableForNotification) { 4059 sendAfterTextChanged((Editable) text); 4060 } 4061 4062 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 4063 if (mEditor != null) mEditor.prepareCursorControllers(); 4064 } 4065 4066 /** 4067 * Sets the TextView to display the specified slice of the specified 4068 * char array. You must promise that you will not change the contents 4069 * of the array except for right before another call to setText(), 4070 * since the TextView has no way to know that the text 4071 * has changed and that it needs to invalidate and re-layout. 4072 */ 4073 public final void setText(char[] text, int start, int len) { 4074 int oldlen = 0; 4075 4076 if (start < 0 || len < 0 || start + len > text.length) { 4077 throw new IndexOutOfBoundsException(start + ", " + len); 4078 } 4079 4080 /* 4081 * We must do the before-notification here ourselves because if 4082 * the old text is a CharWrapper we destroy it before calling 4083 * into the normal path. 4084 */ 4085 if (mText != null) { 4086 oldlen = mText.length(); 4087 sendBeforeTextChanged(mText, 0, oldlen, len); 4088 } else { 4089 sendBeforeTextChanged("", 0, 0, len); 4090 } 4091 4092 if (mCharWrapper == null) { 4093 mCharWrapper = new CharWrapper(text, start, len); 4094 } else { 4095 mCharWrapper.set(text, start, len); 4096 } 4097 4098 setText(mCharWrapper, mBufferType, false, oldlen); 4099 } 4100 4101 /** 4102 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 4103 * except that the cursor position (if any) is retained in the new text. 4104 * 4105 * @see #setText(CharSequence, android.widget.TextView.BufferType) 4106 */ 4107 public final void setTextKeepState(CharSequence text, BufferType type) { 4108 int start = getSelectionStart(); 4109 int end = getSelectionEnd(); 4110 int len = text.length(); 4111 4112 setText(text, type); 4113 4114 if (start >= 0 || end >= 0) { 4115 if (mText instanceof Spannable) { 4116 Selection.setSelection((Spannable) mText, 4117 Math.max(0, Math.min(start, len)), 4118 Math.max(0, Math.min(end, len))); 4119 } 4120 } 4121 } 4122 4123 @android.view.RemotableViewMethod 4124 public final void setText(int resid) { 4125 setText(getContext().getResources().getText(resid)); 4126 } 4127 4128 public final void setText(int resid, BufferType type) { 4129 setText(getContext().getResources().getText(resid), type); 4130 } 4131 4132 /** 4133 * Sets the text to be displayed when the text of the TextView is empty. 4134 * Null means to use the normal empty text. The hint does not currently 4135 * participate in determining the size of the view. 4136 * 4137 * @attr ref android.R.styleable#TextView_hint 4138 */ 4139 @android.view.RemotableViewMethod 4140 public final void setHint(CharSequence hint) { 4141 mHint = TextUtils.stringOrSpannedString(hint); 4142 4143 if (mLayout != null) { 4144 checkForRelayout(); 4145 } 4146 4147 if (mText.length() == 0) { 4148 invalidate(); 4149 } 4150 4151 // Invalidate display list if hint is currently used 4152 if (mEditor != null && mText.length() == 0 && mHint != null) { 4153 mEditor.invalidateTextDisplayList(); 4154 } 4155 } 4156 4157 /** 4158 * Sets the text to be displayed when the text of the TextView is empty, 4159 * from a resource. 4160 * 4161 * @attr ref android.R.styleable#TextView_hint 4162 */ 4163 @android.view.RemotableViewMethod 4164 public final void setHint(int resid) { 4165 setHint(getContext().getResources().getText(resid)); 4166 } 4167 4168 /** 4169 * Returns the hint that is displayed when the text of the TextView 4170 * is empty. 4171 * 4172 * @attr ref android.R.styleable#TextView_hint 4173 */ 4174 @ViewDebug.CapturedViewProperty 4175 public CharSequence getHint() { 4176 return mHint; 4177 } 4178 4179 boolean isSingleLine() { 4180 return mSingleLine; 4181 } 4182 4183 private static boolean isMultilineInputType(int type) { 4184 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 4185 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 4186 } 4187 4188 /** 4189 * Removes the suggestion spans. 4190 */ 4191 CharSequence removeSuggestionSpans(CharSequence text) { 4192 if (text instanceof Spanned) { 4193 Spannable spannable; 4194 if (text instanceof Spannable) { 4195 spannable = (Spannable) text; 4196 } else { 4197 spannable = new SpannableString(text); 4198 text = spannable; 4199 } 4200 4201 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 4202 for (int i = 0; i < spans.length; i++) { 4203 spannable.removeSpan(spans[i]); 4204 } 4205 } 4206 return text; 4207 } 4208 4209 /** 4210 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 4211 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 4212 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 4213 * then a soft keyboard will not be displayed for this text view. 4214 * 4215 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 4216 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 4217 * type. 4218 * 4219 * @see #getInputType() 4220 * @see #setRawInputType(int) 4221 * @see android.text.InputType 4222 * @attr ref android.R.styleable#TextView_inputType 4223 */ 4224 public void setInputType(int type) { 4225 final boolean wasPassword = isPasswordInputType(getInputType()); 4226 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 4227 setInputType(type, false); 4228 final boolean isPassword = isPasswordInputType(type); 4229 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 4230 boolean forceUpdate = false; 4231 if (isPassword) { 4232 setTransformationMethod(PasswordTransformationMethod.getInstance()); 4233 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4234 } else if (isVisiblePassword) { 4235 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4236 forceUpdate = true; 4237 } 4238 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4239 } else if (wasPassword || wasVisiblePassword) { 4240 // not in password mode, clean up typeface and transformation 4241 setTypefaceFromAttrs(null /* fontFamily */, -1, -1); 4242 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4243 forceUpdate = true; 4244 } 4245 } 4246 4247 boolean singleLine = !isMultilineInputType(type); 4248 4249 // We need to update the single line mode if it has changed or we 4250 // were previously in password mode. 4251 if (mSingleLine != singleLine || forceUpdate) { 4252 // Change single line mode, but only change the transformation if 4253 // we are not in password mode. 4254 applySingleLine(singleLine, !isPassword, true); 4255 } 4256 4257 if (!isSuggestionsEnabled()) { 4258 mText = removeSuggestionSpans(mText); 4259 } 4260 4261 InputMethodManager imm = InputMethodManager.peekInstance(); 4262 if (imm != null) imm.restartInput(this); 4263 } 4264 4265 /** 4266 * It would be better to rely on the input type for everything. A password inputType should have 4267 * a password transformation. We should hence use isPasswordInputType instead of this method. 4268 * 4269 * We should: 4270 * - Call setInputType in setKeyListener instead of changing the input type directly (which 4271 * would install the correct transformation). 4272 * - Refuse the installation of a non-password transformation in setTransformation if the input 4273 * type is password. 4274 * 4275 * However, this is like this for legacy reasons and we cannot break existing apps. This method 4276 * is useful since it matches what the user can see (obfuscated text or not). 4277 * 4278 * @return true if the current transformation method is of the password type. 4279 */ 4280 private boolean hasPasswordTransformationMethod() { 4281 return mTransformation instanceof PasswordTransformationMethod; 4282 } 4283 4284 private static boolean isPasswordInputType(int inputType) { 4285 final int variation = 4286 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4287 return variation 4288 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 4289 || variation 4290 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 4291 || variation 4292 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 4293 } 4294 4295 private static boolean isVisiblePasswordInputType(int inputType) { 4296 final int variation = 4297 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4298 return variation 4299 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 4300 } 4301 4302 /** 4303 * Directly change the content type integer of the text view, without 4304 * modifying any other state. 4305 * @see #setInputType(int) 4306 * @see android.text.InputType 4307 * @attr ref android.R.styleable#TextView_inputType 4308 */ 4309 public void setRawInputType(int type) { 4310 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 4311 createEditorIfNeeded(); 4312 mEditor.mInputType = type; 4313 } 4314 4315 private void setInputType(int type, boolean direct) { 4316 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 4317 KeyListener input; 4318 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 4319 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 4320 TextKeyListener.Capitalize cap; 4321 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 4322 cap = TextKeyListener.Capitalize.CHARACTERS; 4323 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 4324 cap = TextKeyListener.Capitalize.WORDS; 4325 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 4326 cap = TextKeyListener.Capitalize.SENTENCES; 4327 } else { 4328 cap = TextKeyListener.Capitalize.NONE; 4329 } 4330 input = TextKeyListener.getInstance(autotext, cap); 4331 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 4332 input = DigitsKeyListener.getInstance( 4333 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 4334 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 4335 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 4336 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 4337 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 4338 input = DateKeyListener.getInstance(); 4339 break; 4340 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 4341 input = TimeKeyListener.getInstance(); 4342 break; 4343 default: 4344 input = DateTimeKeyListener.getInstance(); 4345 break; 4346 } 4347 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 4348 input = DialerKeyListener.getInstance(); 4349 } else { 4350 input = TextKeyListener.getInstance(); 4351 } 4352 setRawInputType(type); 4353 if (direct) { 4354 createEditorIfNeeded(); 4355 mEditor.mKeyListener = input; 4356 } else { 4357 setKeyListenerOnly(input); 4358 } 4359 } 4360 4361 /** 4362 * Get the type of the editable content. 4363 * 4364 * @see #setInputType(int) 4365 * @see android.text.InputType 4366 */ 4367 public int getInputType() { 4368 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 4369 } 4370 4371 /** 4372 * Change the editor type integer associated with the text view, which 4373 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 4374 * has focus. 4375 * @see #getImeOptions 4376 * @see android.view.inputmethod.EditorInfo 4377 * @attr ref android.R.styleable#TextView_imeOptions 4378 */ 4379 public void setImeOptions(int imeOptions) { 4380 createEditorIfNeeded(); 4381 mEditor.createInputContentTypeIfNeeded(); 4382 mEditor.mInputContentType.imeOptions = imeOptions; 4383 } 4384 4385 /** 4386 * Get the type of the IME editor. 4387 * 4388 * @see #setImeOptions(int) 4389 * @see android.view.inputmethod.EditorInfo 4390 */ 4391 public int getImeOptions() { 4392 return mEditor != null && mEditor.mInputContentType != null 4393 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 4394 } 4395 4396 /** 4397 * Change the custom IME action associated with the text view, which 4398 * will be reported to an IME with {@link EditorInfo#actionLabel} 4399 * and {@link EditorInfo#actionId} when it has focus. 4400 * @see #getImeActionLabel 4401 * @see #getImeActionId 4402 * @see android.view.inputmethod.EditorInfo 4403 * @attr ref android.R.styleable#TextView_imeActionLabel 4404 * @attr ref android.R.styleable#TextView_imeActionId 4405 */ 4406 public void setImeActionLabel(CharSequence label, int actionId) { 4407 createEditorIfNeeded(); 4408 mEditor.createInputContentTypeIfNeeded(); 4409 mEditor.mInputContentType.imeActionLabel = label; 4410 mEditor.mInputContentType.imeActionId = actionId; 4411 } 4412 4413 /** 4414 * Get the IME action label previous set with {@link #setImeActionLabel}. 4415 * 4416 * @see #setImeActionLabel 4417 * @see android.view.inputmethod.EditorInfo 4418 */ 4419 public CharSequence getImeActionLabel() { 4420 return mEditor != null && mEditor.mInputContentType != null 4421 ? mEditor.mInputContentType.imeActionLabel : null; 4422 } 4423 4424 /** 4425 * Get the IME action ID previous set with {@link #setImeActionLabel}. 4426 * 4427 * @see #setImeActionLabel 4428 * @see android.view.inputmethod.EditorInfo 4429 */ 4430 public int getImeActionId() { 4431 return mEditor != null && mEditor.mInputContentType != null 4432 ? mEditor.mInputContentType.imeActionId : 0; 4433 } 4434 4435 /** 4436 * Set a special listener to be called when an action is performed 4437 * on the text view. This will be called when the enter key is pressed, 4438 * or when an action supplied to the IME is selected by the user. Setting 4439 * this means that the normal hard key event will not insert a newline 4440 * into the text view, even if it is multi-line; holding down the ALT 4441 * modifier will, however, allow the user to insert a newline character. 4442 */ 4443 public void setOnEditorActionListener(OnEditorActionListener l) { 4444 createEditorIfNeeded(); 4445 mEditor.createInputContentTypeIfNeeded(); 4446 mEditor.mInputContentType.onEditorActionListener = l; 4447 } 4448 4449 /** 4450 * Called when an attached input method calls 4451 * {@link InputConnection#performEditorAction(int) 4452 * InputConnection.performEditorAction()} 4453 * for this text view. The default implementation will call your action 4454 * listener supplied to {@link #setOnEditorActionListener}, or perform 4455 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 4456 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 4457 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 4458 * EditorInfo.IME_ACTION_DONE}. 4459 * 4460 * <p>For backwards compatibility, if no IME options have been set and the 4461 * text view would not normally advance focus on enter, then 4462 * the NEXT and DONE actions received here will be turned into an enter 4463 * key down/up pair to go through the normal key handling. 4464 * 4465 * @param actionCode The code of the action being performed. 4466 * 4467 * @see #setOnEditorActionListener 4468 */ 4469 public void onEditorAction(int actionCode) { 4470 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 4471 if (ict != null) { 4472 if (ict.onEditorActionListener != null) { 4473 if (ict.onEditorActionListener.onEditorAction(this, 4474 actionCode, null)) { 4475 return; 4476 } 4477 } 4478 4479 // This is the handling for some default action. 4480 // Note that for backwards compatibility we don't do this 4481 // default handling if explicit ime options have not been given, 4482 // instead turning this into the normal enter key codes that an 4483 // app may be expecting. 4484 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 4485 View v = focusSearch(FOCUS_FORWARD); 4486 if (v != null) { 4487 if (!v.requestFocus(FOCUS_FORWARD)) { 4488 throw new IllegalStateException("focus search returned a view " + 4489 "that wasn't able to take focus!"); 4490 } 4491 } 4492 return; 4493 4494 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 4495 View v = focusSearch(FOCUS_BACKWARD); 4496 if (v != null) { 4497 if (!v.requestFocus(FOCUS_BACKWARD)) { 4498 throw new IllegalStateException("focus search returned a view " + 4499 "that wasn't able to take focus!"); 4500 } 4501 } 4502 return; 4503 4504 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 4505 InputMethodManager imm = InputMethodManager.peekInstance(); 4506 if (imm != null && imm.isActive(this)) { 4507 imm.hideSoftInputFromWindow(getWindowToken(), 0); 4508 } 4509 return; 4510 } 4511 } 4512 4513 ViewRootImpl viewRootImpl = getViewRootImpl(); 4514 if (viewRootImpl != null) { 4515 long eventTime = SystemClock.uptimeMillis(); 4516 viewRootImpl.dispatchKeyFromIme( 4517 new KeyEvent(eventTime, eventTime, 4518 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 4519 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4520 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4521 | KeyEvent.FLAG_EDITOR_ACTION)); 4522 viewRootImpl.dispatchKeyFromIme( 4523 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 4524 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 4525 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4526 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4527 | KeyEvent.FLAG_EDITOR_ACTION)); 4528 } 4529 } 4530 4531 /** 4532 * Set the private content type of the text, which is the 4533 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 4534 * field that will be filled in when creating an input connection. 4535 * 4536 * @see #getPrivateImeOptions() 4537 * @see EditorInfo#privateImeOptions 4538 * @attr ref android.R.styleable#TextView_privateImeOptions 4539 */ 4540 public void setPrivateImeOptions(String type) { 4541 createEditorIfNeeded(); 4542 mEditor.createInputContentTypeIfNeeded(); 4543 mEditor.mInputContentType.privateImeOptions = type; 4544 } 4545 4546 /** 4547 * Get the private type of the content. 4548 * 4549 * @see #setPrivateImeOptions(String) 4550 * @see EditorInfo#privateImeOptions 4551 */ 4552 public String getPrivateImeOptions() { 4553 return mEditor != null && mEditor.mInputContentType != null 4554 ? mEditor.mInputContentType.privateImeOptions : null; 4555 } 4556 4557 /** 4558 * Set the extra input data of the text, which is the 4559 * {@link EditorInfo#extras TextBoxAttribute.extras} 4560 * Bundle that will be filled in when creating an input connection. The 4561 * given integer is the resource ID of an XML resource holding an 4562 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 4563 * 4564 * @see #getInputExtras(boolean) 4565 * @see EditorInfo#extras 4566 * @attr ref android.R.styleable#TextView_editorExtras 4567 */ 4568 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException { 4569 createEditorIfNeeded(); 4570 XmlResourceParser parser = getResources().getXml(xmlResId); 4571 mEditor.createInputContentTypeIfNeeded(); 4572 mEditor.mInputContentType.extras = new Bundle(); 4573 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 4574 } 4575 4576 /** 4577 * Retrieve the input extras currently associated with the text view, which 4578 * can be viewed as well as modified. 4579 * 4580 * @param create If true, the extras will be created if they don't already 4581 * exist. Otherwise, null will be returned if none have been created. 4582 * @see #setInputExtras(int) 4583 * @see EditorInfo#extras 4584 * @attr ref android.R.styleable#TextView_editorExtras 4585 */ 4586 public Bundle getInputExtras(boolean create) { 4587 if (mEditor == null && !create) return null; 4588 createEditorIfNeeded(); 4589 if (mEditor.mInputContentType == null) { 4590 if (!create) return null; 4591 mEditor.createInputContentTypeIfNeeded(); 4592 } 4593 if (mEditor.mInputContentType.extras == null) { 4594 if (!create) return null; 4595 mEditor.mInputContentType.extras = new Bundle(); 4596 } 4597 return mEditor.mInputContentType.extras; 4598 } 4599 4600 /** 4601 * Returns the error message that was set to be displayed with 4602 * {@link #setError}, or <code>null</code> if no error was set 4603 * or if it the error was cleared by the widget after user input. 4604 */ 4605 public CharSequence getError() { 4606 return mEditor == null ? null : mEditor.mError; 4607 } 4608 4609 /** 4610 * Sets the right-hand compound drawable of the TextView to the "error" 4611 * icon and sets an error message that will be displayed in a popup when 4612 * the TextView has focus. The icon and error message will be reset to 4613 * null when any key events cause changes to the TextView's text. If the 4614 * <code>error</code> is <code>null</code>, the error message and icon 4615 * will be cleared. 4616 */ 4617 @android.view.RemotableViewMethod 4618 public void setError(CharSequence error) { 4619 if (error == null) { 4620 setError(null, null); 4621 } else { 4622 Drawable dr = getContext().getDrawable( 4623 com.android.internal.R.drawable.indicator_input_error); 4624 4625 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 4626 setError(error, dr); 4627 } 4628 } 4629 4630 /** 4631 * Sets the right-hand compound drawable of the TextView to the specified 4632 * icon and sets an error message that will be displayed in a popup when 4633 * the TextView has focus. The icon and error message will be reset to 4634 * null when any key events cause changes to the TextView's text. The 4635 * drawable must already have had {@link Drawable#setBounds} set on it. 4636 * If the <code>error</code> is <code>null</code>, the error message will 4637 * be cleared (and you should provide a <code>null</code> icon as well). 4638 */ 4639 public void setError(CharSequence error, Drawable icon) { 4640 createEditorIfNeeded(); 4641 mEditor.setError(error, icon); 4642 notifyViewAccessibilityStateChangedIfNeeded( 4643 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 4644 } 4645 4646 @Override 4647 protected boolean setFrame(int l, int t, int r, int b) { 4648 boolean result = super.setFrame(l, t, r, b); 4649 4650 if (mEditor != null) mEditor.setFrame(); 4651 4652 restartMarqueeIfNeeded(); 4653 4654 return result; 4655 } 4656 4657 private void restartMarqueeIfNeeded() { 4658 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4659 mRestartMarquee = false; 4660 startMarquee(); 4661 } 4662 } 4663 4664 /** 4665 * Sets the list of input filters that will be used if the buffer is 4666 * Editable. Has no effect otherwise. 4667 * 4668 * @attr ref android.R.styleable#TextView_maxLength 4669 */ 4670 public void setFilters(InputFilter[] filters) { 4671 if (filters == null) { 4672 throw new IllegalArgumentException(); 4673 } 4674 4675 mFilters = filters; 4676 4677 if (mText instanceof Editable) { 4678 setFilters((Editable) mText, filters); 4679 } 4680 } 4681 4682 /** 4683 * Sets the list of input filters on the specified Editable, 4684 * and includes mInput in the list if it is an InputFilter. 4685 */ 4686 private void setFilters(Editable e, InputFilter[] filters) { 4687 if (mEditor != null) { 4688 final boolean undoFilter = mEditor.mUndoInputFilter != null; 4689 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 4690 int num = 0; 4691 if (undoFilter) num++; 4692 if (keyFilter) num++; 4693 if (num > 0) { 4694 InputFilter[] nf = new InputFilter[filters.length + num]; 4695 4696 System.arraycopy(filters, 0, nf, 0, filters.length); 4697 num = 0; 4698 if (undoFilter) { 4699 nf[filters.length] = mEditor.mUndoInputFilter; 4700 num++; 4701 } 4702 if (keyFilter) { 4703 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 4704 } 4705 4706 e.setFilters(nf); 4707 return; 4708 } 4709 } 4710 e.setFilters(filters); 4711 } 4712 4713 /** 4714 * Returns the current list of input filters. 4715 * 4716 * @attr ref android.R.styleable#TextView_maxLength 4717 */ 4718 public InputFilter[] getFilters() { 4719 return mFilters; 4720 } 4721 4722 ///////////////////////////////////////////////////////////////////////// 4723 4724 private int getBoxHeight(Layout l) { 4725 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 4726 int padding = (l == mHintLayout) ? 4727 getCompoundPaddingTop() + getCompoundPaddingBottom() : 4728 getExtendedPaddingTop() + getExtendedPaddingBottom(); 4729 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 4730 } 4731 4732 int getVerticalOffset(boolean forceNormal) { 4733 int voffset = 0; 4734 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4735 4736 Layout l = mLayout; 4737 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4738 l = mHintLayout; 4739 } 4740 4741 if (gravity != Gravity.TOP) { 4742 int boxht = getBoxHeight(l); 4743 int textht = l.getHeight(); 4744 4745 if (textht < boxht) { 4746 if (gravity == Gravity.BOTTOM) 4747 voffset = boxht - textht; 4748 else // (gravity == Gravity.CENTER_VERTICAL) 4749 voffset = (boxht - textht) >> 1; 4750 } 4751 } 4752 return voffset; 4753 } 4754 4755 private int getBottomVerticalOffset(boolean forceNormal) { 4756 int voffset = 0; 4757 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4758 4759 Layout l = mLayout; 4760 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4761 l = mHintLayout; 4762 } 4763 4764 if (gravity != Gravity.BOTTOM) { 4765 int boxht = getBoxHeight(l); 4766 int textht = l.getHeight(); 4767 4768 if (textht < boxht) { 4769 if (gravity == Gravity.TOP) 4770 voffset = boxht - textht; 4771 else // (gravity == Gravity.CENTER_VERTICAL) 4772 voffset = (boxht - textht) >> 1; 4773 } 4774 } 4775 return voffset; 4776 } 4777 4778 void invalidateCursorPath() { 4779 if (mHighlightPathBogus) { 4780 invalidateCursor(); 4781 } else { 4782 final int horizontalPadding = getCompoundPaddingLeft(); 4783 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4784 4785 if (mEditor.mCursorCount == 0) { 4786 synchronized (TEMP_RECTF) { 4787 /* 4788 * The reason for this concern about the thickness of the 4789 * cursor and doing the floor/ceil on the coordinates is that 4790 * some EditTexts (notably textfields in the Browser) have 4791 * anti-aliased text where not all the characters are 4792 * necessarily at integer-multiple locations. This should 4793 * make sure the entire cursor gets invalidated instead of 4794 * sometimes missing half a pixel. 4795 */ 4796 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 4797 if (thick < 1.0f) { 4798 thick = 1.0f; 4799 } 4800 4801 thick /= 2.0f; 4802 4803 // mHighlightPath is guaranteed to be non null at that point. 4804 mHighlightPath.computeBounds(TEMP_RECTF, false); 4805 4806 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 4807 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 4808 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 4809 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 4810 } 4811 } else { 4812 for (int i = 0; i < mEditor.mCursorCount; i++) { 4813 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4814 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 4815 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 4816 } 4817 } 4818 } 4819 } 4820 4821 void invalidateCursor() { 4822 int where = getSelectionEnd(); 4823 4824 invalidateCursor(where, where, where); 4825 } 4826 4827 private void invalidateCursor(int a, int b, int c) { 4828 if (a >= 0 || b >= 0 || c >= 0) { 4829 int start = Math.min(Math.min(a, b), c); 4830 int end = Math.max(Math.max(a, b), c); 4831 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 4832 } 4833 } 4834 4835 /** 4836 * Invalidates the region of text enclosed between the start and end text offsets. 4837 */ 4838 void invalidateRegion(int start, int end, boolean invalidateCursor) { 4839 if (mLayout == null) { 4840 invalidate(); 4841 } else { 4842 int lineStart = mLayout.getLineForOffset(start); 4843 int top = mLayout.getLineTop(lineStart); 4844 4845 // This is ridiculous, but the descent from the line above 4846 // can hang down into the line we really want to redraw, 4847 // so we have to invalidate part of the line above to make 4848 // sure everything that needs to be redrawn really is. 4849 // (But not the whole line above, because that would cause 4850 // the same problem with the descenders on the line above it!) 4851 if (lineStart > 0) { 4852 top -= mLayout.getLineDescent(lineStart - 1); 4853 } 4854 4855 int lineEnd; 4856 4857 if (start == end) 4858 lineEnd = lineStart; 4859 else 4860 lineEnd = mLayout.getLineForOffset(end); 4861 4862 int bottom = mLayout.getLineBottom(lineEnd); 4863 4864 // mEditor can be null in case selection is set programmatically. 4865 if (invalidateCursor && mEditor != null) { 4866 for (int i = 0; i < mEditor.mCursorCount; i++) { 4867 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4868 top = Math.min(top, bounds.top); 4869 bottom = Math.max(bottom, bounds.bottom); 4870 } 4871 } 4872 4873 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4874 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4875 4876 int left, right; 4877 if (lineStart == lineEnd && !invalidateCursor) { 4878 left = (int) mLayout.getPrimaryHorizontal(start); 4879 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 4880 left += compoundPaddingLeft; 4881 right += compoundPaddingLeft; 4882 } else { 4883 // Rectangle bounding box when the region spans several lines 4884 left = compoundPaddingLeft; 4885 right = getWidth() - getCompoundPaddingRight(); 4886 } 4887 4888 invalidate(mScrollX + left, verticalPadding + top, 4889 mScrollX + right, verticalPadding + bottom); 4890 } 4891 } 4892 4893 private void registerForPreDraw() { 4894 if (!mPreDrawRegistered) { 4895 getViewTreeObserver().addOnPreDrawListener(this); 4896 mPreDrawRegistered = true; 4897 } 4898 } 4899 4900 private void unregisterForPreDraw() { 4901 getViewTreeObserver().removeOnPreDrawListener(this); 4902 mPreDrawRegistered = false; 4903 mPreDrawListenerDetached = false; 4904 } 4905 4906 /** 4907 * {@inheritDoc} 4908 */ 4909 public boolean onPreDraw() { 4910 if (mLayout == null) { 4911 assumeLayout(); 4912 } 4913 4914 if (mMovement != null) { 4915 /* This code also provides auto-scrolling when a cursor is moved using a 4916 * CursorController (insertion point or selection limits). 4917 * For selection, ensure start or end is visible depending on controller's state. 4918 */ 4919 int curs = getSelectionEnd(); 4920 // Do not create the controller if it is not already created. 4921 if (mEditor != null && mEditor.mSelectionModifierCursorController != null && 4922 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 4923 curs = getSelectionStart(); 4924 } 4925 4926 /* 4927 * TODO: This should really only keep the end in view if 4928 * it already was before the text changed. I'm not sure 4929 * of a good way to tell from here if it was. 4930 */ 4931 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4932 curs = mText.length(); 4933 } 4934 4935 if (curs >= 0) { 4936 bringPointIntoView(curs); 4937 } 4938 } else { 4939 bringTextIntoView(); 4940 } 4941 4942 // This has to be checked here since: 4943 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 4944 // a screen rotation) since layout is not yet initialized at that point. 4945 if (mEditor != null && mEditor.mCreatedWithASelection) { 4946 mEditor.startSelectionActionMode(); 4947 mEditor.mCreatedWithASelection = false; 4948 } 4949 4950 // Phone specific code (there is no ExtractEditText on tablets). 4951 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can 4952 // not be set. Do the test here instead. 4953 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { 4954 mEditor.startSelectionActionMode(); 4955 } 4956 4957 unregisterForPreDraw(); 4958 4959 return true; 4960 } 4961 4962 @Override 4963 protected void onAttachedToWindow() { 4964 super.onAttachedToWindow(); 4965 4966 mTemporaryDetach = false; 4967 4968 if (mEditor != null) mEditor.onAttachedToWindow(); 4969 4970 if (mPreDrawListenerDetached) { 4971 getViewTreeObserver().addOnPreDrawListener(this); 4972 mPreDrawListenerDetached = false; 4973 } 4974 } 4975 4976 /** @hide */ 4977 @Override 4978 protected void onDetachedFromWindowInternal() { 4979 if (mPreDrawRegistered) { 4980 getViewTreeObserver().removeOnPreDrawListener(this); 4981 mPreDrawListenerDetached = true; 4982 } 4983 4984 resetResolvedDrawables(); 4985 4986 if (mEditor != null) mEditor.onDetachedFromWindow(); 4987 4988 super.onDetachedFromWindowInternal(); 4989 } 4990 4991 @Override 4992 public void onScreenStateChanged(int screenState) { 4993 super.onScreenStateChanged(screenState); 4994 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 4995 } 4996 4997 @Override 4998 protected boolean isPaddingOffsetRequired() { 4999 return mShadowRadius != 0 || mDrawables != null; 5000 } 5001 5002 @Override 5003 protected int getLeftPaddingOffset() { 5004 return getCompoundPaddingLeft() - mPaddingLeft + 5005 (int) Math.min(0, mShadowDx - mShadowRadius); 5006 } 5007 5008 @Override 5009 protected int getTopPaddingOffset() { 5010 return (int) Math.min(0, mShadowDy - mShadowRadius); 5011 } 5012 5013 @Override 5014 protected int getBottomPaddingOffset() { 5015 return (int) Math.max(0, mShadowDy + mShadowRadius); 5016 } 5017 5018 @Override 5019 protected int getRightPaddingOffset() { 5020 return -(getCompoundPaddingRight() - mPaddingRight) + 5021 (int) Math.max(0, mShadowDx + mShadowRadius); 5022 } 5023 5024 @Override 5025 protected boolean verifyDrawable(Drawable who) { 5026 final boolean verified = super.verifyDrawable(who); 5027 if (!verified && mDrawables != null) { 5028 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop || 5029 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom || 5030 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd; 5031 } 5032 return verified; 5033 } 5034 5035 @Override 5036 public void jumpDrawablesToCurrentState() { 5037 super.jumpDrawablesToCurrentState(); 5038 if (mDrawables != null) { 5039 if (mDrawables.mDrawableLeft != null) { 5040 mDrawables.mDrawableLeft.jumpToCurrentState(); 5041 } 5042 if (mDrawables.mDrawableTop != null) { 5043 mDrawables.mDrawableTop.jumpToCurrentState(); 5044 } 5045 if (mDrawables.mDrawableRight != null) { 5046 mDrawables.mDrawableRight.jumpToCurrentState(); 5047 } 5048 if (mDrawables.mDrawableBottom != null) { 5049 mDrawables.mDrawableBottom.jumpToCurrentState(); 5050 } 5051 if (mDrawables.mDrawableStart != null) { 5052 mDrawables.mDrawableStart.jumpToCurrentState(); 5053 } 5054 if (mDrawables.mDrawableEnd != null) { 5055 mDrawables.mDrawableEnd.jumpToCurrentState(); 5056 } 5057 } 5058 } 5059 5060 @Override 5061 public void invalidateDrawable(Drawable drawable) { 5062 boolean handled = false; 5063 5064 if (verifyDrawable(drawable)) { 5065 final Rect dirty = drawable.getBounds(); 5066 int scrollX = mScrollX; 5067 int scrollY = mScrollY; 5068 5069 // IMPORTANT: The coordinates below are based on the coordinates computed 5070 // for each compound drawable in onDraw(). Make sure to update each section 5071 // accordingly. 5072 final TextView.Drawables drawables = mDrawables; 5073 if (drawables != null) { 5074 if (drawable == drawables.mDrawableLeft) { 5075 final int compoundPaddingTop = getCompoundPaddingTop(); 5076 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5077 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5078 5079 scrollX += mPaddingLeft; 5080 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 5081 handled = true; 5082 } else if (drawable == drawables.mDrawableRight) { 5083 final int compoundPaddingTop = getCompoundPaddingTop(); 5084 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5085 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5086 5087 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 5088 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 5089 handled = true; 5090 } else if (drawable == drawables.mDrawableTop) { 5091 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5092 final int compoundPaddingRight = getCompoundPaddingRight(); 5093 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5094 5095 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 5096 scrollY += mPaddingTop; 5097 handled = true; 5098 } else if (drawable == drawables.mDrawableBottom) { 5099 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5100 final int compoundPaddingRight = getCompoundPaddingRight(); 5101 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5102 5103 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 5104 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 5105 handled = true; 5106 } 5107 } 5108 5109 if (handled) { 5110 invalidate(dirty.left + scrollX, dirty.top + scrollY, 5111 dirty.right + scrollX, dirty.bottom + scrollY); 5112 } 5113 } 5114 5115 if (!handled) { 5116 super.invalidateDrawable(drawable); 5117 } 5118 } 5119 5120 @Override 5121 public boolean hasOverlappingRendering() { 5122 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 5123 return ((getBackground() != null && getBackground().getCurrent() != null) 5124 || mText instanceof Spannable || hasSelection() 5125 || isHorizontalFadingEdgeEnabled()); 5126 } 5127 5128 /** 5129 * 5130 * Returns the state of the {@code textIsSelectable} flag (See 5131 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 5132 * to allow users to select and copy text in a non-editable TextView, the content of an 5133 * {@link EditText} can always be selected, independently of the value of this flag. 5134 * <p> 5135 * 5136 * @return True if the text displayed in this TextView can be selected by the user. 5137 * 5138 * @attr ref android.R.styleable#TextView_textIsSelectable 5139 */ 5140 public boolean isTextSelectable() { 5141 return mEditor == null ? false : mEditor.mTextIsSelectable; 5142 } 5143 5144 /** 5145 * Sets whether the content of this view is selectable by the user. The default is 5146 * {@code false}, meaning that the content is not selectable. 5147 * <p> 5148 * When you use a TextView to display a useful piece of information to the user (such as a 5149 * contact's address), make it selectable, so that the user can select and copy its 5150 * content. You can also use set the XML attribute 5151 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 5152 * <p> 5153 * When you call this method to set the value of {@code textIsSelectable}, it sets 5154 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 5155 * and {@code longClickable} to the same value. These flags correspond to the attributes 5156 * {@link android.R.styleable#View_focusable android:focusable}, 5157 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 5158 * {@link android.R.styleable#View_clickable android:clickable}, and 5159 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 5160 * flags to a state you had set previously, call one or more of the following methods: 5161 * {@link #setFocusable(boolean) setFocusable()}, 5162 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 5163 * {@link #setClickable(boolean) setClickable()} or 5164 * {@link #setLongClickable(boolean) setLongClickable()}. 5165 * 5166 * @param selectable Whether the content of this TextView should be selectable. 5167 */ 5168 public void setTextIsSelectable(boolean selectable) { 5169 if (!selectable && mEditor == null) return; // false is default value with no edit data 5170 5171 createEditorIfNeeded(); 5172 if (mEditor.mTextIsSelectable == selectable) return; 5173 5174 mEditor.mTextIsSelectable = selectable; 5175 setFocusableInTouchMode(selectable); 5176 setFocusable(selectable); 5177 setClickable(selectable); 5178 setLongClickable(selectable); 5179 5180 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 5181 5182 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 5183 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 5184 5185 // Called by setText above, but safer in case of future code changes 5186 mEditor.prepareCursorControllers(); 5187 } 5188 5189 @Override 5190 protected int[] onCreateDrawableState(int extraSpace) { 5191 final int[] drawableState; 5192 5193 if (mSingleLine) { 5194 drawableState = super.onCreateDrawableState(extraSpace); 5195 } else { 5196 drawableState = super.onCreateDrawableState(extraSpace + 1); 5197 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 5198 } 5199 5200 if (isTextSelectable()) { 5201 // Disable pressed state, which was introduced when TextView was made clickable. 5202 // Prevents text color change. 5203 // setClickable(false) would have a similar effect, but it also disables focus changes 5204 // and long press actions, which are both needed by text selection. 5205 final int length = drawableState.length; 5206 for (int i = 0; i < length; i++) { 5207 if (drawableState[i] == R.attr.state_pressed) { 5208 final int[] nonPressedState = new int[length - 1]; 5209 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 5210 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 5211 return nonPressedState; 5212 } 5213 } 5214 } 5215 5216 return drawableState; 5217 } 5218 5219 private Path getUpdatedHighlightPath() { 5220 Path highlight = null; 5221 Paint highlightPaint = mHighlightPaint; 5222 5223 final int selStart = getSelectionStart(); 5224 final int selEnd = getSelectionEnd(); 5225 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 5226 if (selStart == selEnd) { 5227 if (mEditor != null && mEditor.isCursorVisible() && 5228 (SystemClock.uptimeMillis() - mEditor.mShowCursor) % 5229 (2 * Editor.BLINK) < Editor.BLINK) { 5230 if (mHighlightPathBogus) { 5231 if (mHighlightPath == null) mHighlightPath = new Path(); 5232 mHighlightPath.reset(); 5233 mLayout.getCursorPath(selStart, mHighlightPath, mText); 5234 mEditor.updateCursorsPositions(); 5235 mHighlightPathBogus = false; 5236 } 5237 5238 // XXX should pass to skin instead of drawing directly 5239 highlightPaint.setColor(mCurTextColor); 5240 highlightPaint.setStyle(Paint.Style.STROKE); 5241 highlight = mHighlightPath; 5242 } 5243 } else { 5244 if (mHighlightPathBogus) { 5245 if (mHighlightPath == null) mHighlightPath = new Path(); 5246 mHighlightPath.reset(); 5247 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5248 mHighlightPathBogus = false; 5249 } 5250 5251 // XXX should pass to skin instead of drawing directly 5252 highlightPaint.setColor(mHighlightColor); 5253 highlightPaint.setStyle(Paint.Style.FILL); 5254 5255 highlight = mHighlightPath; 5256 } 5257 } 5258 return highlight; 5259 } 5260 5261 /** 5262 * @hide 5263 */ 5264 public int getHorizontalOffsetForDrawables() { 5265 return 0; 5266 } 5267 5268 @Override 5269 protected void onDraw(Canvas canvas) { 5270 restartMarqueeIfNeeded(); 5271 5272 // Draw the background for this view 5273 super.onDraw(canvas); 5274 5275 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5276 final int compoundPaddingTop = getCompoundPaddingTop(); 5277 final int compoundPaddingRight = getCompoundPaddingRight(); 5278 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5279 final int scrollX = mScrollX; 5280 final int scrollY = mScrollY; 5281 final int right = mRight; 5282 final int left = mLeft; 5283 final int bottom = mBottom; 5284 final int top = mTop; 5285 final boolean isLayoutRtl = isLayoutRtl(); 5286 final int offset = getHorizontalOffsetForDrawables(); 5287 final int leftOffset = isLayoutRtl ? 0 : offset; 5288 final int rightOffset = isLayoutRtl ? offset : 0 ; 5289 5290 final Drawables dr = mDrawables; 5291 if (dr != null) { 5292 /* 5293 * Compound, not extended, because the icon is not clipped 5294 * if the text height is smaller. 5295 */ 5296 5297 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 5298 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 5299 5300 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5301 // Make sure to update invalidateDrawable() when changing this code. 5302 if (dr.mDrawableLeft != null) { 5303 canvas.save(); 5304 canvas.translate(scrollX + mPaddingLeft + leftOffset, 5305 scrollY + compoundPaddingTop + 5306 (vspace - dr.mDrawableHeightLeft) / 2); 5307 dr.mDrawableLeft.draw(canvas); 5308 canvas.restore(); 5309 } 5310 5311 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5312 // Make sure to update invalidateDrawable() when changing this code. 5313 if (dr.mDrawableRight != null) { 5314 canvas.save(); 5315 canvas.translate(scrollX + right - left - mPaddingRight 5316 - dr.mDrawableSizeRight - rightOffset, 5317 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 5318 dr.mDrawableRight.draw(canvas); 5319 canvas.restore(); 5320 } 5321 5322 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5323 // Make sure to update invalidateDrawable() when changing this code. 5324 if (dr.mDrawableTop != null) { 5325 canvas.save(); 5326 canvas.translate(scrollX + compoundPaddingLeft + 5327 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 5328 dr.mDrawableTop.draw(canvas); 5329 canvas.restore(); 5330 } 5331 5332 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5333 // Make sure to update invalidateDrawable() when changing this code. 5334 if (dr.mDrawableBottom != null) { 5335 canvas.save(); 5336 canvas.translate(scrollX + compoundPaddingLeft + 5337 (hspace - dr.mDrawableWidthBottom) / 2, 5338 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 5339 dr.mDrawableBottom.draw(canvas); 5340 canvas.restore(); 5341 } 5342 } 5343 5344 int color = mCurTextColor; 5345 5346 if (mLayout == null) { 5347 assumeLayout(); 5348 } 5349 5350 Layout layout = mLayout; 5351 5352 if (mHint != null && mText.length() == 0) { 5353 if (mHintTextColor != null) { 5354 color = mCurHintTextColor; 5355 } 5356 5357 layout = mHintLayout; 5358 } 5359 5360 mTextPaint.setColor(color); 5361 mTextPaint.drawableState = getDrawableState(); 5362 5363 canvas.save(); 5364 /* Would be faster if we didn't have to do this. Can we chop the 5365 (displayable) text so that we don't need to do this ever? 5366 */ 5367 5368 int extendedPaddingTop = getExtendedPaddingTop(); 5369 int extendedPaddingBottom = getExtendedPaddingBottom(); 5370 5371 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5372 final int maxScrollY = mLayout.getHeight() - vspace; 5373 5374 // Add sufficient space for cursor and tone marks 5375 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors 5376 int fudgedPaddingRight = Math.max(0, compoundPaddingRight - (cursorWidth - 1)); 5377 5378 float clipLeft = compoundPaddingLeft + scrollX; 5379 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 5380 float clipRight = right - left - fudgedPaddingRight + scrollX; 5381 float clipBottom = bottom - top + scrollY - 5382 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 5383 5384 if (mShadowRadius != 0) { 5385 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 5386 clipRight += Math.max(0, mShadowDx + mShadowRadius); 5387 5388 clipTop += Math.min(0, mShadowDy - mShadowRadius); 5389 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 5390 } 5391 5392 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 5393 5394 int voffsetText = 0; 5395 int voffsetCursor = 0; 5396 5397 // translate in by our padding 5398 /* shortcircuit calling getVerticaOffset() */ 5399 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5400 voffsetText = getVerticalOffset(false); 5401 voffsetCursor = getVerticalOffset(true); 5402 } 5403 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 5404 5405 final int layoutDirection = getLayoutDirection(); 5406 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 5407 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 5408 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 5409 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 5410 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 5411 final int width = mRight - mLeft; 5412 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 5413 final float dx = mLayout.getLineRight(0) - (width - padding); 5414 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5415 } 5416 5417 if (mMarquee != null && mMarquee.isRunning()) { 5418 final float dx = -mMarquee.getScroll(); 5419 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5420 } 5421 } 5422 5423 final int cursorOffsetVertical = voffsetCursor - voffsetText; 5424 5425 Path highlight = getUpdatedHighlightPath(); 5426 if (mEditor != null) { 5427 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 5428 } else { 5429 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5430 } 5431 5432 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 5433 final float dx = mMarquee.getGhostOffset(); 5434 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5435 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5436 } 5437 5438 canvas.restore(); 5439 } 5440 5441 @Override 5442 public void getFocusedRect(Rect r) { 5443 if (mLayout == null) { 5444 super.getFocusedRect(r); 5445 return; 5446 } 5447 5448 int selEnd = getSelectionEnd(); 5449 if (selEnd < 0) { 5450 super.getFocusedRect(r); 5451 return; 5452 } 5453 5454 int selStart = getSelectionStart(); 5455 if (selStart < 0 || selStart >= selEnd) { 5456 int line = mLayout.getLineForOffset(selEnd); 5457 r.top = mLayout.getLineTop(line); 5458 r.bottom = mLayout.getLineBottom(line); 5459 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 5460 r.right = r.left + 4; 5461 } else { 5462 int lineStart = mLayout.getLineForOffset(selStart); 5463 int lineEnd = mLayout.getLineForOffset(selEnd); 5464 r.top = mLayout.getLineTop(lineStart); 5465 r.bottom = mLayout.getLineBottom(lineEnd); 5466 if (lineStart == lineEnd) { 5467 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 5468 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 5469 } else { 5470 // Selection extends across multiple lines -- make the focused 5471 // rect cover the entire width. 5472 if (mHighlightPathBogus) { 5473 if (mHighlightPath == null) mHighlightPath = new Path(); 5474 mHighlightPath.reset(); 5475 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5476 mHighlightPathBogus = false; 5477 } 5478 synchronized (TEMP_RECTF) { 5479 mHighlightPath.computeBounds(TEMP_RECTF, true); 5480 r.left = (int)TEMP_RECTF.left-1; 5481 r.right = (int)TEMP_RECTF.right+1; 5482 } 5483 } 5484 } 5485 5486 // Adjust for padding and gravity. 5487 int paddingLeft = getCompoundPaddingLeft(); 5488 int paddingTop = getExtendedPaddingTop(); 5489 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5490 paddingTop += getVerticalOffset(false); 5491 } 5492 r.offset(paddingLeft, paddingTop); 5493 int paddingBottom = getExtendedPaddingBottom(); 5494 r.bottom += paddingBottom; 5495 } 5496 5497 /** 5498 * Return the number of lines of text, or 0 if the internal Layout has not 5499 * been built. 5500 */ 5501 public int getLineCount() { 5502 return mLayout != null ? mLayout.getLineCount() : 0; 5503 } 5504 5505 /** 5506 * Return the baseline for the specified line (0...getLineCount() - 1) 5507 * If bounds is not null, return the top, left, right, bottom extents 5508 * of the specified line in it. If the internal Layout has not been built, 5509 * return 0 and set bounds to (0, 0, 0, 0) 5510 * @param line which line to examine (0..getLineCount() - 1) 5511 * @param bounds Optional. If not null, it returns the extent of the line 5512 * @return the Y-coordinate of the baseline 5513 */ 5514 public int getLineBounds(int line, Rect bounds) { 5515 if (mLayout == null) { 5516 if (bounds != null) { 5517 bounds.set(0, 0, 0, 0); 5518 } 5519 return 0; 5520 } 5521 else { 5522 int baseline = mLayout.getLineBounds(line, bounds); 5523 5524 int voffset = getExtendedPaddingTop(); 5525 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5526 voffset += getVerticalOffset(true); 5527 } 5528 if (bounds != null) { 5529 bounds.offset(getCompoundPaddingLeft(), voffset); 5530 } 5531 return baseline + voffset; 5532 } 5533 } 5534 5535 @Override 5536 public int getBaseline() { 5537 if (mLayout == null) { 5538 return super.getBaseline(); 5539 } 5540 5541 int voffset = 0; 5542 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5543 voffset = getVerticalOffset(true); 5544 } 5545 5546 if (isLayoutModeOptical(mParent)) { 5547 voffset -= getOpticalInsets().top; 5548 } 5549 5550 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 5551 } 5552 5553 /** 5554 * @hide 5555 */ 5556 @Override 5557 protected int getFadeTop(boolean offsetRequired) { 5558 if (mLayout == null) return 0; 5559 5560 int voffset = 0; 5561 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5562 voffset = getVerticalOffset(true); 5563 } 5564 5565 if (offsetRequired) voffset += getTopPaddingOffset(); 5566 5567 return getExtendedPaddingTop() + voffset; 5568 } 5569 5570 /** 5571 * @hide 5572 */ 5573 @Override 5574 protected int getFadeHeight(boolean offsetRequired) { 5575 return mLayout != null ? mLayout.getHeight() : 0; 5576 } 5577 5578 @Override 5579 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 5580 if (keyCode == KeyEvent.KEYCODE_BACK) { 5581 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null; 5582 5583 if (isInSelectionMode) { 5584 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 5585 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5586 if (state != null) { 5587 state.startTracking(event, this); 5588 } 5589 return true; 5590 } else if (event.getAction() == KeyEvent.ACTION_UP) { 5591 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5592 if (state != null) { 5593 state.handleUpEvent(event); 5594 } 5595 if (event.isTracking() && !event.isCanceled()) { 5596 stopSelectionActionMode(); 5597 return true; 5598 } 5599 } 5600 } 5601 } 5602 return super.onKeyPreIme(keyCode, event); 5603 } 5604 5605 @Override 5606 public boolean onKeyDown(int keyCode, KeyEvent event) { 5607 int which = doKeyDown(keyCode, event, null); 5608 if (which == 0) { 5609 return super.onKeyDown(keyCode, event); 5610 } 5611 5612 return true; 5613 } 5614 5615 @Override 5616 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 5617 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 5618 5619 int which = doKeyDown(keyCode, down, event); 5620 if (which == 0) { 5621 // Go through default dispatching. 5622 return super.onKeyMultiple(keyCode, repeatCount, event); 5623 } 5624 if (which == -1) { 5625 // Consumed the whole thing. 5626 return true; 5627 } 5628 5629 repeatCount--; 5630 5631 // We are going to dispatch the remaining events to either the input 5632 // or movement method. To do this, we will just send a repeated stream 5633 // of down and up events until we have done the complete repeatCount. 5634 // It would be nice if those interfaces had an onKeyMultiple() method, 5635 // but adding that is a more complicated change. 5636 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 5637 if (which == 1) { 5638 // mEditor and mEditor.mInput are not null from doKeyDown 5639 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5640 while (--repeatCount > 0) { 5641 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down); 5642 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5643 } 5644 hideErrorIfUnchanged(); 5645 5646 } else if (which == 2) { 5647 // mMovement is not null from doKeyDown 5648 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5649 while (--repeatCount > 0) { 5650 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 5651 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5652 } 5653 } 5654 5655 return true; 5656 } 5657 5658 /** 5659 * Returns true if pressing ENTER in this field advances focus instead 5660 * of inserting the character. This is true mostly in single-line fields, 5661 * but also in mail addresses and subjects which will display on multiple 5662 * lines but where it doesn't make sense to insert newlines. 5663 */ 5664 private boolean shouldAdvanceFocusOnEnter() { 5665 if (getKeyListener() == null) { 5666 return false; 5667 } 5668 5669 if (mSingleLine) { 5670 return true; 5671 } 5672 5673 if (mEditor != null && 5674 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5675 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5676 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 5677 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 5678 return true; 5679 } 5680 } 5681 5682 return false; 5683 } 5684 5685 /** 5686 * Returns true if pressing TAB in this field advances focus instead 5687 * of inserting the character. Insert tabs only in multi-line editors. 5688 */ 5689 private boolean shouldAdvanceFocusOnTab() { 5690 if (getKeyListener() != null && !mSingleLine && mEditor != null && 5691 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5692 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5693 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 5694 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 5695 return false; 5696 } 5697 } 5698 return true; 5699 } 5700 5701 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 5702 if (!isEnabled()) { 5703 return 0; 5704 } 5705 5706 // If this is the initial keydown, we don't want to prevent a movement away from this view. 5707 // While this shouldn't be necessary because any time we're preventing default movement we 5708 // should be restricting the focus to remain within this view, thus we'll also receive 5709 // the key up event, occasionally key up events will get dropped and we don't want to 5710 // prevent the user from traversing out of this on the next key down. 5711 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 5712 mPreventDefaultMovement = false; 5713 } 5714 5715 switch (keyCode) { 5716 case KeyEvent.KEYCODE_ENTER: 5717 if (event.hasNoModifiers()) { 5718 // When mInputContentType is set, we know that we are 5719 // running in a "modern" cupcake environment, so don't need 5720 // to worry about the application trying to capture 5721 // enter key events. 5722 if (mEditor != null && mEditor.mInputContentType != null) { 5723 // If there is an action listener, given them a 5724 // chance to consume the event. 5725 if (mEditor.mInputContentType.onEditorActionListener != null && 5726 mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5727 this, EditorInfo.IME_NULL, event)) { 5728 mEditor.mInputContentType.enterDown = true; 5729 // We are consuming the enter key for them. 5730 return -1; 5731 } 5732 } 5733 5734 // If our editor should move focus when enter is pressed, or 5735 // this is a generated event from an IME action button, then 5736 // don't let it be inserted into the text. 5737 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5738 || shouldAdvanceFocusOnEnter()) { 5739 if (hasOnClickListeners()) { 5740 return 0; 5741 } 5742 return -1; 5743 } 5744 } 5745 break; 5746 5747 case KeyEvent.KEYCODE_DPAD_CENTER: 5748 if (event.hasNoModifiers()) { 5749 if (shouldAdvanceFocusOnEnter()) { 5750 return 0; 5751 } 5752 } 5753 break; 5754 5755 case KeyEvent.KEYCODE_TAB: 5756 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 5757 if (shouldAdvanceFocusOnTab()) { 5758 return 0; 5759 } 5760 } 5761 break; 5762 5763 // Has to be done on key down (and not on key up) to correctly be intercepted. 5764 case KeyEvent.KEYCODE_BACK: 5765 if (mEditor != null && mEditor.mSelectionActionMode != null) { 5766 stopSelectionActionMode(); 5767 return -1; 5768 } 5769 break; 5770 } 5771 5772 if (mEditor != null && mEditor.mKeyListener != null) { 5773 boolean doDown = true; 5774 if (otherEvent != null) { 5775 try { 5776 beginBatchEdit(); 5777 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 5778 otherEvent); 5779 hideErrorIfUnchanged(); 5780 doDown = false; 5781 if (handled) { 5782 return -1; 5783 } 5784 } catch (AbstractMethodError e) { 5785 // onKeyOther was added after 1.0, so if it isn't 5786 // implemented we need to try to dispatch as a regular down. 5787 } finally { 5788 endBatchEdit(); 5789 } 5790 } 5791 5792 if (doDown) { 5793 beginBatchEdit(); 5794 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 5795 keyCode, event); 5796 endBatchEdit(); 5797 hideErrorIfUnchanged(); 5798 if (handled) return 1; 5799 } 5800 } 5801 5802 // bug 650865: sometimes we get a key event before a layout. 5803 // don't try to move around if we don't know the layout. 5804 5805 if (mMovement != null && mLayout != null) { 5806 boolean doDown = true; 5807 if (otherEvent != null) { 5808 try { 5809 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 5810 otherEvent); 5811 doDown = false; 5812 if (handled) { 5813 return -1; 5814 } 5815 } catch (AbstractMethodError e) { 5816 // onKeyOther was added after 1.0, so if it isn't 5817 // implemented we need to try to dispatch as a regular down. 5818 } 5819 } 5820 if (doDown) { 5821 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) { 5822 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 5823 mPreventDefaultMovement = true; 5824 } 5825 return 2; 5826 } 5827 } 5828 } 5829 5830 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0; 5831 } 5832 5833 /** 5834 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 5835 * can be recorded. 5836 * @hide 5837 */ 5838 public void resetErrorChangedFlag() { 5839 /* 5840 * Keep track of what the error was before doing the input 5841 * so that if an input filter changed the error, we leave 5842 * that error showing. Otherwise, we take down whatever 5843 * error was showing when the user types something. 5844 */ 5845 if (mEditor != null) mEditor.mErrorWasChanged = false; 5846 } 5847 5848 /** 5849 * @hide 5850 */ 5851 public void hideErrorIfUnchanged() { 5852 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 5853 setError(null, null); 5854 } 5855 } 5856 5857 @Override 5858 public boolean onKeyUp(int keyCode, KeyEvent event) { 5859 if (!isEnabled()) { 5860 return super.onKeyUp(keyCode, event); 5861 } 5862 5863 if (!KeyEvent.isModifierKey(keyCode)) { 5864 mPreventDefaultMovement = false; 5865 } 5866 5867 switch (keyCode) { 5868 case KeyEvent.KEYCODE_DPAD_CENTER: 5869 if (event.hasNoModifiers()) { 5870 /* 5871 * If there is a click listener, just call through to 5872 * super, which will invoke it. 5873 * 5874 * If there isn't a click listener, try to show the soft 5875 * input method. (It will also 5876 * call performClick(), but that won't do anything in 5877 * this case.) 5878 */ 5879 if (!hasOnClickListeners()) { 5880 if (mMovement != null && mText instanceof Editable 5881 && mLayout != null && onCheckIsTextEditor()) { 5882 InputMethodManager imm = InputMethodManager.peekInstance(); 5883 viewClicked(imm); 5884 if (imm != null && getShowSoftInputOnFocus()) { 5885 imm.showSoftInput(this, 0); 5886 } 5887 } 5888 } 5889 } 5890 return super.onKeyUp(keyCode, event); 5891 5892 case KeyEvent.KEYCODE_ENTER: 5893 if (event.hasNoModifiers()) { 5894 if (mEditor != null && mEditor.mInputContentType != null 5895 && mEditor.mInputContentType.onEditorActionListener != null 5896 && mEditor.mInputContentType.enterDown) { 5897 mEditor.mInputContentType.enterDown = false; 5898 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5899 this, EditorInfo.IME_NULL, event)) { 5900 return true; 5901 } 5902 } 5903 5904 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5905 || shouldAdvanceFocusOnEnter()) { 5906 /* 5907 * If there is a click listener, just call through to 5908 * super, which will invoke it. 5909 * 5910 * If there isn't a click listener, try to advance focus, 5911 * but still call through to super, which will reset the 5912 * pressed state and longpress state. (It will also 5913 * call performClick(), but that won't do anything in 5914 * this case.) 5915 */ 5916 if (!hasOnClickListeners()) { 5917 View v = focusSearch(FOCUS_DOWN); 5918 5919 if (v != null) { 5920 if (!v.requestFocus(FOCUS_DOWN)) { 5921 throw new IllegalStateException( 5922 "focus search returned a view " + 5923 "that wasn't able to take focus!"); 5924 } 5925 5926 /* 5927 * Return true because we handled the key; super 5928 * will return false because there was no click 5929 * listener. 5930 */ 5931 super.onKeyUp(keyCode, event); 5932 return true; 5933 } else if ((event.getFlags() 5934 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 5935 // No target for next focus, but make sure the IME 5936 // if this came from it. 5937 InputMethodManager imm = InputMethodManager.peekInstance(); 5938 if (imm != null && imm.isActive(this)) { 5939 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5940 } 5941 } 5942 } 5943 } 5944 return super.onKeyUp(keyCode, event); 5945 } 5946 break; 5947 } 5948 5949 if (mEditor != null && mEditor.mKeyListener != null) 5950 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) 5951 return true; 5952 5953 if (mMovement != null && mLayout != null) 5954 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 5955 return true; 5956 5957 return super.onKeyUp(keyCode, event); 5958 } 5959 5960 @Override 5961 public boolean onCheckIsTextEditor() { 5962 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 5963 } 5964 5965 @Override 5966 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5967 if (onCheckIsTextEditor() && isEnabled()) { 5968 mEditor.createInputMethodStateIfNeeded(); 5969 outAttrs.inputType = getInputType(); 5970 if (mEditor.mInputContentType != null) { 5971 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 5972 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 5973 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 5974 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 5975 outAttrs.extras = mEditor.mInputContentType.extras; 5976 } else { 5977 outAttrs.imeOptions = EditorInfo.IME_NULL; 5978 } 5979 if (focusSearch(FOCUS_DOWN) != null) { 5980 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 5981 } 5982 if (focusSearch(FOCUS_UP) != null) { 5983 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 5984 } 5985 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) 5986 == EditorInfo.IME_ACTION_UNSPECIFIED) { 5987 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 5988 // An action has not been set, but the enter key will move to 5989 // the next focus, so set the action to that. 5990 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 5991 } else { 5992 // An action has not been set, and there is no focus to move 5993 // to, so let's just supply a "done" action. 5994 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 5995 } 5996 if (!shouldAdvanceFocusOnEnter()) { 5997 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 5998 } 5999 } 6000 if (isMultilineInputType(outAttrs.inputType)) { 6001 // Multi-line text editors should always show an enter key. 6002 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 6003 } 6004 outAttrs.hintText = mHint; 6005 if (mText instanceof Editable) { 6006 InputConnection ic = new EditableInputConnection(this); 6007 outAttrs.initialSelStart = getSelectionStart(); 6008 outAttrs.initialSelEnd = getSelectionEnd(); 6009 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 6010 return ic; 6011 } 6012 } 6013 return null; 6014 } 6015 6016 /** 6017 * If this TextView contains editable content, extract a portion of it 6018 * based on the information in <var>request</var> in to <var>outText</var>. 6019 * @return Returns true if the text was successfully extracted, else false. 6020 */ 6021 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 6022 createEditorIfNeeded(); 6023 return mEditor.extractText(request, outText); 6024 } 6025 6026 /** 6027 * This is used to remove all style-impacting spans from text before new 6028 * extracted text is being replaced into it, so that we don't have any 6029 * lingering spans applied during the replace. 6030 */ 6031 static void removeParcelableSpans(Spannable spannable, int start, int end) { 6032 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 6033 int i = spans.length; 6034 while (i > 0) { 6035 i--; 6036 spannable.removeSpan(spans[i]); 6037 } 6038 } 6039 6040 /** 6041 * Apply to this text view the given extracted text, as previously 6042 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 6043 */ 6044 public void setExtractedText(ExtractedText text) { 6045 Editable content = getEditableText(); 6046 if (text.text != null) { 6047 if (content == null) { 6048 setText(text.text, TextView.BufferType.EDITABLE); 6049 } else if (text.partialStartOffset < 0) { 6050 removeParcelableSpans(content, 0, content.length()); 6051 content.replace(0, content.length(), text.text); 6052 } else { 6053 final int N = content.length(); 6054 int start = text.partialStartOffset; 6055 if (start > N) start = N; 6056 int end = text.partialEndOffset; 6057 if (end > N) end = N; 6058 removeParcelableSpans(content, start, end); 6059 content.replace(start, end, text.text); 6060 } 6061 } 6062 6063 // Now set the selection position... make sure it is in range, to 6064 // avoid crashes. If this is a partial update, it is possible that 6065 // the underlying text may have changed, causing us problems here. 6066 // Also we just don't want to trust clients to do the right thing. 6067 Spannable sp = (Spannable)getText(); 6068 final int N = sp.length(); 6069 int start = text.selectionStart; 6070 if (start < 0) start = 0; 6071 else if (start > N) start = N; 6072 int end = text.selectionEnd; 6073 if (end < 0) end = 0; 6074 else if (end > N) end = N; 6075 Selection.setSelection(sp, start, end); 6076 6077 // Finally, update the selection mode. 6078 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { 6079 MetaKeyKeyListener.startSelecting(this, sp); 6080 } else { 6081 MetaKeyKeyListener.stopSelecting(this, sp); 6082 } 6083 } 6084 6085 /** 6086 * @hide 6087 */ 6088 public void setExtracting(ExtractedTextRequest req) { 6089 if (mEditor.mInputMethodState != null) { 6090 mEditor.mInputMethodState.mExtractedTextRequest = req; 6091 } 6092 // This would stop a possible selection mode, but no such mode is started in case 6093 // extracted mode will start. Some text is selected though, and will trigger an action mode 6094 // in the extracted view. 6095 mEditor.hideControllers(); 6096 } 6097 6098 /** 6099 * Called by the framework in response to a text completion from 6100 * the current input method, provided by it calling 6101 * {@link InputConnection#commitCompletion 6102 * InputConnection.commitCompletion()}. The default implementation does 6103 * nothing; text views that are supporting auto-completion should override 6104 * this to do their desired behavior. 6105 * 6106 * @param text The auto complete text the user has selected. 6107 */ 6108 public void onCommitCompletion(CompletionInfo text) { 6109 // intentionally empty 6110 } 6111 6112 /** 6113 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 6114 * a dictionnary) from the current input method, provided by it calling 6115 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default 6116 * implementation flashes the background of the corrected word to provide feedback to the user. 6117 * 6118 * @param info The auto correct info about the text that was corrected. 6119 */ 6120 public void onCommitCorrection(CorrectionInfo info) { 6121 if (mEditor != null) mEditor.onCommitCorrection(info); 6122 } 6123 6124 public void beginBatchEdit() { 6125 if (mEditor != null) mEditor.beginBatchEdit(); 6126 } 6127 6128 public void endBatchEdit() { 6129 if (mEditor != null) mEditor.endBatchEdit(); 6130 } 6131 6132 /** 6133 * Called by the framework in response to a request to begin a batch 6134 * of edit operations through a call to link {@link #beginBatchEdit()}. 6135 */ 6136 public void onBeginBatchEdit() { 6137 // intentionally empty 6138 } 6139 6140 /** 6141 * Called by the framework in response to a request to end a batch 6142 * of edit operations through a call to link {@link #endBatchEdit}. 6143 */ 6144 public void onEndBatchEdit() { 6145 // intentionally empty 6146 } 6147 6148 /** 6149 * Called by the framework in response to a private command from the 6150 * current method, provided by it calling 6151 * {@link InputConnection#performPrivateCommand 6152 * InputConnection.performPrivateCommand()}. 6153 * 6154 * @param action The action name of the command. 6155 * @param data Any additional data for the command. This may be null. 6156 * @return Return true if you handled the command, else false. 6157 */ 6158 public boolean onPrivateIMECommand(String action, Bundle data) { 6159 return false; 6160 } 6161 6162 private void nullLayouts() { 6163 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 6164 mSavedLayout = (BoringLayout) mLayout; 6165 } 6166 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 6167 mSavedHintLayout = (BoringLayout) mHintLayout; 6168 } 6169 6170 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 6171 6172 mBoring = mHintBoring = null; 6173 6174 // Since it depends on the value of mLayout 6175 if (mEditor != null) mEditor.prepareCursorControllers(); 6176 } 6177 6178 /** 6179 * Make a new Layout based on the already-measured size of the view, 6180 * on the assumption that it was measured correctly at some point. 6181 */ 6182 private void assumeLayout() { 6183 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6184 6185 if (width < 1) { 6186 width = 0; 6187 } 6188 6189 int physicalWidth = width; 6190 6191 if (mHorizontallyScrolling) { 6192 width = VERY_WIDE; 6193 } 6194 6195 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 6196 physicalWidth, false); 6197 } 6198 6199 private Layout.Alignment getLayoutAlignment() { 6200 Layout.Alignment alignment; 6201 switch (getTextAlignment()) { 6202 case TEXT_ALIGNMENT_GRAVITY: 6203 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 6204 case Gravity.START: 6205 alignment = Layout.Alignment.ALIGN_NORMAL; 6206 break; 6207 case Gravity.END: 6208 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6209 break; 6210 case Gravity.LEFT: 6211 alignment = Layout.Alignment.ALIGN_LEFT; 6212 break; 6213 case Gravity.RIGHT: 6214 alignment = Layout.Alignment.ALIGN_RIGHT; 6215 break; 6216 case Gravity.CENTER_HORIZONTAL: 6217 alignment = Layout.Alignment.ALIGN_CENTER; 6218 break; 6219 default: 6220 alignment = Layout.Alignment.ALIGN_NORMAL; 6221 break; 6222 } 6223 break; 6224 case TEXT_ALIGNMENT_TEXT_START: 6225 alignment = Layout.Alignment.ALIGN_NORMAL; 6226 break; 6227 case TEXT_ALIGNMENT_TEXT_END: 6228 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6229 break; 6230 case TEXT_ALIGNMENT_CENTER: 6231 alignment = Layout.Alignment.ALIGN_CENTER; 6232 break; 6233 case TEXT_ALIGNMENT_VIEW_START: 6234 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6235 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 6236 break; 6237 case TEXT_ALIGNMENT_VIEW_END: 6238 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6239 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 6240 break; 6241 case TEXT_ALIGNMENT_INHERIT: 6242 // This should never happen as we have already resolved the text alignment 6243 // but better safe than sorry so we just fall through 6244 default: 6245 alignment = Layout.Alignment.ALIGN_NORMAL; 6246 break; 6247 } 6248 return alignment; 6249 } 6250 6251 /** 6252 * The width passed in is now the desired layout width, 6253 * not the full view width with padding. 6254 * {@hide} 6255 */ 6256 protected void makeNewLayout(int wantWidth, int hintWidth, 6257 BoringLayout.Metrics boring, 6258 BoringLayout.Metrics hintBoring, 6259 int ellipsisWidth, boolean bringIntoView) { 6260 stopMarquee(); 6261 6262 // Update "old" cached values 6263 mOldMaximum = mMaximum; 6264 mOldMaxMode = mMaxMode; 6265 6266 mHighlightPathBogus = true; 6267 6268 if (wantWidth < 0) { 6269 wantWidth = 0; 6270 } 6271 if (hintWidth < 0) { 6272 hintWidth = 0; 6273 } 6274 6275 Layout.Alignment alignment = getLayoutAlignment(); 6276 final boolean testDirChange = mSingleLine && mLayout != null && 6277 (alignment == Layout.Alignment.ALIGN_NORMAL || 6278 alignment == Layout.Alignment.ALIGN_OPPOSITE); 6279 int oldDir = 0; 6280 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 6281 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 6282 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE && 6283 mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 6284 TruncateAt effectiveEllipsize = mEllipsize; 6285 if (mEllipsize == TruncateAt.MARQUEE && 6286 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 6287 effectiveEllipsize = TruncateAt.END_SMALL; 6288 } 6289 6290 if (mTextDir == null) { 6291 mTextDir = getTextDirectionHeuristic(); 6292 } 6293 6294 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 6295 effectiveEllipsize, effectiveEllipsize == mEllipsize); 6296 if (switchEllipsize) { 6297 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? 6298 TruncateAt.END : TruncateAt.MARQUEE; 6299 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 6300 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 6301 } 6302 6303 shouldEllipsize = mEllipsize != null; 6304 mHintLayout = null; 6305 6306 if (mHint != null) { 6307 if (shouldEllipsize) hintWidth = wantWidth; 6308 6309 if (hintBoring == UNKNOWN_BORING) { 6310 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 6311 mHintBoring); 6312 if (hintBoring != null) { 6313 mHintBoring = hintBoring; 6314 } 6315 } 6316 6317 if (hintBoring != null) { 6318 if (hintBoring.width <= hintWidth && 6319 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 6320 if (mSavedHintLayout != null) { 6321 mHintLayout = mSavedHintLayout. 6322 replaceOrMake(mHint, mTextPaint, 6323 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6324 hintBoring, mIncludePad); 6325 } else { 6326 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6327 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6328 hintBoring, mIncludePad); 6329 } 6330 6331 mSavedHintLayout = (BoringLayout) mHintLayout; 6332 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 6333 if (mSavedHintLayout != null) { 6334 mHintLayout = mSavedHintLayout. 6335 replaceOrMake(mHint, mTextPaint, 6336 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6337 hintBoring, mIncludePad, mEllipsize, 6338 ellipsisWidth); 6339 } else { 6340 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6341 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6342 hintBoring, mIncludePad, mEllipsize, 6343 ellipsisWidth); 6344 } 6345 } else if (shouldEllipsize) { 6346 mHintLayout = new StaticLayout(mHint, 6347 0, mHint.length(), 6348 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 6349 mSpacingAdd, mIncludePad, mEllipsize, 6350 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6351 } else { 6352 mHintLayout = new StaticLayout(mHint, mTextPaint, 6353 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6354 mIncludePad); 6355 } 6356 } else if (shouldEllipsize) { 6357 mHintLayout = new StaticLayout(mHint, 6358 0, mHint.length(), 6359 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 6360 mSpacingAdd, mIncludePad, mEllipsize, 6361 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6362 } else { 6363 mHintLayout = new StaticLayout(mHint, mTextPaint, 6364 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6365 mIncludePad); 6366 } 6367 } 6368 6369 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 6370 registerForPreDraw(); 6371 } 6372 6373 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6374 if (!compressText(ellipsisWidth)) { 6375 final int height = mLayoutParams.height; 6376 // If the size of the view does not depend on the size of the text, try to 6377 // start the marquee immediately 6378 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 6379 startMarquee(); 6380 } else { 6381 // Defer the start of the marquee until we know our width (see setFrame()) 6382 mRestartMarquee = true; 6383 } 6384 } 6385 } 6386 6387 // CursorControllers need a non-null mLayout 6388 if (mEditor != null) mEditor.prepareCursorControllers(); 6389 } 6390 6391 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 6392 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 6393 boolean useSaved) { 6394 Layout result = null; 6395 if (mText instanceof Spannable) { 6396 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 6397 alignment, mTextDir, mSpacingMult, 6398 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null, 6399 ellipsisWidth); 6400 } else { 6401 if (boring == UNKNOWN_BORING) { 6402 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6403 if (boring != null) { 6404 mBoring = boring; 6405 } 6406 } 6407 6408 if (boring != null) { 6409 if (boring.width <= wantWidth && 6410 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 6411 if (useSaved && mSavedLayout != null) { 6412 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6413 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6414 boring, mIncludePad); 6415 } else { 6416 result = BoringLayout.make(mTransformed, mTextPaint, 6417 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6418 boring, mIncludePad); 6419 } 6420 6421 if (useSaved) { 6422 mSavedLayout = (BoringLayout) result; 6423 } 6424 } else if (shouldEllipsize && boring.width <= wantWidth) { 6425 if (useSaved && mSavedLayout != null) { 6426 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6427 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6428 boring, mIncludePad, effectiveEllipsize, 6429 ellipsisWidth); 6430 } else { 6431 result = BoringLayout.make(mTransformed, mTextPaint, 6432 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6433 boring, mIncludePad, effectiveEllipsize, 6434 ellipsisWidth); 6435 } 6436 } else if (shouldEllipsize) { 6437 result = new StaticLayout(mTransformed, 6438 0, mTransformed.length(), 6439 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 6440 mSpacingAdd, mIncludePad, effectiveEllipsize, 6441 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6442 } else { 6443 result = new StaticLayout(mTransformed, mTextPaint, 6444 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6445 mIncludePad); 6446 } 6447 } else if (shouldEllipsize) { 6448 result = new StaticLayout(mTransformed, 6449 0, mTransformed.length(), 6450 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 6451 mSpacingAdd, mIncludePad, effectiveEllipsize, 6452 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6453 } else { 6454 result = new StaticLayout(mTransformed, mTextPaint, 6455 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6456 mIncludePad); 6457 } 6458 } 6459 return result; 6460 } 6461 6462 private boolean compressText(float width) { 6463 if (isHardwareAccelerated()) return false; 6464 6465 // Only compress the text if it hasn't been compressed by the previous pass 6466 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && 6467 mTextPaint.getTextScaleX() == 1.0f) { 6468 final float textWidth = mLayout.getLineWidth(0); 6469 final float overflow = (textWidth + 1.0f - width) / width; 6470 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 6471 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 6472 post(new Runnable() { 6473 public void run() { 6474 requestLayout(); 6475 } 6476 }); 6477 return true; 6478 } 6479 } 6480 6481 return false; 6482 } 6483 6484 private static int desired(Layout layout) { 6485 int n = layout.getLineCount(); 6486 CharSequence text = layout.getText(); 6487 float max = 0; 6488 6489 // if any line was wrapped, we can't use it. 6490 // but it's ok for the last line not to have a newline 6491 6492 for (int i = 0; i < n - 1; i++) { 6493 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 6494 return -1; 6495 } 6496 6497 for (int i = 0; i < n; i++) { 6498 max = Math.max(max, layout.getLineWidth(i)); 6499 } 6500 6501 return (int) Math.ceil(max); 6502 } 6503 6504 /** 6505 * Set whether the TextView includes extra top and bottom padding to make 6506 * room for accents that go above the normal ascent and descent. 6507 * The default is true. 6508 * 6509 * @see #getIncludeFontPadding() 6510 * 6511 * @attr ref android.R.styleable#TextView_includeFontPadding 6512 */ 6513 public void setIncludeFontPadding(boolean includepad) { 6514 if (mIncludePad != includepad) { 6515 mIncludePad = includepad; 6516 6517 if (mLayout != null) { 6518 nullLayouts(); 6519 requestLayout(); 6520 invalidate(); 6521 } 6522 } 6523 } 6524 6525 /** 6526 * Gets whether the TextView includes extra top and bottom padding to make 6527 * room for accents that go above the normal ascent and descent. 6528 * 6529 * @see #setIncludeFontPadding(boolean) 6530 * 6531 * @attr ref android.R.styleable#TextView_includeFontPadding 6532 */ 6533 public boolean getIncludeFontPadding() { 6534 return mIncludePad; 6535 } 6536 6537 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 6538 6539 @Override 6540 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 6541 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 6542 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 6543 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 6544 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 6545 6546 int width; 6547 int height; 6548 6549 BoringLayout.Metrics boring = UNKNOWN_BORING; 6550 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 6551 6552 if (mTextDir == null) { 6553 mTextDir = getTextDirectionHeuristic(); 6554 } 6555 6556 int des = -1; 6557 boolean fromexisting = false; 6558 6559 if (widthMode == MeasureSpec.EXACTLY) { 6560 // Parent has told us how big to be. So be it. 6561 width = widthSize; 6562 } else { 6563 if (mLayout != null && mEllipsize == null) { 6564 des = desired(mLayout); 6565 } 6566 6567 if (des < 0) { 6568 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6569 if (boring != null) { 6570 mBoring = boring; 6571 } 6572 } else { 6573 fromexisting = true; 6574 } 6575 6576 if (boring == null || boring == UNKNOWN_BORING) { 6577 if (des < 0) { 6578 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); 6579 } 6580 width = des; 6581 } else { 6582 width = boring.width; 6583 } 6584 6585 final Drawables dr = mDrawables; 6586 if (dr != null) { 6587 width = Math.max(width, dr.mDrawableWidthTop); 6588 width = Math.max(width, dr.mDrawableWidthBottom); 6589 } 6590 6591 if (mHint != null) { 6592 int hintDes = -1; 6593 int hintWidth; 6594 6595 if (mHintLayout != null && mEllipsize == null) { 6596 hintDes = desired(mHintLayout); 6597 } 6598 6599 if (hintDes < 0) { 6600 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 6601 if (hintBoring != null) { 6602 mHintBoring = hintBoring; 6603 } 6604 } 6605 6606 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 6607 if (hintDes < 0) { 6608 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint)); 6609 } 6610 hintWidth = hintDes; 6611 } else { 6612 hintWidth = hintBoring.width; 6613 } 6614 6615 if (hintWidth > width) { 6616 width = hintWidth; 6617 } 6618 } 6619 6620 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 6621 6622 if (mMaxWidthMode == EMS) { 6623 width = Math.min(width, mMaxWidth * getLineHeight()); 6624 } else { 6625 width = Math.min(width, mMaxWidth); 6626 } 6627 6628 if (mMinWidthMode == EMS) { 6629 width = Math.max(width, mMinWidth * getLineHeight()); 6630 } else { 6631 width = Math.max(width, mMinWidth); 6632 } 6633 6634 // Check against our minimum width 6635 width = Math.max(width, getSuggestedMinimumWidth()); 6636 6637 if (widthMode == MeasureSpec.AT_MOST) { 6638 width = Math.min(widthSize, width); 6639 } 6640 } 6641 6642 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6643 int unpaddedWidth = want; 6644 6645 if (mHorizontallyScrolling) want = VERY_WIDE; 6646 6647 int hintWant = want; 6648 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 6649 6650 if (mLayout == null) { 6651 makeNewLayout(want, hintWant, boring, hintBoring, 6652 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6653 } else { 6654 final boolean layoutChanged = (mLayout.getWidth() != want) || 6655 (hintWidth != hintWant) || 6656 (mLayout.getEllipsizedWidth() != 6657 width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 6658 6659 final boolean widthChanged = (mHint == null) && 6660 (mEllipsize == null) && 6661 (want > mLayout.getWidth()) && 6662 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want)); 6663 6664 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 6665 6666 if (layoutChanged || maximumChanged) { 6667 if (!maximumChanged && widthChanged) { 6668 mLayout.increaseWidthTo(want); 6669 } else { 6670 makeNewLayout(want, hintWant, boring, hintBoring, 6671 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6672 } 6673 } else { 6674 // Nothing has changed 6675 } 6676 } 6677 6678 if (heightMode == MeasureSpec.EXACTLY) { 6679 // Parent has told us how big to be. So be it. 6680 height = heightSize; 6681 mDesiredHeightAtMeasure = -1; 6682 } else { 6683 int desired = getDesiredHeight(); 6684 6685 height = desired; 6686 mDesiredHeightAtMeasure = desired; 6687 6688 if (heightMode == MeasureSpec.AT_MOST) { 6689 height = Math.min(desired, heightSize); 6690 } 6691 } 6692 6693 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 6694 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 6695 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 6696 } 6697 6698 /* 6699 * We didn't let makeNewLayout() register to bring the cursor into view, 6700 * so do it here if there is any possibility that it is needed. 6701 */ 6702 if (mMovement != null || 6703 mLayout.getWidth() > unpaddedWidth || 6704 mLayout.getHeight() > unpaddedHeight) { 6705 registerForPreDraw(); 6706 } else { 6707 scrollTo(0, 0); 6708 } 6709 6710 setMeasuredDimension(width, height); 6711 } 6712 6713 private int getDesiredHeight() { 6714 return Math.max( 6715 getDesiredHeight(mLayout, true), 6716 getDesiredHeight(mHintLayout, mEllipsize != null)); 6717 } 6718 6719 private int getDesiredHeight(Layout layout, boolean cap) { 6720 if (layout == null) { 6721 return 0; 6722 } 6723 6724 int linecount = layout.getLineCount(); 6725 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 6726 int desired = layout.getLineTop(linecount); 6727 6728 final Drawables dr = mDrawables; 6729 if (dr != null) { 6730 desired = Math.max(desired, dr.mDrawableHeightLeft); 6731 desired = Math.max(desired, dr.mDrawableHeightRight); 6732 } 6733 6734 desired += pad; 6735 6736 if (mMaxMode == LINES) { 6737 /* 6738 * Don't cap the hint to a certain number of lines. 6739 * (Do cap it, though, if we have a maximum pixel height.) 6740 */ 6741 if (cap) { 6742 if (linecount > mMaximum) { 6743 desired = layout.getLineTop(mMaximum); 6744 6745 if (dr != null) { 6746 desired = Math.max(desired, dr.mDrawableHeightLeft); 6747 desired = Math.max(desired, dr.mDrawableHeightRight); 6748 } 6749 6750 desired += pad; 6751 linecount = mMaximum; 6752 } 6753 } 6754 } else { 6755 desired = Math.min(desired, mMaximum); 6756 } 6757 6758 if (mMinMode == LINES) { 6759 if (linecount < mMinimum) { 6760 desired += getLineHeight() * (mMinimum - linecount); 6761 } 6762 } else { 6763 desired = Math.max(desired, mMinimum); 6764 } 6765 6766 // Check against our minimum height 6767 desired = Math.max(desired, getSuggestedMinimumHeight()); 6768 6769 return desired; 6770 } 6771 6772 /** 6773 * Check whether a change to the existing text layout requires a 6774 * new view layout. 6775 */ 6776 private void checkForResize() { 6777 boolean sizeChanged = false; 6778 6779 if (mLayout != null) { 6780 // Check if our width changed 6781 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 6782 sizeChanged = true; 6783 invalidate(); 6784 } 6785 6786 // Check if our height changed 6787 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 6788 int desiredHeight = getDesiredHeight(); 6789 6790 if (desiredHeight != this.getHeight()) { 6791 sizeChanged = true; 6792 } 6793 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 6794 if (mDesiredHeightAtMeasure >= 0) { 6795 int desiredHeight = getDesiredHeight(); 6796 6797 if (desiredHeight != mDesiredHeightAtMeasure) { 6798 sizeChanged = true; 6799 } 6800 } 6801 } 6802 } 6803 6804 if (sizeChanged) { 6805 requestLayout(); 6806 // caller will have already invalidated 6807 } 6808 } 6809 6810 /** 6811 * Check whether entirely new text requires a new view layout 6812 * or merely a new text layout. 6813 */ 6814 private void checkForRelayout() { 6815 // If we have a fixed width, we can just swap in a new text layout 6816 // if the text height stays the same or if the view height is fixed. 6817 6818 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 6819 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 6820 (mHint == null || mHintLayout != null) && 6821 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 6822 // Static width, so try making a new text layout. 6823 6824 int oldht = mLayout.getHeight(); 6825 int want = mLayout.getWidth(); 6826 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 6827 6828 /* 6829 * No need to bring the text into view, since the size is not 6830 * changing (unless we do the requestLayout(), in which case it 6831 * will happen at measure). 6832 */ 6833 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 6834 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 6835 false); 6836 6837 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 6838 // In a fixed-height view, so use our new text layout. 6839 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 6840 mLayoutParams.height != LayoutParams.MATCH_PARENT) { 6841 invalidate(); 6842 return; 6843 } 6844 6845 // Dynamic height, but height has stayed the same, 6846 // so use our new text layout. 6847 if (mLayout.getHeight() == oldht && 6848 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 6849 invalidate(); 6850 return; 6851 } 6852 } 6853 6854 // We lose: the height has changed and we have a dynamic height. 6855 // Request a new view layout using our new text layout. 6856 requestLayout(); 6857 invalidate(); 6858 } else { 6859 // Dynamic width, so we have no choice but to request a new 6860 // view layout with a new text layout. 6861 nullLayouts(); 6862 requestLayout(); 6863 invalidate(); 6864 } 6865 } 6866 6867 @Override 6868 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 6869 super.onLayout(changed, left, top, right, bottom); 6870 if (mDeferScroll >= 0) { 6871 int curs = mDeferScroll; 6872 mDeferScroll = -1; 6873 bringPointIntoView(Math.min(curs, mText.length())); 6874 } 6875 } 6876 6877 private boolean isShowingHint() { 6878 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 6879 } 6880 6881 /** 6882 * Returns true if anything changed. 6883 */ 6884 private boolean bringTextIntoView() { 6885 Layout layout = isShowingHint() ? mHintLayout : mLayout; 6886 int line = 0; 6887 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6888 line = layout.getLineCount() - 1; 6889 } 6890 6891 Layout.Alignment a = layout.getParagraphAlignment(line); 6892 int dir = layout.getParagraphDirection(line); 6893 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6894 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 6895 int ht = layout.getHeight(); 6896 6897 int scrollx, scrolly; 6898 6899 // Convert to left, center, or right alignment. 6900 if (a == Layout.Alignment.ALIGN_NORMAL) { 6901 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT : 6902 Layout.Alignment.ALIGN_RIGHT; 6903 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){ 6904 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT : 6905 Layout.Alignment.ALIGN_LEFT; 6906 } 6907 6908 if (a == Layout.Alignment.ALIGN_CENTER) { 6909 /* 6910 * Keep centered if possible, or, if it is too wide to fit, 6911 * keep leading edge in view. 6912 */ 6913 6914 int left = (int) Math.floor(layout.getLineLeft(line)); 6915 int right = (int) Math.ceil(layout.getLineRight(line)); 6916 6917 if (right - left < hspace) { 6918 scrollx = (right + left) / 2 - hspace / 2; 6919 } else { 6920 if (dir < 0) { 6921 scrollx = right - hspace; 6922 } else { 6923 scrollx = left; 6924 } 6925 } 6926 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 6927 int right = (int) Math.ceil(layout.getLineRight(line)); 6928 scrollx = right - hspace; 6929 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 6930 scrollx = (int) Math.floor(layout.getLineLeft(line)); 6931 } 6932 6933 if (ht < vspace) { 6934 scrolly = 0; 6935 } else { 6936 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6937 scrolly = ht - vspace; 6938 } else { 6939 scrolly = 0; 6940 } 6941 } 6942 6943 if (scrollx != mScrollX || scrolly != mScrollY) { 6944 scrollTo(scrollx, scrolly); 6945 return true; 6946 } else { 6947 return false; 6948 } 6949 } 6950 6951 /** 6952 * Move the point, specified by the offset, into the view if it is needed. 6953 * This has to be called after layout. Returns true if anything changed. 6954 */ 6955 public boolean bringPointIntoView(int offset) { 6956 if (isLayoutRequested()) { 6957 mDeferScroll = offset; 6958 return false; 6959 } 6960 boolean changed = false; 6961 6962 Layout layout = isShowingHint() ? mHintLayout: mLayout; 6963 6964 if (layout == null) return changed; 6965 6966 int line = layout.getLineForOffset(offset); 6967 6968 int grav; 6969 6970 switch (layout.getParagraphAlignment(line)) { 6971 case ALIGN_LEFT: 6972 grav = 1; 6973 break; 6974 case ALIGN_RIGHT: 6975 grav = -1; 6976 break; 6977 case ALIGN_NORMAL: 6978 grav = layout.getParagraphDirection(line); 6979 break; 6980 case ALIGN_OPPOSITE: 6981 grav = -layout.getParagraphDirection(line); 6982 break; 6983 case ALIGN_CENTER: 6984 default: 6985 grav = 0; 6986 break; 6987 } 6988 6989 // We only want to clamp the cursor to fit within the layout width 6990 // in left-to-right modes, because in a right to left alignment, 6991 // we want to scroll to keep the line-right on the screen, as other 6992 // lines are likely to have text flush with the right margin, which 6993 // we want to keep visible. 6994 // A better long-term solution would probably be to measure both 6995 // the full line and a blank-trimmed version, and, for example, use 6996 // the latter measurement for centering and right alignment, but for 6997 // the time being we only implement the cursor clamping in left to 6998 // right where it is most likely to be annoying. 6999 final boolean clamped = grav > 0; 7000 // FIXME: Is it okay to truncate this, or should we round? 7001 final int x = (int)layout.getPrimaryHorizontal(offset, clamped); 7002 final int top = layout.getLineTop(line); 7003 final int bottom = layout.getLineTop(line + 1); 7004 7005 int left = (int) Math.floor(layout.getLineLeft(line)); 7006 int right = (int) Math.ceil(layout.getLineRight(line)); 7007 int ht = layout.getHeight(); 7008 7009 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7010 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7011 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 7012 // If cursor has been clamped, make sure we don't scroll. 7013 right = Math.max(x, left + hspace); 7014 } 7015 7016 int hslack = (bottom - top) / 2; 7017 int vslack = hslack; 7018 7019 if (vslack > vspace / 4) 7020 vslack = vspace / 4; 7021 if (hslack > hspace / 4) 7022 hslack = hspace / 4; 7023 7024 int hs = mScrollX; 7025 int vs = mScrollY; 7026 7027 if (top - vs < vslack) 7028 vs = top - vslack; 7029 if (bottom - vs > vspace - vslack) 7030 vs = bottom - (vspace - vslack); 7031 if (ht - vs < vspace) 7032 vs = ht - vspace; 7033 if (0 - vs > 0) 7034 vs = 0; 7035 7036 if (grav != 0) { 7037 if (x - hs < hslack) { 7038 hs = x - hslack; 7039 } 7040 if (x - hs > hspace - hslack) { 7041 hs = x - (hspace - hslack); 7042 } 7043 } 7044 7045 if (grav < 0) { 7046 if (left - hs > 0) 7047 hs = left; 7048 if (right - hs < hspace) 7049 hs = right - hspace; 7050 } else if (grav > 0) { 7051 if (right - hs < hspace) 7052 hs = right - hspace; 7053 if (left - hs > 0) 7054 hs = left; 7055 } else /* grav == 0 */ { 7056 if (right - left <= hspace) { 7057 /* 7058 * If the entire text fits, center it exactly. 7059 */ 7060 hs = left - (hspace - (right - left)) / 2; 7061 } else if (x > right - hslack) { 7062 /* 7063 * If we are near the right edge, keep the right edge 7064 * at the edge of the view. 7065 */ 7066 hs = right - hspace; 7067 } else if (x < left + hslack) { 7068 /* 7069 * If we are near the left edge, keep the left edge 7070 * at the edge of the view. 7071 */ 7072 hs = left; 7073 } else if (left > hs) { 7074 /* 7075 * Is there whitespace visible at the left? Fix it if so. 7076 */ 7077 hs = left; 7078 } else if (right < hs + hspace) { 7079 /* 7080 * Is there whitespace visible at the right? Fix it if so. 7081 */ 7082 hs = right - hspace; 7083 } else { 7084 /* 7085 * Otherwise, float as needed. 7086 */ 7087 if (x - hs < hslack) { 7088 hs = x - hslack; 7089 } 7090 if (x - hs > hspace - hslack) { 7091 hs = x - (hspace - hslack); 7092 } 7093 } 7094 } 7095 7096 if (hs != mScrollX || vs != mScrollY) { 7097 if (mScroller == null) { 7098 scrollTo(hs, vs); 7099 } else { 7100 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 7101 int dx = hs - mScrollX; 7102 int dy = vs - mScrollY; 7103 7104 if (duration > ANIMATED_SCROLL_GAP) { 7105 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 7106 awakenScrollBars(mScroller.getDuration()); 7107 invalidate(); 7108 } else { 7109 if (!mScroller.isFinished()) { 7110 mScroller.abortAnimation(); 7111 } 7112 7113 scrollBy(dx, dy); 7114 } 7115 7116 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 7117 } 7118 7119 changed = true; 7120 } 7121 7122 if (isFocused()) { 7123 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 7124 // requestRectangleOnScreen() is in terms of content coordinates. 7125 7126 // The offsets here are to ensure the rectangle we are using is 7127 // within our view bounds, in case the cursor is on the far left 7128 // or right. If it isn't withing the bounds, then this request 7129 // will be ignored. 7130 if (mTempRect == null) mTempRect = new Rect(); 7131 mTempRect.set(x - 2, top, x + 2, bottom); 7132 getInterestingRect(mTempRect, line); 7133 mTempRect.offset(mScrollX, mScrollY); 7134 7135 if (requestRectangleOnScreen(mTempRect)) { 7136 changed = true; 7137 } 7138 } 7139 7140 return changed; 7141 } 7142 7143 /** 7144 * Move the cursor, if needed, so that it is at an offset that is visible 7145 * to the user. This will not move the cursor if it represents more than 7146 * one character (a selection range). This will only work if the 7147 * TextView contains spannable text; otherwise it will do nothing. 7148 * 7149 * @return True if the cursor was actually moved, false otherwise. 7150 */ 7151 public boolean moveCursorToVisibleOffset() { 7152 if (!(mText instanceof Spannable)) { 7153 return false; 7154 } 7155 int start = getSelectionStart(); 7156 int end = getSelectionEnd(); 7157 if (start != end) { 7158 return false; 7159 } 7160 7161 // First: make sure the line is visible on screen: 7162 7163 int line = mLayout.getLineForOffset(start); 7164 7165 final int top = mLayout.getLineTop(line); 7166 final int bottom = mLayout.getLineTop(line + 1); 7167 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7168 int vslack = (bottom - top) / 2; 7169 if (vslack > vspace / 4) 7170 vslack = vspace / 4; 7171 final int vs = mScrollY; 7172 7173 if (top < (vs+vslack)) { 7174 line = mLayout.getLineForVertical(vs+vslack+(bottom-top)); 7175 } else if (bottom > (vspace+vs-vslack)) { 7176 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top)); 7177 } 7178 7179 // Next: make sure the character is visible on screen: 7180 7181 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7182 final int hs = mScrollX; 7183 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 7184 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs); 7185 7186 // line might contain bidirectional text 7187 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 7188 final int highChar = leftChar > rightChar ? leftChar : rightChar; 7189 7190 int newStart = start; 7191 if (newStart < lowChar) { 7192 newStart = lowChar; 7193 } else if (newStart > highChar) { 7194 newStart = highChar; 7195 } 7196 7197 if (newStart != start) { 7198 Selection.setSelection((Spannable)mText, newStart); 7199 return true; 7200 } 7201 7202 return false; 7203 } 7204 7205 @Override 7206 public void computeScroll() { 7207 if (mScroller != null) { 7208 if (mScroller.computeScrollOffset()) { 7209 mScrollX = mScroller.getCurrX(); 7210 mScrollY = mScroller.getCurrY(); 7211 invalidateParentCaches(); 7212 postInvalidate(); // So we draw again 7213 } 7214 } 7215 } 7216 7217 private void getInterestingRect(Rect r, int line) { 7218 convertFromViewportToContentCoordinates(r); 7219 7220 // Rectangle can can be expanded on first and last line to take 7221 // padding into account. 7222 // TODO Take left/right padding into account too? 7223 if (line == 0) r.top -= getExtendedPaddingTop(); 7224 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 7225 } 7226 7227 private void convertFromViewportToContentCoordinates(Rect r) { 7228 final int horizontalOffset = viewportToContentHorizontalOffset(); 7229 r.left += horizontalOffset; 7230 r.right += horizontalOffset; 7231 7232 final int verticalOffset = viewportToContentVerticalOffset(); 7233 r.top += verticalOffset; 7234 r.bottom += verticalOffset; 7235 } 7236 7237 int viewportToContentHorizontalOffset() { 7238 return getCompoundPaddingLeft() - mScrollX; 7239 } 7240 7241 int viewportToContentVerticalOffset() { 7242 int offset = getExtendedPaddingTop() - mScrollY; 7243 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7244 offset += getVerticalOffset(false); 7245 } 7246 return offset; 7247 } 7248 7249 @Override 7250 public void debug(int depth) { 7251 super.debug(depth); 7252 7253 String output = debugIndent(depth); 7254 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 7255 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 7256 + "} "; 7257 7258 if (mText != null) { 7259 7260 output += "mText=\"" + mText + "\" "; 7261 if (mLayout != null) { 7262 output += "mLayout width=" + mLayout.getWidth() 7263 + " height=" + mLayout.getHeight(); 7264 } 7265 } else { 7266 output += "mText=NULL"; 7267 } 7268 Log.d(VIEW_LOG_TAG, output); 7269 } 7270 7271 /** 7272 * Convenience for {@link Selection#getSelectionStart}. 7273 */ 7274 @ViewDebug.ExportedProperty(category = "text") 7275 public int getSelectionStart() { 7276 return Selection.getSelectionStart(getText()); 7277 } 7278 7279 /** 7280 * Convenience for {@link Selection#getSelectionEnd}. 7281 */ 7282 @ViewDebug.ExportedProperty(category = "text") 7283 public int getSelectionEnd() { 7284 return Selection.getSelectionEnd(getText()); 7285 } 7286 7287 /** 7288 * Return true iff there is a selection inside this text view. 7289 */ 7290 public boolean hasSelection() { 7291 final int selectionStart = getSelectionStart(); 7292 final int selectionEnd = getSelectionEnd(); 7293 7294 return selectionStart >= 0 && selectionStart != selectionEnd; 7295 } 7296 7297 /** 7298 * Sets the properties of this field (lines, horizontally scrolling, 7299 * transformation method) to be for a single-line input. 7300 * 7301 * @attr ref android.R.styleable#TextView_singleLine 7302 */ 7303 public void setSingleLine() { 7304 setSingleLine(true); 7305 } 7306 7307 /** 7308 * Sets the properties of this field to transform input to ALL CAPS 7309 * display. This may use a "small caps" formatting if available. 7310 * This setting will be ignored if this field is editable or selectable. 7311 * 7312 * This call replaces the current transformation method. Disabling this 7313 * will not necessarily restore the previous behavior from before this 7314 * was enabled. 7315 * 7316 * @see #setTransformationMethod(TransformationMethod) 7317 * @attr ref android.R.styleable#TextView_textAllCaps 7318 */ 7319 public void setAllCaps(boolean allCaps) { 7320 if (allCaps) { 7321 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 7322 } else { 7323 setTransformationMethod(null); 7324 } 7325 } 7326 7327 /** 7328 * If true, sets the properties of this field (number of lines, horizontally scrolling, 7329 * transformation method) to be for a single-line input; if false, restores these to the default 7330 * conditions. 7331 * 7332 * Note that the default conditions are not necessarily those that were in effect prior this 7333 * method, and you may want to reset these properties to your custom values. 7334 * 7335 * @attr ref android.R.styleable#TextView_singleLine 7336 */ 7337 @android.view.RemotableViewMethod 7338 public void setSingleLine(boolean singleLine) { 7339 // Could be used, but may break backward compatibility. 7340 // if (mSingleLine == singleLine) return; 7341 setInputTypeSingleLine(singleLine); 7342 applySingleLine(singleLine, true, true); 7343 } 7344 7345 /** 7346 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 7347 * @param singleLine 7348 */ 7349 private void setInputTypeSingleLine(boolean singleLine) { 7350 if (mEditor != null && 7351 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 7352 if (singleLine) { 7353 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7354 } else { 7355 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7356 } 7357 } 7358 } 7359 7360 private void applySingleLine(boolean singleLine, boolean applyTransformation, 7361 boolean changeMaxLines) { 7362 mSingleLine = singleLine; 7363 if (singleLine) { 7364 setLines(1); 7365 setHorizontallyScrolling(true); 7366 if (applyTransformation) { 7367 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 7368 } 7369 } else { 7370 if (changeMaxLines) { 7371 setMaxLines(Integer.MAX_VALUE); 7372 } 7373 setHorizontallyScrolling(false); 7374 if (applyTransformation) { 7375 setTransformationMethod(null); 7376 } 7377 } 7378 } 7379 7380 /** 7381 * Causes words in the text that are longer than the view is wide 7382 * to be ellipsized instead of broken in the middle. You may also 7383 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 7384 * to constrain the text to a single line. Use <code>null</code> 7385 * to turn off ellipsizing. 7386 * 7387 * If {@link #setMaxLines} has been used to set two or more lines, 7388 * only {@link android.text.TextUtils.TruncateAt#END} and 7389 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 7390 * (other ellipsizing types will not do anything). 7391 * 7392 * @attr ref android.R.styleable#TextView_ellipsize 7393 */ 7394 public void setEllipsize(TextUtils.TruncateAt where) { 7395 // TruncateAt is an enum. != comparison is ok between these singleton objects. 7396 if (mEllipsize != where) { 7397 mEllipsize = where; 7398 7399 if (mLayout != null) { 7400 nullLayouts(); 7401 requestLayout(); 7402 invalidate(); 7403 } 7404 } 7405 } 7406 7407 /** 7408 * Sets how many times to repeat the marquee animation. Only applied if the 7409 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 7410 * 7411 * @see #getMarqueeRepeatLimit() 7412 * 7413 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7414 */ 7415 public void setMarqueeRepeatLimit(int marqueeLimit) { 7416 mMarqueeRepeatLimit = marqueeLimit; 7417 } 7418 7419 /** 7420 * Gets the number of times the marquee animation is repeated. Only meaningful if the 7421 * TextView has marquee enabled. 7422 * 7423 * @return the number of times the marquee animation is repeated. -1 if the animation 7424 * repeats indefinitely 7425 * 7426 * @see #setMarqueeRepeatLimit(int) 7427 * 7428 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7429 */ 7430 public int getMarqueeRepeatLimit() { 7431 return mMarqueeRepeatLimit; 7432 } 7433 7434 /** 7435 * Returns where, if anywhere, words that are longer than the view 7436 * is wide should be ellipsized. 7437 */ 7438 @ViewDebug.ExportedProperty 7439 public TextUtils.TruncateAt getEllipsize() { 7440 return mEllipsize; 7441 } 7442 7443 /** 7444 * Set the TextView so that when it takes focus, all the text is 7445 * selected. 7446 * 7447 * @attr ref android.R.styleable#TextView_selectAllOnFocus 7448 */ 7449 @android.view.RemotableViewMethod 7450 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 7451 createEditorIfNeeded(); 7452 mEditor.mSelectAllOnFocus = selectAllOnFocus; 7453 7454 if (selectAllOnFocus && !(mText instanceof Spannable)) { 7455 setText(mText, BufferType.SPANNABLE); 7456 } 7457 } 7458 7459 /** 7460 * Set whether the cursor is visible. The default is true. Note that this property only 7461 * makes sense for editable TextView. 7462 * 7463 * @see #isCursorVisible() 7464 * 7465 * @attr ref android.R.styleable#TextView_cursorVisible 7466 */ 7467 @android.view.RemotableViewMethod 7468 public void setCursorVisible(boolean visible) { 7469 if (visible && mEditor == null) return; // visible is the default value with no edit data 7470 createEditorIfNeeded(); 7471 if (mEditor.mCursorVisible != visible) { 7472 mEditor.mCursorVisible = visible; 7473 invalidate(); 7474 7475 mEditor.makeBlink(); 7476 7477 // InsertionPointCursorController depends on mCursorVisible 7478 mEditor.prepareCursorControllers(); 7479 } 7480 } 7481 7482 /** 7483 * @return whether or not the cursor is visible (assuming this TextView is editable) 7484 * 7485 * @see #setCursorVisible(boolean) 7486 * 7487 * @attr ref android.R.styleable#TextView_cursorVisible 7488 */ 7489 public boolean isCursorVisible() { 7490 // true is the default value 7491 return mEditor == null ? true : mEditor.mCursorVisible; 7492 } 7493 7494 private boolean canMarquee() { 7495 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 7496 return width > 0 && (mLayout.getLineWidth(0) > width || 7497 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null && 7498 mSavedMarqueeModeLayout.getLineWidth(0) > width)); 7499 } 7500 7501 private void startMarquee() { 7502 // Do not ellipsize EditText 7503 if (getKeyListener() != null) return; 7504 7505 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 7506 return; 7507 } 7508 7509 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 7510 getLineCount() == 1 && canMarquee()) { 7511 7512 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7513 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 7514 final Layout tmp = mLayout; 7515 mLayout = mSavedMarqueeModeLayout; 7516 mSavedMarqueeModeLayout = tmp; 7517 setHorizontalFadingEdgeEnabled(true); 7518 requestLayout(); 7519 invalidate(); 7520 } 7521 7522 if (mMarquee == null) mMarquee = new Marquee(this); 7523 mMarquee.start(mMarqueeRepeatLimit); 7524 } 7525 } 7526 7527 private void stopMarquee() { 7528 if (mMarquee != null && !mMarquee.isStopped()) { 7529 mMarquee.stop(); 7530 } 7531 7532 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 7533 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 7534 final Layout tmp = mSavedMarqueeModeLayout; 7535 mSavedMarqueeModeLayout = mLayout; 7536 mLayout = tmp; 7537 setHorizontalFadingEdgeEnabled(false); 7538 requestLayout(); 7539 invalidate(); 7540 } 7541 } 7542 7543 private void startStopMarquee(boolean start) { 7544 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7545 if (start) { 7546 startMarquee(); 7547 } else { 7548 stopMarquee(); 7549 } 7550 } 7551 } 7552 7553 /** 7554 * This method is called when the text is changed, in case any subclasses 7555 * would like to know. 7556 * 7557 * Within <code>text</code>, the <code>lengthAfter</code> characters 7558 * beginning at <code>start</code> have just replaced old text that had 7559 * length <code>lengthBefore</code>. It is an error to attempt to make 7560 * changes to <code>text</code> from this callback. 7561 * 7562 * @param text The text the TextView is displaying 7563 * @param start The offset of the start of the range of the text that was 7564 * modified 7565 * @param lengthBefore The length of the former text that has been replaced 7566 * @param lengthAfter The length of the replacement modified text 7567 */ 7568 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 7569 // intentionally empty, template pattern method can be overridden by subclasses 7570 } 7571 7572 /** 7573 * This method is called when the selection has changed, in case any 7574 * subclasses would like to know. 7575 * 7576 * @param selStart The new selection start location. 7577 * @param selEnd The new selection end location. 7578 */ 7579 protected void onSelectionChanged(int selStart, int selEnd) { 7580 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 7581 } 7582 7583 /** 7584 * Adds a TextWatcher to the list of those whose methods are called 7585 * whenever this TextView's text changes. 7586 * <p> 7587 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 7588 * not called after {@link #setText} calls. Now, doing {@link #setText} 7589 * if there are any text changed listeners forces the buffer type to 7590 * Editable if it would not otherwise be and does call this method. 7591 */ 7592 public void addTextChangedListener(TextWatcher watcher) { 7593 if (mListeners == null) { 7594 mListeners = new ArrayList<TextWatcher>(); 7595 } 7596 7597 mListeners.add(watcher); 7598 } 7599 7600 /** 7601 * Removes the specified TextWatcher from the list of those whose 7602 * methods are called 7603 * whenever this TextView's text changes. 7604 */ 7605 public void removeTextChangedListener(TextWatcher watcher) { 7606 if (mListeners != null) { 7607 int i = mListeners.indexOf(watcher); 7608 7609 if (i >= 0) { 7610 mListeners.remove(i); 7611 } 7612 } 7613 } 7614 7615 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 7616 if (mListeners != null) { 7617 final ArrayList<TextWatcher> list = mListeners; 7618 final int count = list.size(); 7619 for (int i = 0; i < count; i++) { 7620 list.get(i).beforeTextChanged(text, start, before, after); 7621 } 7622 } 7623 7624 // The spans that are inside or intersect the modified region no longer make sense 7625 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 7626 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 7627 } 7628 7629 // Removes all spans that are inside or actually overlap the start..end range 7630 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 7631 if (!(mText instanceof Editable)) return; 7632 Editable text = (Editable) mText; 7633 7634 T[] spans = text.getSpans(start, end, type); 7635 final int length = spans.length; 7636 for (int i = 0; i < length; i++) { 7637 final int spanStart = text.getSpanStart(spans[i]); 7638 final int spanEnd = text.getSpanEnd(spans[i]); 7639 if (spanEnd == start || spanStart == end) break; 7640 text.removeSpan(spans[i]); 7641 } 7642 } 7643 7644 void removeAdjacentSuggestionSpans(final int pos) { 7645 if (!(mText instanceof Editable)) return; 7646 final Editable text = (Editable) mText; 7647 7648 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 7649 final int length = spans.length; 7650 for (int i = 0; i < length; i++) { 7651 final int spanStart = text.getSpanStart(spans[i]); 7652 final int spanEnd = text.getSpanEnd(spans[i]); 7653 if (spanEnd == pos || spanStart == pos) { 7654 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 7655 text.removeSpan(spans[i]); 7656 } 7657 } 7658 } 7659 } 7660 7661 /** 7662 * Not private so it can be called from an inner class without going 7663 * through a thunk. 7664 */ 7665 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 7666 if (mListeners != null) { 7667 final ArrayList<TextWatcher> list = mListeners; 7668 final int count = list.size(); 7669 for (int i = 0; i < count; i++) { 7670 list.get(i).onTextChanged(text, start, before, after); 7671 } 7672 } 7673 7674 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 7675 } 7676 7677 /** 7678 * Not private so it can be called from an inner class without going 7679 * through a thunk. 7680 */ 7681 void sendAfterTextChanged(Editable text) { 7682 if (mListeners != null) { 7683 final ArrayList<TextWatcher> list = mListeners; 7684 final int count = list.size(); 7685 for (int i = 0; i < count; i++) { 7686 list.get(i).afterTextChanged(text); 7687 } 7688 } 7689 hideErrorIfUnchanged(); 7690 } 7691 7692 void updateAfterEdit() { 7693 invalidate(); 7694 int curs = getSelectionStart(); 7695 7696 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7697 registerForPreDraw(); 7698 } 7699 7700 checkForResize(); 7701 7702 if (curs >= 0) { 7703 mHighlightPathBogus = true; 7704 if (mEditor != null) mEditor.makeBlink(); 7705 bringPointIntoView(curs); 7706 } 7707 } 7708 7709 /** 7710 * Not private so it can be called from an inner class without going 7711 * through a thunk. 7712 */ 7713 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 7714 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7715 if (ims == null || ims.mBatchEditNesting == 0) { 7716 updateAfterEdit(); 7717 } 7718 if (ims != null) { 7719 ims.mContentChanged = true; 7720 if (ims.mChangedStart < 0) { 7721 ims.mChangedStart = start; 7722 ims.mChangedEnd = start+before; 7723 } else { 7724 ims.mChangedStart = Math.min(ims.mChangedStart, start); 7725 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 7726 } 7727 ims.mChangedDelta += after-before; 7728 } 7729 resetErrorChangedFlag(); 7730 sendOnTextChanged(buffer, start, before, after); 7731 onTextChanged(buffer, start, before, after); 7732 } 7733 7734 /** 7735 * Not private so it can be called from an inner class without going 7736 * through a thunk. 7737 */ 7738 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 7739 // XXX Make the start and end move together if this ends up 7740 // spending too much time invalidating. 7741 7742 boolean selChanged = false; 7743 int newSelStart=-1, newSelEnd=-1; 7744 7745 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7746 7747 if (what == Selection.SELECTION_END) { 7748 selChanged = true; 7749 newSelEnd = newStart; 7750 7751 if (oldStart >= 0 || newStart >= 0) { 7752 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 7753 checkForResize(); 7754 registerForPreDraw(); 7755 if (mEditor != null) mEditor.makeBlink(); 7756 } 7757 } 7758 7759 if (what == Selection.SELECTION_START) { 7760 selChanged = true; 7761 newSelStart = newStart; 7762 7763 if (oldStart >= 0 || newStart >= 0) { 7764 int end = Selection.getSelectionEnd(buf); 7765 invalidateCursor(end, oldStart, newStart); 7766 } 7767 } 7768 7769 if (selChanged) { 7770 mHighlightPathBogus = true; 7771 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 7772 7773 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) { 7774 if (newSelStart < 0) { 7775 newSelStart = Selection.getSelectionStart(buf); 7776 } 7777 if (newSelEnd < 0) { 7778 newSelEnd = Selection.getSelectionEnd(buf); 7779 } 7780 onSelectionChanged(newSelStart, newSelEnd); 7781 } 7782 } 7783 7784 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle || 7785 what instanceof CharacterStyle) { 7786 if (ims == null || ims.mBatchEditNesting == 0) { 7787 invalidate(); 7788 mHighlightPathBogus = true; 7789 checkForResize(); 7790 } else { 7791 ims.mContentChanged = true; 7792 } 7793 if (mEditor != null) { 7794 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 7795 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 7796 } 7797 } 7798 7799 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 7800 mHighlightPathBogus = true; 7801 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 7802 ims.mSelectionModeChanged = true; 7803 } 7804 7805 if (Selection.getSelectionStart(buf) >= 0) { 7806 if (ims == null || ims.mBatchEditNesting == 0) { 7807 invalidateCursor(); 7808 } else { 7809 ims.mCursorChanged = true; 7810 } 7811 } 7812 } 7813 7814 if (what instanceof ParcelableSpan) { 7815 // If this is a span that can be sent to a remote process, 7816 // the current extract editor would be interested in it. 7817 if (ims != null && ims.mExtractedTextRequest != null) { 7818 if (ims.mBatchEditNesting != 0) { 7819 if (oldStart >= 0) { 7820 if (ims.mChangedStart > oldStart) { 7821 ims.mChangedStart = oldStart; 7822 } 7823 if (ims.mChangedStart > oldEnd) { 7824 ims.mChangedStart = oldEnd; 7825 } 7826 } 7827 if (newStart >= 0) { 7828 if (ims.mChangedStart > newStart) { 7829 ims.mChangedStart = newStart; 7830 } 7831 if (ims.mChangedStart > newEnd) { 7832 ims.mChangedStart = newEnd; 7833 } 7834 } 7835 } else { 7836 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: " 7837 + oldStart + "-" + oldEnd + "," 7838 + newStart + "-" + newEnd + " " + what); 7839 ims.mContentChanged = true; 7840 } 7841 } 7842 } 7843 7844 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 && 7845 what instanceof SpellCheckSpan) { 7846 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 7847 } 7848 } 7849 7850 /** 7851 * @hide 7852 */ 7853 @Override 7854 public void dispatchFinishTemporaryDetach() { 7855 mDispatchTemporaryDetach = true; 7856 super.dispatchFinishTemporaryDetach(); 7857 mDispatchTemporaryDetach = false; 7858 } 7859 7860 @Override 7861 public void onStartTemporaryDetach() { 7862 super.onStartTemporaryDetach(); 7863 // Only track when onStartTemporaryDetach() is called directly, 7864 // usually because this instance is an editable field in a list 7865 if (!mDispatchTemporaryDetach) mTemporaryDetach = true; 7866 7867 // Tell the editor that we are temporarily detached. It can use this to preserve 7868 // selection state as needed. 7869 if (mEditor != null) mEditor.mTemporaryDetach = true; 7870 } 7871 7872 @Override 7873 public void onFinishTemporaryDetach() { 7874 super.onFinishTemporaryDetach(); 7875 // Only track when onStartTemporaryDetach() is called directly, 7876 // usually because this instance is an editable field in a list 7877 if (!mDispatchTemporaryDetach) mTemporaryDetach = false; 7878 if (mEditor != null) mEditor.mTemporaryDetach = false; 7879 } 7880 7881 @Override 7882 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 7883 if (mTemporaryDetach) { 7884 // If we are temporarily in the detach state, then do nothing. 7885 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7886 return; 7887 } 7888 7889 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 7890 7891 if (focused) { 7892 if (mText instanceof Spannable) { 7893 Spannable sp = (Spannable) mText; 7894 MetaKeyKeyListener.resetMetaState(sp); 7895 } 7896 } 7897 7898 startStopMarquee(focused); 7899 7900 if (mTransformation != null) { 7901 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 7902 } 7903 7904 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7905 } 7906 7907 @Override 7908 public void onWindowFocusChanged(boolean hasWindowFocus) { 7909 super.onWindowFocusChanged(hasWindowFocus); 7910 7911 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 7912 7913 startStopMarquee(hasWindowFocus); 7914 } 7915 7916 @Override 7917 protected void onVisibilityChanged(View changedView, int visibility) { 7918 super.onVisibilityChanged(changedView, visibility); 7919 if (mEditor != null && visibility != VISIBLE) { 7920 mEditor.hideControllers(); 7921 } 7922 } 7923 7924 /** 7925 * Use {@link BaseInputConnection#removeComposingSpans 7926 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 7927 * state from this text view. 7928 */ 7929 public void clearComposingText() { 7930 if (mText instanceof Spannable) { 7931 BaseInputConnection.removeComposingSpans((Spannable)mText); 7932 } 7933 } 7934 7935 @Override 7936 public void setSelected(boolean selected) { 7937 boolean wasSelected = isSelected(); 7938 7939 super.setSelected(selected); 7940 7941 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7942 if (selected) { 7943 startMarquee(); 7944 } else { 7945 stopMarquee(); 7946 } 7947 } 7948 } 7949 7950 @Override 7951 public boolean onTouchEvent(MotionEvent event) { 7952 final int action = event.getActionMasked(); 7953 7954 if (mEditor != null) mEditor.onTouchEvent(event); 7955 7956 final boolean superResult = super.onTouchEvent(event); 7957 7958 /* 7959 * Don't handle the release after a long press, because it will 7960 * move the selection away from whatever the menu action was 7961 * trying to affect. 7962 */ 7963 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 7964 mEditor.mDiscardNextActionUp = false; 7965 return superResult; 7966 } 7967 7968 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && 7969 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 7970 7971 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 7972 && mText instanceof Spannable && mLayout != null) { 7973 boolean handled = false; 7974 7975 if (mMovement != null) { 7976 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 7977 } 7978 7979 final boolean textIsSelectable = isTextSelectable(); 7980 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 7981 // The LinkMovementMethod which should handle taps on links has not been installed 7982 // on non editable text that support text selection. 7983 // We reproduce its behavior here to open links for these. 7984 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 7985 getSelectionEnd(), ClickableSpan.class); 7986 7987 if (links.length > 0) { 7988 links[0].onClick(this); 7989 handled = true; 7990 } 7991 } 7992 7993 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 7994 // Show the IME, except when selecting in read-only text. 7995 final InputMethodManager imm = InputMethodManager.peekInstance(); 7996 viewClicked(imm); 7997 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) { 7998 handled |= imm != null && imm.showSoftInput(this, 0); 7999 } 8000 8001 // The above condition ensures that the mEditor is not null 8002 mEditor.onTouchUpEvent(event); 8003 8004 handled = true; 8005 } 8006 8007 if (handled) { 8008 return true; 8009 } 8010 } 8011 8012 return superResult; 8013 } 8014 8015 @Override 8016 public boolean onGenericMotionEvent(MotionEvent event) { 8017 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8018 try { 8019 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 8020 return true; 8021 } 8022 } catch (AbstractMethodError ex) { 8023 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 8024 // Ignore its absence in case third party applications implemented the 8025 // interface directly. 8026 } 8027 } 8028 return super.onGenericMotionEvent(event); 8029 } 8030 8031 /** 8032 * @return True iff this TextView contains a text that can be edited, or if this is 8033 * a selectable TextView. 8034 */ 8035 boolean isTextEditable() { 8036 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 8037 } 8038 8039 /** 8040 * Returns true, only while processing a touch gesture, if the initial 8041 * touch down event caused focus to move to the text view and as a result 8042 * its selection changed. Only valid while processing the touch gesture 8043 * of interest, in an editable text view. 8044 */ 8045 public boolean didTouchFocusSelect() { 8046 return mEditor != null && mEditor.mTouchFocusSelected; 8047 } 8048 8049 @Override 8050 public void cancelLongPress() { 8051 super.cancelLongPress(); 8052 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 8053 } 8054 8055 @Override 8056 public boolean onTrackballEvent(MotionEvent event) { 8057 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8058 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 8059 return true; 8060 } 8061 } 8062 8063 return super.onTrackballEvent(event); 8064 } 8065 8066 public void setScroller(Scroller s) { 8067 mScroller = s; 8068 } 8069 8070 @Override 8071 protected float getLeftFadingEdgeStrength() { 8072 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 8073 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8074 if (mMarquee != null && !mMarquee.isStopped()) { 8075 final Marquee marquee = mMarquee; 8076 if (marquee.shouldDrawLeftFade()) { 8077 final float scroll = marquee.getScroll(); 8078 return scroll / getHorizontalFadingEdgeLength(); 8079 } else { 8080 return 0.0f; 8081 } 8082 } else if (getLineCount() == 1) { 8083 final int layoutDirection = getLayoutDirection(); 8084 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8085 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 8086 case Gravity.LEFT: 8087 return 0.0f; 8088 case Gravity.RIGHT: 8089 return (mLayout.getLineRight(0) - (mRight - mLeft) - 8090 getCompoundPaddingLeft() - getCompoundPaddingRight() - 8091 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 8092 case Gravity.CENTER_HORIZONTAL: 8093 case Gravity.FILL_HORIZONTAL: 8094 final int textDirection = mLayout.getParagraphDirection(0); 8095 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) { 8096 return 0.0f; 8097 } else { 8098 return (mLayout.getLineRight(0) - (mRight - mLeft) - 8099 getCompoundPaddingLeft() - getCompoundPaddingRight() - 8100 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 8101 } 8102 } 8103 } 8104 } 8105 return super.getLeftFadingEdgeStrength(); 8106 } 8107 8108 @Override 8109 protected float getRightFadingEdgeStrength() { 8110 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 8111 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8112 if (mMarquee != null && !mMarquee.isStopped()) { 8113 final Marquee marquee = mMarquee; 8114 final float maxFadeScroll = marquee.getMaxFadeScroll(); 8115 final float scroll = marquee.getScroll(); 8116 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength(); 8117 } else if (getLineCount() == 1) { 8118 final int layoutDirection = getLayoutDirection(); 8119 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8120 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 8121 case Gravity.LEFT: 8122 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() - 8123 getCompoundPaddingRight(); 8124 final float lineWidth = mLayout.getLineWidth(0); 8125 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength(); 8126 case Gravity.RIGHT: 8127 return 0.0f; 8128 case Gravity.CENTER_HORIZONTAL: 8129 case Gravity.FILL_HORIZONTAL: 8130 final int textDirection = mLayout.getParagraphDirection(0); 8131 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) { 8132 return 0.0f; 8133 } else { 8134 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 8135 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 8136 getHorizontalFadingEdgeLength(); 8137 } 8138 } 8139 } 8140 } 8141 return super.getRightFadingEdgeStrength(); 8142 } 8143 8144 @Override 8145 protected int computeHorizontalScrollRange() { 8146 if (mLayout != null) { 8147 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ? 8148 (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 8149 } 8150 8151 return super.computeHorizontalScrollRange(); 8152 } 8153 8154 @Override 8155 protected int computeVerticalScrollRange() { 8156 if (mLayout != null) 8157 return mLayout.getHeight(); 8158 8159 return super.computeVerticalScrollRange(); 8160 } 8161 8162 @Override 8163 protected int computeVerticalScrollExtent() { 8164 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 8165 } 8166 8167 @Override 8168 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 8169 super.findViewsWithText(outViews, searched, flags); 8170 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 8171 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 8172 String searchedLowerCase = searched.toString().toLowerCase(); 8173 String textLowerCase = mText.toString().toLowerCase(); 8174 if (textLowerCase.contains(searchedLowerCase)) { 8175 outViews.add(this); 8176 } 8177 } 8178 } 8179 8180 public enum BufferType { 8181 NORMAL, SPANNABLE, EDITABLE, 8182 } 8183 8184 /** 8185 * Returns the TextView_textColor attribute from the TypedArray, if set, or 8186 * the TextAppearance_textColor from the TextView_textAppearance attribute, 8187 * if TextView_textColor was not set directly. 8188 * 8189 * @removed 8190 */ 8191 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 8192 if (attrs == null) { 8193 // Preserve behavior prior to removal of this API. 8194 throw new NullPointerException(); 8195 } 8196 8197 // It's not safe to use this method from apps. The parameter 'attrs' 8198 // must have been obtained using the TextView filter array which is not 8199 // available to the SDK. As such, we grab a default TypedArray with the 8200 // right filter instead here. 8201 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 8202 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 8203 if (colors == null) { 8204 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 8205 if (ap != 0) { 8206 final TypedArray appearance = context.obtainStyledAttributes( 8207 ap, R.styleable.TextAppearance); 8208 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 8209 appearance.recycle(); 8210 } 8211 } 8212 a.recycle(); 8213 8214 return colors; 8215 } 8216 8217 /** 8218 * Returns the default color from the TextView_textColor attribute from the 8219 * AttributeSet, if set, or the default color from the 8220 * TextAppearance_textColor from the TextView_textAppearance attribute, if 8221 * TextView_textColor was not set directly. 8222 * 8223 * @removed 8224 */ 8225 public static int getTextColor(Context context, TypedArray attrs, int def) { 8226 final ColorStateList colors = getTextColors(context, attrs); 8227 if (colors == null) { 8228 return def; 8229 } else { 8230 return colors.getDefaultColor(); 8231 } 8232 } 8233 8234 @Override 8235 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 8236 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK; 8237 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) { 8238 switch (keyCode) { 8239 case KeyEvent.KEYCODE_A: 8240 if (canSelectText()) { 8241 return onTextContextMenuItem(ID_SELECT_ALL); 8242 } 8243 break; 8244 case KeyEvent.KEYCODE_X: 8245 if (canCut()) { 8246 return onTextContextMenuItem(ID_CUT); 8247 } 8248 break; 8249 case KeyEvent.KEYCODE_C: 8250 if (canCopy()) { 8251 return onTextContextMenuItem(ID_COPY); 8252 } 8253 break; 8254 case KeyEvent.KEYCODE_V: 8255 if (canPaste()) { 8256 return onTextContextMenuItem(ID_PASTE); 8257 } 8258 break; 8259 } 8260 } 8261 return super.onKeyShortcut(keyCode, event); 8262 } 8263 8264 /** 8265 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 8266 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 8267 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 8268 * sufficient. 8269 */ 8270 private boolean canSelectText() { 8271 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 8272 } 8273 8274 /** 8275 * Test based on the <i>intrinsic</i> charateristics of the TextView. 8276 * The text must be spannable and the movement method must allow for arbitary selection. 8277 * 8278 * See also {@link #canSelectText()}. 8279 */ 8280 boolean textCanBeSelected() { 8281 // prepareCursorController() relies on this method. 8282 // If you change this condition, make sure prepareCursorController is called anywhere 8283 // the value of this condition might be changed. 8284 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 8285 return isTextEditable() || 8286 (isTextSelectable() && mText instanceof Spannable && isEnabled()); 8287 } 8288 8289 private Locale getTextServicesLocale(boolean allowNullLocale) { 8290 // Start fetching the text services locale asynchronously. 8291 updateTextServicesLocaleAsync(); 8292 // If !allowNullLocale and there is no cached text services locale, just return the default 8293 // locale. 8294 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 8295 : mCurrentSpellCheckerLocaleCache; 8296 } 8297 8298 /** 8299 * This is a temporary method. Future versions may support multi-locale text. 8300 * Caveat: This method may not return the latest text services locale, but this should be 8301 * acceptable and it's more important to make this method asynchronous. 8302 * 8303 * @return The locale that should be used for a word iterator 8304 * in this TextView, based on the current spell checker settings, 8305 * the current IME's locale, or the system default locale. 8306 * Please note that a word iterator in this TextView is different from another word iterator 8307 * used by SpellChecker.java of TextView. This method should be used for the former. 8308 * @hide 8309 */ 8310 // TODO: Support multi-locale 8311 // TODO: Update the text services locale immediately after the keyboard locale is switched 8312 // by catching intent of keyboard switch event 8313 public Locale getTextServicesLocale() { 8314 return getTextServicesLocale(false /* allowNullLocale */); 8315 } 8316 8317 /** 8318 * This is a temporary method. Future versions may support multi-locale text. 8319 * Caveat: This method may not return the latest spell checker locale, but this should be 8320 * acceptable and it's more important to make this method asynchronous. 8321 * 8322 * @return The locale that should be used for a spell checker in this TextView, 8323 * based on the current spell checker settings, the current IME's locale, or the system default 8324 * locale. 8325 * @hide 8326 */ 8327 public Locale getSpellCheckerLocale() { 8328 return getTextServicesLocale(true /* allowNullLocale */); 8329 } 8330 8331 private void updateTextServicesLocaleAsync() { 8332 // AsyncTask.execute() uses a serial executor which means we don't have 8333 // to lock around updateTextServicesLocaleLocked() to prevent it from 8334 // being executed n times in parallel. 8335 AsyncTask.execute(new Runnable() { 8336 @Override 8337 public void run() { 8338 updateTextServicesLocaleLocked(); 8339 } 8340 }); 8341 } 8342 8343 private void updateTextServicesLocaleLocked() { 8344 final TextServicesManager textServicesManager = (TextServicesManager) 8345 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 8346 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 8347 final Locale locale; 8348 if (subtype != null) { 8349 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); 8350 } else { 8351 locale = null; 8352 } 8353 mCurrentSpellCheckerLocaleCache = locale; 8354 } 8355 8356 void onLocaleChanged() { 8357 // Will be re-created on demand in getWordIterator with the proper new locale 8358 mEditor.mWordIterator = null; 8359 } 8360 8361 /** 8362 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 8363 * Made available to achieve a consistent behavior. 8364 * @hide 8365 */ 8366 public WordIterator getWordIterator() { 8367 if (mEditor != null) { 8368 return mEditor.getWordIterator(); 8369 } else { 8370 return null; 8371 } 8372 } 8373 8374 @Override 8375 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 8376 super.onPopulateAccessibilityEvent(event); 8377 8378 final boolean isPassword = hasPasswordTransformationMethod(); 8379 if (!isPassword || shouldSpeakPasswordsForAccessibility()) { 8380 final CharSequence text = getTextForAccessibility(); 8381 if (!TextUtils.isEmpty(text)) { 8382 event.getText().add(text); 8383 } 8384 } 8385 } 8386 8387 /** 8388 * @return true if the user has explicitly allowed accessibility services 8389 * to speak passwords. 8390 */ 8391 private boolean shouldSpeakPasswordsForAccessibility() { 8392 return (Settings.Secure.getInt(mContext.getContentResolver(), 8393 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); 8394 } 8395 8396 @Override 8397 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 8398 super.onInitializeAccessibilityEvent(event); 8399 8400 event.setClassName(TextView.class.getName()); 8401 final boolean isPassword = hasPasswordTransformationMethod(); 8402 event.setPassword(isPassword); 8403 8404 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 8405 event.setFromIndex(Selection.getSelectionStart(mText)); 8406 event.setToIndex(Selection.getSelectionEnd(mText)); 8407 event.setItemCount(mText.length()); 8408 } 8409 } 8410 8411 @Override 8412 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 8413 super.onInitializeAccessibilityNodeInfo(info); 8414 8415 info.setClassName(TextView.class.getName()); 8416 final boolean isPassword = hasPasswordTransformationMethod(); 8417 info.setPassword(isPassword); 8418 8419 if (!isPassword || shouldSpeakPasswordsForAccessibility()) { 8420 info.setText(getTextForAccessibility()); 8421 } 8422 8423 if (mBufferType == BufferType.EDITABLE) { 8424 info.setEditable(true); 8425 } 8426 8427 if (mEditor != null) { 8428 info.setInputType(mEditor.mInputType); 8429 8430 if (mEditor.mError != null) { 8431 info.setContentInvalid(true); 8432 info.setError(mEditor.mError); 8433 } 8434 } 8435 8436 if (!TextUtils.isEmpty(mText)) { 8437 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 8438 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 8439 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 8440 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 8441 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 8442 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 8443 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 8444 } 8445 8446 if (isFocused()) { 8447 if (canSelectText()) { 8448 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 8449 } 8450 if (canCopy()) { 8451 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 8452 } 8453 if (canPaste()) { 8454 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 8455 } 8456 if (canCut()) { 8457 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 8458 } 8459 } 8460 8461 // Check for known input filter types. 8462 final int numFilters = mFilters.length; 8463 for (int i = 0; i < numFilters; i++) { 8464 final InputFilter filter = mFilters[i]; 8465 if (filter instanceof InputFilter.LengthFilter) { 8466 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 8467 } 8468 } 8469 8470 if (!isSingleLine()) { 8471 info.setMultiLine(true); 8472 } 8473 } 8474 8475 @Override 8476 public boolean performAccessibilityAction(int action, Bundle arguments) { 8477 switch (action) { 8478 case AccessibilityNodeInfo.ACTION_CLICK: { 8479 boolean handled = false; 8480 8481 // Simulate View.onTouchEvent for an ACTION_UP event. 8482 if (isClickable() || isLongClickable()) { 8483 if (isFocusable() && !isFocused()) { 8484 requestFocus(); 8485 } 8486 8487 performClick(); 8488 handled = true; 8489 } 8490 8491 // Simulate TextView.onTouchEvent for an ACTION_UP event. 8492 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 8493 && mText instanceof Spannable && mLayout != null 8494 && (isTextEditable() || isTextSelectable()) && isFocused()) { 8495 // Show the IME, except when selecting in read-only text. 8496 final InputMethodManager imm = InputMethodManager.peekInstance(); 8497 viewClicked(imm); 8498 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 8499 handled |= imm.showSoftInput(this, 0); 8500 } 8501 } 8502 8503 return handled; 8504 } 8505 case AccessibilityNodeInfo.ACTION_COPY: { 8506 if (isFocused() && canCopy()) { 8507 if (onTextContextMenuItem(ID_COPY)) { 8508 return true; 8509 } 8510 } 8511 } return false; 8512 case AccessibilityNodeInfo.ACTION_PASTE: { 8513 if (isFocused() && canPaste()) { 8514 if (onTextContextMenuItem(ID_PASTE)) { 8515 return true; 8516 } 8517 } 8518 } return false; 8519 case AccessibilityNodeInfo.ACTION_CUT: { 8520 if (isFocused() && canCut()) { 8521 if (onTextContextMenuItem(ID_CUT)) { 8522 return true; 8523 } 8524 } 8525 } return false; 8526 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 8527 if (isFocused() && canSelectText()) { 8528 ensureIterableTextForAccessibilitySelectable(); 8529 CharSequence text = getIterableTextForAccessibility(); 8530 if (text == null) { 8531 return false; 8532 } 8533 final int start = (arguments != null) ? arguments.getInt( 8534 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 8535 final int end = (arguments != null) ? arguments.getInt( 8536 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 8537 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 8538 // No arguments clears the selection. 8539 if (start == end && end == -1) { 8540 Selection.removeSelection((Spannable) text); 8541 return true; 8542 } 8543 if (start >= 0 && start <= end && end <= text.length()) { 8544 Selection.setSelection((Spannable) text, start, end); 8545 // Make sure selection mode is engaged. 8546 if (mEditor != null) { 8547 mEditor.startSelectionActionMode(); 8548 } 8549 return true; 8550 } 8551 } 8552 } 8553 } return false; 8554 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 8555 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 8556 ensureIterableTextForAccessibilitySelectable(); 8557 return super.performAccessibilityAction(action, arguments); 8558 } 8559 default: { 8560 return super.performAccessibilityAction(action, arguments); 8561 } 8562 } 8563 } 8564 8565 @Override 8566 public void sendAccessibilityEvent(int eventType) { 8567 // Do not send scroll events since first they are not interesting for 8568 // accessibility and second such events a generated too frequently. 8569 // For details see the implementation of bringTextIntoView(). 8570 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 8571 return; 8572 } 8573 super.sendAccessibilityEvent(eventType); 8574 } 8575 8576 /** 8577 * Gets the text reported for accessibility purposes. 8578 * 8579 * @return The accessibility text. 8580 * 8581 * @hide 8582 */ 8583 public CharSequence getTextForAccessibility() { 8584 CharSequence text = getText(); 8585 if (TextUtils.isEmpty(text)) { 8586 text = getHint(); 8587 } 8588 return text; 8589 } 8590 8591 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 8592 int fromIndex, int removedCount, int addedCount) { 8593 AccessibilityEvent event = 8594 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 8595 event.setFromIndex(fromIndex); 8596 event.setRemovedCount(removedCount); 8597 event.setAddedCount(addedCount); 8598 event.setBeforeText(beforeText); 8599 sendAccessibilityEventUnchecked(event); 8600 } 8601 8602 /** 8603 * Returns whether this text view is a current input method target. The 8604 * default implementation just checks with {@link InputMethodManager}. 8605 */ 8606 public boolean isInputMethodTarget() { 8607 InputMethodManager imm = InputMethodManager.peekInstance(); 8608 return imm != null && imm.isActive(this); 8609 } 8610 8611 static final int ID_SELECT_ALL = android.R.id.selectAll; 8612 static final int ID_CUT = android.R.id.cut; 8613 static final int ID_COPY = android.R.id.copy; 8614 static final int ID_PASTE = android.R.id.paste; 8615 8616 /** 8617 * Called when a context menu option for the text view is selected. Currently 8618 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 8619 * {@link android.R.id#copy} or {@link android.R.id#paste}. 8620 * 8621 * @return true if the context menu item action was performed. 8622 */ 8623 public boolean onTextContextMenuItem(int id) { 8624 int min = 0; 8625 int max = mText.length(); 8626 8627 if (isFocused()) { 8628 final int selStart = getSelectionStart(); 8629 final int selEnd = getSelectionEnd(); 8630 8631 min = Math.max(0, Math.min(selStart, selEnd)); 8632 max = Math.max(0, Math.max(selStart, selEnd)); 8633 } 8634 8635 switch (id) { 8636 case ID_SELECT_ALL: 8637 // This does not enter text selection mode. Text is highlighted, so that it can be 8638 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty. 8639 selectAllText(); 8640 return true; 8641 8642 case ID_PASTE: 8643 paste(min, max); 8644 return true; 8645 8646 case ID_CUT: 8647 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 8648 deleteText_internal(min, max); 8649 stopSelectionActionMode(); 8650 return true; 8651 8652 case ID_COPY: 8653 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 8654 stopSelectionActionMode(); 8655 return true; 8656 } 8657 return false; 8658 } 8659 8660 CharSequence getTransformedText(int start, int end) { 8661 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 8662 } 8663 8664 @Override 8665 public boolean performLongClick() { 8666 boolean handled = false; 8667 8668 if (super.performLongClick()) { 8669 handled = true; 8670 } 8671 8672 if (mEditor != null) { 8673 handled |= mEditor.performLongClick(handled); 8674 } 8675 8676 if (handled) { 8677 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 8678 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 8679 } 8680 8681 return handled; 8682 } 8683 8684 @Override 8685 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 8686 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 8687 if (mEditor != null) { 8688 mEditor.onScrollChanged(); 8689 } 8690 } 8691 8692 /** 8693 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 8694 * by the IME or by the spell checker as the user types. This is done by adding 8695 * {@link SuggestionSpan}s to the text. 8696 * 8697 * When suggestions are enabled (default), this list of suggestions will be displayed when the 8698 * user asks for them on these parts of the text. This value depends on the inputType of this 8699 * TextView. 8700 * 8701 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 8702 * 8703 * In addition, the type variation must be one of 8704 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 8705 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 8706 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 8707 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 8708 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 8709 * 8710 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 8711 * 8712 * @return true if the suggestions popup window is enabled, based on the inputType. 8713 */ 8714 public boolean isSuggestionsEnabled() { 8715 if (mEditor == null) return false; 8716 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 8717 return false; 8718 } 8719 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 8720 8721 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8722 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || 8723 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || 8724 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || 8725 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || 8726 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 8727 } 8728 8729 /** 8730 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 8731 * selection is initiated in this View. 8732 * 8733 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and 8734 * Paste actions, depending on what this View supports. 8735 * 8736 * A custom implementation can add new entries in the default menu in its 8737 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The 8738 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and 8739 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} 8740 * or {@link android.R.id#paste} ids as parameters. 8741 * 8742 * Returning false from 8743 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent 8744 * the action mode from being started. 8745 * 8746 * Action click events should be handled by the custom implementation of 8747 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}. 8748 * 8749 * Note that text selection mode is not started when a TextView receives focus and the 8750 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 8751 * that case, to allow for quick replacement. 8752 */ 8753 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 8754 createEditorIfNeeded(); 8755 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 8756 } 8757 8758 /** 8759 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 8760 * 8761 * @return The current custom selection callback. 8762 */ 8763 public ActionMode.Callback getCustomSelectionActionModeCallback() { 8764 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 8765 } 8766 8767 /** 8768 * @hide 8769 */ 8770 protected void stopSelectionActionMode() { 8771 mEditor.stopSelectionActionMode(); 8772 } 8773 8774 boolean canCut() { 8775 if (hasPasswordTransformationMethod()) { 8776 return false; 8777 } 8778 8779 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && 8780 mEditor.mKeyListener != null) { 8781 return true; 8782 } 8783 8784 return false; 8785 } 8786 8787 boolean canCopy() { 8788 if (hasPasswordTransformationMethod()) { 8789 return false; 8790 } 8791 8792 if (mText.length() > 0 && hasSelection() && mEditor != null) { 8793 return true; 8794 } 8795 8796 return false; 8797 } 8798 8799 boolean canPaste() { 8800 return (mText instanceof Editable && 8801 mEditor != null && mEditor.mKeyListener != null && 8802 getSelectionStart() >= 0 && 8803 getSelectionEnd() >= 0 && 8804 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). 8805 hasPrimaryClip()); 8806 } 8807 8808 boolean selectAllText() { 8809 final int length = mText.length(); 8810 Selection.setSelection((Spannable) mText, 0, length); 8811 return length > 0; 8812 } 8813 8814 /** 8815 * Paste clipboard content between min and max positions. 8816 */ 8817 private void paste(int min, int max) { 8818 ClipboardManager clipboard = 8819 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 8820 ClipData clip = clipboard.getPrimaryClip(); 8821 if (clip != null) { 8822 boolean didFirst = false; 8823 for (int i=0; i<clip.getItemCount(); i++) { 8824 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext()); 8825 if (paste != null) { 8826 if (!didFirst) { 8827 Selection.setSelection((Spannable) mText, max); 8828 ((Editable) mText).replace(min, max, paste); 8829 didFirst = true; 8830 } else { 8831 ((Editable) mText).insert(getSelectionEnd(), "\n"); 8832 ((Editable) mText).insert(getSelectionEnd(), paste); 8833 } 8834 } 8835 } 8836 stopSelectionActionMode(); 8837 LAST_CUT_OR_COPY_TIME = 0; 8838 } 8839 } 8840 8841 private void setPrimaryClip(ClipData clip) { 8842 ClipboardManager clipboard = (ClipboardManager) getContext(). 8843 getSystemService(Context.CLIPBOARD_SERVICE); 8844 clipboard.setPrimaryClip(clip); 8845 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis(); 8846 } 8847 8848 /** 8849 * Get the character offset closest to the specified absolute position. A typical use case is to 8850 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 8851 * 8852 * @param x The horizontal absolute position of a point on screen 8853 * @param y The vertical absolute position of a point on screen 8854 * @return the character offset for the character whose position is closest to the specified 8855 * position. Returns -1 if there is no layout. 8856 */ 8857 public int getOffsetForPosition(float x, float y) { 8858 if (getLayout() == null) return -1; 8859 final int line = getLineAtCoordinate(y); 8860 final int offset = getOffsetAtCoordinate(line, x); 8861 return offset; 8862 } 8863 8864 float convertToLocalHorizontalCoordinate(float x) { 8865 x -= getTotalPaddingLeft(); 8866 // Clamp the position to inside of the view. 8867 x = Math.max(0.0f, x); 8868 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 8869 x += getScrollX(); 8870 return x; 8871 } 8872 8873 int getLineAtCoordinate(float y) { 8874 y -= getTotalPaddingTop(); 8875 // Clamp the position to inside of the view. 8876 y = Math.max(0.0f, y); 8877 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 8878 y += getScrollY(); 8879 return getLayout().getLineForVertical((int) y); 8880 } 8881 8882 private int getOffsetAtCoordinate(int line, float x) { 8883 x = convertToLocalHorizontalCoordinate(x); 8884 return getLayout().getOffsetForHorizontal(line, x); 8885 } 8886 8887 @Override 8888 public boolean onDragEvent(DragEvent event) { 8889 switch (event.getAction()) { 8890 case DragEvent.ACTION_DRAG_STARTED: 8891 return mEditor != null && mEditor.hasInsertionController(); 8892 8893 case DragEvent.ACTION_DRAG_ENTERED: 8894 TextView.this.requestFocus(); 8895 return true; 8896 8897 case DragEvent.ACTION_DRAG_LOCATION: 8898 final int offset = getOffsetForPosition(event.getX(), event.getY()); 8899 Selection.setSelection((Spannable)mText, offset); 8900 return true; 8901 8902 case DragEvent.ACTION_DROP: 8903 if (mEditor != null) mEditor.onDrop(event); 8904 return true; 8905 8906 case DragEvent.ACTION_DRAG_ENDED: 8907 case DragEvent.ACTION_DRAG_EXITED: 8908 default: 8909 return true; 8910 } 8911 } 8912 8913 boolean isInBatchEditMode() { 8914 if (mEditor == null) return false; 8915 final Editor.InputMethodState ims = mEditor.mInputMethodState; 8916 if (ims != null) { 8917 return ims.mBatchEditNesting > 0; 8918 } 8919 return mEditor.mInBatchEditControllers; 8920 } 8921 8922 @Override 8923 public void onRtlPropertiesChanged(int layoutDirection) { 8924 super.onRtlPropertiesChanged(layoutDirection); 8925 8926 mTextDir = getTextDirectionHeuristic(); 8927 8928 if (mLayout != null) { 8929 checkForRelayout(); 8930 } 8931 } 8932 8933 TextDirectionHeuristic getTextDirectionHeuristic() { 8934 if (hasPasswordTransformationMethod()) { 8935 // passwords fields should be LTR 8936 return TextDirectionHeuristics.LTR; 8937 } 8938 8939 // Always need to resolve layout direction first 8940 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 8941 8942 // Now, we can select the heuristic 8943 switch (getTextDirection()) { 8944 default: 8945 case TEXT_DIRECTION_FIRST_STRONG: 8946 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 8947 TextDirectionHeuristics.FIRSTSTRONG_LTR); 8948 case TEXT_DIRECTION_ANY_RTL: 8949 return TextDirectionHeuristics.ANYRTL_LTR; 8950 case TEXT_DIRECTION_LTR: 8951 return TextDirectionHeuristics.LTR; 8952 case TEXT_DIRECTION_RTL: 8953 return TextDirectionHeuristics.RTL; 8954 case TEXT_DIRECTION_LOCALE: 8955 return TextDirectionHeuristics.LOCALE; 8956 } 8957 } 8958 8959 /** 8960 * @hide 8961 */ 8962 @Override 8963 public void onResolveDrawables(int layoutDirection) { 8964 // No need to resolve twice 8965 if (mLastLayoutDirection == layoutDirection) { 8966 return; 8967 } 8968 mLastLayoutDirection = layoutDirection; 8969 8970 // Resolve drawables 8971 if (mDrawables != null) { 8972 mDrawables.resolveWithLayoutDirection(layoutDirection); 8973 } 8974 } 8975 8976 /** 8977 * @hide 8978 */ 8979 protected void resetResolvedDrawables() { 8980 super.resetResolvedDrawables(); 8981 mLastLayoutDirection = -1; 8982 } 8983 8984 /** 8985 * @hide 8986 */ 8987 protected void viewClicked(InputMethodManager imm) { 8988 if (imm != null) { 8989 imm.viewClicked(this); 8990 } 8991 } 8992 8993 /** 8994 * Deletes the range of text [start, end[. 8995 * @hide 8996 */ 8997 protected void deleteText_internal(int start, int end) { 8998 ((Editable) mText).delete(start, end); 8999 } 9000 9001 /** 9002 * Replaces the range of text [start, end[ by replacement text 9003 * @hide 9004 */ 9005 protected void replaceText_internal(int start, int end, CharSequence text) { 9006 ((Editable) mText).replace(start, end, text); 9007 } 9008 9009 /** 9010 * Sets a span on the specified range of text 9011 * @hide 9012 */ 9013 protected void setSpan_internal(Object span, int start, int end, int flags) { 9014 ((Editable) mText).setSpan(span, start, end, flags); 9015 } 9016 9017 /** 9018 * Moves the cursor to the specified offset position in text 9019 * @hide 9020 */ 9021 protected void setCursorPosition_internal(int start, int end) { 9022 Selection.setSelection(((Editable) mText), start, end); 9023 } 9024 9025 /** 9026 * An Editor should be created as soon as any of the editable-specific fields (grouped 9027 * inside the Editor object) is assigned to a non-default value. 9028 * This method will create the Editor if needed. 9029 * 9030 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 9031 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 9032 * Editor for backward compatibility, as soon as one of these fields is assigned. 9033 * 9034 * Also note that for performance reasons, the mEditor is created when needed, but not 9035 * reset when no more edit-specific fields are needed. 9036 */ 9037 private void createEditorIfNeeded() { 9038 if (mEditor == null) { 9039 mEditor = new Editor(this); 9040 } 9041 } 9042 9043 /** 9044 * @hide 9045 */ 9046 @Override 9047 public CharSequence getIterableTextForAccessibility() { 9048 return mText; 9049 } 9050 9051 private void ensureIterableTextForAccessibilitySelectable() { 9052 if (!(mText instanceof Spannable)) { 9053 setText(mText, BufferType.SPANNABLE); 9054 } 9055 } 9056 9057 /** 9058 * @hide 9059 */ 9060 @Override 9061 public TextSegmentIterator getIteratorForGranularity(int granularity) { 9062 switch (granularity) { 9063 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 9064 Spannable text = (Spannable) getIterableTextForAccessibility(); 9065 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9066 AccessibilityIterators.LineTextSegmentIterator iterator = 9067 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 9068 iterator.initialize(text, getLayout()); 9069 return iterator; 9070 } 9071 } break; 9072 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 9073 Spannable text = (Spannable) getIterableTextForAccessibility(); 9074 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9075 AccessibilityIterators.PageTextSegmentIterator iterator = 9076 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 9077 iterator.initialize(this); 9078 return iterator; 9079 } 9080 } break; 9081 } 9082 return super.getIteratorForGranularity(granularity); 9083 } 9084 9085 /** 9086 * @hide 9087 */ 9088 @Override 9089 public int getAccessibilitySelectionStart() { 9090 return getSelectionStart(); 9091 } 9092 9093 /** 9094 * @hide 9095 */ 9096 public boolean isAccessibilitySelectionExtendable() { 9097 return true; 9098 } 9099 9100 /** 9101 * @hide 9102 */ 9103 @Override 9104 public int getAccessibilitySelectionEnd() { 9105 return getSelectionEnd(); 9106 } 9107 9108 /** 9109 * @hide 9110 */ 9111 @Override 9112 public void setAccessibilitySelection(int start, int end) { 9113 if (getAccessibilitySelectionStart() == start 9114 && getAccessibilitySelectionEnd() == end) { 9115 return; 9116 } 9117 // Hide all selection controllers used for adjusting selection 9118 // since we are doing so explicitlty by other means and these 9119 // controllers interact with how selection behaves. 9120 if (mEditor != null) { 9121 mEditor.hideControllers(); 9122 } 9123 CharSequence text = getIterableTextForAccessibility(); 9124 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 9125 Selection.setSelection((Spannable) text, start, end); 9126 } else { 9127 Selection.removeSelection((Spannable) text); 9128 } 9129 } 9130 9131 /** 9132 * User interface state that is stored by TextView for implementing 9133 * {@link View#onSaveInstanceState}. 9134 */ 9135 public static class SavedState extends BaseSavedState { 9136 int selStart; 9137 int selEnd; 9138 CharSequence text; 9139 boolean frozenWithFocus; 9140 CharSequence error; 9141 9142 SavedState(Parcelable superState) { 9143 super(superState); 9144 } 9145 9146 @Override 9147 public void writeToParcel(Parcel out, int flags) { 9148 super.writeToParcel(out, flags); 9149 out.writeInt(selStart); 9150 out.writeInt(selEnd); 9151 out.writeInt(frozenWithFocus ? 1 : 0); 9152 TextUtils.writeToParcel(text, out, flags); 9153 9154 if (error == null) { 9155 out.writeInt(0); 9156 } else { 9157 out.writeInt(1); 9158 TextUtils.writeToParcel(error, out, flags); 9159 } 9160 } 9161 9162 @Override 9163 public String toString() { 9164 String str = "TextView.SavedState{" 9165 + Integer.toHexString(System.identityHashCode(this)) 9166 + " start=" + selStart + " end=" + selEnd; 9167 if (text != null) { 9168 str += " text=" + text; 9169 } 9170 return str + "}"; 9171 } 9172 9173 @SuppressWarnings("hiding") 9174 public static final Parcelable.Creator<SavedState> CREATOR 9175 = new Parcelable.Creator<SavedState>() { 9176 public SavedState createFromParcel(Parcel in) { 9177 return new SavedState(in); 9178 } 9179 9180 public SavedState[] newArray(int size) { 9181 return new SavedState[size]; 9182 } 9183 }; 9184 9185 private SavedState(Parcel in) { 9186 super(in); 9187 selStart = in.readInt(); 9188 selEnd = in.readInt(); 9189 frozenWithFocus = (in.readInt() != 0); 9190 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 9191 9192 if (in.readInt() != 0) { 9193 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 9194 } 9195 } 9196 } 9197 9198 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 9199 private char[] mChars; 9200 private int mStart, mLength; 9201 9202 public CharWrapper(char[] chars, int start, int len) { 9203 mChars = chars; 9204 mStart = start; 9205 mLength = len; 9206 } 9207 9208 /* package */ void set(char[] chars, int start, int len) { 9209 mChars = chars; 9210 mStart = start; 9211 mLength = len; 9212 } 9213 9214 public int length() { 9215 return mLength; 9216 } 9217 9218 public char charAt(int off) { 9219 return mChars[off + mStart]; 9220 } 9221 9222 @Override 9223 public String toString() { 9224 return new String(mChars, mStart, mLength); 9225 } 9226 9227 public CharSequence subSequence(int start, int end) { 9228 if (start < 0 || end < 0 || start > mLength || end > mLength) { 9229 throw new IndexOutOfBoundsException(start + ", " + end); 9230 } 9231 9232 return new String(mChars, start + mStart, end - start); 9233 } 9234 9235 public void getChars(int start, int end, char[] buf, int off) { 9236 if (start < 0 || end < 0 || start > mLength || end > mLength) { 9237 throw new IndexOutOfBoundsException(start + ", " + end); 9238 } 9239 9240 System.arraycopy(mChars, start + mStart, buf, off, end - start); 9241 } 9242 9243 public void drawText(Canvas c, int start, int end, 9244 float x, float y, Paint p) { 9245 c.drawText(mChars, start + mStart, end - start, x, y, p); 9246 } 9247 9248 public void drawTextRun(Canvas c, int start, int end, 9249 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 9250 int count = end - start; 9251 int contextCount = contextEnd - contextStart; 9252 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 9253 contextCount, x, y, isRtl, p); 9254 } 9255 9256 public float measureText(int start, int end, Paint p) { 9257 return p.measureText(mChars, start + mStart, end - start); 9258 } 9259 9260 public int getTextWidths(int start, int end, float[] widths, Paint p) { 9261 return p.getTextWidths(mChars, start + mStart, end - start, widths); 9262 } 9263 9264 public float getTextRunAdvances(int start, int end, int contextStart, 9265 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 9266 Paint p) { 9267 int count = end - start; 9268 int contextCount = contextEnd - contextStart; 9269 return p.getTextRunAdvances(mChars, start + mStart, count, 9270 contextStart + mStart, contextCount, isRtl, advances, 9271 advancesIndex); 9272 } 9273 9274 public int getTextRunCursor(int contextStart, int contextEnd, int dir, 9275 int offset, int cursorOpt, Paint p) { 9276 int contextCount = contextEnd - contextStart; 9277 return p.getTextRunCursor(mChars, contextStart + mStart, 9278 contextCount, dir, offset + mStart, cursorOpt); 9279 } 9280 } 9281 9282 private static final class Marquee { 9283 // TODO: Add an option to configure this 9284 private static final float MARQUEE_DELTA_MAX = 0.07f; 9285 private static final int MARQUEE_DELAY = 1200; 9286 private static final int MARQUEE_RESTART_DELAY = 1200; 9287 private static final int MARQUEE_DP_PER_SECOND = 30; 9288 9289 private static final byte MARQUEE_STOPPED = 0x0; 9290 private static final byte MARQUEE_STARTING = 0x1; 9291 private static final byte MARQUEE_RUNNING = 0x2; 9292 9293 private final WeakReference<TextView> mView; 9294 private final Choreographer mChoreographer; 9295 9296 private byte mStatus = MARQUEE_STOPPED; 9297 private final float mPixelsPerSecond; 9298 private float mMaxScroll; 9299 private float mMaxFadeScroll; 9300 private float mGhostStart; 9301 private float mGhostOffset; 9302 private float mFadeStop; 9303 private int mRepeatLimit; 9304 9305 private float mScroll; 9306 private long mLastAnimationMs; 9307 9308 Marquee(TextView v) { 9309 final float density = v.getContext().getResources().getDisplayMetrics().density; 9310 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; 9311 mView = new WeakReference<TextView>(v); 9312 mChoreographer = Choreographer.getInstance(); 9313 } 9314 9315 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 9316 @Override 9317 public void doFrame(long frameTimeNanos) { 9318 tick(); 9319 } 9320 }; 9321 9322 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 9323 @Override 9324 public void doFrame(long frameTimeNanos) { 9325 mStatus = MARQUEE_RUNNING; 9326 mLastAnimationMs = mChoreographer.getFrameTime(); 9327 tick(); 9328 } 9329 }; 9330 9331 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 9332 @Override 9333 public void doFrame(long frameTimeNanos) { 9334 if (mStatus == MARQUEE_RUNNING) { 9335 if (mRepeatLimit >= 0) { 9336 mRepeatLimit--; 9337 } 9338 start(mRepeatLimit); 9339 } 9340 } 9341 }; 9342 9343 void tick() { 9344 if (mStatus != MARQUEE_RUNNING) { 9345 return; 9346 } 9347 9348 mChoreographer.removeFrameCallback(mTickCallback); 9349 9350 final TextView textView = mView.get(); 9351 if (textView != null && (textView.isFocused() || textView.isSelected())) { 9352 long currentMs = mChoreographer.getFrameTime(); 9353 long deltaMs = currentMs - mLastAnimationMs; 9354 mLastAnimationMs = currentMs; 9355 float deltaPx = deltaMs / 1000f * mPixelsPerSecond; 9356 mScroll += deltaPx; 9357 if (mScroll > mMaxScroll) { 9358 mScroll = mMaxScroll; 9359 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 9360 } else { 9361 mChoreographer.postFrameCallback(mTickCallback); 9362 } 9363 textView.invalidate(); 9364 } 9365 } 9366 9367 void stop() { 9368 mStatus = MARQUEE_STOPPED; 9369 mChoreographer.removeFrameCallback(mStartCallback); 9370 mChoreographer.removeFrameCallback(mRestartCallback); 9371 mChoreographer.removeFrameCallback(mTickCallback); 9372 resetScroll(); 9373 } 9374 9375 private void resetScroll() { 9376 mScroll = 0.0f; 9377 final TextView textView = mView.get(); 9378 if (textView != null) textView.invalidate(); 9379 } 9380 9381 void start(int repeatLimit) { 9382 if (repeatLimit == 0) { 9383 stop(); 9384 return; 9385 } 9386 mRepeatLimit = repeatLimit; 9387 final TextView textView = mView.get(); 9388 if (textView != null && textView.mLayout != null) { 9389 mStatus = MARQUEE_STARTING; 9390 mScroll = 0.0f; 9391 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - 9392 textView.getCompoundPaddingRight(); 9393 final float lineWidth = textView.mLayout.getLineWidth(0); 9394 final float gap = textWidth / 3.0f; 9395 mGhostStart = lineWidth - textWidth + gap; 9396 mMaxScroll = mGhostStart + textWidth; 9397 mGhostOffset = lineWidth + gap; 9398 mFadeStop = lineWidth + textWidth / 6.0f; 9399 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 9400 9401 textView.invalidate(); 9402 mChoreographer.postFrameCallback(mStartCallback); 9403 } 9404 } 9405 9406 float getGhostOffset() { 9407 return mGhostOffset; 9408 } 9409 9410 float getScroll() { 9411 return mScroll; 9412 } 9413 9414 float getMaxFadeScroll() { 9415 return mMaxFadeScroll; 9416 } 9417 9418 boolean shouldDrawLeftFade() { 9419 return mScroll <= mFadeStop; 9420 } 9421 9422 boolean shouldDrawGhost() { 9423 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 9424 } 9425 9426 boolean isRunning() { 9427 return mStatus == MARQUEE_RUNNING; 9428 } 9429 9430 boolean isStopped() { 9431 return mStatus == MARQUEE_STOPPED; 9432 } 9433 } 9434 9435 private class ChangeWatcher implements TextWatcher, SpanWatcher { 9436 9437 private CharSequence mBeforeText; 9438 9439 public void beforeTextChanged(CharSequence buffer, int start, 9440 int before, int after) { 9441 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start 9442 + " before=" + before + " after=" + after + ": " + buffer); 9443 9444 if (AccessibilityManager.getInstance(mContext).isEnabled() 9445 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) 9446 || shouldSpeakPasswordsForAccessibility())) { 9447 mBeforeText = buffer.toString(); 9448 } 9449 9450 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 9451 } 9452 9453 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 9454 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start 9455 + " before=" + before + " after=" + after + ": " + buffer); 9456 TextView.this.handleTextChanged(buffer, start, before, after); 9457 9458 if (AccessibilityManager.getInstance(mContext).isEnabled() && 9459 (isFocused() || isSelected() && isShown())) { 9460 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 9461 mBeforeText = null; 9462 } 9463 } 9464 9465 public void afterTextChanged(Editable buffer) { 9466 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer); 9467 TextView.this.sendAfterTextChanged(buffer); 9468 9469 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 9470 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 9471 } 9472 } 9473 9474 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 9475 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 9476 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 9477 TextView.this.spanChange(buf, what, s, st, e, en); 9478 } 9479 9480 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 9481 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e 9482 + " what=" + what + ": " + buf); 9483 TextView.this.spanChange(buf, what, -1, s, -1, e); 9484 } 9485 9486 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 9487 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e 9488 + " what=" + what + ": " + buf); 9489 TextView.this.spanChange(buf, what, s, -1, e, -1); 9490 } 9491 } 9492} 9493