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