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