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