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