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