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