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