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