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