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