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