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