TextView.java revision 74e7b26a1099b5531096512adf190157c563fd48
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 static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 20import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 21import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 22import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 23 24import android.R; 25import android.annotation.ColorInt; 26import android.annotation.DrawableRes; 27import android.annotation.FloatRange; 28import android.annotation.IntDef; 29import android.annotation.NonNull; 30import android.annotation.Nullable; 31import android.annotation.Size; 32import android.annotation.StringRes; 33import android.annotation.StyleRes; 34import android.annotation.XmlRes; 35import android.app.Activity; 36import android.app.assist.AssistStructure; 37import android.content.ClipData; 38import android.content.ClipDescription; 39import android.content.ClipboardManager; 40import android.content.Context; 41import android.content.Intent; 42import android.content.UndoManager; 43import android.content.res.ColorStateList; 44import android.content.res.CompatibilityInfo; 45import android.content.res.Configuration; 46import android.content.res.Resources; 47import android.content.res.TypedArray; 48import android.content.res.XmlResourceParser; 49import android.graphics.BaseCanvas; 50import android.graphics.Canvas; 51import android.graphics.Insets; 52import android.graphics.Paint; 53import android.graphics.Path; 54import android.graphics.PorterDuff; 55import android.graphics.Rect; 56import android.graphics.RectF; 57import android.graphics.Typeface; 58import android.graphics.drawable.Drawable; 59import android.graphics.fonts.FontVariationAxis; 60import android.icu.text.DecimalFormatSymbols; 61import android.os.AsyncTask; 62import android.os.Build.VERSION_CODES; 63import android.os.Bundle; 64import android.os.LocaleList; 65import android.os.Parcel; 66import android.os.Parcelable; 67import android.os.ParcelableParcel; 68import android.os.SystemClock; 69import android.provider.Settings; 70import android.text.BoringLayout; 71import android.text.DynamicLayout; 72import android.text.Editable; 73import android.text.GetChars; 74import android.text.GraphicsOperations; 75import android.text.InputFilter; 76import android.text.InputType; 77import android.text.Layout; 78import android.text.ParcelableSpan; 79import android.text.Selection; 80import android.text.SpanWatcher; 81import android.text.Spannable; 82import android.text.SpannableStringBuilder; 83import android.text.Spanned; 84import android.text.SpannedString; 85import android.text.StaticLayout; 86import android.text.TextDirectionHeuristic; 87import android.text.TextDirectionHeuristics; 88import android.text.TextPaint; 89import android.text.TextUtils; 90import android.text.TextUtils.TruncateAt; 91import android.text.TextWatcher; 92import android.text.method.AllCapsTransformationMethod; 93import android.text.method.ArrowKeyMovementMethod; 94import android.text.method.DateKeyListener; 95import android.text.method.DateTimeKeyListener; 96import android.text.method.DialerKeyListener; 97import android.text.method.DigitsKeyListener; 98import android.text.method.KeyListener; 99import android.text.method.LinkMovementMethod; 100import android.text.method.MetaKeyKeyListener; 101import android.text.method.MovementMethod; 102import android.text.method.PasswordTransformationMethod; 103import android.text.method.SingleLineTransformationMethod; 104import android.text.method.TextKeyListener; 105import android.text.method.TimeKeyListener; 106import android.text.method.TransformationMethod; 107import android.text.method.TransformationMethod2; 108import android.text.method.WordIterator; 109import android.text.style.CharacterStyle; 110import android.text.style.ClickableSpan; 111import android.text.style.ParagraphStyle; 112import android.text.style.SpellCheckSpan; 113import android.text.style.SuggestionSpan; 114import android.text.style.URLSpan; 115import android.text.style.UpdateAppearance; 116import android.text.util.Linkify; 117import android.util.AttributeSet; 118import android.util.DisplayMetrics; 119import android.util.IntArray; 120import android.util.Log; 121import android.util.TypedValue; 122import android.view.AccessibilityIterators.TextSegmentIterator; 123import android.view.ActionMode; 124import android.view.Choreographer; 125import android.view.ContextMenu; 126import android.view.DragEvent; 127import android.view.Gravity; 128import android.view.HapticFeedbackConstants; 129import android.view.InputDevice; 130import android.view.KeyCharacterMap; 131import android.view.KeyEvent; 132import android.view.MotionEvent; 133import android.view.PointerIcon; 134import android.view.View; 135import android.view.ViewConfiguration; 136import android.view.ViewDebug; 137import android.view.ViewGroup.LayoutParams; 138import android.view.ViewHierarchyEncoder; 139import android.view.ViewParent; 140import android.view.ViewRootImpl; 141import android.view.ViewStructure; 142import android.view.ViewTreeObserver; 143import android.view.accessibility.AccessibilityEvent; 144import android.view.accessibility.AccessibilityManager; 145import android.view.accessibility.AccessibilityNodeInfo; 146import android.view.animation.AnimationUtils; 147import android.view.autofill.AutofillManager; 148import android.view.autofill.AutofillValue; 149import android.view.inputmethod.BaseInputConnection; 150import android.view.inputmethod.CompletionInfo; 151import android.view.inputmethod.CorrectionInfo; 152import android.view.inputmethod.CursorAnchorInfo; 153import android.view.inputmethod.EditorInfo; 154import android.view.inputmethod.ExtractedText; 155import android.view.inputmethod.ExtractedTextRequest; 156import android.view.inputmethod.InputConnection; 157import android.view.inputmethod.InputMethodManager; 158import android.view.textclassifier.TextClassificationManager; 159import android.view.textclassifier.TextClassifier; 160import android.view.textservice.SpellCheckerSubtype; 161import android.view.textservice.TextServicesManager; 162import android.widget.RemoteViews.RemoteView; 163 164import com.android.internal.annotations.VisibleForTesting; 165import com.android.internal.logging.MetricsLogger; 166import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 167import com.android.internal.util.FastMath; 168import com.android.internal.widget.EditableInputConnection; 169 170import libcore.util.EmptyArray; 171 172import org.xmlpull.v1.XmlPullParserException; 173 174import java.io.IOException; 175import java.lang.annotation.Retention; 176import java.lang.annotation.RetentionPolicy; 177import java.lang.ref.WeakReference; 178import java.util.ArrayList; 179import java.util.Arrays; 180import java.util.Locale; 181 182/** 183 * A user interface element that displays text to the user. 184 * To provide user-editable text, see {@link EditText}. 185 * <p> 186 * The following code sample shows a typical use, with an XML layout 187 * and code to modify the contents of the text view: 188 * </p> 189 190 * <pre> 191 * <LinearLayout 192 xmlns:android="http://schemas.android.com/apk/res/android" 193 android:layout_width="match_parent" 194 android:layout_height="match_parent"> 195 * <TextView 196 * android:id="@+id/text_view_id" 197 * android:layout_height="wrap_content" 198 * android:layout_width="wrap_content" 199 * android:text="@string/hello" /> 200 * </LinearLayout> 201 * </pre> 202 * <p> 203 * This code sample demonstrates how to modify the contents of the text view 204 * defined in the previous XML layout: 205 * </p> 206 * <pre> 207 * public class MainActivity extends Activity { 208 * 209 * protected void onCreate(Bundle savedInstanceState) { 210 * super.onCreate(savedInstanceState); 211 * setContentView(R.layout.activity_main); 212 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 213 * helloTextView.setText(R.string.user_greeting); 214 * } 215 * } 216 * </pre> 217 * <p> 218 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 219 * </p> 220 * <p> 221 * <b>XML attributes</b> 222 * <p> 223 * See {@link android.R.styleable#TextView TextView Attributes}, 224 * {@link android.R.styleable#View View Attributes} 225 * 226 * @attr ref android.R.styleable#TextView_text 227 * @attr ref android.R.styleable#TextView_bufferType 228 * @attr ref android.R.styleable#TextView_hint 229 * @attr ref android.R.styleable#TextView_textColor 230 * @attr ref android.R.styleable#TextView_textColorHighlight 231 * @attr ref android.R.styleable#TextView_textColorHint 232 * @attr ref android.R.styleable#TextView_textAppearance 233 * @attr ref android.R.styleable#TextView_textColorLink 234 * @attr ref android.R.styleable#TextView_textSize 235 * @attr ref android.R.styleable#TextView_textScaleX 236 * @attr ref android.R.styleable#TextView_fontFamily 237 * @attr ref android.R.styleable#TextView_typeface 238 * @attr ref android.R.styleable#TextView_textStyle 239 * @attr ref android.R.styleable#TextView_cursorVisible 240 * @attr ref android.R.styleable#TextView_maxLines 241 * @attr ref android.R.styleable#TextView_maxHeight 242 * @attr ref android.R.styleable#TextView_lines 243 * @attr ref android.R.styleable#TextView_height 244 * @attr ref android.R.styleable#TextView_minLines 245 * @attr ref android.R.styleable#TextView_minHeight 246 * @attr ref android.R.styleable#TextView_maxEms 247 * @attr ref android.R.styleable#TextView_maxWidth 248 * @attr ref android.R.styleable#TextView_ems 249 * @attr ref android.R.styleable#TextView_width 250 * @attr ref android.R.styleable#TextView_minEms 251 * @attr ref android.R.styleable#TextView_minWidth 252 * @attr ref android.R.styleable#TextView_gravity 253 * @attr ref android.R.styleable#TextView_scrollHorizontally 254 * @attr ref android.R.styleable#TextView_password 255 * @attr ref android.R.styleable#TextView_singleLine 256 * @attr ref android.R.styleable#TextView_selectAllOnFocus 257 * @attr ref android.R.styleable#TextView_includeFontPadding 258 * @attr ref android.R.styleable#TextView_maxLength 259 * @attr ref android.R.styleable#TextView_shadowColor 260 * @attr ref android.R.styleable#TextView_shadowDx 261 * @attr ref android.R.styleable#TextView_shadowDy 262 * @attr ref android.R.styleable#TextView_shadowRadius 263 * @attr ref android.R.styleable#TextView_autoLink 264 * @attr ref android.R.styleable#TextView_linksClickable 265 * @attr ref android.R.styleable#TextView_numeric 266 * @attr ref android.R.styleable#TextView_digits 267 * @attr ref android.R.styleable#TextView_phoneNumber 268 * @attr ref android.R.styleable#TextView_inputMethod 269 * @attr ref android.R.styleable#TextView_capitalize 270 * @attr ref android.R.styleable#TextView_autoText 271 * @attr ref android.R.styleable#TextView_editable 272 * @attr ref android.R.styleable#TextView_freezesText 273 * @attr ref android.R.styleable#TextView_ellipsize 274 * @attr ref android.R.styleable#TextView_drawableTop 275 * @attr ref android.R.styleable#TextView_drawableBottom 276 * @attr ref android.R.styleable#TextView_drawableRight 277 * @attr ref android.R.styleable#TextView_drawableLeft 278 * @attr ref android.R.styleable#TextView_drawableStart 279 * @attr ref android.R.styleable#TextView_drawableEnd 280 * @attr ref android.R.styleable#TextView_drawablePadding 281 * @attr ref android.R.styleable#TextView_drawableTint 282 * @attr ref android.R.styleable#TextView_drawableTintMode 283 * @attr ref android.R.styleable#TextView_lineSpacingExtra 284 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 285 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 286 * @attr ref android.R.styleable#TextView_inputType 287 * @attr ref android.R.styleable#TextView_imeOptions 288 * @attr ref android.R.styleable#TextView_privateImeOptions 289 * @attr ref android.R.styleable#TextView_imeActionLabel 290 * @attr ref android.R.styleable#TextView_imeActionId 291 * @attr ref android.R.styleable#TextView_editorExtras 292 * @attr ref android.R.styleable#TextView_elegantTextHeight 293 * @attr ref android.R.styleable#TextView_letterSpacing 294 * @attr ref android.R.styleable#TextView_fontFeatureSettings 295 * @attr ref android.R.styleable#TextView_breakStrategy 296 * @attr ref android.R.styleable#TextView_hyphenationFrequency 297 * @attr ref android.R.styleable#TextView_autoSizeTextType 298 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 299 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 300 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 301 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 302 */ 303@RemoteView 304public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 305 static final String LOG_TAG = "TextView"; 306 static final boolean DEBUG_EXTRACT = false; 307 static final boolean DEBUG_AUTOFILL = false; 308 private static final float[] TEMP_POSITION = new float[2]; 309 310 // Enum for the "typeface" XML parameter. 311 // TODO: How can we get this from the XML instead of hardcoding it here? 312 private static final int SANS = 1; 313 private static final int SERIF = 2; 314 private static final int MONOSPACE = 3; 315 316 // Bitfield for the "numeric" XML parameter. 317 // TODO: How can we get this from the XML instead of hardcoding it here? 318 private static final int SIGNED = 2; 319 private static final int DECIMAL = 4; 320 321 /** 322 * Draw marquee text with fading edges as usual 323 */ 324 private static final int MARQUEE_FADE_NORMAL = 0; 325 326 /** 327 * Draw marquee text as ellipsize end while inactive instead of with the fade. 328 * (Useful for devices where the fade can be expensive if overdone) 329 */ 330 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 331 332 /** 333 * Draw marquee text with fading edges because it is currently active/animating. 334 */ 335 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 336 337 private static final int LINES = 1; 338 private static final int EMS = LINES; 339 private static final int PIXELS = 2; 340 341 private static final RectF TEMP_RECTF = new RectF(); 342 343 /** @hide */ 344 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 345 private static final int ANIMATED_SCROLL_GAP = 250; 346 347 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 348 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 349 350 private static final int CHANGE_WATCHER_PRIORITY = 100; 351 352 // New state used to change background based on whether this TextView is multiline. 353 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 354 355 // Accessibility action to share selected text. 356 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 357 358 /** 359 * @hide 360 */ 361 // Accessibility action start id for "process text" actions. 362 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 363 364 /** 365 * @hide 366 */ 367 static final int PROCESS_TEXT_REQUEST_CODE = 100; 368 369 /** 370 * Return code of {@link #doKeyDown}. 371 */ 372 private static final int KEY_EVENT_NOT_HANDLED = 0; 373 private static final int KEY_EVENT_HANDLED = -1; 374 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 375 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 376 377 // System wide time for last cut, copy or text changed action. 378 static long sLastCutCopyOrTextChangedTime; 379 380 private ColorStateList mTextColor; 381 private ColorStateList mHintTextColor; 382 private ColorStateList mLinkTextColor; 383 @ViewDebug.ExportedProperty(category = "text") 384 private int mCurTextColor; 385 private int mCurHintTextColor; 386 private boolean mFreezesText; 387 388 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 389 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 390 391 private float mShadowRadius, mShadowDx, mShadowDy; 392 private int mShadowColor; 393 394 private boolean mPreDrawRegistered; 395 private boolean mPreDrawListenerDetached; 396 397 private TextClassifier mTextClassifier; 398 399 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 400 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 401 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 402 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 403 // into by the user holding the movement key down) then we shouldn't prevent the focus from 404 // changing. 405 private boolean mPreventDefaultMovement; 406 407 private TextUtils.TruncateAt mEllipsize; 408 409 static class Drawables { 410 static final int LEFT = 0; 411 static final int TOP = 1; 412 static final int RIGHT = 2; 413 static final int BOTTOM = 3; 414 415 static final int DRAWABLE_NONE = -1; 416 static final int DRAWABLE_RIGHT = 0; 417 static final int DRAWABLE_LEFT = 1; 418 419 final Rect mCompoundRect = new Rect(); 420 421 final Drawable[] mShowing = new Drawable[4]; 422 423 ColorStateList mTintList; 424 PorterDuff.Mode mTintMode; 425 boolean mHasTint; 426 boolean mHasTintMode; 427 428 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 429 Drawable mDrawableLeftInitial, mDrawableRightInitial; 430 431 boolean mIsRtlCompatibilityMode; 432 boolean mOverride; 433 434 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 435 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 436 437 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 438 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 439 440 int mDrawablePadding; 441 442 int mDrawableSaved = DRAWABLE_NONE; 443 444 public Drawables(Context context) { 445 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 446 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 447 || !context.getApplicationInfo().hasRtlSupport(); 448 mOverride = false; 449 } 450 451 /** 452 * @return {@code true} if this object contains metadata that needs to 453 * be retained, {@code false} otherwise 454 */ 455 public boolean hasMetadata() { 456 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 457 } 458 459 /** 460 * Updates the list of displayed drawables to account for the current 461 * layout direction. 462 * 463 * @param layoutDirection the current layout direction 464 * @return {@code true} if the displayed drawables changed 465 */ 466 public boolean resolveWithLayoutDirection(int layoutDirection) { 467 final Drawable previousLeft = mShowing[Drawables.LEFT]; 468 final Drawable previousRight = mShowing[Drawables.RIGHT]; 469 470 // First reset "left" and "right" drawables to their initial values 471 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 472 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 473 474 if (mIsRtlCompatibilityMode) { 475 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 476 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 477 mShowing[Drawables.LEFT] = mDrawableStart; 478 mDrawableSizeLeft = mDrawableSizeStart; 479 mDrawableHeightLeft = mDrawableHeightStart; 480 } 481 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 482 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 483 mShowing[Drawables.RIGHT] = mDrawableEnd; 484 mDrawableSizeRight = mDrawableSizeEnd; 485 mDrawableHeightRight = mDrawableHeightEnd; 486 } 487 } else { 488 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 489 // drawable if and only if they have been defined 490 switch(layoutDirection) { 491 case LAYOUT_DIRECTION_RTL: 492 if (mOverride) { 493 mShowing[Drawables.RIGHT] = mDrawableStart; 494 mDrawableSizeRight = mDrawableSizeStart; 495 mDrawableHeightRight = mDrawableHeightStart; 496 497 mShowing[Drawables.LEFT] = mDrawableEnd; 498 mDrawableSizeLeft = mDrawableSizeEnd; 499 mDrawableHeightLeft = mDrawableHeightEnd; 500 } 501 break; 502 503 case LAYOUT_DIRECTION_LTR: 504 default: 505 if (mOverride) { 506 mShowing[Drawables.LEFT] = mDrawableStart; 507 mDrawableSizeLeft = mDrawableSizeStart; 508 mDrawableHeightLeft = mDrawableHeightStart; 509 510 mShowing[Drawables.RIGHT] = mDrawableEnd; 511 mDrawableSizeRight = mDrawableSizeEnd; 512 mDrawableHeightRight = mDrawableHeightEnd; 513 } 514 break; 515 } 516 } 517 518 applyErrorDrawableIfNeeded(layoutDirection); 519 520 return mShowing[Drawables.LEFT] != previousLeft 521 || mShowing[Drawables.RIGHT] != previousRight; 522 } 523 524 public void setErrorDrawable(Drawable dr, TextView tv) { 525 if (mDrawableError != dr && mDrawableError != null) { 526 mDrawableError.setCallback(null); 527 } 528 mDrawableError = dr; 529 530 if (mDrawableError != null) { 531 final Rect compoundRect = mCompoundRect; 532 final int[] state = tv.getDrawableState(); 533 534 mDrawableError.setState(state); 535 mDrawableError.copyBounds(compoundRect); 536 mDrawableError.setCallback(tv); 537 mDrawableSizeError = compoundRect.width(); 538 mDrawableHeightError = compoundRect.height(); 539 } else { 540 mDrawableSizeError = mDrawableHeightError = 0; 541 } 542 } 543 544 private void applyErrorDrawableIfNeeded(int layoutDirection) { 545 // first restore the initial state if needed 546 switch (mDrawableSaved) { 547 case DRAWABLE_LEFT: 548 mShowing[Drawables.LEFT] = mDrawableTemp; 549 mDrawableSizeLeft = mDrawableSizeTemp; 550 mDrawableHeightLeft = mDrawableHeightTemp; 551 break; 552 case DRAWABLE_RIGHT: 553 mShowing[Drawables.RIGHT] = mDrawableTemp; 554 mDrawableSizeRight = mDrawableSizeTemp; 555 mDrawableHeightRight = mDrawableHeightTemp; 556 break; 557 case DRAWABLE_NONE: 558 default: 559 } 560 // then, if needed, assign the Error drawable to the correct location 561 if (mDrawableError != null) { 562 switch(layoutDirection) { 563 case LAYOUT_DIRECTION_RTL: 564 mDrawableSaved = DRAWABLE_LEFT; 565 566 mDrawableTemp = mShowing[Drawables.LEFT]; 567 mDrawableSizeTemp = mDrawableSizeLeft; 568 mDrawableHeightTemp = mDrawableHeightLeft; 569 570 mShowing[Drawables.LEFT] = mDrawableError; 571 mDrawableSizeLeft = mDrawableSizeError; 572 mDrawableHeightLeft = mDrawableHeightError; 573 break; 574 case LAYOUT_DIRECTION_LTR: 575 default: 576 mDrawableSaved = DRAWABLE_RIGHT; 577 578 mDrawableTemp = mShowing[Drawables.RIGHT]; 579 mDrawableSizeTemp = mDrawableSizeRight; 580 mDrawableHeightTemp = mDrawableHeightRight; 581 582 mShowing[Drawables.RIGHT] = mDrawableError; 583 mDrawableSizeRight = mDrawableSizeError; 584 mDrawableHeightRight = mDrawableHeightError; 585 break; 586 } 587 } 588 } 589 } 590 591 Drawables mDrawables; 592 593 private CharWrapper mCharWrapper; 594 595 private Marquee mMarquee; 596 private boolean mRestartMarquee; 597 598 private int mMarqueeRepeatLimit = 3; 599 600 private int mLastLayoutDirection = -1; 601 602 /** 603 * On some devices the fading edges add a performance penalty if used 604 * extensively in the same layout. This mode indicates how the marquee 605 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 606 */ 607 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 608 609 /** 610 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 611 * the layout that should be used when the mode switches. 612 */ 613 private Layout mSavedMarqueeModeLayout; 614 615 @ViewDebug.ExportedProperty(category = "text") 616 private CharSequence mText; 617 private CharSequence mTransformed; 618 private BufferType mBufferType = BufferType.NORMAL; 619 620 private CharSequence mHint; 621 private Layout mHintLayout; 622 623 private MovementMethod mMovement; 624 625 private TransformationMethod mTransformation; 626 private boolean mAllowTransformationLengthChange; 627 private ChangeWatcher mChangeWatcher; 628 629 private ArrayList<TextWatcher> mListeners; 630 631 // display attributes 632 private final TextPaint mTextPaint; 633 private boolean mUserSetTextScaleX; 634 private Layout mLayout; 635 private boolean mLocalesChanged = false; 636 637 // True if setKeyListener() has been explicitly called 638 private boolean mListenerChanged = false; 639 // True if internationalized input should be used for numbers and date and time. 640 private final boolean mUseInternationalizedInput; 641 642 @ViewDebug.ExportedProperty(category = "text") 643 private int mGravity = Gravity.TOP | Gravity.START; 644 private boolean mHorizontallyScrolling; 645 646 private int mAutoLinkMask; 647 private boolean mLinksClickable = true; 648 649 private float mSpacingMult = 1.0f; 650 private float mSpacingAdd = 0.0f; 651 652 private int mBreakStrategy; 653 private int mHyphenationFrequency; 654 private int mJustificationMode; 655 656 private int mMaximum = Integer.MAX_VALUE; 657 private int mMaxMode = LINES; 658 private int mMinimum = 0; 659 private int mMinMode = LINES; 660 661 private int mOldMaximum = mMaximum; 662 private int mOldMaxMode = mMaxMode; 663 664 private int mMaxWidth = Integer.MAX_VALUE; 665 private int mMaxWidthMode = PIXELS; 666 private int mMinWidth = 0; 667 private int mMinWidthMode = PIXELS; 668 669 private boolean mSingleLine; 670 private int mDesiredHeightAtMeasure = -1; 671 private boolean mIncludePad = true; 672 private int mDeferScroll = -1; 673 674 // tmp primitives, so we don't alloc them on each draw 675 private Rect mTempRect; 676 private long mLastScroll; 677 private Scroller mScroller; 678 private TextPaint mTempTextPaint; 679 680 private BoringLayout.Metrics mBoring, mHintBoring; 681 private BoringLayout mSavedLayout, mSavedHintLayout; 682 683 private TextDirectionHeuristic mTextDir; 684 685 private InputFilter[] mFilters = NO_FILTERS; 686 687 private volatile Locale mCurrentSpellCheckerLocaleCache; 688 689 // It is possible to have a selection even when mEditor is null (programmatically set, like when 690 // a link is pressed). These highlight-related fields do not go in mEditor. 691 int mHighlightColor = 0x6633B5E5; 692 private Path mHighlightPath; 693 private final Paint mHighlightPaint; 694 private boolean mHighlightPathBogus = true; 695 696 // Although these fields are specific to editable text, they are not added to Editor because 697 // they are defined by the TextView's style and are theme-dependent. 698 int mCursorDrawableRes; 699 // These six fields, could be moved to Editor, since we know their default values and we 700 // could condition the creation of the Editor to a non standard value. This is however 701 // brittle since the hardcoded values here (such as 702 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the 703 // default style is modified. 704 int mTextSelectHandleLeftRes; 705 int mTextSelectHandleRightRes; 706 int mTextSelectHandleRes; 707 int mTextEditSuggestionItemLayout; 708 int mTextEditSuggestionContainerLayout; 709 int mTextEditSuggestionHighlightStyle; 710 711 /** 712 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 713 * See {@link #createEditorIfNeeded()}. 714 */ 715 private Editor mEditor; 716 717 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 718 private static final int DEVICE_PROVISIONED_NO = 1; 719 private static final int DEVICE_PROVISIONED_YES = 2; 720 721 /** 722 * Some special options such as sharing selected text should only be shown if the device 723 * is provisioned. Only check the provisioned state once for a given view instance. 724 */ 725 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 726 727 /** 728 * The TextView does not auto-size text (default). 729 */ 730 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 731 732 /** 733 * The TextView scales text size both horizontally and vertically to fit within the 734 * container. 735 */ 736 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 737 738 /** @hide */ 739 @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM}) 740 @Retention(RetentionPolicy.SOURCE) 741 public @interface AutoSizeTextType {} 742 // Default minimum size for auto-sizing text in scaled pixels. 743 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 744 // Default maximum size for auto-sizing text in scaled pixels. 745 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 746 // Default value for the step size in pixels. 747 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 748 // Use this to specify that any of the auto-size configuration int values have not been set. 749 private static final int UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1; 750 // Auto-size text type. 751 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 752 // Specify if auto-size text is needed. 753 private boolean mNeedsAutoSizeText = false; 754 // Step size for auto-sizing in pixels. 755 private int mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 756 // Minimum text size for auto-sizing in pixels. 757 private int mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 758 // Maximum text size for auto-sizing in pixels. 759 private int mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 760 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 761 // when auto-sizing text. 762 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 763 // Specifies whether auto-size should use the provided auto size steps set or if it should 764 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 765 // mAutoSizeStepGranularityInPx. 766 private boolean mHasPresetAutoSizeValues = false; 767 768 // Indicates whether the text was set from resources or dynamically, so it can be used to 769 // sanitize autofill requests. 770 private boolean mTextFromResource = false; 771 772 /** 773 * Kick-start the font cache for the zygote process (to pay the cost of 774 * initializing freetype for our default font only once). 775 * @hide 776 */ 777 public static void preloadFontCache() { 778 Paint p = new Paint(); 779 p.setAntiAlias(true); 780 // Ensure that the Typeface is loaded here. 781 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 782 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 783 // since Paint.measureText can not be called without Typeface static initializer. 784 p.setTypeface(Typeface.DEFAULT); 785 // We don't care about the result, just the side-effect of measuring. 786 p.measureText("H"); 787 } 788 789 /** 790 * Interface definition for a callback to be invoked when an action is 791 * performed on the editor. 792 */ 793 public interface OnEditorActionListener { 794 /** 795 * Called when an action is being performed. 796 * 797 * @param v The view that was clicked. 798 * @param actionId Identifier of the action. This will be either the 799 * identifier you supplied, or {@link EditorInfo#IME_NULL 800 * EditorInfo.IME_NULL} if being called due to the enter key 801 * being pressed. 802 * @param event If triggered by an enter key, this is the event; 803 * otherwise, this is null. 804 * @return Return true if you have consumed the action, else false. 805 */ 806 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 807 } 808 809 public TextView(Context context) { 810 this(context, null); 811 } 812 813 public TextView(Context context, @Nullable AttributeSet attrs) { 814 this(context, attrs, com.android.internal.R.attr.textViewStyle); 815 } 816 817 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 818 this(context, attrs, defStyleAttr, 0); 819 } 820 821 @SuppressWarnings("deprecation") 822 public TextView( 823 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 824 super(context, attrs, defStyleAttr, defStyleRes); 825 826 // TextView is important by default, unless app developer overrode attribute. 827 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 828 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 829 } 830 831 mText = ""; 832 833 final Resources res = getResources(); 834 final CompatibilityInfo compat = res.getCompatibilityInfo(); 835 836 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 837 mTextPaint.density = res.getDisplayMetrics().density; 838 mTextPaint.setCompatibilityScaling(compat.applicationScale); 839 840 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 841 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 842 843 mMovement = getDefaultMovementMethod(); 844 845 mTransformation = null; 846 847 int textColorHighlight = 0; 848 ColorStateList textColor = null; 849 ColorStateList textColorHint = null; 850 ColorStateList textColorLink = null; 851 int textSize = 15; 852 String fontFamily = null; 853 Typeface fontTypeface = null; 854 boolean fontFamilyExplicit = false; 855 int typefaceIndex = -1; 856 int styleIndex = -1; 857 boolean allCaps = false; 858 int shadowcolor = 0; 859 float dx = 0, dy = 0, r = 0; 860 boolean elegant = false; 861 float letterSpacing = 0; 862 String fontFeatureSettings = null; 863 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 864 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 865 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 866 867 final Resources.Theme theme = context.getTheme(); 868 869 /* 870 * Look the appearance up without checking first if it exists because 871 * almost every TextView has one and it greatly simplifies the logic 872 * to be able to parse the appearance first and then let specific tags 873 * for this View override it. 874 */ 875 TypedArray a = theme.obtainStyledAttributes(attrs, 876 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 877 TypedArray appearance = null; 878 int ap = a.getResourceId( 879 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 880 a.recycle(); 881 if (ap != -1) { 882 appearance = theme.obtainStyledAttributes( 883 ap, com.android.internal.R.styleable.TextAppearance); 884 } 885 if (appearance != null) { 886 int n = appearance.getIndexCount(); 887 for (int i = 0; i < n; i++) { 888 int attr = appearance.getIndex(i); 889 890 switch (attr) { 891 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 892 textColorHighlight = appearance.getColor(attr, textColorHighlight); 893 break; 894 895 case com.android.internal.R.styleable.TextAppearance_textColor: 896 textColor = appearance.getColorStateList(attr); 897 break; 898 899 case com.android.internal.R.styleable.TextAppearance_textColorHint: 900 textColorHint = appearance.getColorStateList(attr); 901 break; 902 903 case com.android.internal.R.styleable.TextAppearance_textColorLink: 904 textColorLink = appearance.getColorStateList(attr); 905 break; 906 907 case com.android.internal.R.styleable.TextAppearance_textSize: 908 textSize = appearance.getDimensionPixelSize(attr, textSize); 909 break; 910 911 case com.android.internal.R.styleable.TextAppearance_typeface: 912 typefaceIndex = appearance.getInt(attr, -1); 913 break; 914 915 case com.android.internal.R.styleable.TextAppearance_fontFamily: 916 if (!context.isRestricted()) { 917 try { 918 fontTypeface = appearance.getFont(attr); 919 } catch (UnsupportedOperationException 920 | Resources.NotFoundException e) { 921 // Expected if it is not a font resource. 922 } 923 } 924 if (fontTypeface == null) { 925 fontFamily = appearance.getString(attr); 926 } 927 break; 928 929 case com.android.internal.R.styleable.TextAppearance_textStyle: 930 styleIndex = appearance.getInt(attr, -1); 931 break; 932 933 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 934 allCaps = appearance.getBoolean(attr, false); 935 break; 936 937 case com.android.internal.R.styleable.TextAppearance_shadowColor: 938 shadowcolor = appearance.getInt(attr, 0); 939 break; 940 941 case com.android.internal.R.styleable.TextAppearance_shadowDx: 942 dx = appearance.getFloat(attr, 0); 943 break; 944 945 case com.android.internal.R.styleable.TextAppearance_shadowDy: 946 dy = appearance.getFloat(attr, 0); 947 break; 948 949 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 950 r = appearance.getFloat(attr, 0); 951 break; 952 953 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 954 elegant = appearance.getBoolean(attr, false); 955 break; 956 957 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 958 letterSpacing = appearance.getFloat(attr, 0); 959 break; 960 961 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 962 fontFeatureSettings = appearance.getString(attr); 963 break; 964 } 965 } 966 967 appearance.recycle(); 968 } 969 970 boolean editable = getDefaultEditable(); 971 CharSequence inputMethod = null; 972 int numeric = 0; 973 CharSequence digits = null; 974 boolean phone = false; 975 boolean autotext = false; 976 int autocap = -1; 977 int buffertype = 0; 978 boolean selectallonfocus = false; 979 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 980 drawableBottom = null, drawableStart = null, drawableEnd = null; 981 ColorStateList drawableTint = null; 982 PorterDuff.Mode drawableTintMode = null; 983 int drawablePadding = 0; 984 int ellipsize = -1; 985 boolean singleLine = false; 986 int maxlength = -1; 987 CharSequence text = ""; 988 CharSequence hint = null; 989 boolean password = false; 990 int autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 991 int autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 992 int autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 993 int inputType = EditorInfo.TYPE_NULL; 994 a = theme.obtainStyledAttributes( 995 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 996 997 int n = a.getIndexCount(); 998 999 boolean fromResourceId = false; 1000 for (int i = 0; i < n; i++) { 1001 int attr = a.getIndex(i); 1002 1003 switch (attr) { 1004 case com.android.internal.R.styleable.TextView_editable: 1005 editable = a.getBoolean(attr, editable); 1006 break; 1007 1008 case com.android.internal.R.styleable.TextView_inputMethod: 1009 inputMethod = a.getText(attr); 1010 break; 1011 1012 case com.android.internal.R.styleable.TextView_numeric: 1013 numeric = a.getInt(attr, numeric); 1014 break; 1015 1016 case com.android.internal.R.styleable.TextView_digits: 1017 digits = a.getText(attr); 1018 break; 1019 1020 case com.android.internal.R.styleable.TextView_phoneNumber: 1021 phone = a.getBoolean(attr, phone); 1022 break; 1023 1024 case com.android.internal.R.styleable.TextView_autoText: 1025 autotext = a.getBoolean(attr, autotext); 1026 break; 1027 1028 case com.android.internal.R.styleable.TextView_capitalize: 1029 autocap = a.getInt(attr, autocap); 1030 break; 1031 1032 case com.android.internal.R.styleable.TextView_bufferType: 1033 buffertype = a.getInt(attr, buffertype); 1034 break; 1035 1036 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1037 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1038 break; 1039 1040 case com.android.internal.R.styleable.TextView_autoLink: 1041 mAutoLinkMask = a.getInt(attr, 0); 1042 break; 1043 1044 case com.android.internal.R.styleable.TextView_linksClickable: 1045 mLinksClickable = a.getBoolean(attr, true); 1046 break; 1047 1048 case com.android.internal.R.styleable.TextView_drawableLeft: 1049 drawableLeft = a.getDrawable(attr); 1050 break; 1051 1052 case com.android.internal.R.styleable.TextView_drawableTop: 1053 drawableTop = a.getDrawable(attr); 1054 break; 1055 1056 case com.android.internal.R.styleable.TextView_drawableRight: 1057 drawableRight = a.getDrawable(attr); 1058 break; 1059 1060 case com.android.internal.R.styleable.TextView_drawableBottom: 1061 drawableBottom = a.getDrawable(attr); 1062 break; 1063 1064 case com.android.internal.R.styleable.TextView_drawableStart: 1065 drawableStart = a.getDrawable(attr); 1066 break; 1067 1068 case com.android.internal.R.styleable.TextView_drawableEnd: 1069 drawableEnd = a.getDrawable(attr); 1070 break; 1071 1072 case com.android.internal.R.styleable.TextView_drawableTint: 1073 drawableTint = a.getColorStateList(attr); 1074 break; 1075 1076 case com.android.internal.R.styleable.TextView_drawableTintMode: 1077 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode); 1078 break; 1079 1080 case com.android.internal.R.styleable.TextView_drawablePadding: 1081 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1082 break; 1083 1084 case com.android.internal.R.styleable.TextView_maxLines: 1085 setMaxLines(a.getInt(attr, -1)); 1086 break; 1087 1088 case com.android.internal.R.styleable.TextView_maxHeight: 1089 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1090 break; 1091 1092 case com.android.internal.R.styleable.TextView_lines: 1093 setLines(a.getInt(attr, -1)); 1094 break; 1095 1096 case com.android.internal.R.styleable.TextView_height: 1097 setHeight(a.getDimensionPixelSize(attr, -1)); 1098 break; 1099 1100 case com.android.internal.R.styleable.TextView_minLines: 1101 setMinLines(a.getInt(attr, -1)); 1102 break; 1103 1104 case com.android.internal.R.styleable.TextView_minHeight: 1105 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1106 break; 1107 1108 case com.android.internal.R.styleable.TextView_maxEms: 1109 setMaxEms(a.getInt(attr, -1)); 1110 break; 1111 1112 case com.android.internal.R.styleable.TextView_maxWidth: 1113 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1114 break; 1115 1116 case com.android.internal.R.styleable.TextView_ems: 1117 setEms(a.getInt(attr, -1)); 1118 break; 1119 1120 case com.android.internal.R.styleable.TextView_width: 1121 setWidth(a.getDimensionPixelSize(attr, -1)); 1122 break; 1123 1124 case com.android.internal.R.styleable.TextView_minEms: 1125 setMinEms(a.getInt(attr, -1)); 1126 break; 1127 1128 case com.android.internal.R.styleable.TextView_minWidth: 1129 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1130 break; 1131 1132 case com.android.internal.R.styleable.TextView_gravity: 1133 setGravity(a.getInt(attr, -1)); 1134 break; 1135 1136 case com.android.internal.R.styleable.TextView_hint: 1137 hint = a.getText(attr); 1138 break; 1139 1140 case com.android.internal.R.styleable.TextView_text: 1141 fromResourceId = true; 1142 text = a.getText(attr); 1143 break; 1144 1145 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1146 if (a.getBoolean(attr, false)) { 1147 setHorizontallyScrolling(true); 1148 } 1149 break; 1150 1151 case com.android.internal.R.styleable.TextView_singleLine: 1152 singleLine = a.getBoolean(attr, singleLine); 1153 break; 1154 1155 case com.android.internal.R.styleable.TextView_ellipsize: 1156 ellipsize = a.getInt(attr, ellipsize); 1157 break; 1158 1159 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1160 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1161 break; 1162 1163 case com.android.internal.R.styleable.TextView_includeFontPadding: 1164 if (!a.getBoolean(attr, true)) { 1165 setIncludeFontPadding(false); 1166 } 1167 break; 1168 1169 case com.android.internal.R.styleable.TextView_cursorVisible: 1170 if (!a.getBoolean(attr, true)) { 1171 setCursorVisible(false); 1172 } 1173 break; 1174 1175 case com.android.internal.R.styleable.TextView_maxLength: 1176 maxlength = a.getInt(attr, -1); 1177 break; 1178 1179 case com.android.internal.R.styleable.TextView_textScaleX: 1180 setTextScaleX(a.getFloat(attr, 1.0f)); 1181 break; 1182 1183 case com.android.internal.R.styleable.TextView_freezesText: 1184 mFreezesText = a.getBoolean(attr, false); 1185 break; 1186 1187 case com.android.internal.R.styleable.TextView_shadowColor: 1188 shadowcolor = a.getInt(attr, 0); 1189 break; 1190 1191 case com.android.internal.R.styleable.TextView_shadowDx: 1192 dx = a.getFloat(attr, 0); 1193 break; 1194 1195 case com.android.internal.R.styleable.TextView_shadowDy: 1196 dy = a.getFloat(attr, 0); 1197 break; 1198 1199 case com.android.internal.R.styleable.TextView_shadowRadius: 1200 r = a.getFloat(attr, 0); 1201 break; 1202 1203 case com.android.internal.R.styleable.TextView_enabled: 1204 setEnabled(a.getBoolean(attr, isEnabled())); 1205 break; 1206 1207 case com.android.internal.R.styleable.TextView_textColorHighlight: 1208 textColorHighlight = a.getColor(attr, textColorHighlight); 1209 break; 1210 1211 case com.android.internal.R.styleable.TextView_textColor: 1212 textColor = a.getColorStateList(attr); 1213 break; 1214 1215 case com.android.internal.R.styleable.TextView_textColorHint: 1216 textColorHint = a.getColorStateList(attr); 1217 break; 1218 1219 case com.android.internal.R.styleable.TextView_textColorLink: 1220 textColorLink = a.getColorStateList(attr); 1221 break; 1222 1223 case com.android.internal.R.styleable.TextView_textSize: 1224 textSize = a.getDimensionPixelSize(attr, textSize); 1225 break; 1226 1227 case com.android.internal.R.styleable.TextView_typeface: 1228 typefaceIndex = a.getInt(attr, typefaceIndex); 1229 break; 1230 1231 case com.android.internal.R.styleable.TextView_textStyle: 1232 styleIndex = a.getInt(attr, styleIndex); 1233 break; 1234 1235 case com.android.internal.R.styleable.TextView_fontFamily: 1236 if (!context.isRestricted()) { 1237 try { 1238 fontTypeface = a.getFont(attr); 1239 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 1240 // Expected if it is not a resource reference or if it is a reference to 1241 // another resource type. 1242 } 1243 } 1244 if (fontTypeface == null) { 1245 fontFamily = a.getString(attr); 1246 } 1247 fontFamilyExplicit = true; 1248 break; 1249 1250 case com.android.internal.R.styleable.TextView_password: 1251 password = a.getBoolean(attr, password); 1252 break; 1253 1254 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1255 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1256 break; 1257 1258 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1259 mSpacingMult = a.getFloat(attr, mSpacingMult); 1260 break; 1261 1262 case com.android.internal.R.styleable.TextView_inputType: 1263 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1264 break; 1265 1266 case com.android.internal.R.styleable.TextView_allowUndo: 1267 createEditorIfNeeded(); 1268 mEditor.mAllowUndo = a.getBoolean(attr, true); 1269 break; 1270 1271 case com.android.internal.R.styleable.TextView_imeOptions: 1272 createEditorIfNeeded(); 1273 mEditor.createInputContentTypeIfNeeded(); 1274 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1275 mEditor.mInputContentType.imeOptions); 1276 break; 1277 1278 case com.android.internal.R.styleable.TextView_imeActionLabel: 1279 createEditorIfNeeded(); 1280 mEditor.createInputContentTypeIfNeeded(); 1281 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1282 break; 1283 1284 case com.android.internal.R.styleable.TextView_imeActionId: 1285 createEditorIfNeeded(); 1286 mEditor.createInputContentTypeIfNeeded(); 1287 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1288 mEditor.mInputContentType.imeActionId); 1289 break; 1290 1291 case com.android.internal.R.styleable.TextView_privateImeOptions: 1292 setPrivateImeOptions(a.getString(attr)); 1293 break; 1294 1295 case com.android.internal.R.styleable.TextView_editorExtras: 1296 try { 1297 setInputExtras(a.getResourceId(attr, 0)); 1298 } catch (XmlPullParserException e) { 1299 Log.w(LOG_TAG, "Failure reading input extras", e); 1300 } catch (IOException e) { 1301 Log.w(LOG_TAG, "Failure reading input extras", e); 1302 } 1303 break; 1304 1305 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1306 mCursorDrawableRes = a.getResourceId(attr, 0); 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1310 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1314 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1315 break; 1316 1317 case com.android.internal.R.styleable.TextView_textSelectHandle: 1318 mTextSelectHandleRes = a.getResourceId(attr, 0); 1319 break; 1320 1321 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1322 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1323 break; 1324 1325 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1326 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1327 break; 1328 1329 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1330 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1331 break; 1332 1333 case com.android.internal.R.styleable.TextView_textIsSelectable: 1334 setTextIsSelectable(a.getBoolean(attr, false)); 1335 break; 1336 1337 case com.android.internal.R.styleable.TextView_textAllCaps: 1338 allCaps = a.getBoolean(attr, false); 1339 break; 1340 1341 case com.android.internal.R.styleable.TextView_elegantTextHeight: 1342 elegant = a.getBoolean(attr, false); 1343 break; 1344 1345 case com.android.internal.R.styleable.TextView_letterSpacing: 1346 letterSpacing = a.getFloat(attr, 0); 1347 break; 1348 1349 case com.android.internal.R.styleable.TextView_fontFeatureSettings: 1350 fontFeatureSettings = a.getString(attr); 1351 break; 1352 1353 case com.android.internal.R.styleable.TextView_breakStrategy: 1354 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1355 break; 1356 1357 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1358 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1359 break; 1360 1361 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1362 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1363 break; 1364 1365 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1366 autoSizeStepGranularityInPx = a.getDimensionPixelSize(attr, 1367 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1368 break; 1369 1370 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1371 autoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr, 1372 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1373 break; 1374 1375 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1376 autoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr, 1377 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1378 break; 1379 1380 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1381 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1382 if (autoSizeStepSizeArrayResId > 0) { 1383 final TypedArray autoSizePresetTextSizes = a.getResources() 1384 .obtainTypedArray(autoSizeStepSizeArrayResId); 1385 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1386 autoSizePresetTextSizes.recycle(); 1387 } 1388 break; 1389 case com.android.internal.R.styleable.TextView_justificationMode: 1390 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1391 break; 1392 } 1393 } 1394 1395 a.recycle(); 1396 1397 BufferType bufferType = BufferType.EDITABLE; 1398 1399 final int variation = 1400 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1401 final boolean passwordInputType = variation 1402 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1403 final boolean webPasswordInputType = variation 1404 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1405 final boolean numberPasswordInputType = variation 1406 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1407 1408 mUseInternationalizedInput = 1409 context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.O; 1410 1411 if (inputMethod != null) { 1412 Class<?> c; 1413 1414 try { 1415 c = Class.forName(inputMethod.toString()); 1416 } catch (ClassNotFoundException ex) { 1417 throw new RuntimeException(ex); 1418 } 1419 1420 try { 1421 createEditorIfNeeded(); 1422 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1423 } catch (InstantiationException ex) { 1424 throw new RuntimeException(ex); 1425 } catch (IllegalAccessException ex) { 1426 throw new RuntimeException(ex); 1427 } 1428 try { 1429 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1430 ? inputType 1431 : mEditor.mKeyListener.getInputType(); 1432 } catch (IncompatibleClassChangeError e) { 1433 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1434 } 1435 } else if (digits != null) { 1436 createEditorIfNeeded(); 1437 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1438 // If no input type was specified, we will default to generic 1439 // text, since we can't tell the IME about the set of digits 1440 // that was selected. 1441 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1442 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1443 } else if (inputType != EditorInfo.TYPE_NULL) { 1444 setInputType(inputType, true); 1445 // If set, the input type overrides what was set using the deprecated singleLine flag. 1446 singleLine = !isMultilineInputType(inputType); 1447 } else if (phone) { 1448 createEditorIfNeeded(); 1449 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1450 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1451 } else if (numeric != 0) { 1452 createEditorIfNeeded(); 1453 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1454 null, // locale 1455 (numeric & SIGNED) != 0, 1456 (numeric & DECIMAL) != 0); 1457 inputType = mEditor.mKeyListener.getInputType(); 1458 mEditor.mInputType = inputType; 1459 } else if (autotext || autocap != -1) { 1460 TextKeyListener.Capitalize cap; 1461 1462 inputType = EditorInfo.TYPE_CLASS_TEXT; 1463 1464 switch (autocap) { 1465 case 1: 1466 cap = TextKeyListener.Capitalize.SENTENCES; 1467 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1468 break; 1469 1470 case 2: 1471 cap = TextKeyListener.Capitalize.WORDS; 1472 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1473 break; 1474 1475 case 3: 1476 cap = TextKeyListener.Capitalize.CHARACTERS; 1477 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1478 break; 1479 1480 default: 1481 cap = TextKeyListener.Capitalize.NONE; 1482 break; 1483 } 1484 1485 createEditorIfNeeded(); 1486 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1487 mEditor.mInputType = inputType; 1488 } else if (editable) { 1489 createEditorIfNeeded(); 1490 mEditor.mKeyListener = TextKeyListener.getInstance(); 1491 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1492 } else if (isTextSelectable()) { 1493 // Prevent text changes from keyboard. 1494 if (mEditor != null) { 1495 mEditor.mKeyListener = null; 1496 mEditor.mInputType = EditorInfo.TYPE_NULL; 1497 } 1498 bufferType = BufferType.SPANNABLE; 1499 // So that selection can be changed using arrow keys and touch is handled. 1500 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1501 } else { 1502 if (mEditor != null) mEditor.mKeyListener = null; 1503 1504 switch (buffertype) { 1505 case 0: 1506 bufferType = BufferType.NORMAL; 1507 break; 1508 case 1: 1509 bufferType = BufferType.SPANNABLE; 1510 break; 1511 case 2: 1512 bufferType = BufferType.EDITABLE; 1513 break; 1514 } 1515 } 1516 1517 if (mEditor != null) { 1518 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1519 numberPasswordInputType); 1520 } 1521 1522 if (selectallonfocus) { 1523 createEditorIfNeeded(); 1524 mEditor.mSelectAllOnFocus = true; 1525 1526 if (bufferType == BufferType.NORMAL) { 1527 bufferType = BufferType.SPANNABLE; 1528 } 1529 } 1530 1531 // Set up the tint (if needed) before setting the drawables so that it 1532 // gets applied correctly. 1533 if (drawableTint != null || drawableTintMode != null) { 1534 if (mDrawables == null) { 1535 mDrawables = new Drawables(context); 1536 } 1537 if (drawableTint != null) { 1538 mDrawables.mTintList = drawableTint; 1539 mDrawables.mHasTint = true; 1540 } 1541 if (drawableTintMode != null) { 1542 mDrawables.mTintMode = drawableTintMode; 1543 mDrawables.mHasTintMode = true; 1544 } 1545 } 1546 1547 // This call will save the initial left/right drawables 1548 setCompoundDrawablesWithIntrinsicBounds( 1549 drawableLeft, drawableTop, drawableRight, drawableBottom); 1550 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1551 setCompoundDrawablePadding(drawablePadding); 1552 1553 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1554 // of lines of height are unchanged for multi-line TextViews. 1555 setInputTypeSingleLine(singleLine); 1556 applySingleLine(singleLine, singleLine, singleLine); 1557 1558 if (singleLine && getKeyListener() == null && ellipsize < 0) { 1559 ellipsize = 3; // END 1560 } 1561 1562 switch (ellipsize) { 1563 case 1: 1564 setEllipsize(TextUtils.TruncateAt.START); 1565 break; 1566 case 2: 1567 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1568 break; 1569 case 3: 1570 setEllipsize(TextUtils.TruncateAt.END); 1571 break; 1572 case 4: 1573 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1574 setHorizontalFadingEdgeEnabled(true); 1575 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1576 } else { 1577 setHorizontalFadingEdgeEnabled(false); 1578 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1579 } 1580 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1581 break; 1582 } 1583 1584 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 1585 setHintTextColor(textColorHint); 1586 setLinkTextColor(textColorLink); 1587 if (textColorHighlight != 0) { 1588 setHighlightColor(textColorHighlight); 1589 } 1590 setRawTextSize(textSize); 1591 setElegantTextHeight(elegant); 1592 setLetterSpacing(letterSpacing); 1593 setFontFeatureSettings(fontFeatureSettings); 1594 1595 if (allCaps) { 1596 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 1597 } 1598 1599 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) { 1600 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1601 typefaceIndex = MONOSPACE; 1602 } else if (mEditor != null 1603 && (mEditor.mInputType 1604 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1605 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 1606 typefaceIndex = MONOSPACE; 1607 } 1608 1609 if (typefaceIndex != -1 && !fontFamilyExplicit) { 1610 fontFamily = null; 1611 } 1612 setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex); 1613 1614 if (shadowcolor != 0) { 1615 setShadowLayer(r, dx, dy, shadowcolor); 1616 } 1617 1618 if (maxlength >= 0) { 1619 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1620 } else { 1621 setFilters(NO_FILTERS); 1622 } 1623 1624 setText(text, bufferType); 1625 if (fromResourceId) { 1626 mTextFromResource = true; 1627 } 1628 1629 if (hint != null) setHint(hint); 1630 1631 /* 1632 * Views are not normally clickable unless specified to be. 1633 * However, TextViews that have input or movement methods *are* 1634 * clickable by default. By setting clickable here, we implicitly set focusable as well 1635 * if not overridden by the developer. 1636 */ 1637 a = context.obtainStyledAttributes( 1638 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1639 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1640 boolean clickable = canInputOrMove || isClickable(); 1641 boolean longClickable = canInputOrMove || isLongClickable(); 1642 int focusable = getFocusable(); 1643 1644 n = a.getIndexCount(); 1645 for (int i = 0; i < n; i++) { 1646 int attr = a.getIndex(i); 1647 1648 switch (attr) { 1649 case com.android.internal.R.styleable.View_focusable: 1650 TypedValue val = new TypedValue(); 1651 if (a.getValue(attr, val)) { 1652 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1653 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1654 : val.data; 1655 } 1656 1657 case com.android.internal.R.styleable.View_clickable: 1658 clickable = a.getBoolean(attr, clickable); 1659 break; 1660 1661 case com.android.internal.R.styleable.View_longClickable: 1662 longClickable = a.getBoolean(attr, longClickable); 1663 break; 1664 } 1665 } 1666 a.recycle(); 1667 1668 // Some apps were relying on the undefined behavior of focusable winning over 1669 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1670 // when starting with EditText and setting only focusable=false). To keep those apps from 1671 // breaking, re-apply the focusable attribute here. 1672 if (focusable != getFocusable()) { 1673 setFocusable(focusable); 1674 } 1675 setClickable(clickable); 1676 setLongClickable(longClickable); 1677 1678 if (mEditor != null) mEditor.prepareCursorControllers(); 1679 1680 // If not explicitly specified this view is important for accessibility. 1681 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1682 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1683 } 1684 1685 if (supportsAutoSizeText()) { 1686 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1687 // If uniform auto-size has been specified but preset values have not been set then 1688 // replace the auto-size configuration values that have not been specified with the 1689 // defaults. 1690 if (!mHasPresetAutoSizeValues) { 1691 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1692 1693 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1694 autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension( 1695 TypedValue.COMPLEX_UNIT_SP, 1696 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1697 displayMetrics); 1698 } 1699 1700 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1701 autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension( 1702 TypedValue.COMPLEX_UNIT_SP, 1703 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1704 displayMetrics); 1705 } 1706 1707 if (autoSizeStepGranularityInPx 1708 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1709 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1710 } 1711 1712 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1713 autoSizeMaxTextSizeInPx, 1714 autoSizeStepGranularityInPx); 1715 } 1716 1717 setupAutoSizeText(); 1718 } 1719 } else { 1720 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1721 } 1722 } 1723 1724 /** 1725 * Specify whether this widget should automatically scale the text to try to perfectly fit 1726 * within the layout bounds by using the default auto-size configuration. 1727 * 1728 * @param autoSizeTextType the type of auto-size. Must be one of 1729 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1730 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1731 * 1732 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1733 * 1734 * @attr ref android.R.styleable#TextView_autoSizeTextType 1735 * 1736 * @see #getAutoSizeTextType() 1737 */ 1738 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1739 if (supportsAutoSizeText()) { 1740 switch (autoSizeTextType) { 1741 case AUTO_SIZE_TEXT_TYPE_NONE: 1742 clearAutoSizeConfiguration(); 1743 break; 1744 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1745 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1746 final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension( 1747 TypedValue.COMPLEX_UNIT_SP, 1748 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1749 displayMetrics); 1750 final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension( 1751 TypedValue.COMPLEX_UNIT_SP, 1752 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1753 displayMetrics); 1754 1755 validateAndSetAutoSizeTextTypeUniformConfiguration( 1756 autoSizeMinTextSizeInPx, 1757 autoSizeMaxTextSizeInPx, 1758 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 1759 setupAutoSizeText(); 1760 break; 1761 default: 1762 throw new IllegalArgumentException( 1763 "Unknown auto-size text type: " + autoSizeTextType); 1764 } 1765 } 1766 } 1767 1768 /** 1769 * Specify whether this widget should automatically scale the text to try to perfectly fit 1770 * within the layout bounds. If all the configuration params are valid the type of auto-size is 1771 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1772 * 1773 * @param autoSizeMinTextSize the minimum text size available for auto-size 1774 * @param autoSizeMaxTextSize the maximum text size available for auto-size 1775 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 1776 * the minimum and maximum text size in order to build the set of 1777 * text sizes the system uses to choose from when auto-sizing 1778 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 1779 * possible dimension units 1780 * 1781 * @throws IllegalArgumentException if any of the configuration params are invalid. 1782 * 1783 * @attr ref android.R.styleable#TextView_autoSizeTextType 1784 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1785 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1786 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1787 * 1788 * @see #setAutoSizeTextTypeWithDefaults(int) 1789 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1790 * @see #getAutoSizeMinTextSize() 1791 * @see #getAutoSizeMaxTextSize() 1792 * @see #getAutoSizeStepGranularity() 1793 * @see #getAutoSizeTextAvailableSizes() 1794 */ 1795 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 1796 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 1797 if (supportsAutoSizeText()) { 1798 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1799 final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension( 1800 unit, autoSizeMinTextSize, displayMetrics); 1801 final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension( 1802 unit, autoSizeMaxTextSize, displayMetrics); 1803 final int autoSizeStepGranularityInPx = (int) TypedValue.applyDimension( 1804 unit, autoSizeStepGranularity, displayMetrics); 1805 1806 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1807 autoSizeMaxTextSizeInPx, 1808 autoSizeStepGranularityInPx); 1809 setupAutoSizeText(); 1810 } 1811 } 1812 1813 /** 1814 * Specify whether this widget should automatically scale the text to try to perfectly fit 1815 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 1816 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 1817 * 1818 * @param presetSizes an {@code int} array of sizes in pixels 1819 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 1820 * the possible dimension units 1821 * 1822 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 1823 * 1824 * @attr ref android.R.styleable#TextView_autoSizeTextType 1825 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 1826 * 1827 * @see #setAutoSizeTextTypeWithDefaults(int) 1828 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1829 * @see #getAutoSizeMinTextSize() 1830 * @see #getAutoSizeMaxTextSize() 1831 * @see #getAutoSizeTextAvailableSizes() 1832 */ 1833 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 1834 if (supportsAutoSizeText()) { 1835 final int presetSizesLength = presetSizes.length; 1836 if (presetSizesLength > 0) { 1837 int[] presetSizesInPx = new int[presetSizesLength]; 1838 1839 if (unit == TypedValue.COMPLEX_UNIT_PX) { 1840 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 1841 } else { 1842 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1843 // Convert all to sizes to pixels. 1844 for (int i = 0; i < presetSizesLength; i++) { 1845 presetSizesInPx[i] = (int) TypedValue.applyDimension(unit, presetSizes[i], 1846 displayMetrics); 1847 } 1848 } 1849 1850 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 1851 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 1852 throw new IllegalArgumentException("None of the preset sizes is valid: " 1853 + Arrays.toString(presetSizes)); 1854 } 1855 } else { 1856 mHasPresetAutoSizeValues = false; 1857 } 1858 setupAutoSizeText(); 1859 } 1860 } 1861 1862 /** 1863 * Returns the type of auto-size set for this widget. 1864 * 1865 * @return an {@code int} corresponding to one of the auto-size types: 1866 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1867 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1868 * 1869 * @attr ref android.R.styleable#TextView_autoSizeTextType 1870 * 1871 * @see #setAutoSizeTextTypeWithDefaults(int) 1872 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1873 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1874 */ 1875 @AutoSizeTextType 1876 public int getAutoSizeTextType() { 1877 return mAutoSizeTextType; 1878 } 1879 1880 /** 1881 * @return the current auto-size step granularity in pixels. 1882 * 1883 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 1884 * 1885 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1886 */ 1887 public int getAutoSizeStepGranularity() { 1888 return mAutoSizeStepGranularityInPx; 1889 } 1890 1891 /** 1892 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 1893 * if auto-size has not been configured this function returns {@code -1}. 1894 * 1895 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 1896 * 1897 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1898 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1899 */ 1900 public int getAutoSizeMinTextSize() { 1901 return mAutoSizeMinTextSizeInPx; 1902 } 1903 1904 /** 1905 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 1906 * if auto-size has not been configured this function returns {@code -1}. 1907 * 1908 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 1909 * 1910 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1911 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1912 */ 1913 public int getAutoSizeMaxTextSize() { 1914 return mAutoSizeMaxTextSizeInPx; 1915 } 1916 1917 /** 1918 * @return the current auto-size {@code int} sizes array (in pixels). 1919 * 1920 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 1921 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 1922 */ 1923 public int[] getAutoSizeTextAvailableSizes() { 1924 return mAutoSizeTextSizesInPx; 1925 } 1926 1927 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 1928 final int textSizesLength = textSizes.length(); 1929 final int[] parsedSizes = new int[textSizesLength]; 1930 1931 if (textSizesLength > 0) { 1932 for (int i = 0; i < textSizesLength; i++) { 1933 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 1934 } 1935 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 1936 setupAutoSizeUniformPresetSizesConfiguration(); 1937 } 1938 } 1939 1940 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 1941 final int sizesLength = mAutoSizeTextSizesInPx.length; 1942 mHasPresetAutoSizeValues = sizesLength > 0; 1943 if (mHasPresetAutoSizeValues) { 1944 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 1945 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 1946 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 1947 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1948 } 1949 return mHasPresetAutoSizeValues; 1950 } 1951 1952 /** 1953 * If all params are valid then save the auto-size configuration. 1954 * 1955 * @throws IllegalArgumentException if any of the params are invalid 1956 */ 1957 private void validateAndSetAutoSizeTextTypeUniformConfiguration(int autoSizeMinTextSizeInPx, 1958 int autoSizeMaxTextSizeInPx, int autoSizeStepGranularityInPx) { 1959 // First validate. 1960 if (autoSizeMinTextSizeInPx <= 0) { 1961 throw new IllegalArgumentException("Minimum auto-size text size (" 1962 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 1963 } 1964 1965 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 1966 throw new IllegalArgumentException("Maximum auto-size text size (" 1967 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 1968 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 1969 } 1970 1971 if (autoSizeStepGranularityInPx <= 0) { 1972 throw new IllegalArgumentException("The auto-size step granularity (" 1973 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 1974 } 1975 1976 // All good, persist the configuration. 1977 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 1978 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 1979 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 1980 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 1981 mHasPresetAutoSizeValues = false; 1982 } 1983 1984 private void clearAutoSizeConfiguration() { 1985 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1986 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1987 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1988 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1989 mAutoSizeTextSizesInPx = EmptyArray.INT; 1990 mNeedsAutoSizeText = false; 1991 } 1992 1993 // Returns distinct sorted positive values. 1994 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 1995 final int presetValuesLength = presetValues.length; 1996 if (presetValuesLength == 0) { 1997 return presetValues; 1998 } 1999 Arrays.sort(presetValues); 2000 2001 final IntArray uniqueValidSizes = new IntArray(); 2002 for (int i = 0; i < presetValuesLength; i++) { 2003 final int currentPresetValue = presetValues[i]; 2004 2005 if (currentPresetValue > 0 2006 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2007 uniqueValidSizes.add(currentPresetValue); 2008 } 2009 } 2010 2011 return presetValuesLength == uniqueValidSizes.size() 2012 ? presetValues 2013 : uniqueValidSizes.toArray(); 2014 } 2015 2016 private void setupAutoSizeText() { 2017 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2018 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2019 // not have a predefined set of sizes or if the current sizes array is empty. 2020 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2021 // Calculate sizes to choose from based on the current auto-size configuration. 2022 int autoSizeValuesLength = (int) Math.ceil( 2023 (mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx) 2024 / (float) mAutoSizeStepGranularityInPx); 2025 // Also reserve a slot for the max size if it fits. 2026 if ((mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx) 2027 % mAutoSizeStepGranularityInPx == 0) { 2028 autoSizeValuesLength++; 2029 } 2030 mAutoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2031 int sizeToAdd = mAutoSizeMinTextSizeInPx; 2032 for (int i = 0; i < autoSizeValuesLength; i++) { 2033 mAutoSizeTextSizesInPx[i] = sizeToAdd; 2034 sizeToAdd += mAutoSizeStepGranularityInPx; 2035 } 2036 } 2037 2038 mNeedsAutoSizeText = true; 2039 autoSizeText(); 2040 } 2041 } 2042 2043 private int[] parseDimensionArray(TypedArray dimens) { 2044 if (dimens == null) { 2045 return null; 2046 } 2047 int[] result = new int[dimens.length()]; 2048 for (int i = 0; i < result.length; i++) { 2049 result[i] = dimens.getDimensionPixelSize(i, 0); 2050 } 2051 return result; 2052 } 2053 2054 /** 2055 * @hide 2056 */ 2057 @Override 2058 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2059 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2060 if (resultCode == Activity.RESULT_OK && data != null) { 2061 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2062 if (result != null) { 2063 if (isTextEditable()) { 2064 replaceSelectionWithText(result); 2065 if (mEditor != null) { 2066 mEditor.refreshTextActionMode(); 2067 } 2068 } else { 2069 if (result.length() > 0) { 2070 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2071 .show(); 2072 } 2073 } 2074 } 2075 } else if (mText instanceof Spannable) { 2076 // Reset the selection. 2077 Selection.setSelection((Spannable) mText, getSelectionEnd()); 2078 } 2079 } 2080 } 2081 2082 private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex, 2083 int styleIndex) { 2084 Typeface tf = fontTypeface; 2085 if (tf == null && familyName != null) { 2086 tf = Typeface.create(familyName, styleIndex); 2087 } else if (tf != null && tf.getStyle() != styleIndex) { 2088 tf = Typeface.create(tf, styleIndex); 2089 } 2090 if (tf != null) { 2091 setTypeface(tf); 2092 return; 2093 } 2094 switch (typefaceIndex) { 2095 case SANS: 2096 tf = Typeface.SANS_SERIF; 2097 break; 2098 2099 case SERIF: 2100 tf = Typeface.SERIF; 2101 break; 2102 2103 case MONOSPACE: 2104 tf = Typeface.MONOSPACE; 2105 break; 2106 } 2107 2108 setTypeface(tf, styleIndex); 2109 } 2110 2111 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2112 boolean hasRelativeDrawables = (start != null) || (end != null); 2113 if (hasRelativeDrawables) { 2114 Drawables dr = mDrawables; 2115 if (dr == null) { 2116 mDrawables = dr = new Drawables(getContext()); 2117 } 2118 mDrawables.mOverride = true; 2119 final Rect compoundRect = dr.mCompoundRect; 2120 int[] state = getDrawableState(); 2121 if (start != null) { 2122 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2123 start.setState(state); 2124 start.copyBounds(compoundRect); 2125 start.setCallback(this); 2126 2127 dr.mDrawableStart = start; 2128 dr.mDrawableSizeStart = compoundRect.width(); 2129 dr.mDrawableHeightStart = compoundRect.height(); 2130 } else { 2131 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2132 } 2133 if (end != null) { 2134 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2135 end.setState(state); 2136 end.copyBounds(compoundRect); 2137 end.setCallback(this); 2138 2139 dr.mDrawableEnd = end; 2140 dr.mDrawableSizeEnd = compoundRect.width(); 2141 dr.mDrawableHeightEnd = compoundRect.height(); 2142 } else { 2143 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2144 } 2145 resetResolvedDrawables(); 2146 resolveDrawables(); 2147 applyCompoundDrawableTint(); 2148 } 2149 } 2150 2151 @android.view.RemotableViewMethod 2152 @Override 2153 public void setEnabled(boolean enabled) { 2154 if (enabled == isEnabled()) { 2155 return; 2156 } 2157 2158 if (!enabled) { 2159 // Hide the soft input if the currently active TextView is disabled 2160 InputMethodManager imm = InputMethodManager.peekInstance(); 2161 if (imm != null && imm.isActive(this)) { 2162 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2163 } 2164 } 2165 2166 super.setEnabled(enabled); 2167 2168 if (enabled) { 2169 // Make sure IME is updated with current editor info. 2170 InputMethodManager imm = InputMethodManager.peekInstance(); 2171 if (imm != null) imm.restartInput(this); 2172 } 2173 2174 // Will change text color 2175 if (mEditor != null) { 2176 mEditor.invalidateTextDisplayList(); 2177 mEditor.prepareCursorControllers(); 2178 2179 // start or stop the cursor blinking as appropriate 2180 mEditor.makeBlink(); 2181 } 2182 } 2183 2184 /** 2185 * Sets the typeface and style in which the text should be displayed, 2186 * and turns on the fake bold and italic bits in the Paint if the 2187 * Typeface that you provided does not have all the bits in the 2188 * style that you specified. 2189 * 2190 * @attr ref android.R.styleable#TextView_typeface 2191 * @attr ref android.R.styleable#TextView_textStyle 2192 */ 2193 public void setTypeface(Typeface tf, int style) { 2194 if (style > 0) { 2195 if (tf == null) { 2196 tf = Typeface.defaultFromStyle(style); 2197 } else { 2198 tf = Typeface.create(tf, style); 2199 } 2200 2201 setTypeface(tf); 2202 // now compute what (if any) algorithmic styling is needed 2203 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2204 int need = style & ~typefaceStyle; 2205 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2206 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2207 } else { 2208 mTextPaint.setFakeBoldText(false); 2209 mTextPaint.setTextSkewX(0); 2210 setTypeface(tf); 2211 } 2212 } 2213 2214 /** 2215 * Subclasses override this to specify that they have a KeyListener 2216 * by default even if not specifically called for in the XML options. 2217 */ 2218 protected boolean getDefaultEditable() { 2219 return false; 2220 } 2221 2222 /** 2223 * Subclasses override this to specify a default movement method. 2224 */ 2225 protected MovementMethod getDefaultMovementMethod() { 2226 return null; 2227 } 2228 2229 /** 2230 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2231 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2232 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2233 * the return value from this method to Spannable or Editable, respectively. 2234 * 2235 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2236 * should make your own copy first.</p> 2237 * 2238 * @return The text displayed by the text view. 2239 * @attr ref android.R.styleable#TextView_text 2240 */ 2241 @ViewDebug.CapturedViewProperty 2242 public CharSequence getText() { 2243 return mText; 2244 } 2245 2246 /** 2247 * Returns the length, in characters, of the text managed by this TextView 2248 * @return The length of the text managed by the TextView in characters. 2249 */ 2250 public int length() { 2251 return mText.length(); 2252 } 2253 2254 /** 2255 * Return the text that TextView is displaying as an Editable object. If the text is not 2256 * editable, null is returned. 2257 * 2258 * @see #getText 2259 */ 2260 public Editable getEditableText() { 2261 return (mText instanceof Editable) ? (Editable) mText : null; 2262 } 2263 2264 /** 2265 * Gets the vertical distance between lines of text, in pixels. 2266 * Note that markup within the text can cause individual lines 2267 * to be taller or shorter than this height, and the layout may 2268 * contain additional first-or last-line padding. 2269 * @return The height of one standard line in pixels. 2270 */ 2271 public int getLineHeight() { 2272 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2273 } 2274 2275 /** 2276 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2277 * This value can be null if the text or width has recently changed. 2278 * @return The Layout that is currently being used to display the text. 2279 */ 2280 public final Layout getLayout() { 2281 return mLayout; 2282 } 2283 2284 /** 2285 * @return the {@link android.text.Layout} that is currently being used to 2286 * display the hint text. This can be null. 2287 */ 2288 final Layout getHintLayout() { 2289 return mHintLayout; 2290 } 2291 2292 /** 2293 * Retrieve the {@link android.content.UndoManager} that is currently associated 2294 * with this TextView. By default there is no associated UndoManager, so null 2295 * is returned. One can be associated with the TextView through 2296 * {@link #setUndoManager(android.content.UndoManager, String)} 2297 * 2298 * @hide 2299 */ 2300 public final UndoManager getUndoManager() { 2301 // TODO: Consider supporting a global undo manager. 2302 throw new UnsupportedOperationException("not implemented"); 2303 } 2304 2305 2306 /** 2307 * @hide 2308 */ 2309 @VisibleForTesting 2310 public final Editor getEditorForTesting() { 2311 return mEditor; 2312 } 2313 2314 /** 2315 * Associate an {@link android.content.UndoManager} with this TextView. Once 2316 * done, all edit operations on the TextView will result in appropriate 2317 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2318 * stack. 2319 * 2320 * @param undoManager The {@link android.content.UndoManager} to associate with 2321 * this TextView, or null to clear any existing association. 2322 * @param tag String tag identifying this particular TextView owner in the 2323 * UndoManager. This is used to keep the correct association with the 2324 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2325 * 2326 * @hide 2327 */ 2328 public final void setUndoManager(UndoManager undoManager, String tag) { 2329 // TODO: Consider supporting a global undo manager. An implementation will need to: 2330 // * createEditorIfNeeded() 2331 // * Promote to BufferType.EDITABLE if needed. 2332 // * Update the UndoManager and UndoOwner. 2333 // Likewise it will need to be able to restore the default UndoManager. 2334 throw new UnsupportedOperationException("not implemented"); 2335 } 2336 2337 /** 2338 * Gets the current {@link KeyListener} for the TextView. 2339 * This will frequently be null for non-EditText TextViews. 2340 * @return the current key listener for this TextView. 2341 * 2342 * @attr ref android.R.styleable#TextView_numeric 2343 * @attr ref android.R.styleable#TextView_digits 2344 * @attr ref android.R.styleable#TextView_phoneNumber 2345 * @attr ref android.R.styleable#TextView_inputMethod 2346 * @attr ref android.R.styleable#TextView_capitalize 2347 * @attr ref android.R.styleable#TextView_autoText 2348 */ 2349 public final KeyListener getKeyListener() { 2350 return mEditor == null ? null : mEditor.mKeyListener; 2351 } 2352 2353 /** 2354 * Sets the key listener to be used with this TextView. This can be null 2355 * to disallow user input. Note that this method has significant and 2356 * subtle interactions with soft keyboards and other input method: 2357 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 2358 * for important details. Calling this method will replace the current 2359 * content type of the text view with the content type returned by the 2360 * key listener. 2361 * <p> 2362 * Be warned that if you want a TextView with a key listener or movement 2363 * method not to be focusable, or if you want a TextView without a 2364 * key listener or movement method to be focusable, you must call 2365 * {@link #setFocusable} again after calling this to get the focusability 2366 * back the way you want it. 2367 * 2368 * @attr ref android.R.styleable#TextView_numeric 2369 * @attr ref android.R.styleable#TextView_digits 2370 * @attr ref android.R.styleable#TextView_phoneNumber 2371 * @attr ref android.R.styleable#TextView_inputMethod 2372 * @attr ref android.R.styleable#TextView_capitalize 2373 * @attr ref android.R.styleable#TextView_autoText 2374 */ 2375 public void setKeyListener(KeyListener input) { 2376 mListenerChanged = true; 2377 setKeyListenerOnly(input); 2378 fixFocusableAndClickableSettings(); 2379 2380 if (input != null) { 2381 createEditorIfNeeded(); 2382 setInputTypeFromEditor(); 2383 } else { 2384 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2385 } 2386 2387 InputMethodManager imm = InputMethodManager.peekInstance(); 2388 if (imm != null) imm.restartInput(this); 2389 } 2390 2391 private void setInputTypeFromEditor() { 2392 try { 2393 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2394 } catch (IncompatibleClassChangeError e) { 2395 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2396 } 2397 // Change inputType, without affecting transformation. 2398 // No need to applySingleLine since mSingleLine is unchanged. 2399 setInputTypeSingleLine(mSingleLine); 2400 } 2401 2402 private void setKeyListenerOnly(KeyListener input) { 2403 if (mEditor == null && input == null) return; // null is the default value 2404 2405 createEditorIfNeeded(); 2406 if (mEditor.mKeyListener != input) { 2407 mEditor.mKeyListener = input; 2408 if (input != null && !(mText instanceof Editable)) { 2409 setText(mText); 2410 } 2411 2412 setFilters((Editable) mText, mFilters); 2413 } 2414 } 2415 2416 /** 2417 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2418 * which provides positioning, scrolling, and text selection functionality. 2419 * This will frequently be null for non-EditText TextViews. 2420 * @return the movement method being used for this TextView. 2421 * @see android.text.method.MovementMethod 2422 */ 2423 public final MovementMethod getMovementMethod() { 2424 return mMovement; 2425 } 2426 2427 /** 2428 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2429 * for this TextView. This can be null to disallow using the arrow keys to move the 2430 * cursor or scroll the view. 2431 * <p> 2432 * Be warned that if you want a TextView with a key listener or movement 2433 * method not to be focusable, or if you want a TextView without a 2434 * key listener or movement method to be focusable, you must call 2435 * {@link #setFocusable} again after calling this to get the focusability 2436 * back the way you want it. 2437 */ 2438 public final void setMovementMethod(MovementMethod movement) { 2439 if (mMovement != movement) { 2440 mMovement = movement; 2441 2442 if (movement != null && !(mText instanceof Spannable)) { 2443 setText(mText); 2444 } 2445 2446 fixFocusableAndClickableSettings(); 2447 2448 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2449 // mMovement 2450 if (mEditor != null) mEditor.prepareCursorControllers(); 2451 } 2452 } 2453 2454 private void fixFocusableAndClickableSettings() { 2455 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2456 setFocusable(FOCUSABLE); 2457 setClickable(true); 2458 setLongClickable(true); 2459 } else { 2460 setFocusable(FOCUSABLE_AUTO); 2461 setClickable(false); 2462 setLongClickable(false); 2463 } 2464 } 2465 2466 /** 2467 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2468 * This is frequently null, except for single-line and password fields. 2469 * @return the current transformation method for this TextView. 2470 * 2471 * @attr ref android.R.styleable#TextView_password 2472 * @attr ref android.R.styleable#TextView_singleLine 2473 */ 2474 public final TransformationMethod getTransformationMethod() { 2475 return mTransformation; 2476 } 2477 2478 /** 2479 * Sets the transformation that is applied to the text that this 2480 * TextView is displaying. 2481 * 2482 * @attr ref android.R.styleable#TextView_password 2483 * @attr ref android.R.styleable#TextView_singleLine 2484 */ 2485 public final void setTransformationMethod(TransformationMethod method) { 2486 if (method == mTransformation) { 2487 // Avoid the setText() below if the transformation is 2488 // the same. 2489 return; 2490 } 2491 if (mTransformation != null) { 2492 if (mText instanceof Spannable) { 2493 ((Spannable) mText).removeSpan(mTransformation); 2494 } 2495 } 2496 2497 mTransformation = method; 2498 2499 if (method instanceof TransformationMethod2) { 2500 TransformationMethod2 method2 = (TransformationMethod2) method; 2501 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2502 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2503 } else { 2504 mAllowTransformationLengthChange = false; 2505 } 2506 2507 setText(mText); 2508 2509 if (hasPasswordTransformationMethod()) { 2510 notifyViewAccessibilityStateChangedIfNeeded( 2511 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2512 } 2513 2514 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2515 // getTextDirectionHeuristic, needs reset 2516 mTextDir = getTextDirectionHeuristic(); 2517 } 2518 2519 /** 2520 * Returns the top padding of the view, plus space for the top 2521 * Drawable if any. 2522 */ 2523 public int getCompoundPaddingTop() { 2524 final Drawables dr = mDrawables; 2525 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2526 return mPaddingTop; 2527 } else { 2528 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2529 } 2530 } 2531 2532 /** 2533 * Returns the bottom padding of the view, plus space for the bottom 2534 * Drawable if any. 2535 */ 2536 public int getCompoundPaddingBottom() { 2537 final Drawables dr = mDrawables; 2538 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2539 return mPaddingBottom; 2540 } else { 2541 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2542 } 2543 } 2544 2545 /** 2546 * Returns the left padding of the view, plus space for the left 2547 * Drawable if any. 2548 */ 2549 public int getCompoundPaddingLeft() { 2550 final Drawables dr = mDrawables; 2551 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2552 return mPaddingLeft; 2553 } else { 2554 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2555 } 2556 } 2557 2558 /** 2559 * Returns the right padding of the view, plus space for the right 2560 * Drawable if any. 2561 */ 2562 public int getCompoundPaddingRight() { 2563 final Drawables dr = mDrawables; 2564 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2565 return mPaddingRight; 2566 } else { 2567 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2568 } 2569 } 2570 2571 /** 2572 * Returns the start padding of the view, plus space for the start 2573 * Drawable if any. 2574 */ 2575 public int getCompoundPaddingStart() { 2576 resolveDrawables(); 2577 switch(getLayoutDirection()) { 2578 default: 2579 case LAYOUT_DIRECTION_LTR: 2580 return getCompoundPaddingLeft(); 2581 case LAYOUT_DIRECTION_RTL: 2582 return getCompoundPaddingRight(); 2583 } 2584 } 2585 2586 /** 2587 * Returns the end padding of the view, plus space for the end 2588 * Drawable if any. 2589 */ 2590 public int getCompoundPaddingEnd() { 2591 resolveDrawables(); 2592 switch(getLayoutDirection()) { 2593 default: 2594 case LAYOUT_DIRECTION_LTR: 2595 return getCompoundPaddingRight(); 2596 case LAYOUT_DIRECTION_RTL: 2597 return getCompoundPaddingLeft(); 2598 } 2599 } 2600 2601 /** 2602 * Returns the extended top padding of the view, including both the 2603 * top Drawable if any and any extra space to keep more than maxLines 2604 * of text from showing. It is only valid to call this after measuring. 2605 */ 2606 public int getExtendedPaddingTop() { 2607 if (mMaxMode != LINES) { 2608 return getCompoundPaddingTop(); 2609 } 2610 2611 if (mLayout == null) { 2612 assumeLayout(); 2613 } 2614 2615 if (mLayout.getLineCount() <= mMaximum) { 2616 return getCompoundPaddingTop(); 2617 } 2618 2619 int top = getCompoundPaddingTop(); 2620 int bottom = getCompoundPaddingBottom(); 2621 int viewht = getHeight() - top - bottom; 2622 int layoutht = mLayout.getLineTop(mMaximum); 2623 2624 if (layoutht >= viewht) { 2625 return top; 2626 } 2627 2628 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2629 if (gravity == Gravity.TOP) { 2630 return top; 2631 } else if (gravity == Gravity.BOTTOM) { 2632 return top + viewht - layoutht; 2633 } else { // (gravity == Gravity.CENTER_VERTICAL) 2634 return top + (viewht - layoutht) / 2; 2635 } 2636 } 2637 2638 /** 2639 * Returns the extended bottom padding of the view, including both the 2640 * bottom Drawable if any and any extra space to keep more than maxLines 2641 * of text from showing. It is only valid to call this after measuring. 2642 */ 2643 public int getExtendedPaddingBottom() { 2644 if (mMaxMode != LINES) { 2645 return getCompoundPaddingBottom(); 2646 } 2647 2648 if (mLayout == null) { 2649 assumeLayout(); 2650 } 2651 2652 if (mLayout.getLineCount() <= mMaximum) { 2653 return getCompoundPaddingBottom(); 2654 } 2655 2656 int top = getCompoundPaddingTop(); 2657 int bottom = getCompoundPaddingBottom(); 2658 int viewht = getHeight() - top - bottom; 2659 int layoutht = mLayout.getLineTop(mMaximum); 2660 2661 if (layoutht >= viewht) { 2662 return bottom; 2663 } 2664 2665 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2666 if (gravity == Gravity.TOP) { 2667 return bottom + viewht - layoutht; 2668 } else if (gravity == Gravity.BOTTOM) { 2669 return bottom; 2670 } else { // (gravity == Gravity.CENTER_VERTICAL) 2671 return bottom + (viewht - layoutht) / 2; 2672 } 2673 } 2674 2675 /** 2676 * Returns the total left padding of the view, including the left 2677 * Drawable if any. 2678 */ 2679 public int getTotalPaddingLeft() { 2680 return getCompoundPaddingLeft(); 2681 } 2682 2683 /** 2684 * Returns the total right padding of the view, including the right 2685 * Drawable if any. 2686 */ 2687 public int getTotalPaddingRight() { 2688 return getCompoundPaddingRight(); 2689 } 2690 2691 /** 2692 * Returns the total start padding of the view, including the start 2693 * Drawable if any. 2694 */ 2695 public int getTotalPaddingStart() { 2696 return getCompoundPaddingStart(); 2697 } 2698 2699 /** 2700 * Returns the total end padding of the view, including the end 2701 * Drawable if any. 2702 */ 2703 public int getTotalPaddingEnd() { 2704 return getCompoundPaddingEnd(); 2705 } 2706 2707 /** 2708 * Returns the total top padding of the view, including the top 2709 * Drawable if any, the extra space to keep more than maxLines 2710 * from showing, and the vertical offset for gravity, if any. 2711 */ 2712 public int getTotalPaddingTop() { 2713 return getExtendedPaddingTop() + getVerticalOffset(true); 2714 } 2715 2716 /** 2717 * Returns the total bottom padding of the view, including the bottom 2718 * Drawable if any, the extra space to keep more than maxLines 2719 * from showing, and the vertical offset for gravity, if any. 2720 */ 2721 public int getTotalPaddingBottom() { 2722 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 2723 } 2724 2725 /** 2726 * Sets the Drawables (if any) to appear to the left of, above, to the 2727 * right of, and below the text. Use {@code null} if you do not want a 2728 * Drawable there. The Drawables must already have had 2729 * {@link Drawable#setBounds} called. 2730 * <p> 2731 * Calling this method will overwrite any Drawables previously set using 2732 * {@link #setCompoundDrawablesRelative} or related methods. 2733 * 2734 * @attr ref android.R.styleable#TextView_drawableLeft 2735 * @attr ref android.R.styleable#TextView_drawableTop 2736 * @attr ref android.R.styleable#TextView_drawableRight 2737 * @attr ref android.R.styleable#TextView_drawableBottom 2738 */ 2739 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 2740 @Nullable Drawable right, @Nullable Drawable bottom) { 2741 Drawables dr = mDrawables; 2742 2743 // We're switching to absolute, discard relative. 2744 if (dr != null) { 2745 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2746 dr.mDrawableStart = null; 2747 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2748 dr.mDrawableEnd = null; 2749 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2750 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2751 } 2752 2753 final boolean drawables = left != null || top != null || right != null || bottom != null; 2754 if (!drawables) { 2755 // Clearing drawables... can we free the data structure? 2756 if (dr != null) { 2757 if (!dr.hasMetadata()) { 2758 mDrawables = null; 2759 } else { 2760 // We need to retain the last set padding, so just clear 2761 // out all of the fields in the existing structure. 2762 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 2763 if (dr.mShowing[i] != null) { 2764 dr.mShowing[i].setCallback(null); 2765 } 2766 dr.mShowing[i] = null; 2767 } 2768 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2769 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2770 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2771 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2772 } 2773 } 2774 } else { 2775 if (dr == null) { 2776 mDrawables = dr = new Drawables(getContext()); 2777 } 2778 2779 mDrawables.mOverride = false; 2780 2781 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 2782 dr.mShowing[Drawables.LEFT].setCallback(null); 2783 } 2784 dr.mShowing[Drawables.LEFT] = left; 2785 2786 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2787 dr.mShowing[Drawables.TOP].setCallback(null); 2788 } 2789 dr.mShowing[Drawables.TOP] = top; 2790 2791 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 2792 dr.mShowing[Drawables.RIGHT].setCallback(null); 2793 } 2794 dr.mShowing[Drawables.RIGHT] = right; 2795 2796 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 2797 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2798 } 2799 dr.mShowing[Drawables.BOTTOM] = bottom; 2800 2801 final Rect compoundRect = dr.mCompoundRect; 2802 int[] state; 2803 2804 state = getDrawableState(); 2805 2806 if (left != null) { 2807 left.setState(state); 2808 left.copyBounds(compoundRect); 2809 left.setCallback(this); 2810 dr.mDrawableSizeLeft = compoundRect.width(); 2811 dr.mDrawableHeightLeft = compoundRect.height(); 2812 } else { 2813 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2814 } 2815 2816 if (right != null) { 2817 right.setState(state); 2818 right.copyBounds(compoundRect); 2819 right.setCallback(this); 2820 dr.mDrawableSizeRight = compoundRect.width(); 2821 dr.mDrawableHeightRight = compoundRect.height(); 2822 } else { 2823 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2824 } 2825 2826 if (top != null) { 2827 top.setState(state); 2828 top.copyBounds(compoundRect); 2829 top.setCallback(this); 2830 dr.mDrawableSizeTop = compoundRect.height(); 2831 dr.mDrawableWidthTop = compoundRect.width(); 2832 } else { 2833 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2834 } 2835 2836 if (bottom != null) { 2837 bottom.setState(state); 2838 bottom.copyBounds(compoundRect); 2839 bottom.setCallback(this); 2840 dr.mDrawableSizeBottom = compoundRect.height(); 2841 dr.mDrawableWidthBottom = compoundRect.width(); 2842 } else { 2843 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2844 } 2845 } 2846 2847 // Save initial left/right drawables 2848 if (dr != null) { 2849 dr.mDrawableLeftInitial = left; 2850 dr.mDrawableRightInitial = right; 2851 } 2852 2853 resetResolvedDrawables(); 2854 resolveDrawables(); 2855 applyCompoundDrawableTint(); 2856 invalidate(); 2857 requestLayout(); 2858 } 2859 2860 /** 2861 * Sets the Drawables (if any) to appear to the left of, above, to the 2862 * right of, and below the text. Use 0 if you do not want a Drawable there. 2863 * The Drawables' bounds will be set to their intrinsic bounds. 2864 * <p> 2865 * Calling this method will overwrite any Drawables previously set using 2866 * {@link #setCompoundDrawablesRelative} or related methods. 2867 * 2868 * @param left Resource identifier of the left Drawable. 2869 * @param top Resource identifier of the top Drawable. 2870 * @param right Resource identifier of the right Drawable. 2871 * @param bottom Resource identifier of the bottom Drawable. 2872 * 2873 * @attr ref android.R.styleable#TextView_drawableLeft 2874 * @attr ref android.R.styleable#TextView_drawableTop 2875 * @attr ref android.R.styleable#TextView_drawableRight 2876 * @attr ref android.R.styleable#TextView_drawableBottom 2877 */ 2878 @android.view.RemotableViewMethod 2879 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 2880 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 2881 final Context context = getContext(); 2882 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2883 top != 0 ? context.getDrawable(top) : null, 2884 right != 0 ? context.getDrawable(right) : null, 2885 bottom != 0 ? context.getDrawable(bottom) : null); 2886 } 2887 2888 /** 2889 * Sets the Drawables (if any) to appear to the left of, above, to the 2890 * right of, and below the text. Use {@code null} if you do not want a 2891 * Drawable there. The Drawables' bounds will be set to their intrinsic 2892 * bounds. 2893 * <p> 2894 * Calling this method will overwrite any Drawables previously set using 2895 * {@link #setCompoundDrawablesRelative} or related methods. 2896 * 2897 * @attr ref android.R.styleable#TextView_drawableLeft 2898 * @attr ref android.R.styleable#TextView_drawableTop 2899 * @attr ref android.R.styleable#TextView_drawableRight 2900 * @attr ref android.R.styleable#TextView_drawableBottom 2901 */ 2902 @android.view.RemotableViewMethod 2903 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 2904 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 2905 2906 if (left != null) { 2907 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 2908 } 2909 if (right != null) { 2910 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 2911 } 2912 if (top != null) { 2913 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2914 } 2915 if (bottom != null) { 2916 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2917 } 2918 setCompoundDrawables(left, top, right, bottom); 2919 } 2920 2921 /** 2922 * Sets the Drawables (if any) to appear to the start of, above, to the end 2923 * of, and below the text. Use {@code null} if you do not want a Drawable 2924 * there. The Drawables must already have had {@link Drawable#setBounds} 2925 * called. 2926 * <p> 2927 * Calling this method will overwrite any Drawables previously set using 2928 * {@link #setCompoundDrawables} or related methods. 2929 * 2930 * @attr ref android.R.styleable#TextView_drawableStart 2931 * @attr ref android.R.styleable#TextView_drawableTop 2932 * @attr ref android.R.styleable#TextView_drawableEnd 2933 * @attr ref android.R.styleable#TextView_drawableBottom 2934 */ 2935 @android.view.RemotableViewMethod 2936 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 2937 @Nullable Drawable end, @Nullable Drawable bottom) { 2938 Drawables dr = mDrawables; 2939 2940 // We're switching to relative, discard absolute. 2941 if (dr != null) { 2942 if (dr.mShowing[Drawables.LEFT] != null) { 2943 dr.mShowing[Drawables.LEFT].setCallback(null); 2944 } 2945 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 2946 if (dr.mShowing[Drawables.RIGHT] != null) { 2947 dr.mShowing[Drawables.RIGHT].setCallback(null); 2948 } 2949 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 2950 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2951 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2952 } 2953 2954 final boolean drawables = start != null || top != null 2955 || end != null || bottom != null; 2956 2957 if (!drawables) { 2958 // Clearing drawables... can we free the data structure? 2959 if (dr != null) { 2960 if (!dr.hasMetadata()) { 2961 mDrawables = null; 2962 } else { 2963 // We need to retain the last set padding, so just clear 2964 // out all of the fields in the existing structure. 2965 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2966 dr.mDrawableStart = null; 2967 if (dr.mShowing[Drawables.TOP] != null) { 2968 dr.mShowing[Drawables.TOP].setCallback(null); 2969 } 2970 dr.mShowing[Drawables.TOP] = null; 2971 if (dr.mDrawableEnd != null) { 2972 dr.mDrawableEnd.setCallback(null); 2973 } 2974 dr.mDrawableEnd = null; 2975 if (dr.mShowing[Drawables.BOTTOM] != null) { 2976 dr.mShowing[Drawables.BOTTOM].setCallback(null); 2977 } 2978 dr.mShowing[Drawables.BOTTOM] = null; 2979 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2980 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2981 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2982 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2983 } 2984 } 2985 } else { 2986 if (dr == null) { 2987 mDrawables = dr = new Drawables(getContext()); 2988 } 2989 2990 mDrawables.mOverride = true; 2991 2992 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 2993 dr.mDrawableStart.setCallback(null); 2994 } 2995 dr.mDrawableStart = start; 2996 2997 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 2998 dr.mShowing[Drawables.TOP].setCallback(null); 2999 } 3000 dr.mShowing[Drawables.TOP] = top; 3001 3002 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3003 dr.mDrawableEnd.setCallback(null); 3004 } 3005 dr.mDrawableEnd = end; 3006 3007 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3008 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3009 } 3010 dr.mShowing[Drawables.BOTTOM] = bottom; 3011 3012 final Rect compoundRect = dr.mCompoundRect; 3013 int[] state; 3014 3015 state = getDrawableState(); 3016 3017 if (start != null) { 3018 start.setState(state); 3019 start.copyBounds(compoundRect); 3020 start.setCallback(this); 3021 dr.mDrawableSizeStart = compoundRect.width(); 3022 dr.mDrawableHeightStart = compoundRect.height(); 3023 } else { 3024 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3025 } 3026 3027 if (end != null) { 3028 end.setState(state); 3029 end.copyBounds(compoundRect); 3030 end.setCallback(this); 3031 dr.mDrawableSizeEnd = compoundRect.width(); 3032 dr.mDrawableHeightEnd = compoundRect.height(); 3033 } else { 3034 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3035 } 3036 3037 if (top != null) { 3038 top.setState(state); 3039 top.copyBounds(compoundRect); 3040 top.setCallback(this); 3041 dr.mDrawableSizeTop = compoundRect.height(); 3042 dr.mDrawableWidthTop = compoundRect.width(); 3043 } else { 3044 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3045 } 3046 3047 if (bottom != null) { 3048 bottom.setState(state); 3049 bottom.copyBounds(compoundRect); 3050 bottom.setCallback(this); 3051 dr.mDrawableSizeBottom = compoundRect.height(); 3052 dr.mDrawableWidthBottom = compoundRect.width(); 3053 } else { 3054 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3055 } 3056 } 3057 3058 resetResolvedDrawables(); 3059 resolveDrawables(); 3060 invalidate(); 3061 requestLayout(); 3062 } 3063 3064 /** 3065 * Sets the Drawables (if any) to appear to the start of, above, to the end 3066 * of, and below the text. Use 0 if you do not want a Drawable there. The 3067 * Drawables' bounds will be set to their intrinsic bounds. 3068 * <p> 3069 * Calling this method will overwrite any Drawables previously set using 3070 * {@link #setCompoundDrawables} or related methods. 3071 * 3072 * @param start Resource identifier of the start Drawable. 3073 * @param top Resource identifier of the top Drawable. 3074 * @param end Resource identifier of the end Drawable. 3075 * @param bottom Resource identifier of the bottom Drawable. 3076 * 3077 * @attr ref android.R.styleable#TextView_drawableStart 3078 * @attr ref android.R.styleable#TextView_drawableTop 3079 * @attr ref android.R.styleable#TextView_drawableEnd 3080 * @attr ref android.R.styleable#TextView_drawableBottom 3081 */ 3082 @android.view.RemotableViewMethod 3083 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3084 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3085 final Context context = getContext(); 3086 setCompoundDrawablesRelativeWithIntrinsicBounds( 3087 start != 0 ? context.getDrawable(start) : null, 3088 top != 0 ? context.getDrawable(top) : null, 3089 end != 0 ? context.getDrawable(end) : null, 3090 bottom != 0 ? context.getDrawable(bottom) : null); 3091 } 3092 3093 /** 3094 * Sets the Drawables (if any) to appear to the start of, above, to the end 3095 * of, and below the text. Use {@code null} if you do not want a Drawable 3096 * there. The Drawables' bounds will be set to their intrinsic bounds. 3097 * <p> 3098 * Calling this method will overwrite any Drawables previously set using 3099 * {@link #setCompoundDrawables} or related methods. 3100 * 3101 * @attr ref android.R.styleable#TextView_drawableStart 3102 * @attr ref android.R.styleable#TextView_drawableTop 3103 * @attr ref android.R.styleable#TextView_drawableEnd 3104 * @attr ref android.R.styleable#TextView_drawableBottom 3105 */ 3106 @android.view.RemotableViewMethod 3107 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3108 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3109 3110 if (start != null) { 3111 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3112 } 3113 if (end != null) { 3114 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3115 } 3116 if (top != null) { 3117 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3118 } 3119 if (bottom != null) { 3120 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3121 } 3122 setCompoundDrawablesRelative(start, top, end, bottom); 3123 } 3124 3125 /** 3126 * Returns drawables for the left, top, right, and bottom borders. 3127 * 3128 * @attr ref android.R.styleable#TextView_drawableLeft 3129 * @attr ref android.R.styleable#TextView_drawableTop 3130 * @attr ref android.R.styleable#TextView_drawableRight 3131 * @attr ref android.R.styleable#TextView_drawableBottom 3132 */ 3133 @NonNull 3134 public Drawable[] getCompoundDrawables() { 3135 final Drawables dr = mDrawables; 3136 if (dr != null) { 3137 return dr.mShowing.clone(); 3138 } else { 3139 return new Drawable[] { null, null, null, null }; 3140 } 3141 } 3142 3143 /** 3144 * Returns drawables for the start, top, end, and bottom borders. 3145 * 3146 * @attr ref android.R.styleable#TextView_drawableStart 3147 * @attr ref android.R.styleable#TextView_drawableTop 3148 * @attr ref android.R.styleable#TextView_drawableEnd 3149 * @attr ref android.R.styleable#TextView_drawableBottom 3150 */ 3151 @NonNull 3152 public Drawable[] getCompoundDrawablesRelative() { 3153 final Drawables dr = mDrawables; 3154 if (dr != null) { 3155 return new Drawable[] { 3156 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3157 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3158 }; 3159 } else { 3160 return new Drawable[] { null, null, null, null }; 3161 } 3162 } 3163 3164 /** 3165 * Sets the size of the padding between the compound drawables and 3166 * the text. 3167 * 3168 * @attr ref android.R.styleable#TextView_drawablePadding 3169 */ 3170 @android.view.RemotableViewMethod 3171 public void setCompoundDrawablePadding(int pad) { 3172 Drawables dr = mDrawables; 3173 if (pad == 0) { 3174 if (dr != null) { 3175 dr.mDrawablePadding = pad; 3176 } 3177 } else { 3178 if (dr == null) { 3179 mDrawables = dr = new Drawables(getContext()); 3180 } 3181 dr.mDrawablePadding = pad; 3182 } 3183 3184 invalidate(); 3185 requestLayout(); 3186 } 3187 3188 /** 3189 * Returns the padding between the compound drawables and the text. 3190 * 3191 * @attr ref android.R.styleable#TextView_drawablePadding 3192 */ 3193 public int getCompoundDrawablePadding() { 3194 final Drawables dr = mDrawables; 3195 return dr != null ? dr.mDrawablePadding : 0; 3196 } 3197 3198 /** 3199 * Applies a tint to the compound drawables. Does not modify the 3200 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 3201 * <p> 3202 * Subsequent calls to 3203 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3204 * and related methods will automatically mutate the drawables and apply 3205 * the specified tint and tint mode using 3206 * {@link Drawable#setTintList(ColorStateList)}. 3207 * 3208 * @param tint the tint to apply, may be {@code null} to clear tint 3209 * 3210 * @attr ref android.R.styleable#TextView_drawableTint 3211 * @see #getCompoundDrawableTintList() 3212 * @see Drawable#setTintList(ColorStateList) 3213 */ 3214 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3215 if (mDrawables == null) { 3216 mDrawables = new Drawables(getContext()); 3217 } 3218 mDrawables.mTintList = tint; 3219 mDrawables.mHasTint = true; 3220 3221 applyCompoundDrawableTint(); 3222 } 3223 3224 /** 3225 * @return the tint applied to the compound drawables 3226 * @attr ref android.R.styleable#TextView_drawableTint 3227 * @see #setCompoundDrawableTintList(ColorStateList) 3228 */ 3229 public ColorStateList getCompoundDrawableTintList() { 3230 return mDrawables != null ? mDrawables.mTintList : null; 3231 } 3232 3233 /** 3234 * Specifies the blending mode used to apply the tint specified by 3235 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3236 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3237 * 3238 * @param tintMode the blending mode used to apply the tint, may be 3239 * {@code null} to clear tint 3240 * @attr ref android.R.styleable#TextView_drawableTintMode 3241 * @see #setCompoundDrawableTintList(ColorStateList) 3242 * @see Drawable#setTintMode(PorterDuff.Mode) 3243 */ 3244 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3245 if (mDrawables == null) { 3246 mDrawables = new Drawables(getContext()); 3247 } 3248 mDrawables.mTintMode = tintMode; 3249 mDrawables.mHasTintMode = true; 3250 3251 applyCompoundDrawableTint(); 3252 } 3253 3254 /** 3255 * Returns the blending mode used to apply the tint to the compound 3256 * drawables, if specified. 3257 * 3258 * @return the blending mode used to apply the tint to the compound 3259 * drawables 3260 * @attr ref android.R.styleable#TextView_drawableTintMode 3261 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3262 */ 3263 public PorterDuff.Mode getCompoundDrawableTintMode() { 3264 return mDrawables != null ? mDrawables.mTintMode : null; 3265 } 3266 3267 private void applyCompoundDrawableTint() { 3268 if (mDrawables == null) { 3269 return; 3270 } 3271 3272 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3273 final ColorStateList tintList = mDrawables.mTintList; 3274 final PorterDuff.Mode tintMode = mDrawables.mTintMode; 3275 final boolean hasTint = mDrawables.mHasTint; 3276 final boolean hasTintMode = mDrawables.mHasTintMode; 3277 final int[] state = getDrawableState(); 3278 3279 for (Drawable dr : mDrawables.mShowing) { 3280 if (dr == null) { 3281 continue; 3282 } 3283 3284 if (dr == mDrawables.mDrawableError) { 3285 // From a developer's perspective, the error drawable isn't 3286 // a compound drawable. Don't apply the generic compound 3287 // drawable tint to it. 3288 continue; 3289 } 3290 3291 dr.mutate(); 3292 3293 if (hasTint) { 3294 dr.setTintList(tintList); 3295 } 3296 3297 if (hasTintMode) { 3298 dr.setTintMode(tintMode); 3299 } 3300 3301 // The drawable (or one of its children) may not have been 3302 // stateful before applying the tint, so let's try again. 3303 if (dr.isStateful()) { 3304 dr.setState(state); 3305 } 3306 } 3307 } 3308 } 3309 3310 @Override 3311 public void setPadding(int left, int top, int right, int bottom) { 3312 if (left != mPaddingLeft 3313 || right != mPaddingRight 3314 || top != mPaddingTop 3315 || bottom != mPaddingBottom) { 3316 nullLayouts(); 3317 } 3318 3319 // the super call will requestLayout() 3320 super.setPadding(left, top, right, bottom); 3321 invalidate(); 3322 } 3323 3324 @Override 3325 public void setPaddingRelative(int start, int top, int end, int bottom) { 3326 if (start != getPaddingStart() 3327 || end != getPaddingEnd() 3328 || top != mPaddingTop 3329 || bottom != mPaddingBottom) { 3330 nullLayouts(); 3331 } 3332 3333 // the super call will requestLayout() 3334 super.setPaddingRelative(start, top, end, bottom); 3335 invalidate(); 3336 } 3337 3338 /** 3339 * Gets the autolink mask of the text. See {@link 3340 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 3341 * possible values. 3342 * 3343 * @attr ref android.R.styleable#TextView_autoLink 3344 */ 3345 public final int getAutoLinkMask() { 3346 return mAutoLinkMask; 3347 } 3348 3349 /** 3350 * Sets the text appearance from the specified style resource. 3351 * <p> 3352 * Use a framework-defined {@code TextAppearance} style like 3353 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 3354 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 3355 * set of attributes that can be used in a custom style. 3356 * 3357 * @param resId the resource identifier of the style to apply 3358 * @attr ref android.R.styleable#TextView_textAppearance 3359 */ 3360 @SuppressWarnings("deprecation") 3361 public void setTextAppearance(@StyleRes int resId) { 3362 setTextAppearance(mContext, resId); 3363 } 3364 3365 /** 3366 * Sets the text color, size, style, hint color, and highlight color 3367 * from the specified TextAppearance resource. 3368 * 3369 * @deprecated Use {@link #setTextAppearance(int)} instead. 3370 */ 3371 @Deprecated 3372 public void setTextAppearance(Context context, @StyleRes int resId) { 3373 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 3374 3375 final int textColorHighlight = ta.getColor( 3376 R.styleable.TextAppearance_textColorHighlight, 0); 3377 if (textColorHighlight != 0) { 3378 setHighlightColor(textColorHighlight); 3379 } 3380 3381 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor); 3382 if (textColor != null) { 3383 setTextColor(textColor); 3384 } 3385 3386 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0); 3387 if (textSize != 0) { 3388 setRawTextSize(textSize); 3389 } 3390 3391 final ColorStateList textColorHint = ta.getColorStateList( 3392 R.styleable.TextAppearance_textColorHint); 3393 if (textColorHint != null) { 3394 setHintTextColor(textColorHint); 3395 } 3396 3397 final ColorStateList textColorLink = ta.getColorStateList( 3398 R.styleable.TextAppearance_textColorLink); 3399 if (textColorLink != null) { 3400 setLinkTextColor(textColorLink); 3401 } 3402 3403 Typeface fontTypeface = null; 3404 String fontFamily = null; 3405 if (!context.isRestricted()) { 3406 try { 3407 fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily); 3408 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 3409 // Expected if it is not a font resource. 3410 } 3411 } 3412 if (fontTypeface == null) { 3413 fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily); 3414 } 3415 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1); 3416 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1); 3417 setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex); 3418 3419 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0); 3420 if (shadowColor != 0) { 3421 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0); 3422 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0); 3423 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0); 3424 setShadowLayer(r, dx, dy, shadowColor); 3425 } 3426 3427 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) { 3428 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 3429 } 3430 3431 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) { 3432 setElegantTextHeight(ta.getBoolean( 3433 R.styleable.TextAppearance_elegantTextHeight, false)); 3434 } 3435 3436 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) { 3437 setLetterSpacing(ta.getFloat( 3438 R.styleable.TextAppearance_letterSpacing, 0)); 3439 } 3440 3441 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) { 3442 setFontFeatureSettings(ta.getString( 3443 R.styleable.TextAppearance_fontFeatureSettings)); 3444 } 3445 3446 ta.recycle(); 3447 } 3448 3449 /** 3450 * Get the default primary {@link Locale} of the text in this TextView. This will always be 3451 * the first member of {@link #getTextLocales()}. 3452 * @return the default primary {@link Locale} of the text in this TextView. 3453 */ 3454 @NonNull 3455 public Locale getTextLocale() { 3456 return mTextPaint.getTextLocale(); 3457 } 3458 3459 /** 3460 * Get the default {@link LocaleList} of the text in this TextView. 3461 * @return the default {@link LocaleList} of the text in this TextView. 3462 */ 3463 @NonNull @Size(min = 1) 3464 public LocaleList getTextLocales() { 3465 return mTextPaint.getTextLocales(); 3466 } 3467 3468 private void changeListenerLocaleTo(@Nullable Locale locale) { 3469 if (mListenerChanged) { 3470 // If a listener has been explicitly set, don't change it. We may break something. 3471 return; 3472 } 3473 // The following null check is not absolutely necessary since all calling points of 3474 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 3475 // here in case others would want to call this method in the future. 3476 if (mEditor != null) { 3477 KeyListener listener = mEditor.mKeyListener; 3478 if (listener instanceof DigitsKeyListener) { 3479 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 3480 } else if (listener instanceof DateKeyListener) { 3481 listener = DateKeyListener.getInstance(locale); 3482 } else if (listener instanceof TimeKeyListener) { 3483 listener = TimeKeyListener.getInstance(locale); 3484 } else if (listener instanceof DateTimeKeyListener) { 3485 listener = DateTimeKeyListener.getInstance(locale); 3486 } else { 3487 return; 3488 } 3489 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 3490 setKeyListenerOnly(listener); 3491 setInputTypeFromEditor(); 3492 if (wasPasswordType) { 3493 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 3494 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 3495 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 3496 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 3497 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 3498 } 3499 } 3500 } 3501 } 3502 3503 /** 3504 * Set the default {@link Locale} of the text in this TextView to a one-member 3505 * {@link LocaleList} containing just the given Locale. 3506 * 3507 * @param locale the {@link Locale} for drawing text, must not be null. 3508 * 3509 * @see #setTextLocales 3510 */ 3511 public void setTextLocale(@NonNull Locale locale) { 3512 mLocalesChanged = true; 3513 mTextPaint.setTextLocale(locale); 3514 if (mLayout != null) { 3515 nullLayouts(); 3516 requestLayout(); 3517 invalidate(); 3518 } 3519 } 3520 3521 /** 3522 * Set the default {@link LocaleList} of the text in this TextView to the given value. 3523 * 3524 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 3525 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 3526 * other aspects of text display, including line breaking. 3527 * 3528 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 3529 * 3530 * @see Paint#setTextLocales 3531 */ 3532 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 3533 mLocalesChanged = true; 3534 mTextPaint.setTextLocales(locales); 3535 if (mLayout != null) { 3536 nullLayouts(); 3537 requestLayout(); 3538 invalidate(); 3539 } 3540 } 3541 3542 @Override 3543 protected void onConfigurationChanged(Configuration newConfig) { 3544 super.onConfigurationChanged(newConfig); 3545 if (!mLocalesChanged) { 3546 mTextPaint.setTextLocales(LocaleList.getDefault()); 3547 if (mLayout != null) { 3548 nullLayouts(); 3549 requestLayout(); 3550 invalidate(); 3551 } 3552 } 3553 } 3554 3555 /** 3556 * @return the size (in pixels) of the default text size in this TextView. 3557 */ 3558 @ViewDebug.ExportedProperty(category = "text") 3559 public float getTextSize() { 3560 return mTextPaint.getTextSize(); 3561 } 3562 3563 /** 3564 * @return the size (in scaled pixels) of the default text size in this TextView. 3565 * @hide 3566 */ 3567 @ViewDebug.ExportedProperty(category = "text") 3568 public float getScaledTextSize() { 3569 return mTextPaint.getTextSize() / mTextPaint.density; 3570 } 3571 3572 /** @hide */ 3573 @ViewDebug.ExportedProperty(category = "text", mapping = { 3574 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 3575 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 3576 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 3577 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 3578 }) 3579 public int getTypefaceStyle() { 3580 Typeface typeface = mTextPaint.getTypeface(); 3581 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 3582 } 3583 3584 /** 3585 * Set the default text size to the given value, interpreted as "scaled 3586 * pixel" units. This size is adjusted based on the current density and 3587 * user font size preference. 3588 * 3589 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 3590 * 3591 * @param size The scaled pixel size. 3592 * 3593 * @attr ref android.R.styleable#TextView_textSize 3594 */ 3595 @android.view.RemotableViewMethod 3596 public void setTextSize(float size) { 3597 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 3598 } 3599 3600 /** 3601 * Set the default text size to a given unit and value. See {@link 3602 * TypedValue} for the possible dimension units. 3603 * 3604 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. 3605 * 3606 * @param unit The desired dimension unit. 3607 * @param size The desired size in the given units. 3608 * 3609 * @attr ref android.R.styleable#TextView_textSize 3610 */ 3611 public void setTextSize(int unit, float size) { 3612 if (!isAutoSizeEnabled()) { 3613 setTextSizeInternal(unit, size); 3614 } 3615 } 3616 3617 private void setTextSizeInternal(int unit, float size) { 3618 Context c = getContext(); 3619 Resources r; 3620 3621 if (c == null) { 3622 r = Resources.getSystem(); 3623 } else { 3624 r = c.getResources(); 3625 } 3626 3627 setRawTextSize(TypedValue.applyDimension( 3628 unit, size, r.getDisplayMetrics())); 3629 } 3630 3631 private void setRawTextSize(float size) { 3632 if (size != mTextPaint.getTextSize()) { 3633 mTextPaint.setTextSize(size); 3634 3635 if (mLayout != null) { 3636 // Do not auto-size right after setting the text size. 3637 mNeedsAutoSizeText = false; 3638 nullLayouts(); 3639 requestLayout(); 3640 invalidate(); 3641 } 3642 } 3643 } 3644 3645 /** 3646 * Gets the extent by which text should be stretched horizontally. 3647 * This will usually be 1.0. 3648 * @return The horizontal scale factor. 3649 */ 3650 public float getTextScaleX() { 3651 return mTextPaint.getTextScaleX(); 3652 } 3653 3654 /** 3655 * Sets the horizontal scale factor for text. The default value 3656 * is 1.0. Values greater than 1.0 stretch the text wider. 3657 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 3658 * @param size The horizontal scale factor. 3659 * @attr ref android.R.styleable#TextView_textScaleX 3660 */ 3661 @android.view.RemotableViewMethod 3662 public void setTextScaleX(float size) { 3663 if (size != mTextPaint.getTextScaleX()) { 3664 mUserSetTextScaleX = true; 3665 mTextPaint.setTextScaleX(size); 3666 3667 if (mLayout != null) { 3668 nullLayouts(); 3669 requestLayout(); 3670 invalidate(); 3671 } 3672 } 3673 } 3674 3675 /** 3676 * Sets the typeface and style in which the text should be displayed. 3677 * Note that not all Typeface families actually have bold and italic 3678 * variants, so you may need to use 3679 * {@link #setTypeface(Typeface, int)} to get the appearance 3680 * that you actually want. 3681 * 3682 * @see #getTypeface() 3683 * 3684 * @attr ref android.R.styleable#TextView_fontFamily 3685 * @attr ref android.R.styleable#TextView_typeface 3686 * @attr ref android.R.styleable#TextView_textStyle 3687 */ 3688 public void setTypeface(Typeface tf) { 3689 if (mTextPaint.getTypeface() != tf) { 3690 mTextPaint.setTypeface(tf); 3691 3692 if (mLayout != null) { 3693 nullLayouts(); 3694 requestLayout(); 3695 invalidate(); 3696 } 3697 } 3698 } 3699 3700 /** 3701 * Gets the current {@link Typeface} that is used to style the text. 3702 * @return The current Typeface. 3703 * 3704 * @see #setTypeface(Typeface) 3705 * 3706 * @attr ref android.R.styleable#TextView_fontFamily 3707 * @attr ref android.R.styleable#TextView_typeface 3708 * @attr ref android.R.styleable#TextView_textStyle 3709 */ 3710 public Typeface getTypeface() { 3711 return mTextPaint.getTypeface(); 3712 } 3713 3714 /** 3715 * Set the TextView's elegant height metrics flag. This setting selects font 3716 * variants that have not been compacted to fit Latin-based vertical 3717 * metrics, and also increases top and bottom bounds to provide more space. 3718 * 3719 * @param elegant set the paint's elegant metrics flag. 3720 * 3721 * @attr ref android.R.styleable#TextView_elegantTextHeight 3722 */ 3723 public void setElegantTextHeight(boolean elegant) { 3724 if (elegant != mTextPaint.isElegantTextHeight()) { 3725 mTextPaint.setElegantTextHeight(elegant); 3726 if (mLayout != null) { 3727 nullLayouts(); 3728 requestLayout(); 3729 invalidate(); 3730 } 3731 } 3732 } 3733 3734 /** 3735 * Gets the text letter-space value, which determines the spacing between characters. 3736 * The value returned is in ems. Normally, this value is 0.0. 3737 * @return The text letter-space value in ems. 3738 * 3739 * @see #setLetterSpacing(float) 3740 * @see Paint#setLetterSpacing 3741 */ 3742 public float getLetterSpacing() { 3743 return mTextPaint.getLetterSpacing(); 3744 } 3745 3746 /** 3747 * Sets text letter-spacing in em units. Typical values 3748 * for slight expansion will be around 0.05. Negative values tighten text. 3749 * 3750 * @see #getLetterSpacing() 3751 * @see Paint#getLetterSpacing 3752 * 3753 * @param letterSpacing A text letter-space value in ems. 3754 * @attr ref android.R.styleable#TextView_letterSpacing 3755 */ 3756 @android.view.RemotableViewMethod 3757 public void setLetterSpacing(float letterSpacing) { 3758 if (letterSpacing != mTextPaint.getLetterSpacing()) { 3759 mTextPaint.setLetterSpacing(letterSpacing); 3760 3761 if (mLayout != null) { 3762 nullLayouts(); 3763 requestLayout(); 3764 invalidate(); 3765 } 3766 } 3767 } 3768 3769 /** 3770 * Returns the font feature settings. The format is the same as the CSS 3771 * font-feature-settings attribute: 3772 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 3773 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 3774 * 3775 * @return the currently set font feature settings. Default is null. 3776 * 3777 * @see #setFontFeatureSettings(String) 3778 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 3779 */ 3780 @Nullable 3781 public String getFontFeatureSettings() { 3782 return mTextPaint.getFontFeatureSettings(); 3783 } 3784 3785 /** 3786 * Returns the font variation settings. 3787 * 3788 * @return the currently set font variation settings. Returns null if no variation is 3789 * specified. 3790 * 3791 * @see #setFontVariationSettings(String) 3792 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 3793 */ 3794 @Nullable 3795 public String getFontVariationSettings() { 3796 return mTextPaint.getFontVariationSettings(); 3797 } 3798 3799 /** 3800 * Sets the break strategy for breaking paragraphs into lines. The default value for 3801 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 3802 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 3803 * text "dancing" when being edited. 3804 * 3805 * @attr ref android.R.styleable#TextView_breakStrategy 3806 * @see #getBreakStrategy() 3807 */ 3808 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 3809 mBreakStrategy = breakStrategy; 3810 if (mLayout != null) { 3811 nullLayouts(); 3812 requestLayout(); 3813 invalidate(); 3814 } 3815 } 3816 3817 /** 3818 * Gets the current strategy for breaking paragraphs into lines. 3819 * @return the current strategy for breaking paragraphs into lines. 3820 * 3821 * @attr ref android.R.styleable#TextView_breakStrategy 3822 * @see #setBreakStrategy(int) 3823 */ 3824 @Layout.BreakStrategy 3825 public int getBreakStrategy() { 3826 return mBreakStrategy; 3827 } 3828 3829 /** 3830 * Sets the frequency of automatic hyphenation to use when determining word breaks. 3831 * The default value for both TextView and {@link EditText} is 3832 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. 3833 * Note that the default hyphenation frequency value is set from the theme. 3834 * 3835 * @param hyphenationFrequency The hyphenation frequency to use. 3836 * @attr ref android.R.styleable#TextView_hyphenationFrequency 3837 * @see #getHyphenationFrequency() 3838 */ 3839 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 3840 mHyphenationFrequency = hyphenationFrequency; 3841 if (mLayout != null) { 3842 nullLayouts(); 3843 requestLayout(); 3844 invalidate(); 3845 } 3846 } 3847 3848 /** 3849 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 3850 * @return the current frequency of automatic hyphenation to be used when determining word 3851 * breaks. 3852 * 3853 * @attr ref android.R.styleable#TextView_hyphenationFrequency 3854 * @see #setHyphenationFrequency(int) 3855 */ 3856 @Layout.HyphenationFrequency 3857 public int getHyphenationFrequency() { 3858 return mHyphenationFrequency; 3859 } 3860 3861 /** 3862 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 3863 * last line is too short for justification, the last line will be displayed with the 3864 * alignment set by {@link android.view.View#setTextAlignment}. 3865 * 3866 * @see #getJustificationMode() 3867 */ 3868 @Layout.JustificationMode 3869 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 3870 mJustificationMode = justificationMode; 3871 if (mLayout != null) { 3872 nullLayouts(); 3873 requestLayout(); 3874 invalidate(); 3875 } 3876 } 3877 3878 /** 3879 * @return true if currently paragraph justification mode. 3880 * 3881 * @see #setJustificationMode(int) 3882 */ 3883 public @Layout.JustificationMode int getJustificationMode() { 3884 return mJustificationMode; 3885 } 3886 3887 /** 3888 * Sets font feature settings. The format is the same as the CSS 3889 * font-feature-settings attribute: 3890 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 3891 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 3892 * 3893 * @param fontFeatureSettings font feature settings represented as CSS compatible string 3894 * 3895 * @see #getFontFeatureSettings() 3896 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 3897 * 3898 * @attr ref android.R.styleable#TextView_fontFeatureSettings 3899 */ 3900 @android.view.RemotableViewMethod 3901 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 3902 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 3903 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 3904 3905 if (mLayout != null) { 3906 nullLayouts(); 3907 requestLayout(); 3908 invalidate(); 3909 } 3910 } 3911 } 3912 3913 3914 /** 3915 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 3916 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 3917 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 3918 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 3919 * are invalid. If a specified axis name is not defined in the font, the settings will be 3920 * ignored. 3921 * 3922 * <p> 3923 * Examples, 3924 * <ul> 3925 * <li>Set font width to 150. 3926 * <pre> 3927 * <code> 3928 * TextView textView = (TextView) findViewById(R.id.textView); 3929 * textView.setFontVariationSettings("'wdth' 150"); 3930 * </code> 3931 * </pre> 3932 * </li> 3933 * 3934 * <li>Set the font slant to 20 degrees and ask for italic style. 3935 * <pre> 3936 * <code> 3937 * TextView textView = (TextView) findViewById(R.id.textView); 3938 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 3939 * </code> 3940 * </pre> 3941 * </p> 3942 * </li> 3943 * </ul> 3944 * 3945 * @param fontVariationSettings font variation settings. You can pass null or empty string as 3946 * no variation settings. 3947 * @return true if the given settings is effective to at least one font file underlying this 3948 * TextView. This function also returns true for empty settings string. Otherwise 3949 * returns false. 3950 * 3951 * @throws IllegalArgumentException If given string is not a valid font variation settings 3952 * format. 3953 * 3954 * @see #getFontVariationSettings() 3955 * @see FontVariationAxis 3956 */ 3957 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 3958 final String existingSettings = mTextPaint.getFontVariationSettings(); 3959 if (fontVariationSettings == existingSettings 3960 || (fontVariationSettings != null 3961 && fontVariationSettings.equals(existingSettings))) { 3962 return true; 3963 } 3964 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 3965 3966 if (effective && mLayout != null) { 3967 nullLayouts(); 3968 requestLayout(); 3969 invalidate(); 3970 } 3971 return effective; 3972 } 3973 3974 /** 3975 * Sets the text color for all the states (normal, selected, 3976 * focused) to be this color. 3977 * 3978 * @param color A color value in the form 0xAARRGGBB. 3979 * Do not pass a resource ID. To get a color value from a resource ID, call 3980 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. 3981 * 3982 * @see #setTextColor(ColorStateList) 3983 * @see #getTextColors() 3984 * 3985 * @attr ref android.R.styleable#TextView_textColor 3986 */ 3987 @android.view.RemotableViewMethod 3988 public void setTextColor(@ColorInt int color) { 3989 mTextColor = ColorStateList.valueOf(color); 3990 updateTextColors(); 3991 } 3992 3993 /** 3994 * Sets the text color. 3995 * 3996 * @see #setTextColor(int) 3997 * @see #getTextColors() 3998 * @see #setHintTextColor(ColorStateList) 3999 * @see #setLinkTextColor(ColorStateList) 4000 * 4001 * @attr ref android.R.styleable#TextView_textColor 4002 */ 4003 @android.view.RemotableViewMethod 4004 public void setTextColor(ColorStateList colors) { 4005 if (colors == null) { 4006 throw new NullPointerException(); 4007 } 4008 4009 mTextColor = colors; 4010 updateTextColors(); 4011 } 4012 4013 /** 4014 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 4015 * 4016 * @see #setTextColor(ColorStateList) 4017 * @see #setTextColor(int) 4018 * 4019 * @attr ref android.R.styleable#TextView_textColor 4020 */ 4021 public final ColorStateList getTextColors() { 4022 return mTextColor; 4023 } 4024 4025 /** 4026 * Return the current color selected for normal text. 4027 * 4028 * @return Returns the current text color. 4029 */ 4030 @ColorInt 4031 public final int getCurrentTextColor() { 4032 return mCurTextColor; 4033 } 4034 4035 /** 4036 * Sets the color used to display the selection highlight. 4037 * 4038 * @attr ref android.R.styleable#TextView_textColorHighlight 4039 */ 4040 @android.view.RemotableViewMethod 4041 public void setHighlightColor(@ColorInt int color) { 4042 if (mHighlightColor != color) { 4043 mHighlightColor = color; 4044 invalidate(); 4045 } 4046 } 4047 4048 /** 4049 * @return the color used to display the selection highlight 4050 * 4051 * @see #setHighlightColor(int) 4052 * 4053 * @attr ref android.R.styleable#TextView_textColorHighlight 4054 */ 4055 @ColorInt 4056 public int getHighlightColor() { 4057 return mHighlightColor; 4058 } 4059 4060 /** 4061 * Sets whether the soft input method will be made visible when this 4062 * TextView gets focused. The default is true. 4063 */ 4064 @android.view.RemotableViewMethod 4065 public final void setShowSoftInputOnFocus(boolean show) { 4066 createEditorIfNeeded(); 4067 mEditor.mShowSoftInputOnFocus = show; 4068 } 4069 4070 /** 4071 * Returns whether the soft input method will be made visible when this 4072 * TextView gets focused. The default is true. 4073 */ 4074 public final boolean getShowSoftInputOnFocus() { 4075 // When there is no Editor, return default true value 4076 return mEditor == null || mEditor.mShowSoftInputOnFocus; 4077 } 4078 4079 /** 4080 * Gives the text a shadow of the specified blur radius and color, the specified 4081 * distance from its drawn position. 4082 * <p> 4083 * The text shadow produced does not interact with the properties on view 4084 * that are responsible for real time shadows, 4085 * {@link View#getElevation() elevation} and 4086 * {@link View#getTranslationZ() translationZ}. 4087 * 4088 * @see Paint#setShadowLayer(float, float, float, int) 4089 * 4090 * @attr ref android.R.styleable#TextView_shadowColor 4091 * @attr ref android.R.styleable#TextView_shadowDx 4092 * @attr ref android.R.styleable#TextView_shadowDy 4093 * @attr ref android.R.styleable#TextView_shadowRadius 4094 */ 4095 public void setShadowLayer(float radius, float dx, float dy, int color) { 4096 mTextPaint.setShadowLayer(radius, dx, dy, color); 4097 4098 mShadowRadius = radius; 4099 mShadowDx = dx; 4100 mShadowDy = dy; 4101 mShadowColor = color; 4102 4103 // Will change text clip region 4104 if (mEditor != null) { 4105 mEditor.invalidateTextDisplayList(); 4106 mEditor.invalidateHandlesAndActionMode(); 4107 } 4108 invalidate(); 4109 } 4110 4111 /** 4112 * Gets the radius of the shadow layer. 4113 * 4114 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 4115 * 4116 * @see #setShadowLayer(float, float, float, int) 4117 * 4118 * @attr ref android.R.styleable#TextView_shadowRadius 4119 */ 4120 public float getShadowRadius() { 4121 return mShadowRadius; 4122 } 4123 4124 /** 4125 * @return the horizontal offset of the shadow layer 4126 * 4127 * @see #setShadowLayer(float, float, float, int) 4128 * 4129 * @attr ref android.R.styleable#TextView_shadowDx 4130 */ 4131 public float getShadowDx() { 4132 return mShadowDx; 4133 } 4134 4135 /** 4136 * Gets the vertical offset of the shadow layer. 4137 * @return The vertical offset of the shadow layer. 4138 * 4139 * @see #setShadowLayer(float, float, float, int) 4140 * 4141 * @attr ref android.R.styleable#TextView_shadowDy 4142 */ 4143 public float getShadowDy() { 4144 return mShadowDy; 4145 } 4146 4147 /** 4148 * Gets the color of the shadow layer. 4149 * @return the color of the shadow layer 4150 * 4151 * @see #setShadowLayer(float, float, float, int) 4152 * 4153 * @attr ref android.R.styleable#TextView_shadowColor 4154 */ 4155 @ColorInt 4156 public int getShadowColor() { 4157 return mShadowColor; 4158 } 4159 4160 /** 4161 * Gets the {@link TextPaint} used for the text. 4162 * Use this only to consult the Paint's properties and not to change them. 4163 * @return The base paint used for the text. 4164 */ 4165 public TextPaint getPaint() { 4166 return mTextPaint; 4167 } 4168 4169 /** 4170 * Sets the autolink mask of the text. See {@link 4171 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 4172 * possible values. 4173 * 4174 * @attr ref android.R.styleable#TextView_autoLink 4175 */ 4176 @android.view.RemotableViewMethod 4177 public final void setAutoLinkMask(int mask) { 4178 mAutoLinkMask = mask; 4179 } 4180 4181 /** 4182 * Sets whether the movement method will automatically be set to 4183 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4184 * set to nonzero and links are detected in {@link #setText}. 4185 * The default is true. 4186 * 4187 * @attr ref android.R.styleable#TextView_linksClickable 4188 */ 4189 @android.view.RemotableViewMethod 4190 public final void setLinksClickable(boolean whether) { 4191 mLinksClickable = whether; 4192 } 4193 4194 /** 4195 * Returns whether the movement method will automatically be set to 4196 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 4197 * set to nonzero and links are detected in {@link #setText}. 4198 * The default is true. 4199 * 4200 * @attr ref android.R.styleable#TextView_linksClickable 4201 */ 4202 public final boolean getLinksClickable() { 4203 return mLinksClickable; 4204 } 4205 4206 /** 4207 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 4208 * (by {@link Linkify} or otherwise) if any. You can call 4209 * {@link URLSpan#getURL} on them to find where they link to 4210 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 4211 * to find the region of the text they are attached to. 4212 */ 4213 public URLSpan[] getUrls() { 4214 if (mText instanceof Spanned) { 4215 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 4216 } else { 4217 return new URLSpan[0]; 4218 } 4219 } 4220 4221 /** 4222 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 4223 * TextView. 4224 * 4225 * @see #setHintTextColor(ColorStateList) 4226 * @see #getHintTextColors() 4227 * @see #setTextColor(int) 4228 * 4229 * @attr ref android.R.styleable#TextView_textColorHint 4230 */ 4231 @android.view.RemotableViewMethod 4232 public final void setHintTextColor(@ColorInt int color) { 4233 mHintTextColor = ColorStateList.valueOf(color); 4234 updateTextColors(); 4235 } 4236 4237 /** 4238 * Sets the color of the hint text. 4239 * 4240 * @see #getHintTextColors() 4241 * @see #setHintTextColor(int) 4242 * @see #setTextColor(ColorStateList) 4243 * @see #setLinkTextColor(ColorStateList) 4244 * 4245 * @attr ref android.R.styleable#TextView_textColorHint 4246 */ 4247 public final void setHintTextColor(ColorStateList colors) { 4248 mHintTextColor = colors; 4249 updateTextColors(); 4250 } 4251 4252 /** 4253 * @return the color of the hint text, for the different states of this TextView. 4254 * 4255 * @see #setHintTextColor(ColorStateList) 4256 * @see #setHintTextColor(int) 4257 * @see #setTextColor(ColorStateList) 4258 * @see #setLinkTextColor(ColorStateList) 4259 * 4260 * @attr ref android.R.styleable#TextView_textColorHint 4261 */ 4262 public final ColorStateList getHintTextColors() { 4263 return mHintTextColor; 4264 } 4265 4266 /** 4267 * <p>Return the current color selected to paint the hint text.</p> 4268 * 4269 * @return Returns the current hint text color. 4270 */ 4271 @ColorInt 4272 public final int getCurrentHintTextColor() { 4273 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 4274 } 4275 4276 /** 4277 * Sets the color of links in the text. 4278 * 4279 * @see #setLinkTextColor(ColorStateList) 4280 * @see #getLinkTextColors() 4281 * 4282 * @attr ref android.R.styleable#TextView_textColorLink 4283 */ 4284 @android.view.RemotableViewMethod 4285 public final void setLinkTextColor(@ColorInt int color) { 4286 mLinkTextColor = ColorStateList.valueOf(color); 4287 updateTextColors(); 4288 } 4289 4290 /** 4291 * Sets the color of links in the text. 4292 * 4293 * @see #setLinkTextColor(int) 4294 * @see #getLinkTextColors() 4295 * @see #setTextColor(ColorStateList) 4296 * @see #setHintTextColor(ColorStateList) 4297 * 4298 * @attr ref android.R.styleable#TextView_textColorLink 4299 */ 4300 public final void setLinkTextColor(ColorStateList colors) { 4301 mLinkTextColor = colors; 4302 updateTextColors(); 4303 } 4304 4305 /** 4306 * @return the list of colors used to paint the links in the text, for the different states of 4307 * this TextView 4308 * 4309 * @see #setLinkTextColor(ColorStateList) 4310 * @see #setLinkTextColor(int) 4311 * 4312 * @attr ref android.R.styleable#TextView_textColorLink 4313 */ 4314 public final ColorStateList getLinkTextColors() { 4315 return mLinkTextColor; 4316 } 4317 4318 /** 4319 * Sets the horizontal alignment of the text and the 4320 * vertical gravity that will be used when there is extra space 4321 * in the TextView beyond what is required for the text itself. 4322 * 4323 * @see android.view.Gravity 4324 * @attr ref android.R.styleable#TextView_gravity 4325 */ 4326 public void setGravity(int gravity) { 4327 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 4328 gravity |= Gravity.START; 4329 } 4330 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 4331 gravity |= Gravity.TOP; 4332 } 4333 4334 boolean newLayout = false; 4335 4336 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 4337 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 4338 newLayout = true; 4339 } 4340 4341 if (gravity != mGravity) { 4342 invalidate(); 4343 } 4344 4345 mGravity = gravity; 4346 4347 if (mLayout != null && newLayout) { 4348 // XXX this is heavy-handed because no actual content changes. 4349 int want = mLayout.getWidth(); 4350 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 4351 4352 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 4353 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 4354 } 4355 } 4356 4357 /** 4358 * Returns the horizontal and vertical alignment of this TextView. 4359 * 4360 * @see android.view.Gravity 4361 * @attr ref android.R.styleable#TextView_gravity 4362 */ 4363 public int getGravity() { 4364 return mGravity; 4365 } 4366 4367 /** 4368 * Gets the flags on the Paint being used to display the text. 4369 * @return The flags on the Paint being used to display the text. 4370 * @see Paint#getFlags 4371 */ 4372 public int getPaintFlags() { 4373 return mTextPaint.getFlags(); 4374 } 4375 4376 /** 4377 * Sets flags on the Paint being used to display the text and 4378 * reflows the text if they are different from the old flags. 4379 * @see Paint#setFlags 4380 */ 4381 @android.view.RemotableViewMethod 4382 public void setPaintFlags(int flags) { 4383 if (mTextPaint.getFlags() != flags) { 4384 mTextPaint.setFlags(flags); 4385 4386 if (mLayout != null) { 4387 nullLayouts(); 4388 requestLayout(); 4389 invalidate(); 4390 } 4391 } 4392 } 4393 4394 /** 4395 * Sets whether the text should be allowed to be wider than the 4396 * View is. If false, it will be wrapped to the width of the View. 4397 * 4398 * @attr ref android.R.styleable#TextView_scrollHorizontally 4399 */ 4400 public void setHorizontallyScrolling(boolean whether) { 4401 if (mHorizontallyScrolling != whether) { 4402 mHorizontallyScrolling = whether; 4403 4404 if (mLayout != null) { 4405 nullLayouts(); 4406 requestLayout(); 4407 invalidate(); 4408 } 4409 } 4410 } 4411 4412 /** 4413 * Returns whether the text is allowed to be wider than the View is. 4414 * If false, the text will be wrapped to the width of the View. 4415 * 4416 * @attr ref android.R.styleable#TextView_scrollHorizontally 4417 * @hide 4418 */ 4419 public boolean getHorizontallyScrolling() { 4420 return mHorizontallyScrolling; 4421 } 4422 4423 /** 4424 * Sets the height of the TextView to be at least {@code minLines} tall. 4425 * <p> 4426 * This value is used for height calculation if LayoutParams does not force TextView to have an 4427 * exact height. Setting this value overrides other previous minimum height configurations such 4428 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 4429 * this value to 1. 4430 * 4431 * @param minLines the minimum height of TextView in terms of number of lines 4432 * 4433 * @see #getMinLines() 4434 * @see #setLines(int) 4435 * 4436 * @attr ref android.R.styleable#TextView_minLines 4437 */ 4438 @android.view.RemotableViewMethod 4439 public void setMinLines(int minLines) { 4440 mMinimum = minLines; 4441 mMinMode = LINES; 4442 4443 requestLayout(); 4444 invalidate(); 4445 } 4446 4447 /** 4448 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 4449 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 4450 * 4451 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 4452 * height is not defined in lines 4453 * 4454 * @see #setMinLines(int) 4455 * @see #setLines(int) 4456 * 4457 * @attr ref android.R.styleable#TextView_minLines 4458 */ 4459 public int getMinLines() { 4460 return mMinMode == LINES ? mMinimum : -1; 4461 } 4462 4463 /** 4464 * Sets the height of the TextView to be at least {@code minPixels} tall. 4465 * <p> 4466 * This value is used for height calculation if LayoutParams does not force TextView to have an 4467 * exact height. Setting this value overrides previous minimum height configurations such as 4468 * {@link #setMinLines(int)} or {@link #setLines(int)}. 4469 * <p> 4470 * The value given here is different than {@link #setMinimumHeight(int)}. Between 4471 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 4472 * used to decide the final height. 4473 * 4474 * @param minPixels the minimum height of TextView in terms of pixels 4475 * 4476 * @see #getMinHeight() 4477 * @see #setHeight(int) 4478 * 4479 * @attr ref android.R.styleable#TextView_minHeight 4480 */ 4481 @android.view.RemotableViewMethod 4482 public void setMinHeight(int minPixels) { 4483 mMinimum = minPixels; 4484 mMinMode = PIXELS; 4485 4486 requestLayout(); 4487 invalidate(); 4488 } 4489 4490 /** 4491 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 4492 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 4493 * 4494 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 4495 * defined in pixels 4496 * 4497 * @see #setMinHeight(int) 4498 * @see #setHeight(int) 4499 * 4500 * @attr ref android.R.styleable#TextView_minHeight 4501 */ 4502 public int getMinHeight() { 4503 return mMinMode == PIXELS ? mMinimum : -1; 4504 } 4505 4506 /** 4507 * Sets the height of the TextView to be at most {@code maxLines} tall. 4508 * <p> 4509 * This value is used for height calculation if LayoutParams does not force TextView to have an 4510 * exact height. Setting this value overrides previous maximum height configurations such as 4511 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 4512 * 4513 * @param maxLines the maximum height of TextView in terms of number of lines 4514 * 4515 * @see #getMaxLines() 4516 * @see #setLines(int) 4517 * 4518 * @attr ref android.R.styleable#TextView_maxLines 4519 */ 4520 @android.view.RemotableViewMethod 4521 public void setMaxLines(int maxLines) { 4522 mMaximum = maxLines; 4523 mMaxMode = LINES; 4524 4525 requestLayout(); 4526 invalidate(); 4527 } 4528 4529 /** 4530 * Returns the maximum height of TextView in terms of number of lines or -1 if the 4531 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 4532 * 4533 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 4534 * is not defined in lines. 4535 * 4536 * @see #setMaxLines(int) 4537 * @see #setLines(int) 4538 * 4539 * @attr ref android.R.styleable#TextView_maxLines 4540 */ 4541 public int getMaxLines() { 4542 return mMaxMode == LINES ? mMaximum : -1; 4543 } 4544 4545 /** 4546 * Sets the height of the TextView to be at most {@code maxPixels} tall. 4547 * <p> 4548 * This value is used for height calculation if LayoutParams does not force TextView to have an 4549 * exact height. Setting this value overrides previous maximum height configurations such as 4550 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 4551 * 4552 * @param maxPixels the maximum height of TextView in terms of pixels 4553 * 4554 * @see #getMaxHeight() 4555 * @see #setHeight(int) 4556 * 4557 * @attr ref android.R.styleable#TextView_maxHeight 4558 */ 4559 @android.view.RemotableViewMethod 4560 public void setMaxHeight(int maxPixels) { 4561 mMaximum = maxPixels; 4562 mMaxMode = PIXELS; 4563 4564 requestLayout(); 4565 invalidate(); 4566 } 4567 4568 /** 4569 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 4570 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 4571 * 4572 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 4573 * is not defined in pixels 4574 * 4575 * @see #setMaxHeight(int) 4576 * @see #setHeight(int) 4577 * 4578 * @attr ref android.R.styleable#TextView_maxHeight 4579 */ 4580 public int getMaxHeight() { 4581 return mMaxMode == PIXELS ? mMaximum : -1; 4582 } 4583 4584 /** 4585 * Sets the height of the TextView to be exactly {@code lines} tall. 4586 * <p> 4587 * This value is used for height calculation if LayoutParams does not force TextView to have an 4588 * exact height. Setting this value overrides previous minimum/maximum height configurations 4589 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 4590 * set this value to 1. 4591 * 4592 * @param lines the exact height of the TextView in terms of lines 4593 * 4594 * @see #setHeight(int) 4595 * 4596 * @attr ref android.R.styleable#TextView_lines 4597 */ 4598 @android.view.RemotableViewMethod 4599 public void setLines(int lines) { 4600 mMaximum = mMinimum = lines; 4601 mMaxMode = mMinMode = LINES; 4602 4603 requestLayout(); 4604 invalidate(); 4605 } 4606 4607 /** 4608 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 4609 * <p> 4610 * This value is used for height calculation if LayoutParams does not force TextView to have an 4611 * exact height. Setting this value overrides previous minimum/maximum height configurations 4612 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 4613 * 4614 * @param pixels the exact height of the TextView in terms of pixels 4615 * 4616 * @see #setLines(int) 4617 * 4618 * @attr ref android.R.styleable#TextView_height 4619 */ 4620 @android.view.RemotableViewMethod 4621 public void setHeight(int pixels) { 4622 mMaximum = mMinimum = pixels; 4623 mMaxMode = mMinMode = PIXELS; 4624 4625 requestLayout(); 4626 invalidate(); 4627 } 4628 4629 /** 4630 * Sets the width of the TextView to be at least {@code minEms} wide. 4631 * <p> 4632 * This value is used for width calculation if LayoutParams does not force TextView to have an 4633 * exact width. Setting this value overrides previous minimum width configurations such as 4634 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 4635 * 4636 * @param minEms the minimum width of TextView in terms of ems 4637 * 4638 * @see #getMinEms() 4639 * @see #setEms(int) 4640 * 4641 * @attr ref android.R.styleable#TextView_minEms 4642 */ 4643 @android.view.RemotableViewMethod 4644 public void setMinEms(int minEms) { 4645 mMinWidth = minEms; 4646 mMinWidthMode = EMS; 4647 4648 requestLayout(); 4649 invalidate(); 4650 } 4651 4652 /** 4653 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 4654 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 4655 * 4656 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 4657 * defined in ems 4658 * 4659 * @see #setMinEms(int) 4660 * @see #setEms(int) 4661 * 4662 * @attr ref android.R.styleable#TextView_minEms 4663 */ 4664 public int getMinEms() { 4665 return mMinWidthMode == EMS ? mMinWidth : -1; 4666 } 4667 4668 /** 4669 * Sets the width of the TextView to be at least {@code minPixels} wide. 4670 * <p> 4671 * This value is used for width calculation if LayoutParams does not force TextView to have an 4672 * exact width. Setting this value overrides previous minimum width configurations such as 4673 * {@link #setMinEms(int)} or {@link #setEms(int)}. 4674 * <p> 4675 * The value given here is different than {@link #setMinimumWidth(int)}. Between 4676 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 4677 * to decide the final width. 4678 * 4679 * @param minPixels the minimum width of TextView in terms of pixels 4680 * 4681 * @see #getMinWidth() 4682 * @see #setWidth(int) 4683 * 4684 * @attr ref android.R.styleable#TextView_minWidth 4685 */ 4686 @android.view.RemotableViewMethod 4687 public void setMinWidth(int minPixels) { 4688 mMinWidth = minPixels; 4689 mMinWidthMode = PIXELS; 4690 4691 requestLayout(); 4692 invalidate(); 4693 } 4694 4695 /** 4696 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 4697 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 4698 * 4699 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 4700 * defined in pixels 4701 * 4702 * @see #setMinWidth(int) 4703 * @see #setWidth(int) 4704 * 4705 * @attr ref android.R.styleable#TextView_minWidth 4706 */ 4707 public int getMinWidth() { 4708 return mMinWidthMode == PIXELS ? mMinWidth : -1; 4709 } 4710 4711 /** 4712 * Sets the width of the TextView to be at most {@code maxEms} wide. 4713 * <p> 4714 * This value is used for width calculation if LayoutParams does not force TextView to have an 4715 * exact width. Setting this value overrides previous maximum width configurations such as 4716 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 4717 * 4718 * @param maxEms the maximum width of TextView in terms of ems 4719 * 4720 * @see #getMaxEms() 4721 * @see #setEms(int) 4722 * 4723 * @attr ref android.R.styleable#TextView_maxEms 4724 */ 4725 @android.view.RemotableViewMethod 4726 public void setMaxEms(int maxEms) { 4727 mMaxWidth = maxEms; 4728 mMaxWidthMode = EMS; 4729 4730 requestLayout(); 4731 invalidate(); 4732 } 4733 4734 /** 4735 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 4736 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 4737 * 4738 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 4739 * defined in ems 4740 * 4741 * @see #setMaxEms(int) 4742 * @see #setEms(int) 4743 * 4744 * @attr ref android.R.styleable#TextView_maxEms 4745 */ 4746 public int getMaxEms() { 4747 return mMaxWidthMode == EMS ? mMaxWidth : -1; 4748 } 4749 4750 /** 4751 * Sets the width of the TextView to be at most {@code maxPixels} wide. 4752 * <p> 4753 * This value is used for width calculation if LayoutParams does not force TextView to have an 4754 * exact width. Setting this value overrides previous maximum width configurations such as 4755 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 4756 * 4757 * @param maxPixels the maximum width of TextView in terms of pixels 4758 * 4759 * @see #getMaxWidth() 4760 * @see #setWidth(int) 4761 * 4762 * @attr ref android.R.styleable#TextView_maxWidth 4763 */ 4764 @android.view.RemotableViewMethod 4765 public void setMaxWidth(int maxPixels) { 4766 mMaxWidth = maxPixels; 4767 mMaxWidthMode = PIXELS; 4768 4769 requestLayout(); 4770 invalidate(); 4771 } 4772 4773 /** 4774 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 4775 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 4776 * 4777 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 4778 * defined in pixels 4779 * 4780 * @see #setMaxWidth(int) 4781 * @see #setWidth(int) 4782 * 4783 * @attr ref android.R.styleable#TextView_maxWidth 4784 */ 4785 public int getMaxWidth() { 4786 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 4787 } 4788 4789 /** 4790 * Sets the width of the TextView to be exactly {@code ems} wide. 4791 * 4792 * This value is used for width calculation if LayoutParams does not force TextView to have an 4793 * exact width. Setting this value overrides previous minimum/maximum configurations such as 4794 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 4795 * 4796 * @param ems the exact width of the TextView in terms of ems 4797 * 4798 * @see #setWidth(int) 4799 * 4800 * @attr ref android.R.styleable#TextView_ems 4801 */ 4802 @android.view.RemotableViewMethod 4803 public void setEms(int ems) { 4804 mMaxWidth = mMinWidth = ems; 4805 mMaxWidthMode = mMinWidthMode = EMS; 4806 4807 requestLayout(); 4808 invalidate(); 4809 } 4810 4811 /** 4812 * Sets the width of the TextView to be exactly {@code pixels} wide. 4813 * <p> 4814 * This value is used for width calculation if LayoutParams does not force TextView to have an 4815 * exact width. Setting this value overrides previous minimum/maximum width configurations 4816 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 4817 * 4818 * @param pixels the exact width of the TextView in terms of pixels 4819 * 4820 * @see #setEms(int) 4821 * 4822 * @attr ref android.R.styleable#TextView_width 4823 */ 4824 @android.view.RemotableViewMethod 4825 public void setWidth(int pixels) { 4826 mMaxWidth = mMinWidth = pixels; 4827 mMaxWidthMode = mMinWidthMode = PIXELS; 4828 4829 requestLayout(); 4830 invalidate(); 4831 } 4832 4833 /** 4834 * Sets line spacing for this TextView. Each line will have its height 4835 * multiplied by <code>mult</code> and have <code>add</code> added to it. 4836 * 4837 * @attr ref android.R.styleable#TextView_lineSpacingExtra 4838 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 4839 */ 4840 public void setLineSpacing(float add, float mult) { 4841 if (mSpacingAdd != add || mSpacingMult != mult) { 4842 mSpacingAdd = add; 4843 mSpacingMult = mult; 4844 4845 if (mLayout != null) { 4846 nullLayouts(); 4847 requestLayout(); 4848 invalidate(); 4849 } 4850 } 4851 } 4852 4853 /** 4854 * Gets the line spacing multiplier 4855 * 4856 * @return the value by which each line's height is multiplied to get its actual height. 4857 * 4858 * @see #setLineSpacing(float, float) 4859 * @see #getLineSpacingExtra() 4860 * 4861 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 4862 */ 4863 public float getLineSpacingMultiplier() { 4864 return mSpacingMult; 4865 } 4866 4867 /** 4868 * Gets the line spacing extra space 4869 * 4870 * @return the extra space that is added to the height of each lines of this TextView. 4871 * 4872 * @see #setLineSpacing(float, float) 4873 * @see #getLineSpacingMultiplier() 4874 * 4875 * @attr ref android.R.styleable#TextView_lineSpacingExtra 4876 */ 4877 public float getLineSpacingExtra() { 4878 return mSpacingAdd; 4879 } 4880 4881 /** 4882 * Convenience method to append the specified text to the TextView's 4883 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 4884 * if it was not already editable. 4885 * 4886 * @param text text to be appended to the already displayed text 4887 */ 4888 public final void append(CharSequence text) { 4889 append(text, 0, text.length()); 4890 } 4891 4892 /** 4893 * Convenience method to append the specified text slice to the TextView's 4894 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 4895 * if it was not already editable. 4896 * 4897 * @param text text to be appended to the already displayed text 4898 * @param start the index of the first character in the {@code text} 4899 * @param end the index of the character following the last character in the {@code text} 4900 * 4901 * @see Appendable#append(CharSequence, int, int) 4902 */ 4903 public void append(CharSequence text, int start, int end) { 4904 if (!(mText instanceof Editable)) { 4905 setText(mText, BufferType.EDITABLE); 4906 } 4907 4908 ((Editable) mText).append(text, start, end); 4909 4910 if (mAutoLinkMask != 0) { 4911 boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask); 4912 // Do not change the movement method for text that support text selection as it 4913 // would prevent an arbitrary cursor displacement. 4914 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 4915 setMovementMethod(LinkMovementMethod.getInstance()); 4916 } 4917 } 4918 } 4919 4920 private void updateTextColors() { 4921 boolean inval = false; 4922 int color = mTextColor.getColorForState(getDrawableState(), 0); 4923 if (color != mCurTextColor) { 4924 mCurTextColor = color; 4925 inval = true; 4926 } 4927 if (mLinkTextColor != null) { 4928 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 4929 if (color != mTextPaint.linkColor) { 4930 mTextPaint.linkColor = color; 4931 inval = true; 4932 } 4933 } 4934 if (mHintTextColor != null) { 4935 color = mHintTextColor.getColorForState(getDrawableState(), 0); 4936 if (color != mCurHintTextColor) { 4937 mCurHintTextColor = color; 4938 if (mText.length() == 0) { 4939 inval = true; 4940 } 4941 } 4942 } 4943 if (inval) { 4944 // Text needs to be redrawn with the new color 4945 if (mEditor != null) mEditor.invalidateTextDisplayList(); 4946 invalidate(); 4947 } 4948 } 4949 4950 @Override 4951 protected void drawableStateChanged() { 4952 super.drawableStateChanged(); 4953 4954 if (mTextColor != null && mTextColor.isStateful() 4955 || (mHintTextColor != null && mHintTextColor.isStateful()) 4956 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 4957 updateTextColors(); 4958 } 4959 4960 if (mDrawables != null) { 4961 final int[] state = getDrawableState(); 4962 for (Drawable dr : mDrawables.mShowing) { 4963 if (dr != null && dr.isStateful() && dr.setState(state)) { 4964 invalidateDrawable(dr); 4965 } 4966 } 4967 } 4968 } 4969 4970 @Override 4971 public void drawableHotspotChanged(float x, float y) { 4972 super.drawableHotspotChanged(x, y); 4973 4974 if (mDrawables != null) { 4975 for (Drawable dr : mDrawables.mShowing) { 4976 if (dr != null) { 4977 dr.setHotspot(x, y); 4978 } 4979 } 4980 } 4981 } 4982 4983 @Override 4984 public Parcelable onSaveInstanceState() { 4985 Parcelable superState = super.onSaveInstanceState(); 4986 4987 // Save state if we are forced to 4988 final boolean freezesText = getFreezesText(); 4989 boolean hasSelection = false; 4990 int start = -1; 4991 int end = -1; 4992 4993 if (mText != null) { 4994 start = getSelectionStart(); 4995 end = getSelectionEnd(); 4996 if (start >= 0 || end >= 0) { 4997 // Or save state if there is a selection 4998 hasSelection = true; 4999 } 5000 } 5001 5002 if (freezesText || hasSelection) { 5003 SavedState ss = new SavedState(superState); 5004 5005 if (freezesText) { 5006 if (mText instanceof Spanned) { 5007 final Spannable sp = new SpannableStringBuilder(mText); 5008 5009 if (mEditor != null) { 5010 removeMisspelledSpans(sp); 5011 sp.removeSpan(mEditor.mSuggestionRangeSpan); 5012 } 5013 5014 ss.text = sp; 5015 } else { 5016 ss.text = mText.toString(); 5017 } 5018 } 5019 5020 if (hasSelection) { 5021 // XXX Should also save the current scroll position! 5022 ss.selStart = start; 5023 ss.selEnd = end; 5024 } 5025 5026 if (isFocused() && start >= 0 && end >= 0) { 5027 ss.frozenWithFocus = true; 5028 } 5029 5030 ss.error = getError(); 5031 5032 if (mEditor != null) { 5033 ss.editorState = mEditor.saveInstanceState(); 5034 } 5035 return ss; 5036 } 5037 5038 return superState; 5039 } 5040 5041 void removeMisspelledSpans(Spannable spannable) { 5042 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 5043 SuggestionSpan.class); 5044 for (int i = 0; i < suggestionSpans.length; i++) { 5045 int flags = suggestionSpans[i].getFlags(); 5046 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 5047 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 5048 spannable.removeSpan(suggestionSpans[i]); 5049 } 5050 } 5051 } 5052 5053 @Override 5054 public void onRestoreInstanceState(Parcelable state) { 5055 if (!(state instanceof SavedState)) { 5056 super.onRestoreInstanceState(state); 5057 return; 5058 } 5059 5060 SavedState ss = (SavedState) state; 5061 super.onRestoreInstanceState(ss.getSuperState()); 5062 5063 // XXX restore buffer type too, as well as lots of other stuff 5064 if (ss.text != null) { 5065 setText(ss.text); 5066 } 5067 5068 if (ss.selStart >= 0 && ss.selEnd >= 0) { 5069 if (mText instanceof Spannable) { 5070 int len = mText.length(); 5071 5072 if (ss.selStart > len || ss.selEnd > len) { 5073 String restored = ""; 5074 5075 if (ss.text != null) { 5076 restored = "(restored) "; 5077 } 5078 5079 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 5080 + " out of range for " + restored + "text " + mText); 5081 } else { 5082 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); 5083 5084 if (ss.frozenWithFocus) { 5085 createEditorIfNeeded(); 5086 mEditor.mFrozenWithFocus = true; 5087 } 5088 } 5089 } 5090 } 5091 5092 if (ss.error != null) { 5093 final CharSequence error = ss.error; 5094 // Display the error later, after the first layout pass 5095 post(new Runnable() { 5096 public void run() { 5097 if (mEditor == null || !mEditor.mErrorWasChanged) { 5098 setError(error); 5099 } 5100 } 5101 }); 5102 } 5103 5104 if (ss.editorState != null) { 5105 createEditorIfNeeded(); 5106 mEditor.restoreInstanceState(ss.editorState); 5107 } 5108 } 5109 5110 /** 5111 * Control whether this text view saves its entire text contents when 5112 * freezing to an icicle, in addition to dynamic state such as cursor 5113 * position. By default this is false, not saving the text. Set to true 5114 * if the text in the text view is not being saved somewhere else in 5115 * persistent storage (such as in a content provider) so that if the 5116 * view is later thawed the user will not lose their data. For 5117 * {@link android.widget.EditText} it is always enabled, regardless of 5118 * the value of the attribute. 5119 * 5120 * @param freezesText Controls whether a frozen icicle should include the 5121 * entire text data: true to include it, false to not. 5122 * 5123 * @attr ref android.R.styleable#TextView_freezesText 5124 */ 5125 @android.view.RemotableViewMethod 5126 public void setFreezesText(boolean freezesText) { 5127 mFreezesText = freezesText; 5128 } 5129 5130 /** 5131 * Return whether this text view is including its entire text contents 5132 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 5133 * 5134 * @return Returns true if text is included, false if it isn't. 5135 * 5136 * @see #setFreezesText 5137 */ 5138 public boolean getFreezesText() { 5139 return mFreezesText; 5140 } 5141 5142 /////////////////////////////////////////////////////////////////////////// 5143 5144 /** 5145 * Sets the Factory used to create new {@link Editable Editables}. 5146 * 5147 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 5148 * 5149 * @see android.text.Editable.Factory 5150 * @see android.widget.TextView.BufferType#EDITABLE 5151 */ 5152 public final void setEditableFactory(Editable.Factory factory) { 5153 mEditableFactory = factory; 5154 setText(mText); 5155 } 5156 5157 /** 5158 * Sets the Factory used to create new {@link Spannable Spannables}. 5159 * 5160 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 5161 * 5162 * @see android.text.Spannable.Factory 5163 * @see android.widget.TextView.BufferType#SPANNABLE 5164 */ 5165 public final void setSpannableFactory(Spannable.Factory factory) { 5166 mSpannableFactory = factory; 5167 setText(mText); 5168 } 5169 5170 /** 5171 * Sets the text to be displayed. TextView <em>does not</em> accept 5172 * HTML-like formatting, which you can do with text strings in XML resource files. 5173 * To style your strings, attach android.text.style.* objects to a 5174 * {@link android.text.SpannableString}, or see the 5175 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 5176 * Available Resource Types</a> documentation for an example of setting 5177 * formatted text in the XML resource file. 5178 * <p/> 5179 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5180 * intermediate {@link Spannable Spannables}. Likewise it will use 5181 * {@link android.text.Editable.Factory} to create final or intermediate 5182 * {@link Editable Editables}. 5183 * 5184 * @param text text to be displayed 5185 * 5186 * @attr ref android.R.styleable#TextView_text 5187 */ 5188 @android.view.RemotableViewMethod 5189 public final void setText(CharSequence text) { 5190 setText(text, mBufferType); 5191 } 5192 5193 /** 5194 * Sets the text to be displayed but retains the cursor position. Same as 5195 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 5196 * new text. 5197 * <p/> 5198 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5199 * intermediate {@link Spannable Spannables}. Likewise it will use 5200 * {@link android.text.Editable.Factory} to create final or intermediate 5201 * {@link Editable Editables}. 5202 * 5203 * @param text text to be displayed 5204 * 5205 * @see #setText(CharSequence) 5206 */ 5207 @android.view.RemotableViewMethod 5208 public final void setTextKeepState(CharSequence text) { 5209 setTextKeepState(text, mBufferType); 5210 } 5211 5212 /** 5213 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 5214 * <p/> 5215 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5216 * intermediate {@link Spannable Spannables}. Likewise it will use 5217 * {@link android.text.Editable.Factory} to create final or intermediate 5218 * {@link Editable Editables}. 5219 * 5220 * @param text text to be displayed 5221 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 5222 * stored as a static text, styleable/spannable text, or editable text 5223 * 5224 * @see #setText(CharSequence) 5225 * @see android.widget.TextView.BufferType 5226 * @see #setSpannableFactory(Spannable.Factory) 5227 * @see #setEditableFactory(Editable.Factory) 5228 * 5229 * @attr ref android.R.styleable#TextView_text 5230 * @attr ref android.R.styleable#TextView_bufferType 5231 */ 5232 public void setText(CharSequence text, BufferType type) { 5233 setText(text, type, true, 0); 5234 5235 if (mCharWrapper != null) { 5236 mCharWrapper.mChars = null; 5237 } 5238 } 5239 5240 private void setText(CharSequence text, BufferType type, 5241 boolean notifyBefore, int oldlen) { 5242 mTextFromResource = false; 5243 if (text == null) { 5244 text = ""; 5245 } 5246 5247 // If suggestions are not enabled, remove the suggestion spans from the text 5248 if (!isSuggestionsEnabled()) { 5249 text = removeSuggestionSpans(text); 5250 } 5251 5252 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 5253 5254 if (text instanceof Spanned 5255 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 5256 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 5257 setHorizontalFadingEdgeEnabled(true); 5258 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 5259 } else { 5260 setHorizontalFadingEdgeEnabled(false); 5261 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 5262 } 5263 setEllipsize(TextUtils.TruncateAt.MARQUEE); 5264 } 5265 5266 int n = mFilters.length; 5267 for (int i = 0; i < n; i++) { 5268 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 5269 if (out != null) { 5270 text = out; 5271 } 5272 } 5273 5274 if (notifyBefore) { 5275 if (mText != null) { 5276 oldlen = mText.length(); 5277 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 5278 } else { 5279 sendBeforeTextChanged("", 0, 0, text.length()); 5280 } 5281 } 5282 5283 boolean needEditableForNotification = false; 5284 5285 if (mListeners != null && mListeners.size() != 0) { 5286 needEditableForNotification = true; 5287 } 5288 5289 if (type == BufferType.EDITABLE || getKeyListener() != null 5290 || needEditableForNotification) { 5291 createEditorIfNeeded(); 5292 mEditor.forgetUndoRedo(); 5293 Editable t = mEditableFactory.newEditable(text); 5294 text = t; 5295 setFilters(t, mFilters); 5296 InputMethodManager imm = InputMethodManager.peekInstance(); 5297 if (imm != null) imm.restartInput(this); 5298 } else if (type == BufferType.SPANNABLE || mMovement != null) { 5299 text = mSpannableFactory.newSpannable(text); 5300 } else if (!(text instanceof CharWrapper)) { 5301 text = TextUtils.stringOrSpannedString(text); 5302 } 5303 5304 if (mAutoLinkMask != 0) { 5305 Spannable s2; 5306 5307 if (type == BufferType.EDITABLE || text instanceof Spannable) { 5308 s2 = (Spannable) text; 5309 } else { 5310 s2 = mSpannableFactory.newSpannable(text); 5311 } 5312 5313 if (Linkify.addLinks(s2, mAutoLinkMask)) { 5314 text = s2; 5315 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 5316 5317 /* 5318 * We must go ahead and set the text before changing the 5319 * movement method, because setMovementMethod() may call 5320 * setText() again to try to upgrade the buffer type. 5321 */ 5322 mText = text; 5323 5324 // Do not change the movement method for text that support text selection as it 5325 // would prevent an arbitrary cursor displacement. 5326 if (mLinksClickable && !textCanBeSelected()) { 5327 setMovementMethod(LinkMovementMethod.getInstance()); 5328 } 5329 } 5330 } 5331 5332 mBufferType = type; 5333 mText = text; 5334 5335 if (mTransformation == null) { 5336 mTransformed = text; 5337 } else { 5338 mTransformed = mTransformation.getTransformation(text, this); 5339 } 5340 5341 final int textLength = text.length(); 5342 5343 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 5344 Spannable sp = (Spannable) text; 5345 5346 // Remove any ChangeWatchers that might have come from other TextViews. 5347 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 5348 final int count = watchers.length; 5349 for (int i = 0; i < count; i++) { 5350 sp.removeSpan(watchers[i]); 5351 } 5352 5353 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 5354 5355 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 5356 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 5357 5358 if (mEditor != null) mEditor.addSpanWatchers(sp); 5359 5360 if (mTransformation != null) { 5361 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 5362 } 5363 5364 if (mMovement != null) { 5365 mMovement.initialize(this, (Spannable) text); 5366 5367 /* 5368 * Initializing the movement method will have set the 5369 * selection, so reset mSelectionMoved to keep that from 5370 * interfering with the normal on-focus selection-setting. 5371 */ 5372 if (mEditor != null) mEditor.mSelectionMoved = false; 5373 } 5374 } 5375 5376 if (mLayout != null) { 5377 checkForRelayout(); 5378 } 5379 5380 sendOnTextChanged(text, 0, oldlen, textLength); 5381 onTextChanged(text, 0, oldlen, textLength); 5382 5383 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 5384 5385 if (needEditableForNotification) { 5386 sendAfterTextChanged((Editable) text); 5387 } else { 5388 // Always notify AutoFillManager - it will return right away if autofill is disabled. 5389 notifyAutoFillManagerAfterTextChangedIfNeeded(); 5390 } 5391 5392 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 5393 if (mEditor != null) mEditor.prepareCursorControllers(); 5394 } 5395 5396 /** 5397 * Sets the TextView to display the specified slice of the specified 5398 * char array. You must promise that you will not change the contents 5399 * of the array except for right before another call to setText(), 5400 * since the TextView has no way to know that the text 5401 * has changed and that it needs to invalidate and re-layout. 5402 * 5403 * @param text char array to be displayed 5404 * @param start start index in the char array 5405 * @param len length of char count after {@code start} 5406 */ 5407 public final void setText(char[] text, int start, int len) { 5408 int oldlen = 0; 5409 5410 if (start < 0 || len < 0 || start + len > text.length) { 5411 throw new IndexOutOfBoundsException(start + ", " + len); 5412 } 5413 5414 /* 5415 * We must do the before-notification here ourselves because if 5416 * the old text is a CharWrapper we destroy it before calling 5417 * into the normal path. 5418 */ 5419 if (mText != null) { 5420 oldlen = mText.length(); 5421 sendBeforeTextChanged(mText, 0, oldlen, len); 5422 } else { 5423 sendBeforeTextChanged("", 0, 0, len); 5424 } 5425 5426 if (mCharWrapper == null) { 5427 mCharWrapper = new CharWrapper(text, start, len); 5428 } else { 5429 mCharWrapper.set(text, start, len); 5430 } 5431 5432 setText(mCharWrapper, mBufferType, false, oldlen); 5433 } 5434 5435 /** 5436 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 5437 * the cursor position. Same as 5438 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 5439 * position (if any) is retained in the new text. 5440 * <p/> 5441 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5442 * intermediate {@link Spannable Spannables}. Likewise it will use 5443 * {@link android.text.Editable.Factory} to create final or intermediate 5444 * {@link Editable Editables}. 5445 * 5446 * @param text text to be displayed 5447 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 5448 * stored as a static text, styleable/spannable text, or editable text 5449 * 5450 * @see #setText(CharSequence, android.widget.TextView.BufferType) 5451 */ 5452 public final void setTextKeepState(CharSequence text, BufferType type) { 5453 int start = getSelectionStart(); 5454 int end = getSelectionEnd(); 5455 int len = text.length(); 5456 5457 setText(text, type); 5458 5459 if (start >= 0 || end >= 0) { 5460 if (mText instanceof Spannable) { 5461 Selection.setSelection((Spannable) mText, 5462 Math.max(0, Math.min(start, len)), 5463 Math.max(0, Math.min(end, len))); 5464 } 5465 } 5466 } 5467 5468 /** 5469 * Sets the text to be displayed using a string resource identifier. 5470 * 5471 * @param resid the resource identifier of the string resource to be displayed 5472 * 5473 * @see #setText(CharSequence) 5474 * 5475 * @attr ref android.R.styleable#TextView_text 5476 */ 5477 @android.view.RemotableViewMethod 5478 public final void setText(@StringRes int resid) { 5479 setText(getContext().getResources().getText(resid)); 5480 mTextFromResource = true; 5481 } 5482 5483 /** 5484 * Sets the text to be displayed using a string resource identifier and the 5485 * {@link android.widget.TextView.BufferType}. 5486 * <p/> 5487 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 5488 * intermediate {@link Spannable Spannables}. Likewise it will use 5489 * {@link android.text.Editable.Factory} to create final or intermediate 5490 * {@link Editable Editables}. 5491 * 5492 * @param resid the resource identifier of the string resource to be displayed 5493 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 5494 * stored as a static text, styleable/spannable text, or editable text 5495 * 5496 * @see #setText(int) 5497 * @see #setText(CharSequence) 5498 * @see android.widget.TextView.BufferType 5499 * @see #setSpannableFactory(Spannable.Factory) 5500 * @see #setEditableFactory(Editable.Factory) 5501 * 5502 * @attr ref android.R.styleable#TextView_text 5503 * @attr ref android.R.styleable#TextView_bufferType 5504 */ 5505 public final void setText(@StringRes int resid, BufferType type) { 5506 setText(getContext().getResources().getText(resid), type); 5507 mTextFromResource = true; 5508 } 5509 5510 /** 5511 * Sets the text to be displayed when the text of the TextView is empty. 5512 * Null means to use the normal empty text. The hint does not currently 5513 * participate in determining the size of the view. 5514 * 5515 * @attr ref android.R.styleable#TextView_hint 5516 */ 5517 @android.view.RemotableViewMethod 5518 public final void setHint(CharSequence hint) { 5519 mHint = TextUtils.stringOrSpannedString(hint); 5520 5521 if (mLayout != null) { 5522 checkForRelayout(); 5523 } 5524 5525 if (mText.length() == 0) { 5526 invalidate(); 5527 } 5528 5529 // Invalidate display list if hint is currently used 5530 if (mEditor != null && mText.length() == 0 && mHint != null) { 5531 mEditor.invalidateTextDisplayList(); 5532 } 5533 } 5534 5535 /** 5536 * Sets the text to be displayed when the text of the TextView is empty, 5537 * from a resource. 5538 * 5539 * @attr ref android.R.styleable#TextView_hint 5540 */ 5541 @android.view.RemotableViewMethod 5542 public final void setHint(@StringRes int resid) { 5543 setHint(getContext().getResources().getText(resid)); 5544 } 5545 5546 /** 5547 * Returns the hint that is displayed when the text of the TextView 5548 * is empty. 5549 * 5550 * @attr ref android.R.styleable#TextView_hint 5551 */ 5552 @ViewDebug.CapturedViewProperty 5553 public CharSequence getHint() { 5554 return mHint; 5555 } 5556 5557 boolean isSingleLine() { 5558 return mSingleLine; 5559 } 5560 5561 private static boolean isMultilineInputType(int type) { 5562 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 5563 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 5564 } 5565 5566 /** 5567 * Removes the suggestion spans. 5568 */ 5569 CharSequence removeSuggestionSpans(CharSequence text) { 5570 if (text instanceof Spanned) { 5571 Spannable spannable; 5572 if (text instanceof Spannable) { 5573 spannable = (Spannable) text; 5574 } else { 5575 spannable = mSpannableFactory.newSpannable(text); 5576 text = spannable; 5577 } 5578 5579 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 5580 for (int i = 0; i < spans.length; i++) { 5581 spannable.removeSpan(spans[i]); 5582 } 5583 } 5584 return text; 5585 } 5586 5587 /** 5588 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 5589 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 5590 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 5591 * then a soft keyboard will not be displayed for this text view. 5592 * 5593 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 5594 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 5595 * type. 5596 * 5597 * @see #getInputType() 5598 * @see #setRawInputType(int) 5599 * @see android.text.InputType 5600 * @attr ref android.R.styleable#TextView_inputType 5601 */ 5602 public void setInputType(int type) { 5603 final boolean wasPassword = isPasswordInputType(getInputType()); 5604 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 5605 setInputType(type, false); 5606 final boolean isPassword = isPasswordInputType(type); 5607 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 5608 boolean forceUpdate = false; 5609 if (isPassword) { 5610 setTransformationMethod(PasswordTransformationMethod.getInstance()); 5611 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0); 5612 } else if (isVisiblePassword) { 5613 if (mTransformation == PasswordTransformationMethod.getInstance()) { 5614 forceUpdate = true; 5615 } 5616 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0); 5617 } else if (wasPassword || wasVisiblePassword) { 5618 // not in password mode, clean up typeface and transformation 5619 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1); 5620 if (mTransformation == PasswordTransformationMethod.getInstance()) { 5621 forceUpdate = true; 5622 } 5623 } 5624 5625 boolean singleLine = !isMultilineInputType(type); 5626 5627 // We need to update the single line mode if it has changed or we 5628 // were previously in password mode. 5629 if (mSingleLine != singleLine || forceUpdate) { 5630 // Change single line mode, but only change the transformation if 5631 // we are not in password mode. 5632 applySingleLine(singleLine, !isPassword, true); 5633 } 5634 5635 if (!isSuggestionsEnabled()) { 5636 mText = removeSuggestionSpans(mText); 5637 } 5638 5639 InputMethodManager imm = InputMethodManager.peekInstance(); 5640 if (imm != null) imm.restartInput(this); 5641 } 5642 5643 /** 5644 * It would be better to rely on the input type for everything. A password inputType should have 5645 * a password transformation. We should hence use isPasswordInputType instead of this method. 5646 * 5647 * We should: 5648 * - Call setInputType in setKeyListener instead of changing the input type directly (which 5649 * would install the correct transformation). 5650 * - Refuse the installation of a non-password transformation in setTransformation if the input 5651 * type is password. 5652 * 5653 * However, this is like this for legacy reasons and we cannot break existing apps. This method 5654 * is useful since it matches what the user can see (obfuscated text or not). 5655 * 5656 * @return true if the current transformation method is of the password type. 5657 */ 5658 boolean hasPasswordTransformationMethod() { 5659 return mTransformation instanceof PasswordTransformationMethod; 5660 } 5661 5662 private static boolean isPasswordInputType(int inputType) { 5663 final int variation = 5664 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 5665 return variation 5666 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 5667 || variation 5668 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 5669 || variation 5670 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 5671 } 5672 5673 private static boolean isVisiblePasswordInputType(int inputType) { 5674 final int variation = 5675 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 5676 return variation 5677 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 5678 } 5679 5680 /** 5681 * Directly change the content type integer of the text view, without 5682 * modifying any other state. 5683 * @see #setInputType(int) 5684 * @see android.text.InputType 5685 * @attr ref android.R.styleable#TextView_inputType 5686 */ 5687 public void setRawInputType(int type) { 5688 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 5689 createEditorIfNeeded(); 5690 mEditor.mInputType = type; 5691 } 5692 5693 /** 5694 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 5695 * a {@code Locale} object that can be used to customize key various listeners. 5696 * @see DateKeyListener#getInstance(Locale) 5697 * @see DateTimeKeyListener#getInstance(Locale) 5698 * @see DigitsKeyListener#getInstance(Locale) 5699 * @see TimeKeyListener#getInstance(Locale) 5700 */ 5701 @Nullable 5702 private Locale getCustomLocaleForKeyListenerOrNull() { 5703 if (!mUseInternationalizedInput) { 5704 // If the application does not target O, stick to the previous behavior. 5705 return null; 5706 } 5707 final LocaleList locales = getImeHintLocales(); 5708 if (locales == null) { 5709 // If the application does not explicitly specify IME hint locale, also stick to the 5710 // previous behavior. 5711 return null; 5712 } 5713 return locales.get(0); 5714 } 5715 5716 private void setInputType(int type, boolean direct) { 5717 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 5718 KeyListener input; 5719 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 5720 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 5721 TextKeyListener.Capitalize cap; 5722 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 5723 cap = TextKeyListener.Capitalize.CHARACTERS; 5724 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 5725 cap = TextKeyListener.Capitalize.WORDS; 5726 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 5727 cap = TextKeyListener.Capitalize.SENTENCES; 5728 } else { 5729 cap = TextKeyListener.Capitalize.NONE; 5730 } 5731 input = TextKeyListener.getInstance(autotext, cap); 5732 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 5733 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 5734 input = DigitsKeyListener.getInstance( 5735 locale, 5736 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 5737 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 5738 if (locale != null) { 5739 // Override type, if necessary for i18n. 5740 int newType = input.getInputType(); 5741 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 5742 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 5743 // The class is different from the original class. So we need to override 5744 // 'type'. But we want to keep the password flag if it's there. 5745 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 5746 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 5747 } 5748 type = newType; 5749 } 5750 } 5751 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 5752 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 5753 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 5754 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 5755 input = DateKeyListener.getInstance(locale); 5756 break; 5757 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 5758 input = TimeKeyListener.getInstance(locale); 5759 break; 5760 default: 5761 input = DateTimeKeyListener.getInstance(locale); 5762 break; 5763 } 5764 if (mUseInternationalizedInput) { 5765 type = input.getInputType(); // Override type, if necessary for i18n. 5766 } 5767 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 5768 input = DialerKeyListener.getInstance(); 5769 } else { 5770 input = TextKeyListener.getInstance(); 5771 } 5772 setRawInputType(type); 5773 mListenerChanged = false; 5774 if (direct) { 5775 createEditorIfNeeded(); 5776 mEditor.mKeyListener = input; 5777 } else { 5778 setKeyListenerOnly(input); 5779 } 5780 } 5781 5782 /** 5783 * Get the type of the editable content. 5784 * 5785 * @see #setInputType(int) 5786 * @see android.text.InputType 5787 */ 5788 public int getInputType() { 5789 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 5790 } 5791 5792 /** 5793 * Change the editor type integer associated with the text view, which 5794 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 5795 * when it has focus. 5796 * @see #getImeOptions 5797 * @see android.view.inputmethod.EditorInfo 5798 * @attr ref android.R.styleable#TextView_imeOptions 5799 */ 5800 public void setImeOptions(int imeOptions) { 5801 createEditorIfNeeded(); 5802 mEditor.createInputContentTypeIfNeeded(); 5803 mEditor.mInputContentType.imeOptions = imeOptions; 5804 } 5805 5806 /** 5807 * Get the type of the Input Method Editor (IME). 5808 * @return the type of the IME 5809 * @see #setImeOptions(int) 5810 * @see android.view.inputmethod.EditorInfo 5811 */ 5812 public int getImeOptions() { 5813 return mEditor != null && mEditor.mInputContentType != null 5814 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 5815 } 5816 5817 /** 5818 * Change the custom IME action associated with the text view, which 5819 * will be reported to an IME with {@link EditorInfo#actionLabel} 5820 * and {@link EditorInfo#actionId} when it has focus. 5821 * @see #getImeActionLabel 5822 * @see #getImeActionId 5823 * @see android.view.inputmethod.EditorInfo 5824 * @attr ref android.R.styleable#TextView_imeActionLabel 5825 * @attr ref android.R.styleable#TextView_imeActionId 5826 */ 5827 public void setImeActionLabel(CharSequence label, int actionId) { 5828 createEditorIfNeeded(); 5829 mEditor.createInputContentTypeIfNeeded(); 5830 mEditor.mInputContentType.imeActionLabel = label; 5831 mEditor.mInputContentType.imeActionId = actionId; 5832 } 5833 5834 /** 5835 * Get the IME action label previous set with {@link #setImeActionLabel}. 5836 * 5837 * @see #setImeActionLabel 5838 * @see android.view.inputmethod.EditorInfo 5839 */ 5840 public CharSequence getImeActionLabel() { 5841 return mEditor != null && mEditor.mInputContentType != null 5842 ? mEditor.mInputContentType.imeActionLabel : null; 5843 } 5844 5845 /** 5846 * Get the IME action ID previous set with {@link #setImeActionLabel}. 5847 * 5848 * @see #setImeActionLabel 5849 * @see android.view.inputmethod.EditorInfo 5850 */ 5851 public int getImeActionId() { 5852 return mEditor != null && mEditor.mInputContentType != null 5853 ? mEditor.mInputContentType.imeActionId : 0; 5854 } 5855 5856 /** 5857 * Set a special listener to be called when an action is performed 5858 * on the text view. This will be called when the enter key is pressed, 5859 * or when an action supplied to the IME is selected by the user. Setting 5860 * this means that the normal hard key event will not insert a newline 5861 * into the text view, even if it is multi-line; holding down the ALT 5862 * modifier will, however, allow the user to insert a newline character. 5863 */ 5864 public void setOnEditorActionListener(OnEditorActionListener l) { 5865 createEditorIfNeeded(); 5866 mEditor.createInputContentTypeIfNeeded(); 5867 mEditor.mInputContentType.onEditorActionListener = l; 5868 } 5869 5870 /** 5871 * Called when an attached input method calls 5872 * {@link InputConnection#performEditorAction(int) 5873 * InputConnection.performEditorAction()} 5874 * for this text view. The default implementation will call your action 5875 * listener supplied to {@link #setOnEditorActionListener}, or perform 5876 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 5877 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 5878 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 5879 * EditorInfo.IME_ACTION_DONE}. 5880 * 5881 * <p>For backwards compatibility, if no IME options have been set and the 5882 * text view would not normally advance focus on enter, then 5883 * the NEXT and DONE actions received here will be turned into an enter 5884 * key down/up pair to go through the normal key handling. 5885 * 5886 * @param actionCode The code of the action being performed. 5887 * 5888 * @see #setOnEditorActionListener 5889 */ 5890 public void onEditorAction(int actionCode) { 5891 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 5892 if (ict != null) { 5893 if (ict.onEditorActionListener != null) { 5894 if (ict.onEditorActionListener.onEditorAction(this, 5895 actionCode, null)) { 5896 return; 5897 } 5898 } 5899 5900 // This is the handling for some default action. 5901 // Note that for backwards compatibility we don't do this 5902 // default handling if explicit ime options have not been given, 5903 // instead turning this into the normal enter key codes that an 5904 // app may be expecting. 5905 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 5906 View v = focusSearch(FOCUS_FORWARD); 5907 if (v != null) { 5908 if (!v.requestFocus(FOCUS_FORWARD)) { 5909 throw new IllegalStateException("focus search returned a view " 5910 + "that wasn't able to take focus!"); 5911 } 5912 } 5913 return; 5914 5915 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 5916 View v = focusSearch(FOCUS_BACKWARD); 5917 if (v != null) { 5918 if (!v.requestFocus(FOCUS_BACKWARD)) { 5919 throw new IllegalStateException("focus search returned a view " 5920 + "that wasn't able to take focus!"); 5921 } 5922 } 5923 return; 5924 5925 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 5926 InputMethodManager imm = InputMethodManager.peekInstance(); 5927 if (imm != null && imm.isActive(this)) { 5928 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5929 } 5930 return; 5931 } 5932 } 5933 5934 ViewRootImpl viewRootImpl = getViewRootImpl(); 5935 if (viewRootImpl != null) { 5936 long eventTime = SystemClock.uptimeMillis(); 5937 viewRootImpl.dispatchKeyFromIme( 5938 new KeyEvent(eventTime, eventTime, 5939 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 5940 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 5941 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 5942 | KeyEvent.FLAG_EDITOR_ACTION)); 5943 viewRootImpl.dispatchKeyFromIme( 5944 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 5945 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 5946 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 5947 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 5948 | KeyEvent.FLAG_EDITOR_ACTION)); 5949 } 5950 } 5951 5952 /** 5953 * Set the private content type of the text, which is the 5954 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 5955 * field that will be filled in when creating an input connection. 5956 * 5957 * @see #getPrivateImeOptions() 5958 * @see EditorInfo#privateImeOptions 5959 * @attr ref android.R.styleable#TextView_privateImeOptions 5960 */ 5961 public void setPrivateImeOptions(String type) { 5962 createEditorIfNeeded(); 5963 mEditor.createInputContentTypeIfNeeded(); 5964 mEditor.mInputContentType.privateImeOptions = type; 5965 } 5966 5967 /** 5968 * Get the private type of the content. 5969 * 5970 * @see #setPrivateImeOptions(String) 5971 * @see EditorInfo#privateImeOptions 5972 */ 5973 public String getPrivateImeOptions() { 5974 return mEditor != null && mEditor.mInputContentType != null 5975 ? mEditor.mInputContentType.privateImeOptions : null; 5976 } 5977 5978 /** 5979 * Set the extra input data of the text, which is the 5980 * {@link EditorInfo#extras TextBoxAttribute.extras} 5981 * Bundle that will be filled in when creating an input connection. The 5982 * given integer is the resource identifier of an XML resource holding an 5983 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 5984 * 5985 * @see #getInputExtras(boolean) 5986 * @see EditorInfo#extras 5987 * @attr ref android.R.styleable#TextView_editorExtras 5988 */ 5989 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 5990 createEditorIfNeeded(); 5991 XmlResourceParser parser = getResources().getXml(xmlResId); 5992 mEditor.createInputContentTypeIfNeeded(); 5993 mEditor.mInputContentType.extras = new Bundle(); 5994 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 5995 } 5996 5997 /** 5998 * Retrieve the input extras currently associated with the text view, which 5999 * can be viewed as well as modified. 6000 * 6001 * @param create If true, the extras will be created if they don't already 6002 * exist. Otherwise, null will be returned if none have been created. 6003 * @see #setInputExtras(int) 6004 * @see EditorInfo#extras 6005 * @attr ref android.R.styleable#TextView_editorExtras 6006 */ 6007 public Bundle getInputExtras(boolean create) { 6008 if (mEditor == null && !create) return null; 6009 createEditorIfNeeded(); 6010 if (mEditor.mInputContentType == null) { 6011 if (!create) return null; 6012 mEditor.createInputContentTypeIfNeeded(); 6013 } 6014 if (mEditor.mInputContentType.extras == null) { 6015 if (!create) return null; 6016 mEditor.mInputContentType.extras = new Bundle(); 6017 } 6018 return mEditor.mInputContentType.extras; 6019 } 6020 6021 /** 6022 * Change "hint" locales associated with the text view, which will be reported to an IME with 6023 * {@link EditorInfo#hintLocales} when it has focus. 6024 * 6025 * Starting with Android O, this also causes internationalized listeners to be created (or 6026 * change locale) based on the first locale in the input locale list. 6027 * 6028 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 6029 * call {@link InputMethodManager#restartInput(View)}.</p> 6030 * @param hintLocales List of the languages that the user is supposed to switch to no matter 6031 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 6032 * @see #getImeHintLocales() 6033 * @see android.view.inputmethod.EditorInfo#hintLocales 6034 */ 6035 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 6036 createEditorIfNeeded(); 6037 mEditor.createInputContentTypeIfNeeded(); 6038 mEditor.mInputContentType.imeHintLocales = hintLocales; 6039 if (mUseInternationalizedInput) { 6040 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 6041 } 6042 } 6043 6044 /** 6045 * @return The current languages list "hint". {@code null} when no "hint" is available. 6046 * @see #setImeHintLocales(LocaleList) 6047 * @see android.view.inputmethod.EditorInfo#hintLocales 6048 */ 6049 @Nullable 6050 public LocaleList getImeHintLocales() { 6051 if (mEditor == null) { 6052 return null; 6053 } 6054 if (mEditor.mInputContentType == null) { 6055 return null; 6056 } 6057 return mEditor.mInputContentType.imeHintLocales; 6058 } 6059 6060 /** 6061 * Returns the error message that was set to be displayed with 6062 * {@link #setError}, or <code>null</code> if no error was set 6063 * or if it the error was cleared by the widget after user input. 6064 */ 6065 public CharSequence getError() { 6066 return mEditor == null ? null : mEditor.mError; 6067 } 6068 6069 /** 6070 * Sets the right-hand compound drawable of the TextView to the "error" 6071 * icon and sets an error message that will be displayed in a popup when 6072 * the TextView has focus. The icon and error message will be reset to 6073 * null when any key events cause changes to the TextView's text. If the 6074 * <code>error</code> is <code>null</code>, the error message and icon 6075 * will be cleared. 6076 */ 6077 @android.view.RemotableViewMethod 6078 public void setError(CharSequence error) { 6079 if (error == null) { 6080 setError(null, null); 6081 } else { 6082 Drawable dr = getContext().getDrawable( 6083 com.android.internal.R.drawable.indicator_input_error); 6084 6085 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 6086 setError(error, dr); 6087 } 6088 } 6089 6090 /** 6091 * Sets the right-hand compound drawable of the TextView to the specified 6092 * icon and sets an error message that will be displayed in a popup when 6093 * the TextView has focus. The icon and error message will be reset to 6094 * null when any key events cause changes to the TextView's text. The 6095 * drawable must already have had {@link Drawable#setBounds} set on it. 6096 * If the <code>error</code> is <code>null</code>, the error message will 6097 * be cleared (and you should provide a <code>null</code> icon as well). 6098 */ 6099 public void setError(CharSequence error, Drawable icon) { 6100 createEditorIfNeeded(); 6101 mEditor.setError(error, icon); 6102 notifyViewAccessibilityStateChangedIfNeeded( 6103 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 6104 } 6105 6106 @Override 6107 protected boolean setFrame(int l, int t, int r, int b) { 6108 boolean result = super.setFrame(l, t, r, b); 6109 6110 if (mEditor != null) mEditor.setFrame(); 6111 6112 restartMarqueeIfNeeded(); 6113 6114 return result; 6115 } 6116 6117 private void restartMarqueeIfNeeded() { 6118 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6119 mRestartMarquee = false; 6120 startMarquee(); 6121 } 6122 } 6123 6124 /** 6125 * Sets the list of input filters that will be used if the buffer is 6126 * Editable. Has no effect otherwise. 6127 * 6128 * @attr ref android.R.styleable#TextView_maxLength 6129 */ 6130 public void setFilters(InputFilter[] filters) { 6131 if (filters == null) { 6132 throw new IllegalArgumentException(); 6133 } 6134 6135 mFilters = filters; 6136 6137 if (mText instanceof Editable) { 6138 setFilters((Editable) mText, filters); 6139 } 6140 } 6141 6142 /** 6143 * Sets the list of input filters on the specified Editable, 6144 * and includes mInput in the list if it is an InputFilter. 6145 */ 6146 private void setFilters(Editable e, InputFilter[] filters) { 6147 if (mEditor != null) { 6148 final boolean undoFilter = mEditor.mUndoInputFilter != null; 6149 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 6150 int num = 0; 6151 if (undoFilter) num++; 6152 if (keyFilter) num++; 6153 if (num > 0) { 6154 InputFilter[] nf = new InputFilter[filters.length + num]; 6155 6156 System.arraycopy(filters, 0, nf, 0, filters.length); 6157 num = 0; 6158 if (undoFilter) { 6159 nf[filters.length] = mEditor.mUndoInputFilter; 6160 num++; 6161 } 6162 if (keyFilter) { 6163 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 6164 } 6165 6166 e.setFilters(nf); 6167 return; 6168 } 6169 } 6170 e.setFilters(filters); 6171 } 6172 6173 /** 6174 * Returns the current list of input filters. 6175 * 6176 * @attr ref android.R.styleable#TextView_maxLength 6177 */ 6178 public InputFilter[] getFilters() { 6179 return mFilters; 6180 } 6181 6182 ///////////////////////////////////////////////////////////////////////// 6183 6184 private int getBoxHeight(Layout l) { 6185 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 6186 int padding = (l == mHintLayout) 6187 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 6188 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 6189 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 6190 } 6191 6192 int getVerticalOffset(boolean forceNormal) { 6193 int voffset = 0; 6194 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 6195 6196 Layout l = mLayout; 6197 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 6198 l = mHintLayout; 6199 } 6200 6201 if (gravity != Gravity.TOP) { 6202 int boxht = getBoxHeight(l); 6203 int textht = l.getHeight(); 6204 6205 if (textht < boxht) { 6206 if (gravity == Gravity.BOTTOM) { 6207 voffset = boxht - textht; 6208 } else { // (gravity == Gravity.CENTER_VERTICAL) 6209 voffset = (boxht - textht) >> 1; 6210 } 6211 } 6212 } 6213 return voffset; 6214 } 6215 6216 private int getBottomVerticalOffset(boolean forceNormal) { 6217 int voffset = 0; 6218 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 6219 6220 Layout l = mLayout; 6221 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 6222 l = mHintLayout; 6223 } 6224 6225 if (gravity != Gravity.BOTTOM) { 6226 int boxht = getBoxHeight(l); 6227 int textht = l.getHeight(); 6228 6229 if (textht < boxht) { 6230 if (gravity == Gravity.TOP) { 6231 voffset = boxht - textht; 6232 } else { // (gravity == Gravity.CENTER_VERTICAL) 6233 voffset = (boxht - textht) >> 1; 6234 } 6235 } 6236 } 6237 return voffset; 6238 } 6239 6240 void invalidateCursorPath() { 6241 if (mHighlightPathBogus) { 6242 invalidateCursor(); 6243 } else { 6244 final int horizontalPadding = getCompoundPaddingLeft(); 6245 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 6246 6247 if (mEditor.mCursorCount == 0) { 6248 synchronized (TEMP_RECTF) { 6249 /* 6250 * The reason for this concern about the thickness of the 6251 * cursor and doing the floor/ceil on the coordinates is that 6252 * some EditTexts (notably textfields in the Browser) have 6253 * anti-aliased text where not all the characters are 6254 * necessarily at integer-multiple locations. This should 6255 * make sure the entire cursor gets invalidated instead of 6256 * sometimes missing half a pixel. 6257 */ 6258 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 6259 if (thick < 1.0f) { 6260 thick = 1.0f; 6261 } 6262 6263 thick /= 2.0f; 6264 6265 // mHighlightPath is guaranteed to be non null at that point. 6266 mHighlightPath.computeBounds(TEMP_RECTF, false); 6267 6268 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 6269 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 6270 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 6271 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 6272 } 6273 } else { 6274 for (int i = 0; i < mEditor.mCursorCount; i++) { 6275 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 6276 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 6277 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 6278 } 6279 } 6280 } 6281 } 6282 6283 void invalidateCursor() { 6284 int where = getSelectionEnd(); 6285 6286 invalidateCursor(where, where, where); 6287 } 6288 6289 private void invalidateCursor(int a, int b, int c) { 6290 if (a >= 0 || b >= 0 || c >= 0) { 6291 int start = Math.min(Math.min(a, b), c); 6292 int end = Math.max(Math.max(a, b), c); 6293 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 6294 } 6295 } 6296 6297 /** 6298 * Invalidates the region of text enclosed between the start and end text offsets. 6299 */ 6300 void invalidateRegion(int start, int end, boolean invalidateCursor) { 6301 if (mLayout == null) { 6302 invalidate(); 6303 } else { 6304 int lineStart = mLayout.getLineForOffset(start); 6305 int top = mLayout.getLineTop(lineStart); 6306 6307 // This is ridiculous, but the descent from the line above 6308 // can hang down into the line we really want to redraw, 6309 // so we have to invalidate part of the line above to make 6310 // sure everything that needs to be redrawn really is. 6311 // (But not the whole line above, because that would cause 6312 // the same problem with the descenders on the line above it!) 6313 if (lineStart > 0) { 6314 top -= mLayout.getLineDescent(lineStart - 1); 6315 } 6316 6317 int lineEnd; 6318 6319 if (start == end) { 6320 lineEnd = lineStart; 6321 } else { 6322 lineEnd = mLayout.getLineForOffset(end); 6323 } 6324 6325 int bottom = mLayout.getLineBottom(lineEnd); 6326 6327 // mEditor can be null in case selection is set programmatically. 6328 if (invalidateCursor && mEditor != null) { 6329 for (int i = 0; i < mEditor.mCursorCount; i++) { 6330 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 6331 top = Math.min(top, bounds.top); 6332 bottom = Math.max(bottom, bounds.bottom); 6333 } 6334 } 6335 6336 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6337 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 6338 6339 int left, right; 6340 if (lineStart == lineEnd && !invalidateCursor) { 6341 left = (int) mLayout.getPrimaryHorizontal(start); 6342 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 6343 left += compoundPaddingLeft; 6344 right += compoundPaddingLeft; 6345 } else { 6346 // Rectangle bounding box when the region spans several lines 6347 left = compoundPaddingLeft; 6348 right = getWidth() - getCompoundPaddingRight(); 6349 } 6350 6351 invalidate(mScrollX + left, verticalPadding + top, 6352 mScrollX + right, verticalPadding + bottom); 6353 } 6354 } 6355 6356 private void registerForPreDraw() { 6357 if (!mPreDrawRegistered) { 6358 getViewTreeObserver().addOnPreDrawListener(this); 6359 mPreDrawRegistered = true; 6360 } 6361 } 6362 6363 private void unregisterForPreDraw() { 6364 getViewTreeObserver().removeOnPreDrawListener(this); 6365 mPreDrawRegistered = false; 6366 mPreDrawListenerDetached = false; 6367 } 6368 6369 /** 6370 * {@inheritDoc} 6371 */ 6372 @Override 6373 public boolean onPreDraw() { 6374 if (mLayout == null) { 6375 assumeLayout(); 6376 } 6377 6378 if (mMovement != null) { 6379 /* This code also provides auto-scrolling when a cursor is moved using a 6380 * CursorController (insertion point or selection limits). 6381 * For selection, ensure start or end is visible depending on controller's state. 6382 */ 6383 int curs = getSelectionEnd(); 6384 // Do not create the controller if it is not already created. 6385 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 6386 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 6387 curs = getSelectionStart(); 6388 } 6389 6390 /* 6391 * TODO: This should really only keep the end in view if 6392 * it already was before the text changed. I'm not sure 6393 * of a good way to tell from here if it was. 6394 */ 6395 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6396 curs = mText.length(); 6397 } 6398 6399 if (curs >= 0) { 6400 bringPointIntoView(curs); 6401 } 6402 } else { 6403 bringTextIntoView(); 6404 } 6405 6406 // This has to be checked here since: 6407 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 6408 // a screen rotation) since layout is not yet initialized at that point. 6409 if (mEditor != null && mEditor.mCreatedWithASelection) { 6410 mEditor.refreshTextActionMode(); 6411 mEditor.mCreatedWithASelection = false; 6412 } 6413 6414 unregisterForPreDraw(); 6415 6416 return true; 6417 } 6418 6419 @Override 6420 protected void onAttachedToWindow() { 6421 super.onAttachedToWindow(); 6422 6423 if (mEditor != null) mEditor.onAttachedToWindow(); 6424 6425 if (mPreDrawListenerDetached) { 6426 getViewTreeObserver().addOnPreDrawListener(this); 6427 mPreDrawListenerDetached = false; 6428 } 6429 } 6430 6431 /** @hide */ 6432 @Override 6433 protected void onDetachedFromWindowInternal() { 6434 if (mPreDrawRegistered) { 6435 getViewTreeObserver().removeOnPreDrawListener(this); 6436 mPreDrawListenerDetached = true; 6437 } 6438 6439 resetResolvedDrawables(); 6440 6441 if (mEditor != null) mEditor.onDetachedFromWindow(); 6442 6443 super.onDetachedFromWindowInternal(); 6444 } 6445 6446 @Override 6447 public void onScreenStateChanged(int screenState) { 6448 super.onScreenStateChanged(screenState); 6449 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 6450 } 6451 6452 @Override 6453 protected boolean isPaddingOffsetRequired() { 6454 return mShadowRadius != 0 || mDrawables != null; 6455 } 6456 6457 @Override 6458 protected int getLeftPaddingOffset() { 6459 return getCompoundPaddingLeft() - mPaddingLeft 6460 + (int) Math.min(0, mShadowDx - mShadowRadius); 6461 } 6462 6463 @Override 6464 protected int getTopPaddingOffset() { 6465 return (int) Math.min(0, mShadowDy - mShadowRadius); 6466 } 6467 6468 @Override 6469 protected int getBottomPaddingOffset() { 6470 return (int) Math.max(0, mShadowDy + mShadowRadius); 6471 } 6472 6473 @Override 6474 protected int getRightPaddingOffset() { 6475 return -(getCompoundPaddingRight() - mPaddingRight) 6476 + (int) Math.max(0, mShadowDx + mShadowRadius); 6477 } 6478 6479 @Override 6480 protected boolean verifyDrawable(@NonNull Drawable who) { 6481 final boolean verified = super.verifyDrawable(who); 6482 if (!verified && mDrawables != null) { 6483 for (Drawable dr : mDrawables.mShowing) { 6484 if (who == dr) { 6485 return true; 6486 } 6487 } 6488 } 6489 return verified; 6490 } 6491 6492 @Override 6493 public void jumpDrawablesToCurrentState() { 6494 super.jumpDrawablesToCurrentState(); 6495 if (mDrawables != null) { 6496 for (Drawable dr : mDrawables.mShowing) { 6497 if (dr != null) { 6498 dr.jumpToCurrentState(); 6499 } 6500 } 6501 } 6502 } 6503 6504 @Override 6505 public void invalidateDrawable(@NonNull Drawable drawable) { 6506 boolean handled = false; 6507 6508 if (verifyDrawable(drawable)) { 6509 final Rect dirty = drawable.getBounds(); 6510 int scrollX = mScrollX; 6511 int scrollY = mScrollY; 6512 6513 // IMPORTANT: The coordinates below are based on the coordinates computed 6514 // for each compound drawable in onDraw(). Make sure to update each section 6515 // accordingly. 6516 final TextView.Drawables drawables = mDrawables; 6517 if (drawables != null) { 6518 if (drawable == drawables.mShowing[Drawables.LEFT]) { 6519 final int compoundPaddingTop = getCompoundPaddingTop(); 6520 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6521 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6522 6523 scrollX += mPaddingLeft; 6524 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 6525 handled = true; 6526 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 6527 final int compoundPaddingTop = getCompoundPaddingTop(); 6528 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6529 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6530 6531 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 6532 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 6533 handled = true; 6534 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 6535 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6536 final int compoundPaddingRight = getCompoundPaddingRight(); 6537 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 6538 6539 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 6540 scrollY += mPaddingTop; 6541 handled = true; 6542 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 6543 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6544 final int compoundPaddingRight = getCompoundPaddingRight(); 6545 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 6546 6547 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 6548 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 6549 handled = true; 6550 } 6551 } 6552 6553 if (handled) { 6554 invalidate(dirty.left + scrollX, dirty.top + scrollY, 6555 dirty.right + scrollX, dirty.bottom + scrollY); 6556 } 6557 } 6558 6559 if (!handled) { 6560 super.invalidateDrawable(drawable); 6561 } 6562 } 6563 6564 @Override 6565 public boolean hasOverlappingRendering() { 6566 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 6567 return ((getBackground() != null && getBackground().getCurrent() != null) 6568 || mText instanceof Spannable || hasSelection() 6569 || isHorizontalFadingEdgeEnabled()); 6570 } 6571 6572 /** 6573 * 6574 * Returns the state of the {@code textIsSelectable} flag (See 6575 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 6576 * to allow users to select and copy text in a non-editable TextView, the content of an 6577 * {@link EditText} can always be selected, independently of the value of this flag. 6578 * <p> 6579 * 6580 * @return True if the text displayed in this TextView can be selected by the user. 6581 * 6582 * @attr ref android.R.styleable#TextView_textIsSelectable 6583 */ 6584 public boolean isTextSelectable() { 6585 return mEditor == null ? false : mEditor.mTextIsSelectable; 6586 } 6587 6588 /** 6589 * Sets whether the content of this view is selectable by the user. The default is 6590 * {@code false}, meaning that the content is not selectable. 6591 * <p> 6592 * When you use a TextView to display a useful piece of information to the user (such as a 6593 * contact's address), make it selectable, so that the user can select and copy its 6594 * content. You can also use set the XML attribute 6595 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 6596 * <p> 6597 * When you call this method to set the value of {@code textIsSelectable}, it sets 6598 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 6599 * and {@code longClickable} to the same value. These flags correspond to the attributes 6600 * {@link android.R.styleable#View_focusable android:focusable}, 6601 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 6602 * {@link android.R.styleable#View_clickable android:clickable}, and 6603 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 6604 * flags to a state you had set previously, call one or more of the following methods: 6605 * {@link #setFocusable(boolean) setFocusable()}, 6606 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 6607 * {@link #setClickable(boolean) setClickable()} or 6608 * {@link #setLongClickable(boolean) setLongClickable()}. 6609 * 6610 * @param selectable Whether the content of this TextView should be selectable. 6611 */ 6612 public void setTextIsSelectable(boolean selectable) { 6613 if (!selectable && mEditor == null) return; // false is default value with no edit data 6614 6615 createEditorIfNeeded(); 6616 if (mEditor.mTextIsSelectable == selectable) return; 6617 6618 mEditor.mTextIsSelectable = selectable; 6619 setFocusableInTouchMode(selectable); 6620 setFocusable(FOCUSABLE_AUTO); 6621 setClickable(selectable); 6622 setLongClickable(selectable); 6623 6624 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 6625 6626 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 6627 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 6628 6629 // Called by setText above, but safer in case of future code changes 6630 mEditor.prepareCursorControllers(); 6631 } 6632 6633 @Override 6634 protected int[] onCreateDrawableState(int extraSpace) { 6635 final int[] drawableState; 6636 6637 if (mSingleLine) { 6638 drawableState = super.onCreateDrawableState(extraSpace); 6639 } else { 6640 drawableState = super.onCreateDrawableState(extraSpace + 1); 6641 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 6642 } 6643 6644 if (isTextSelectable()) { 6645 // Disable pressed state, which was introduced when TextView was made clickable. 6646 // Prevents text color change. 6647 // setClickable(false) would have a similar effect, but it also disables focus changes 6648 // and long press actions, which are both needed by text selection. 6649 final int length = drawableState.length; 6650 for (int i = 0; i < length; i++) { 6651 if (drawableState[i] == R.attr.state_pressed) { 6652 final int[] nonPressedState = new int[length - 1]; 6653 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 6654 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 6655 return nonPressedState; 6656 } 6657 } 6658 } 6659 6660 return drawableState; 6661 } 6662 6663 private Path getUpdatedHighlightPath() { 6664 Path highlight = null; 6665 Paint highlightPaint = mHighlightPaint; 6666 6667 final int selStart = getSelectionStart(); 6668 final int selEnd = getSelectionEnd(); 6669 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 6670 if (selStart == selEnd) { 6671 if (mEditor != null && mEditor.isCursorVisible() 6672 && (SystemClock.uptimeMillis() - mEditor.mShowCursor) 6673 % (2 * Editor.BLINK) < Editor.BLINK) { 6674 if (mHighlightPathBogus) { 6675 if (mHighlightPath == null) mHighlightPath = new Path(); 6676 mHighlightPath.reset(); 6677 mLayout.getCursorPath(selStart, mHighlightPath, mText); 6678 mEditor.updateCursorsPositions(); 6679 mHighlightPathBogus = false; 6680 } 6681 6682 // XXX should pass to skin instead of drawing directly 6683 highlightPaint.setColor(mCurTextColor); 6684 highlightPaint.setStyle(Paint.Style.STROKE); 6685 highlight = mHighlightPath; 6686 } 6687 } else { 6688 if (mHighlightPathBogus) { 6689 if (mHighlightPath == null) mHighlightPath = new Path(); 6690 mHighlightPath.reset(); 6691 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 6692 mHighlightPathBogus = false; 6693 } 6694 6695 // XXX should pass to skin instead of drawing directly 6696 highlightPaint.setColor(mHighlightColor); 6697 highlightPaint.setStyle(Paint.Style.FILL); 6698 6699 highlight = mHighlightPath; 6700 } 6701 } 6702 return highlight; 6703 } 6704 6705 /** 6706 * @hide 6707 */ 6708 public int getHorizontalOffsetForDrawables() { 6709 return 0; 6710 } 6711 6712 @Override 6713 protected void onDraw(Canvas canvas) { 6714 restartMarqueeIfNeeded(); 6715 6716 // Draw the background for this view 6717 super.onDraw(canvas); 6718 6719 final int compoundPaddingLeft = getCompoundPaddingLeft(); 6720 final int compoundPaddingTop = getCompoundPaddingTop(); 6721 final int compoundPaddingRight = getCompoundPaddingRight(); 6722 final int compoundPaddingBottom = getCompoundPaddingBottom(); 6723 final int scrollX = mScrollX; 6724 final int scrollY = mScrollY; 6725 final int right = mRight; 6726 final int left = mLeft; 6727 final int bottom = mBottom; 6728 final int top = mTop; 6729 final boolean isLayoutRtl = isLayoutRtl(); 6730 final int offset = getHorizontalOffsetForDrawables(); 6731 final int leftOffset = isLayoutRtl ? 0 : offset; 6732 final int rightOffset = isLayoutRtl ? offset : 0; 6733 6734 final Drawables dr = mDrawables; 6735 if (dr != null) { 6736 /* 6737 * Compound, not extended, because the icon is not clipped 6738 * if the text height is smaller. 6739 */ 6740 6741 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 6742 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 6743 6744 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6745 // Make sure to update invalidateDrawable() when changing this code. 6746 if (dr.mShowing[Drawables.LEFT] != null) { 6747 canvas.save(); 6748 canvas.translate(scrollX + mPaddingLeft + leftOffset, 6749 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 6750 dr.mShowing[Drawables.LEFT].draw(canvas); 6751 canvas.restore(); 6752 } 6753 6754 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6755 // Make sure to update invalidateDrawable() when changing this code. 6756 if (dr.mShowing[Drawables.RIGHT] != null) { 6757 canvas.save(); 6758 canvas.translate(scrollX + right - left - mPaddingRight 6759 - dr.mDrawableSizeRight - rightOffset, 6760 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 6761 dr.mShowing[Drawables.RIGHT].draw(canvas); 6762 canvas.restore(); 6763 } 6764 6765 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6766 // Make sure to update invalidateDrawable() when changing this code. 6767 if (dr.mShowing[Drawables.TOP] != null) { 6768 canvas.save(); 6769 canvas.translate(scrollX + compoundPaddingLeft 6770 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 6771 dr.mShowing[Drawables.TOP].draw(canvas); 6772 canvas.restore(); 6773 } 6774 6775 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 6776 // Make sure to update invalidateDrawable() when changing this code. 6777 if (dr.mShowing[Drawables.BOTTOM] != null) { 6778 canvas.save(); 6779 canvas.translate(scrollX + compoundPaddingLeft 6780 + (hspace - dr.mDrawableWidthBottom) / 2, 6781 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 6782 dr.mShowing[Drawables.BOTTOM].draw(canvas); 6783 canvas.restore(); 6784 } 6785 } 6786 6787 int color = mCurTextColor; 6788 6789 if (mLayout == null) { 6790 assumeLayout(); 6791 } 6792 6793 Layout layout = mLayout; 6794 6795 if (mHint != null && mText.length() == 0) { 6796 if (mHintTextColor != null) { 6797 color = mCurHintTextColor; 6798 } 6799 6800 layout = mHintLayout; 6801 } 6802 6803 mTextPaint.setColor(color); 6804 mTextPaint.drawableState = getDrawableState(); 6805 6806 canvas.save(); 6807 /* Would be faster if we didn't have to do this. Can we chop the 6808 (displayable) text so that we don't need to do this ever? 6809 */ 6810 6811 int extendedPaddingTop = getExtendedPaddingTop(); 6812 int extendedPaddingBottom = getExtendedPaddingBottom(); 6813 6814 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 6815 final int maxScrollY = mLayout.getHeight() - vspace; 6816 6817 float clipLeft = compoundPaddingLeft + scrollX; 6818 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 6819 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 6820 float clipBottom = bottom - top + scrollY 6821 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 6822 6823 if (mShadowRadius != 0) { 6824 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 6825 clipRight += Math.max(0, mShadowDx + mShadowRadius); 6826 6827 clipTop += Math.min(0, mShadowDy - mShadowRadius); 6828 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 6829 } 6830 6831 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 6832 6833 int voffsetText = 0; 6834 int voffsetCursor = 0; 6835 6836 // translate in by our padding 6837 /* shortcircuit calling getVerticaOffset() */ 6838 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6839 voffsetText = getVerticalOffset(false); 6840 voffsetCursor = getVerticalOffset(true); 6841 } 6842 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 6843 6844 final int layoutDirection = getLayoutDirection(); 6845 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 6846 if (isMarqueeFadeEnabled()) { 6847 if (!mSingleLine && getLineCount() == 1 && canMarquee() 6848 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 6849 final int width = mRight - mLeft; 6850 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 6851 final float dx = mLayout.getLineRight(0) - (width - padding); 6852 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6853 } 6854 6855 if (mMarquee != null && mMarquee.isRunning()) { 6856 final float dx = -mMarquee.getScroll(); 6857 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6858 } 6859 } 6860 6861 final int cursorOffsetVertical = voffsetCursor - voffsetText; 6862 6863 Path highlight = getUpdatedHighlightPath(); 6864 if (mEditor != null) { 6865 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 6866 } else { 6867 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 6868 } 6869 6870 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 6871 final float dx = mMarquee.getGhostOffset(); 6872 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 6873 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 6874 } 6875 6876 canvas.restore(); 6877 } 6878 6879 @Override 6880 public void getFocusedRect(Rect r) { 6881 if (mLayout == null) { 6882 super.getFocusedRect(r); 6883 return; 6884 } 6885 6886 int selEnd = getSelectionEnd(); 6887 if (selEnd < 0) { 6888 super.getFocusedRect(r); 6889 return; 6890 } 6891 6892 int selStart = getSelectionStart(); 6893 if (selStart < 0 || selStart >= selEnd) { 6894 int line = mLayout.getLineForOffset(selEnd); 6895 r.top = mLayout.getLineTop(line); 6896 r.bottom = mLayout.getLineBottom(line); 6897 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 6898 r.right = r.left + 4; 6899 } else { 6900 int lineStart = mLayout.getLineForOffset(selStart); 6901 int lineEnd = mLayout.getLineForOffset(selEnd); 6902 r.top = mLayout.getLineTop(lineStart); 6903 r.bottom = mLayout.getLineBottom(lineEnd); 6904 if (lineStart == lineEnd) { 6905 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 6906 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 6907 } else { 6908 // Selection extends across multiple lines -- make the focused 6909 // rect cover the entire width. 6910 if (mHighlightPathBogus) { 6911 if (mHighlightPath == null) mHighlightPath = new Path(); 6912 mHighlightPath.reset(); 6913 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 6914 mHighlightPathBogus = false; 6915 } 6916 synchronized (TEMP_RECTF) { 6917 mHighlightPath.computeBounds(TEMP_RECTF, true); 6918 r.left = (int) TEMP_RECTF.left - 1; 6919 r.right = (int) TEMP_RECTF.right + 1; 6920 } 6921 } 6922 } 6923 6924 // Adjust for padding and gravity. 6925 int paddingLeft = getCompoundPaddingLeft(); 6926 int paddingTop = getExtendedPaddingTop(); 6927 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6928 paddingTop += getVerticalOffset(false); 6929 } 6930 r.offset(paddingLeft, paddingTop); 6931 int paddingBottom = getExtendedPaddingBottom(); 6932 r.bottom += paddingBottom; 6933 } 6934 6935 /** 6936 * Return the number of lines of text, or 0 if the internal Layout has not 6937 * been built. 6938 */ 6939 public int getLineCount() { 6940 return mLayout != null ? mLayout.getLineCount() : 0; 6941 } 6942 6943 /** 6944 * Return the baseline for the specified line (0...getLineCount() - 1) 6945 * If bounds is not null, return the top, left, right, bottom extents 6946 * of the specified line in it. If the internal Layout has not been built, 6947 * return 0 and set bounds to (0, 0, 0, 0) 6948 * @param line which line to examine (0..getLineCount() - 1) 6949 * @param bounds Optional. If not null, it returns the extent of the line 6950 * @return the Y-coordinate of the baseline 6951 */ 6952 public int getLineBounds(int line, Rect bounds) { 6953 if (mLayout == null) { 6954 if (bounds != null) { 6955 bounds.set(0, 0, 0, 0); 6956 } 6957 return 0; 6958 } else { 6959 int baseline = mLayout.getLineBounds(line, bounds); 6960 6961 int voffset = getExtendedPaddingTop(); 6962 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6963 voffset += getVerticalOffset(true); 6964 } 6965 if (bounds != null) { 6966 bounds.offset(getCompoundPaddingLeft(), voffset); 6967 } 6968 return baseline + voffset; 6969 } 6970 } 6971 6972 @Override 6973 public int getBaseline() { 6974 if (mLayout == null) { 6975 return super.getBaseline(); 6976 } 6977 6978 return getBaselineOffset() + mLayout.getLineBaseline(0); 6979 } 6980 6981 int getBaselineOffset() { 6982 int voffset = 0; 6983 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 6984 voffset = getVerticalOffset(true); 6985 } 6986 6987 if (isLayoutModeOptical(mParent)) { 6988 voffset -= getOpticalInsets().top; 6989 } 6990 6991 return getExtendedPaddingTop() + voffset; 6992 } 6993 6994 /** 6995 * @hide 6996 */ 6997 @Override 6998 protected int getFadeTop(boolean offsetRequired) { 6999 if (mLayout == null) return 0; 7000 7001 int voffset = 0; 7002 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7003 voffset = getVerticalOffset(true); 7004 } 7005 7006 if (offsetRequired) voffset += getTopPaddingOffset(); 7007 7008 return getExtendedPaddingTop() + voffset; 7009 } 7010 7011 /** 7012 * @hide 7013 */ 7014 @Override 7015 protected int getFadeHeight(boolean offsetRequired) { 7016 return mLayout != null ? mLayout.getHeight() : 0; 7017 } 7018 7019 @Override 7020 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 7021 if (mText instanceof Spannable && mLinksClickable) { 7022 final float x = event.getX(pointerIndex); 7023 final float y = event.getY(pointerIndex); 7024 final int offset = getOffsetForPosition(x, y); 7025 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset, 7026 ClickableSpan.class); 7027 if (clickables.length > 0) { 7028 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 7029 } 7030 } 7031 if (isTextSelectable() || isTextEditable()) { 7032 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 7033 } 7034 return super.onResolvePointerIcon(event, pointerIndex); 7035 } 7036 7037 @Override 7038 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 7039 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 7040 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 7041 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 7042 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 7043 return true; 7044 } 7045 return super.onKeyPreIme(keyCode, event); 7046 } 7047 7048 /** 7049 * @hide 7050 */ 7051 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 7052 // Do nothing unless mEditor is in text action mode. 7053 if (mEditor == null || mEditor.getTextActionMode() == null) { 7054 return false; 7055 } 7056 7057 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 7058 KeyEvent.DispatcherState state = getKeyDispatcherState(); 7059 if (state != null) { 7060 state.startTracking(event, this); 7061 } 7062 return true; 7063 } else if (event.getAction() == KeyEvent.ACTION_UP) { 7064 KeyEvent.DispatcherState state = getKeyDispatcherState(); 7065 if (state != null) { 7066 state.handleUpEvent(event); 7067 } 7068 if (event.isTracking() && !event.isCanceled()) { 7069 stopTextActionMode(); 7070 return true; 7071 } 7072 } 7073 return false; 7074 } 7075 7076 @Override 7077 public boolean onKeyDown(int keyCode, KeyEvent event) { 7078 final int which = doKeyDown(keyCode, event, null); 7079 if (which == KEY_EVENT_NOT_HANDLED) { 7080 return super.onKeyDown(keyCode, event); 7081 } 7082 7083 return true; 7084 } 7085 7086 @Override 7087 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 7088 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 7089 final int which = doKeyDown(keyCode, down, event); 7090 if (which == KEY_EVENT_NOT_HANDLED) { 7091 // Go through default dispatching. 7092 return super.onKeyMultiple(keyCode, repeatCount, event); 7093 } 7094 if (which == KEY_EVENT_HANDLED) { 7095 // Consumed the whole thing. 7096 return true; 7097 } 7098 7099 repeatCount--; 7100 7101 // We are going to dispatch the remaining events to either the input 7102 // or movement method. To do this, we will just send a repeated stream 7103 // of down and up events until we have done the complete repeatCount. 7104 // It would be nice if those interfaces had an onKeyMultiple() method, 7105 // but adding that is a more complicated change. 7106 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 7107 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 7108 // mEditor and mEditor.mInput are not null from doKeyDown 7109 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 7110 while (--repeatCount > 0) { 7111 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 7112 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 7113 } 7114 hideErrorIfUnchanged(); 7115 7116 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 7117 // mMovement is not null from doKeyDown 7118 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up); 7119 while (--repeatCount > 0) { 7120 mMovement.onKeyDown(this, (Spannable) mText, keyCode, down); 7121 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up); 7122 } 7123 } 7124 7125 return true; 7126 } 7127 7128 /** 7129 * Returns true if pressing ENTER in this field advances focus instead 7130 * of inserting the character. This is true mostly in single-line fields, 7131 * but also in mail addresses and subjects which will display on multiple 7132 * lines but where it doesn't make sense to insert newlines. 7133 */ 7134 private boolean shouldAdvanceFocusOnEnter() { 7135 if (getKeyListener() == null) { 7136 return false; 7137 } 7138 7139 if (mSingleLine) { 7140 return true; 7141 } 7142 7143 if (mEditor != null 7144 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 7145 == EditorInfo.TYPE_CLASS_TEXT) { 7146 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 7147 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 7148 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 7149 return true; 7150 } 7151 } 7152 7153 return false; 7154 } 7155 7156 /** 7157 * Returns true if pressing TAB in this field advances focus instead 7158 * of inserting the character. Insert tabs only in multi-line editors. 7159 */ 7160 private boolean shouldAdvanceFocusOnTab() { 7161 if (getKeyListener() != null && !mSingleLine && mEditor != null 7162 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 7163 == EditorInfo.TYPE_CLASS_TEXT) { 7164 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 7165 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 7166 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 7167 return false; 7168 } 7169 } 7170 return true; 7171 } 7172 7173 private boolean isDirectionalNavigationKey(int keyCode) { 7174 switch(keyCode) { 7175 case KeyEvent.KEYCODE_DPAD_UP: 7176 case KeyEvent.KEYCODE_DPAD_DOWN: 7177 case KeyEvent.KEYCODE_DPAD_LEFT: 7178 case KeyEvent.KEYCODE_DPAD_RIGHT: 7179 return true; 7180 } 7181 return false; 7182 } 7183 7184 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 7185 if (!isEnabled()) { 7186 return KEY_EVENT_NOT_HANDLED; 7187 } 7188 7189 // If this is the initial keydown, we don't want to prevent a movement away from this view. 7190 // While this shouldn't be necessary because any time we're preventing default movement we 7191 // should be restricting the focus to remain within this view, thus we'll also receive 7192 // the key up event, occasionally key up events will get dropped and we don't want to 7193 // prevent the user from traversing out of this on the next key down. 7194 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 7195 mPreventDefaultMovement = false; 7196 } 7197 7198 switch (keyCode) { 7199 case KeyEvent.KEYCODE_ENTER: 7200 if (event.hasNoModifiers()) { 7201 // When mInputContentType is set, we know that we are 7202 // running in a "modern" cupcake environment, so don't need 7203 // to worry about the application trying to capture 7204 // enter key events. 7205 if (mEditor != null && mEditor.mInputContentType != null) { 7206 // If there is an action listener, given them a 7207 // chance to consume the event. 7208 if (mEditor.mInputContentType.onEditorActionListener != null 7209 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 7210 this, EditorInfo.IME_NULL, event)) { 7211 mEditor.mInputContentType.enterDown = true; 7212 // We are consuming the enter key for them. 7213 return KEY_EVENT_HANDLED; 7214 } 7215 } 7216 7217 // If our editor should move focus when enter is pressed, or 7218 // this is a generated event from an IME action button, then 7219 // don't let it be inserted into the text. 7220 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 7221 || shouldAdvanceFocusOnEnter()) { 7222 if (hasOnClickListeners()) { 7223 return KEY_EVENT_NOT_HANDLED; 7224 } 7225 return KEY_EVENT_HANDLED; 7226 } 7227 } 7228 break; 7229 7230 case KeyEvent.KEYCODE_DPAD_CENTER: 7231 if (event.hasNoModifiers()) { 7232 if (shouldAdvanceFocusOnEnter()) { 7233 return KEY_EVENT_NOT_HANDLED; 7234 } 7235 } 7236 break; 7237 7238 case KeyEvent.KEYCODE_TAB: 7239 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 7240 if (shouldAdvanceFocusOnTab()) { 7241 return KEY_EVENT_NOT_HANDLED; 7242 } 7243 } 7244 break; 7245 7246 // Has to be done on key down (and not on key up) to correctly be intercepted. 7247 case KeyEvent.KEYCODE_BACK: 7248 if (mEditor != null && mEditor.getTextActionMode() != null) { 7249 stopTextActionMode(); 7250 return KEY_EVENT_HANDLED; 7251 } 7252 break; 7253 7254 case KeyEvent.KEYCODE_CUT: 7255 if (event.hasNoModifiers() && canCut()) { 7256 if (onTextContextMenuItem(ID_CUT)) { 7257 return KEY_EVENT_HANDLED; 7258 } 7259 } 7260 break; 7261 7262 case KeyEvent.KEYCODE_COPY: 7263 if (event.hasNoModifiers() && canCopy()) { 7264 if (onTextContextMenuItem(ID_COPY)) { 7265 return KEY_EVENT_HANDLED; 7266 } 7267 } 7268 break; 7269 7270 case KeyEvent.KEYCODE_PASTE: 7271 if (event.hasNoModifiers() && canPaste()) { 7272 if (onTextContextMenuItem(ID_PASTE)) { 7273 return KEY_EVENT_HANDLED; 7274 } 7275 } 7276 break; 7277 } 7278 7279 if (mEditor != null && mEditor.mKeyListener != null) { 7280 boolean doDown = true; 7281 if (otherEvent != null) { 7282 try { 7283 beginBatchEdit(); 7284 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 7285 otherEvent); 7286 hideErrorIfUnchanged(); 7287 doDown = false; 7288 if (handled) { 7289 return KEY_EVENT_HANDLED; 7290 } 7291 } catch (AbstractMethodError e) { 7292 // onKeyOther was added after 1.0, so if it isn't 7293 // implemented we need to try to dispatch as a regular down. 7294 } finally { 7295 endBatchEdit(); 7296 } 7297 } 7298 7299 if (doDown) { 7300 beginBatchEdit(); 7301 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 7302 keyCode, event); 7303 endBatchEdit(); 7304 hideErrorIfUnchanged(); 7305 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 7306 } 7307 } 7308 7309 // bug 650865: sometimes we get a key event before a layout. 7310 // don't try to move around if we don't know the layout. 7311 7312 if (mMovement != null && mLayout != null) { 7313 boolean doDown = true; 7314 if (otherEvent != null) { 7315 try { 7316 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 7317 otherEvent); 7318 doDown = false; 7319 if (handled) { 7320 return KEY_EVENT_HANDLED; 7321 } 7322 } catch (AbstractMethodError e) { 7323 // onKeyOther was added after 1.0, so if it isn't 7324 // implemented we need to try to dispatch as a regular down. 7325 } 7326 } 7327 if (doDown) { 7328 if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) { 7329 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 7330 mPreventDefaultMovement = true; 7331 } 7332 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 7333 } 7334 } 7335 // Consume arrows from keyboard devices to prevent focus leaving the editor. 7336 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 7337 // to move focus with arrows. 7338 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 7339 && isDirectionalNavigationKey(keyCode)) { 7340 return KEY_EVENT_HANDLED; 7341 } 7342 } 7343 7344 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 7345 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 7346 } 7347 7348 /** 7349 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 7350 * can be recorded. 7351 * @hide 7352 */ 7353 public void resetErrorChangedFlag() { 7354 /* 7355 * Keep track of what the error was before doing the input 7356 * so that if an input filter changed the error, we leave 7357 * that error showing. Otherwise, we take down whatever 7358 * error was showing when the user types something. 7359 */ 7360 if (mEditor != null) mEditor.mErrorWasChanged = false; 7361 } 7362 7363 /** 7364 * @hide 7365 */ 7366 public void hideErrorIfUnchanged() { 7367 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 7368 setError(null, null); 7369 } 7370 } 7371 7372 @Override 7373 public boolean onKeyUp(int keyCode, KeyEvent event) { 7374 if (!isEnabled()) { 7375 return super.onKeyUp(keyCode, event); 7376 } 7377 7378 if (!KeyEvent.isModifierKey(keyCode)) { 7379 mPreventDefaultMovement = false; 7380 } 7381 7382 switch (keyCode) { 7383 case KeyEvent.KEYCODE_DPAD_CENTER: 7384 if (event.hasNoModifiers()) { 7385 /* 7386 * If there is a click listener, just call through to 7387 * super, which will invoke it. 7388 * 7389 * If there isn't a click listener, try to show the soft 7390 * input method. (It will also 7391 * call performClick(), but that won't do anything in 7392 * this case.) 7393 */ 7394 if (!hasOnClickListeners()) { 7395 if (mMovement != null && mText instanceof Editable 7396 && mLayout != null && onCheckIsTextEditor()) { 7397 InputMethodManager imm = InputMethodManager.peekInstance(); 7398 viewClicked(imm); 7399 if (imm != null && getShowSoftInputOnFocus()) { 7400 imm.showSoftInput(this, 0); 7401 } 7402 } 7403 } 7404 } 7405 return super.onKeyUp(keyCode, event); 7406 7407 case KeyEvent.KEYCODE_ENTER: 7408 if (event.hasNoModifiers()) { 7409 if (mEditor != null && mEditor.mInputContentType != null 7410 && mEditor.mInputContentType.onEditorActionListener != null 7411 && mEditor.mInputContentType.enterDown) { 7412 mEditor.mInputContentType.enterDown = false; 7413 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 7414 this, EditorInfo.IME_NULL, event)) { 7415 return true; 7416 } 7417 } 7418 7419 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 7420 || shouldAdvanceFocusOnEnter()) { 7421 /* 7422 * If there is a click listener, just call through to 7423 * super, which will invoke it. 7424 * 7425 * If there isn't a click listener, try to advance focus, 7426 * but still call through to super, which will reset the 7427 * pressed state and longpress state. (It will also 7428 * call performClick(), but that won't do anything in 7429 * this case.) 7430 */ 7431 if (!hasOnClickListeners()) { 7432 View v = focusSearch(FOCUS_DOWN); 7433 7434 if (v != null) { 7435 if (!v.requestFocus(FOCUS_DOWN)) { 7436 throw new IllegalStateException("focus search returned a view " 7437 + "that wasn't able to take focus!"); 7438 } 7439 7440 /* 7441 * Return true because we handled the key; super 7442 * will return false because there was no click 7443 * listener. 7444 */ 7445 super.onKeyUp(keyCode, event); 7446 return true; 7447 } else if ((event.getFlags() 7448 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 7449 // No target for next focus, but make sure the IME 7450 // if this came from it. 7451 InputMethodManager imm = InputMethodManager.peekInstance(); 7452 if (imm != null && imm.isActive(this)) { 7453 imm.hideSoftInputFromWindow(getWindowToken(), 0); 7454 } 7455 } 7456 } 7457 } 7458 return super.onKeyUp(keyCode, event); 7459 } 7460 break; 7461 } 7462 7463 if (mEditor != null && mEditor.mKeyListener != null) { 7464 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 7465 return true; 7466 } 7467 } 7468 7469 if (mMovement != null && mLayout != null) { 7470 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) { 7471 return true; 7472 } 7473 } 7474 7475 return super.onKeyUp(keyCode, event); 7476 } 7477 7478 @Override 7479 public boolean onCheckIsTextEditor() { 7480 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 7481 } 7482 7483 @Override 7484 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 7485 if (onCheckIsTextEditor() && isEnabled()) { 7486 mEditor.createInputMethodStateIfNeeded(); 7487 outAttrs.inputType = getInputType(); 7488 if (mEditor.mInputContentType != null) { 7489 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 7490 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 7491 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 7492 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 7493 outAttrs.extras = mEditor.mInputContentType.extras; 7494 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 7495 } else { 7496 outAttrs.imeOptions = EditorInfo.IME_NULL; 7497 outAttrs.hintLocales = null; 7498 } 7499 if (focusSearch(FOCUS_DOWN) != null) { 7500 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 7501 } 7502 if (focusSearch(FOCUS_UP) != null) { 7503 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 7504 } 7505 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 7506 == EditorInfo.IME_ACTION_UNSPECIFIED) { 7507 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 7508 // An action has not been set, but the enter key will move to 7509 // the next focus, so set the action to that. 7510 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 7511 } else { 7512 // An action has not been set, and there is no focus to move 7513 // to, so let's just supply a "done" action. 7514 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 7515 } 7516 if (!shouldAdvanceFocusOnEnter()) { 7517 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 7518 } 7519 } 7520 if (isMultilineInputType(outAttrs.inputType)) { 7521 // Multi-line text editors should always show an enter key. 7522 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 7523 } 7524 outAttrs.hintText = mHint; 7525 if (mText instanceof Editable) { 7526 InputConnection ic = new EditableInputConnection(this); 7527 outAttrs.initialSelStart = getSelectionStart(); 7528 outAttrs.initialSelEnd = getSelectionEnd(); 7529 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 7530 return ic; 7531 } 7532 } 7533 return null; 7534 } 7535 7536 /** 7537 * If this TextView contains editable content, extract a portion of it 7538 * based on the information in <var>request</var> in to <var>outText</var>. 7539 * @return Returns true if the text was successfully extracted, else false. 7540 */ 7541 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 7542 createEditorIfNeeded(); 7543 return mEditor.extractText(request, outText); 7544 } 7545 7546 /** 7547 * This is used to remove all style-impacting spans from text before new 7548 * extracted text is being replaced into it, so that we don't have any 7549 * lingering spans applied during the replace. 7550 */ 7551 static void removeParcelableSpans(Spannable spannable, int start, int end) { 7552 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 7553 int i = spans.length; 7554 while (i > 0) { 7555 i--; 7556 spannable.removeSpan(spans[i]); 7557 } 7558 } 7559 7560 /** 7561 * Apply to this text view the given extracted text, as previously 7562 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 7563 */ 7564 public void setExtractedText(ExtractedText text) { 7565 Editable content = getEditableText(); 7566 if (text.text != null) { 7567 if (content == null) { 7568 setText(text.text, TextView.BufferType.EDITABLE); 7569 } else { 7570 int start = 0; 7571 int end = content.length(); 7572 7573 if (text.partialStartOffset >= 0) { 7574 final int N = content.length(); 7575 start = text.partialStartOffset; 7576 if (start > N) start = N; 7577 end = text.partialEndOffset; 7578 if (end > N) end = N; 7579 } 7580 7581 removeParcelableSpans(content, start, end); 7582 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 7583 if (text.text instanceof Spanned) { 7584 // OK to copy spans only. 7585 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 7586 Object.class, content, start); 7587 } 7588 } else { 7589 content.replace(start, end, text.text); 7590 } 7591 } 7592 } 7593 7594 // Now set the selection position... make sure it is in range, to 7595 // avoid crashes. If this is a partial update, it is possible that 7596 // the underlying text may have changed, causing us problems here. 7597 // Also we just don't want to trust clients to do the right thing. 7598 Spannable sp = (Spannable) getText(); 7599 final int N = sp.length(); 7600 int start = text.selectionStart; 7601 if (start < 0) { 7602 start = 0; 7603 } else if (start > N) { 7604 start = N; 7605 } 7606 int end = text.selectionEnd; 7607 if (end < 0) { 7608 end = 0; 7609 } else if (end > N) { 7610 end = N; 7611 } 7612 Selection.setSelection(sp, start, end); 7613 7614 // Finally, update the selection mode. 7615 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 7616 MetaKeyKeyListener.startSelecting(this, sp); 7617 } else { 7618 MetaKeyKeyListener.stopSelecting(this, sp); 7619 } 7620 } 7621 7622 /** 7623 * @hide 7624 */ 7625 public void setExtracting(ExtractedTextRequest req) { 7626 if (mEditor.mInputMethodState != null) { 7627 mEditor.mInputMethodState.mExtractedTextRequest = req; 7628 } 7629 // This would stop a possible selection mode, but no such mode is started in case 7630 // extracted mode will start. Some text is selected though, and will trigger an action mode 7631 // in the extracted view. 7632 mEditor.hideCursorAndSpanControllers(); 7633 stopTextActionMode(); 7634 if (mEditor.mSelectionModifierCursorController != null) { 7635 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 7636 } 7637 } 7638 7639 /** 7640 * Called by the framework in response to a text completion from 7641 * the current input method, provided by it calling 7642 * {@link InputConnection#commitCompletion 7643 * InputConnection.commitCompletion()}. The default implementation does 7644 * nothing; text views that are supporting auto-completion should override 7645 * this to do their desired behavior. 7646 * 7647 * @param text The auto complete text the user has selected. 7648 */ 7649 public void onCommitCompletion(CompletionInfo text) { 7650 // intentionally empty 7651 } 7652 7653 /** 7654 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 7655 * dictionary) from the current input method, provided by it calling 7656 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 7657 * The default implementation flashes the background of the corrected word to provide 7658 * feedback to the user. 7659 * 7660 * @param info The auto correct info about the text that was corrected. 7661 */ 7662 public void onCommitCorrection(CorrectionInfo info) { 7663 if (mEditor != null) mEditor.onCommitCorrection(info); 7664 } 7665 7666 public void beginBatchEdit() { 7667 if (mEditor != null) mEditor.beginBatchEdit(); 7668 } 7669 7670 public void endBatchEdit() { 7671 if (mEditor != null) mEditor.endBatchEdit(); 7672 } 7673 7674 /** 7675 * Called by the framework in response to a request to begin a batch 7676 * of edit operations through a call to link {@link #beginBatchEdit()}. 7677 */ 7678 public void onBeginBatchEdit() { 7679 // intentionally empty 7680 } 7681 7682 /** 7683 * Called by the framework in response to a request to end a batch 7684 * of edit operations through a call to link {@link #endBatchEdit}. 7685 */ 7686 public void onEndBatchEdit() { 7687 // intentionally empty 7688 } 7689 7690 /** 7691 * Called by the framework in response to a private command from the 7692 * current method, provided by it calling 7693 * {@link InputConnection#performPrivateCommand 7694 * InputConnection.performPrivateCommand()}. 7695 * 7696 * @param action The action name of the command. 7697 * @param data Any additional data for the command. This may be null. 7698 * @return Return true if you handled the command, else false. 7699 */ 7700 public boolean onPrivateIMECommand(String action, Bundle data) { 7701 return false; 7702 } 7703 7704 private void nullLayouts() { 7705 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 7706 mSavedLayout = (BoringLayout) mLayout; 7707 } 7708 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 7709 mSavedHintLayout = (BoringLayout) mHintLayout; 7710 } 7711 7712 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 7713 7714 mBoring = mHintBoring = null; 7715 7716 // Since it depends on the value of mLayout 7717 if (mEditor != null) mEditor.prepareCursorControllers(); 7718 } 7719 7720 /** 7721 * Make a new Layout based on the already-measured size of the view, 7722 * on the assumption that it was measured correctly at some point. 7723 */ 7724 private void assumeLayout() { 7725 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7726 7727 if (width < 1) { 7728 width = 0; 7729 } 7730 7731 int physicalWidth = width; 7732 7733 if (mHorizontallyScrolling) { 7734 width = VERY_WIDE; 7735 } 7736 7737 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 7738 physicalWidth, false); 7739 } 7740 7741 private Layout.Alignment getLayoutAlignment() { 7742 Layout.Alignment alignment; 7743 switch (getTextAlignment()) { 7744 case TEXT_ALIGNMENT_GRAVITY: 7745 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 7746 case Gravity.START: 7747 alignment = Layout.Alignment.ALIGN_NORMAL; 7748 break; 7749 case Gravity.END: 7750 alignment = Layout.Alignment.ALIGN_OPPOSITE; 7751 break; 7752 case Gravity.LEFT: 7753 alignment = Layout.Alignment.ALIGN_LEFT; 7754 break; 7755 case Gravity.RIGHT: 7756 alignment = Layout.Alignment.ALIGN_RIGHT; 7757 break; 7758 case Gravity.CENTER_HORIZONTAL: 7759 alignment = Layout.Alignment.ALIGN_CENTER; 7760 break; 7761 default: 7762 alignment = Layout.Alignment.ALIGN_NORMAL; 7763 break; 7764 } 7765 break; 7766 case TEXT_ALIGNMENT_TEXT_START: 7767 alignment = Layout.Alignment.ALIGN_NORMAL; 7768 break; 7769 case TEXT_ALIGNMENT_TEXT_END: 7770 alignment = Layout.Alignment.ALIGN_OPPOSITE; 7771 break; 7772 case TEXT_ALIGNMENT_CENTER: 7773 alignment = Layout.Alignment.ALIGN_CENTER; 7774 break; 7775 case TEXT_ALIGNMENT_VIEW_START: 7776 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 7777 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 7778 break; 7779 case TEXT_ALIGNMENT_VIEW_END: 7780 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 7781 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 7782 break; 7783 case TEXT_ALIGNMENT_INHERIT: 7784 // This should never happen as we have already resolved the text alignment 7785 // but better safe than sorry so we just fall through 7786 default: 7787 alignment = Layout.Alignment.ALIGN_NORMAL; 7788 break; 7789 } 7790 return alignment; 7791 } 7792 7793 /** 7794 * The width passed in is now the desired layout width, 7795 * not the full view width with padding. 7796 * {@hide} 7797 */ 7798 protected void makeNewLayout(int wantWidth, int hintWidth, 7799 BoringLayout.Metrics boring, 7800 BoringLayout.Metrics hintBoring, 7801 int ellipsisWidth, boolean bringIntoView) { 7802 stopMarquee(); 7803 7804 // Update "old" cached values 7805 mOldMaximum = mMaximum; 7806 mOldMaxMode = mMaxMode; 7807 7808 mHighlightPathBogus = true; 7809 7810 if (wantWidth < 0) { 7811 wantWidth = 0; 7812 } 7813 if (hintWidth < 0) { 7814 hintWidth = 0; 7815 } 7816 7817 Layout.Alignment alignment = getLayoutAlignment(); 7818 final boolean testDirChange = mSingleLine && mLayout != null 7819 && (alignment == Layout.Alignment.ALIGN_NORMAL 7820 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 7821 int oldDir = 0; 7822 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 7823 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 7824 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 7825 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 7826 TruncateAt effectiveEllipsize = mEllipsize; 7827 if (mEllipsize == TruncateAt.MARQUEE 7828 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7829 effectiveEllipsize = TruncateAt.END_SMALL; 7830 } 7831 7832 if (mTextDir == null) { 7833 mTextDir = getTextDirectionHeuristic(); 7834 } 7835 7836 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 7837 effectiveEllipsize, effectiveEllipsize == mEllipsize); 7838 if (switchEllipsize) { 7839 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 7840 ? TruncateAt.END : TruncateAt.MARQUEE; 7841 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 7842 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 7843 } 7844 7845 shouldEllipsize = mEllipsize != null; 7846 mHintLayout = null; 7847 7848 if (mHint != null) { 7849 if (shouldEllipsize) hintWidth = wantWidth; 7850 7851 if (hintBoring == UNKNOWN_BORING) { 7852 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 7853 mHintBoring); 7854 if (hintBoring != null) { 7855 mHintBoring = hintBoring; 7856 } 7857 } 7858 7859 if (hintBoring != null) { 7860 if (hintBoring.width <= hintWidth 7861 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 7862 if (mSavedHintLayout != null) { 7863 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 7864 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7865 hintBoring, mIncludePad); 7866 } else { 7867 mHintLayout = BoringLayout.make(mHint, mTextPaint, 7868 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7869 hintBoring, mIncludePad); 7870 } 7871 7872 mSavedHintLayout = (BoringLayout) mHintLayout; 7873 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 7874 if (mSavedHintLayout != null) { 7875 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 7876 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7877 hintBoring, mIncludePad, mEllipsize, 7878 ellipsisWidth); 7879 } else { 7880 mHintLayout = BoringLayout.make(mHint, mTextPaint, 7881 hintWidth, alignment, mSpacingMult, mSpacingAdd, 7882 hintBoring, mIncludePad, mEllipsize, 7883 ellipsisWidth); 7884 } 7885 } 7886 } 7887 // TODO: code duplication with makeSingleLayout() 7888 if (mHintLayout == null) { 7889 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 7890 mHint.length(), mTextPaint, hintWidth) 7891 .setAlignment(alignment) 7892 .setTextDirection(mTextDir) 7893 .setLineSpacing(mSpacingAdd, mSpacingMult) 7894 .setIncludePad(mIncludePad) 7895 .setBreakStrategy(mBreakStrategy) 7896 .setHyphenationFrequency(mHyphenationFrequency) 7897 .setJustificationMode(mJustificationMode) 7898 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 7899 if (shouldEllipsize) { 7900 builder.setEllipsize(mEllipsize) 7901 .setEllipsizedWidth(ellipsisWidth); 7902 } 7903 mHintLayout = builder.build(); 7904 } 7905 } 7906 7907 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 7908 registerForPreDraw(); 7909 } 7910 7911 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7912 if (!compressText(ellipsisWidth)) { 7913 final int height = mLayoutParams.height; 7914 // If the size of the view does not depend on the size of the text, try to 7915 // start the marquee immediately 7916 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 7917 startMarquee(); 7918 } else { 7919 // Defer the start of the marquee until we know our width (see setFrame()) 7920 mRestartMarquee = true; 7921 } 7922 } 7923 } 7924 7925 // CursorControllers need a non-null mLayout 7926 if (mEditor != null) mEditor.prepareCursorControllers(); 7927 } 7928 7929 /** 7930 * @hide 7931 */ 7932 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 7933 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 7934 boolean useSaved) { 7935 Layout result = null; 7936 if (mText instanceof Spannable) { 7937 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 7938 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, 7939 mBreakStrategy, mHyphenationFrequency, mJustificationMode, 7940 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); 7941 } else { 7942 if (boring == UNKNOWN_BORING) { 7943 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 7944 if (boring != null) { 7945 mBoring = boring; 7946 } 7947 } 7948 7949 if (boring != null) { 7950 if (boring.width <= wantWidth 7951 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 7952 if (useSaved && mSavedLayout != null) { 7953 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 7954 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7955 boring, mIncludePad); 7956 } else { 7957 result = BoringLayout.make(mTransformed, mTextPaint, 7958 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7959 boring, mIncludePad); 7960 } 7961 7962 if (useSaved) { 7963 mSavedLayout = (BoringLayout) result; 7964 } 7965 } else if (shouldEllipsize && boring.width <= wantWidth) { 7966 if (useSaved && mSavedLayout != null) { 7967 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 7968 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7969 boring, mIncludePad, effectiveEllipsize, 7970 ellipsisWidth); 7971 } else { 7972 result = BoringLayout.make(mTransformed, mTextPaint, 7973 wantWidth, alignment, mSpacingMult, mSpacingAdd, 7974 boring, mIncludePad, effectiveEllipsize, 7975 ellipsisWidth); 7976 } 7977 } 7978 } 7979 } 7980 if (result == null) { 7981 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 7982 0, mTransformed.length(), mTextPaint, wantWidth) 7983 .setAlignment(alignment) 7984 .setTextDirection(mTextDir) 7985 .setLineSpacing(mSpacingAdd, mSpacingMult) 7986 .setIncludePad(mIncludePad) 7987 .setBreakStrategy(mBreakStrategy) 7988 .setHyphenationFrequency(mHyphenationFrequency) 7989 .setJustificationMode(mJustificationMode) 7990 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 7991 if (shouldEllipsize) { 7992 builder.setEllipsize(effectiveEllipsize) 7993 .setEllipsizedWidth(ellipsisWidth); 7994 } 7995 // TODO: explore always setting maxLines 7996 result = builder.build(); 7997 } 7998 return result; 7999 } 8000 8001 private boolean compressText(float width) { 8002 if (isHardwareAccelerated()) return false; 8003 8004 // Only compress the text if it hasn't been compressed by the previous pass 8005 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 8006 && mTextPaint.getTextScaleX() == 1.0f) { 8007 final float textWidth = mLayout.getLineWidth(0); 8008 final float overflow = (textWidth + 1.0f - width) / width; 8009 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 8010 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 8011 post(new Runnable() { 8012 public void run() { 8013 requestLayout(); 8014 } 8015 }); 8016 return true; 8017 } 8018 } 8019 8020 return false; 8021 } 8022 8023 private static int desired(Layout layout) { 8024 int n = layout.getLineCount(); 8025 CharSequence text = layout.getText(); 8026 float max = 0; 8027 8028 // if any line was wrapped, we can't use it. 8029 // but it's ok for the last line not to have a newline 8030 8031 for (int i = 0; i < n - 1; i++) { 8032 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 8033 return -1; 8034 } 8035 } 8036 8037 for (int i = 0; i < n; i++) { 8038 max = Math.max(max, layout.getLineWidth(i)); 8039 } 8040 8041 return (int) Math.ceil(max); 8042 } 8043 8044 /** 8045 * Set whether the TextView includes extra top and bottom padding to make 8046 * room for accents that go above the normal ascent and descent. 8047 * The default is true. 8048 * 8049 * @see #getIncludeFontPadding() 8050 * 8051 * @attr ref android.R.styleable#TextView_includeFontPadding 8052 */ 8053 public void setIncludeFontPadding(boolean includepad) { 8054 if (mIncludePad != includepad) { 8055 mIncludePad = includepad; 8056 8057 if (mLayout != null) { 8058 nullLayouts(); 8059 requestLayout(); 8060 invalidate(); 8061 } 8062 } 8063 } 8064 8065 /** 8066 * Gets whether the TextView includes extra top and bottom padding to make 8067 * room for accents that go above the normal ascent and descent. 8068 * 8069 * @see #setIncludeFontPadding(boolean) 8070 * 8071 * @attr ref android.R.styleable#TextView_includeFontPadding 8072 */ 8073 public boolean getIncludeFontPadding() { 8074 return mIncludePad; 8075 } 8076 8077 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 8078 8079 @Override 8080 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 8081 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 8082 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 8083 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 8084 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 8085 8086 int width; 8087 int height; 8088 8089 BoringLayout.Metrics boring = UNKNOWN_BORING; 8090 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 8091 8092 if (mTextDir == null) { 8093 mTextDir = getTextDirectionHeuristic(); 8094 } 8095 8096 int des = -1; 8097 boolean fromexisting = false; 8098 8099 if (widthMode == MeasureSpec.EXACTLY) { 8100 // Parent has told us how big to be. So be it. 8101 width = widthSize; 8102 } else { 8103 if (mLayout != null && mEllipsize == null) { 8104 des = desired(mLayout); 8105 } 8106 8107 if (des < 0) { 8108 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 8109 if (boring != null) { 8110 mBoring = boring; 8111 } 8112 } else { 8113 fromexisting = true; 8114 } 8115 8116 if (boring == null || boring == UNKNOWN_BORING) { 8117 if (des < 0) { 8118 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0, 8119 mTransformed.length(), mTextPaint, mTextDir)); 8120 } 8121 width = des; 8122 } else { 8123 width = boring.width; 8124 } 8125 8126 final Drawables dr = mDrawables; 8127 if (dr != null) { 8128 width = Math.max(width, dr.mDrawableWidthTop); 8129 width = Math.max(width, dr.mDrawableWidthBottom); 8130 } 8131 8132 if (mHint != null) { 8133 int hintDes = -1; 8134 int hintWidth; 8135 8136 if (mHintLayout != null && mEllipsize == null) { 8137 hintDes = desired(mHintLayout); 8138 } 8139 8140 if (hintDes < 0) { 8141 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 8142 if (hintBoring != null) { 8143 mHintBoring = hintBoring; 8144 } 8145 } 8146 8147 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 8148 if (hintDes < 0) { 8149 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, 0, mHint.length(), 8150 mTextPaint, mTextDir)); 8151 } 8152 hintWidth = hintDes; 8153 } else { 8154 hintWidth = hintBoring.width; 8155 } 8156 8157 if (hintWidth > width) { 8158 width = hintWidth; 8159 } 8160 } 8161 8162 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 8163 8164 if (mMaxWidthMode == EMS) { 8165 width = Math.min(width, mMaxWidth * getLineHeight()); 8166 } else { 8167 width = Math.min(width, mMaxWidth); 8168 } 8169 8170 if (mMinWidthMode == EMS) { 8171 width = Math.max(width, mMinWidth * getLineHeight()); 8172 } else { 8173 width = Math.max(width, mMinWidth); 8174 } 8175 8176 // Check against our minimum width 8177 width = Math.max(width, getSuggestedMinimumWidth()); 8178 8179 if (widthMode == MeasureSpec.AT_MOST) { 8180 width = Math.min(widthSize, width); 8181 } 8182 } 8183 8184 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8185 int unpaddedWidth = want; 8186 8187 if (mHorizontallyScrolling) want = VERY_WIDE; 8188 8189 int hintWant = want; 8190 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 8191 8192 if (mLayout == null) { 8193 makeNewLayout(want, hintWant, boring, hintBoring, 8194 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 8195 } else { 8196 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 8197 || (mLayout.getEllipsizedWidth() 8198 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 8199 8200 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 8201 && (want > mLayout.getWidth()) 8202 && (mLayout instanceof BoringLayout 8203 || (fromexisting && des >= 0 && des <= want)); 8204 8205 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 8206 8207 if (layoutChanged || maximumChanged) { 8208 if (!maximumChanged && widthChanged) { 8209 mLayout.increaseWidthTo(want); 8210 } else { 8211 makeNewLayout(want, hintWant, boring, hintBoring, 8212 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 8213 } 8214 } else { 8215 // Nothing has changed 8216 } 8217 } 8218 8219 if (heightMode == MeasureSpec.EXACTLY) { 8220 // Parent has told us how big to be. So be it. 8221 height = heightSize; 8222 mDesiredHeightAtMeasure = -1; 8223 } else { 8224 int desired = getDesiredHeight(); 8225 8226 height = desired; 8227 mDesiredHeightAtMeasure = desired; 8228 8229 if (heightMode == MeasureSpec.AT_MOST) { 8230 height = Math.min(desired, heightSize); 8231 } 8232 } 8233 8234 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 8235 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 8236 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 8237 } 8238 8239 /* 8240 * We didn't let makeNewLayout() register to bring the cursor into view, 8241 * so do it here if there is any possibility that it is needed. 8242 */ 8243 if (mMovement != null 8244 || mLayout.getWidth() > unpaddedWidth 8245 || mLayout.getHeight() > unpaddedHeight) { 8246 registerForPreDraw(); 8247 } else { 8248 scrollTo(0, 0); 8249 } 8250 8251 setMeasuredDimension(width, height); 8252 } 8253 8254 /** 8255 * Automatically computes and sets the text size. 8256 */ 8257 private void autoSizeText() { 8258 final int maxWidth = getWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 8259 final int maxHeight = getHeight() - getExtendedPaddingBottom() - getExtendedPaddingTop(); 8260 8261 if (maxWidth <= 0 || maxHeight <= 0) { 8262 return; 8263 } 8264 8265 synchronized (TEMP_RECTF) { 8266 TEMP_RECTF.setEmpty(); 8267 TEMP_RECTF.right = maxWidth; 8268 TEMP_RECTF.bottom = maxHeight; 8269 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 8270 if (optimalTextSize != getTextSize()) { 8271 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize); 8272 } 8273 } 8274 } 8275 8276 /** 8277 * Performs a binary search to find the largest text size that will still fit within the size 8278 * available to this view. 8279 */ 8280 private int findLargestTextSizeWhichFits(RectF availableSpace) { 8281 final int sizesCount = mAutoSizeTextSizesInPx.length; 8282 if (sizesCount == 0) { 8283 throw new IllegalStateException("No available text sizes to choose from."); 8284 } 8285 8286 int bestSizeIndex = 0; 8287 int lowIndex = bestSizeIndex + 1; 8288 int highIndex = sizesCount - 1; 8289 int sizeToTryIndex; 8290 while (lowIndex <= highIndex) { 8291 sizeToTryIndex = (lowIndex + highIndex) / 2; 8292 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 8293 bestSizeIndex = lowIndex; 8294 lowIndex = sizeToTryIndex + 1; 8295 } else { 8296 highIndex = sizeToTryIndex - 1; 8297 bestSizeIndex = highIndex; 8298 } 8299 } 8300 8301 return mAutoSizeTextSizesInPx[bestSizeIndex]; 8302 } 8303 8304 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 8305 final CharSequence text = getText(); 8306 final int maxLines = getMaxLines(); 8307 if (mTempTextPaint == null) { 8308 mTempTextPaint = new TextPaint(); 8309 } else { 8310 mTempTextPaint.reset(); 8311 } 8312 mTempTextPaint.set(getPaint()); 8313 mTempTextPaint.setTextSize(suggestedSizeInPx); 8314 8315 final int availableWidth = mHorizontallyScrolling 8316 ? VERY_WIDE 8317 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 8318 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 8319 text, 0, text.length(), mTempTextPaint, availableWidth); 8320 8321 layoutBuilder.setAlignment(getLayoutAlignment()) 8322 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 8323 .setIncludePad(getIncludeFontPadding()) 8324 .setBreakStrategy(getBreakStrategy()) 8325 .setHyphenationFrequency(getHyphenationFrequency()) 8326 .setJustificationMode(getJustificationMode()) 8327 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 8328 .setTextDirection(getTextDirectionHeuristic()); 8329 8330 final StaticLayout layout = layoutBuilder.build(); 8331 8332 // Lines overflow. 8333 if (maxLines != -1 && layout.getLineCount() > maxLines) { 8334 return false; 8335 } 8336 8337 // Height overflow. 8338 if (layout.getHeight() > availableSpace.bottom) { 8339 return false; 8340 } 8341 8342 return true; 8343 } 8344 8345 private int getDesiredHeight() { 8346 return Math.max( 8347 getDesiredHeight(mLayout, true), 8348 getDesiredHeight(mHintLayout, mEllipsize != null)); 8349 } 8350 8351 private int getDesiredHeight(Layout layout, boolean cap) { 8352 if (layout == null) { 8353 return 0; 8354 } 8355 8356 /* 8357 * Don't cap the hint to a certain number of lines. 8358 * (Do cap it, though, if we have a maximum pixel height.) 8359 */ 8360 int desired = layout.getHeight(cap); 8361 8362 final Drawables dr = mDrawables; 8363 if (dr != null) { 8364 desired = Math.max(desired, dr.mDrawableHeightLeft); 8365 desired = Math.max(desired, dr.mDrawableHeightRight); 8366 } 8367 8368 int linecount = layout.getLineCount(); 8369 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 8370 desired += padding; 8371 8372 if (mMaxMode != LINES) { 8373 desired = Math.min(desired, mMaximum); 8374 } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) { 8375 desired = layout.getLineTop(mMaximum); 8376 8377 if (dr != null) { 8378 desired = Math.max(desired, dr.mDrawableHeightLeft); 8379 desired = Math.max(desired, dr.mDrawableHeightRight); 8380 } 8381 8382 desired += padding; 8383 linecount = mMaximum; 8384 } 8385 8386 if (mMinMode == LINES) { 8387 if (linecount < mMinimum) { 8388 desired += getLineHeight() * (mMinimum - linecount); 8389 } 8390 } else { 8391 desired = Math.max(desired, mMinimum); 8392 } 8393 8394 // Check against our minimum height 8395 desired = Math.max(desired, getSuggestedMinimumHeight()); 8396 8397 return desired; 8398 } 8399 8400 /** 8401 * Check whether a change to the existing text layout requires a 8402 * new view layout. 8403 */ 8404 private void checkForResize() { 8405 boolean sizeChanged = false; 8406 8407 if (mLayout != null) { 8408 // Check if our width changed 8409 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 8410 sizeChanged = true; 8411 invalidate(); 8412 } 8413 8414 // Check if our height changed 8415 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 8416 int desiredHeight = getDesiredHeight(); 8417 8418 if (desiredHeight != this.getHeight()) { 8419 sizeChanged = true; 8420 } 8421 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 8422 if (mDesiredHeightAtMeasure >= 0) { 8423 int desiredHeight = getDesiredHeight(); 8424 8425 if (desiredHeight != mDesiredHeightAtMeasure) { 8426 sizeChanged = true; 8427 } 8428 } 8429 } 8430 } 8431 8432 if (sizeChanged) { 8433 requestLayout(); 8434 // caller will have already invalidated 8435 } 8436 } 8437 8438 /** 8439 * Check whether entirely new text requires a new view layout 8440 * or merely a new text layout. 8441 */ 8442 private void checkForRelayout() { 8443 // If we have a fixed width, we can just swap in a new text layout 8444 // if the text height stays the same or if the view height is fixed. 8445 8446 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 8447 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 8448 && (mHint == null || mHintLayout != null) 8449 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 8450 // Static width, so try making a new text layout. 8451 8452 int oldht = mLayout.getHeight(); 8453 int want = mLayout.getWidth(); 8454 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 8455 8456 /* 8457 * No need to bring the text into view, since the size is not 8458 * changing (unless we do the requestLayout(), in which case it 8459 * will happen at measure). 8460 */ 8461 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 8462 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 8463 false); 8464 8465 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 8466 // In a fixed-height view, so use our new text layout. 8467 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 8468 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 8469 invalidate(); 8470 return; 8471 } 8472 8473 // Dynamic height, but height has stayed the same, 8474 // so use our new text layout. 8475 if (mLayout.getHeight() == oldht 8476 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 8477 invalidate(); 8478 return; 8479 } 8480 } 8481 8482 // We lose: the height has changed and we have a dynamic height. 8483 // Request a new view layout using our new text layout. 8484 requestLayout(); 8485 invalidate(); 8486 } else { 8487 // Dynamic width, so we have no choice but to request a new 8488 // view layout with a new text layout. 8489 nullLayouts(); 8490 requestLayout(); 8491 invalidate(); 8492 } 8493 } 8494 8495 @Override 8496 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 8497 super.onLayout(changed, left, top, right, bottom); 8498 if (mDeferScroll >= 0) { 8499 int curs = mDeferScroll; 8500 mDeferScroll = -1; 8501 bringPointIntoView(Math.min(curs, mText.length())); 8502 } 8503 8504 if (isAutoSizeEnabled()) { 8505 if (mNeedsAutoSizeText) { 8506 // Call auto-size after the width and height have been calculated. 8507 autoSizeText(); 8508 } 8509 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 8510 // after the next layout round should set this to false. 8511 mNeedsAutoSizeText = true; 8512 } 8513 } 8514 8515 private boolean isShowingHint() { 8516 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 8517 } 8518 8519 /** 8520 * Returns true if anything changed. 8521 */ 8522 private boolean bringTextIntoView() { 8523 Layout layout = isShowingHint() ? mHintLayout : mLayout; 8524 int line = 0; 8525 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8526 line = layout.getLineCount() - 1; 8527 } 8528 8529 Layout.Alignment a = layout.getParagraphAlignment(line); 8530 int dir = layout.getParagraphDirection(line); 8531 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8532 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8533 int ht = layout.getHeight(); 8534 8535 int scrollx, scrolly; 8536 8537 // Convert to left, center, or right alignment. 8538 if (a == Layout.Alignment.ALIGN_NORMAL) { 8539 a = dir == Layout.DIR_LEFT_TO_RIGHT 8540 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 8541 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 8542 a = dir == Layout.DIR_LEFT_TO_RIGHT 8543 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 8544 } 8545 8546 if (a == Layout.Alignment.ALIGN_CENTER) { 8547 /* 8548 * Keep centered if possible, or, if it is too wide to fit, 8549 * keep leading edge in view. 8550 */ 8551 8552 int left = (int) Math.floor(layout.getLineLeft(line)); 8553 int right = (int) Math.ceil(layout.getLineRight(line)); 8554 8555 if (right - left < hspace) { 8556 scrollx = (right + left) / 2 - hspace / 2; 8557 } else { 8558 if (dir < 0) { 8559 scrollx = right - hspace; 8560 } else { 8561 scrollx = left; 8562 } 8563 } 8564 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 8565 int right = (int) Math.ceil(layout.getLineRight(line)); 8566 scrollx = right - hspace; 8567 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 8568 scrollx = (int) Math.floor(layout.getLineLeft(line)); 8569 } 8570 8571 if (ht < vspace) { 8572 scrolly = 0; 8573 } else { 8574 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8575 scrolly = ht - vspace; 8576 } else { 8577 scrolly = 0; 8578 } 8579 } 8580 8581 if (scrollx != mScrollX || scrolly != mScrollY) { 8582 scrollTo(scrollx, scrolly); 8583 return true; 8584 } else { 8585 return false; 8586 } 8587 } 8588 8589 /** 8590 * Move the point, specified by the offset, into the view if it is needed. 8591 * This has to be called after layout. Returns true if anything changed. 8592 */ 8593 public boolean bringPointIntoView(int offset) { 8594 if (isLayoutRequested()) { 8595 mDeferScroll = offset; 8596 return false; 8597 } 8598 boolean changed = false; 8599 8600 Layout layout = isShowingHint() ? mHintLayout : mLayout; 8601 8602 if (layout == null) return changed; 8603 8604 int line = layout.getLineForOffset(offset); 8605 8606 int grav; 8607 8608 switch (layout.getParagraphAlignment(line)) { 8609 case ALIGN_LEFT: 8610 grav = 1; 8611 break; 8612 case ALIGN_RIGHT: 8613 grav = -1; 8614 break; 8615 case ALIGN_NORMAL: 8616 grav = layout.getParagraphDirection(line); 8617 break; 8618 case ALIGN_OPPOSITE: 8619 grav = -layout.getParagraphDirection(line); 8620 break; 8621 case ALIGN_CENTER: 8622 default: 8623 grav = 0; 8624 break; 8625 } 8626 8627 // We only want to clamp the cursor to fit within the layout width 8628 // in left-to-right modes, because in a right to left alignment, 8629 // we want to scroll to keep the line-right on the screen, as other 8630 // lines are likely to have text flush with the right margin, which 8631 // we want to keep visible. 8632 // A better long-term solution would probably be to measure both 8633 // the full line and a blank-trimmed version, and, for example, use 8634 // the latter measurement for centering and right alignment, but for 8635 // the time being we only implement the cursor clamping in left to 8636 // right where it is most likely to be annoying. 8637 final boolean clamped = grav > 0; 8638 // FIXME: Is it okay to truncate this, or should we round? 8639 final int x = (int) layout.getPrimaryHorizontal(offset, clamped); 8640 final int top = layout.getLineTop(line); 8641 final int bottom = layout.getLineTop(line + 1); 8642 8643 int left = (int) Math.floor(layout.getLineLeft(line)); 8644 int right = (int) Math.ceil(layout.getLineRight(line)); 8645 int ht = layout.getHeight(); 8646 8647 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8648 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8649 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 8650 // If cursor has been clamped, make sure we don't scroll. 8651 right = Math.max(x, left + hspace); 8652 } 8653 8654 int hslack = (bottom - top) / 2; 8655 int vslack = hslack; 8656 8657 if (vslack > vspace / 4) { 8658 vslack = vspace / 4; 8659 } 8660 if (hslack > hspace / 4) { 8661 hslack = hspace / 4; 8662 } 8663 8664 int hs = mScrollX; 8665 int vs = mScrollY; 8666 8667 if (top - vs < vslack) { 8668 vs = top - vslack; 8669 } 8670 if (bottom - vs > vspace - vslack) { 8671 vs = bottom - (vspace - vslack); 8672 } 8673 if (ht - vs < vspace) { 8674 vs = ht - vspace; 8675 } 8676 if (0 - vs > 0) { 8677 vs = 0; 8678 } 8679 8680 if (grav != 0) { 8681 if (x - hs < hslack) { 8682 hs = x - hslack; 8683 } 8684 if (x - hs > hspace - hslack) { 8685 hs = x - (hspace - hslack); 8686 } 8687 } 8688 8689 if (grav < 0) { 8690 if (left - hs > 0) { 8691 hs = left; 8692 } 8693 if (right - hs < hspace) { 8694 hs = right - hspace; 8695 } 8696 } else if (grav > 0) { 8697 if (right - hs < hspace) { 8698 hs = right - hspace; 8699 } 8700 if (left - hs > 0) { 8701 hs = left; 8702 } 8703 } else /* grav == 0 */ { 8704 if (right - left <= hspace) { 8705 /* 8706 * If the entire text fits, center it exactly. 8707 */ 8708 hs = left - (hspace - (right - left)) / 2; 8709 } else if (x > right - hslack) { 8710 /* 8711 * If we are near the right edge, keep the right edge 8712 * at the edge of the view. 8713 */ 8714 hs = right - hspace; 8715 } else if (x < left + hslack) { 8716 /* 8717 * If we are near the left edge, keep the left edge 8718 * at the edge of the view. 8719 */ 8720 hs = left; 8721 } else if (left > hs) { 8722 /* 8723 * Is there whitespace visible at the left? Fix it if so. 8724 */ 8725 hs = left; 8726 } else if (right < hs + hspace) { 8727 /* 8728 * Is there whitespace visible at the right? Fix it if so. 8729 */ 8730 hs = right - hspace; 8731 } else { 8732 /* 8733 * Otherwise, float as needed. 8734 */ 8735 if (x - hs < hslack) { 8736 hs = x - hslack; 8737 } 8738 if (x - hs > hspace - hslack) { 8739 hs = x - (hspace - hslack); 8740 } 8741 } 8742 } 8743 8744 if (hs != mScrollX || vs != mScrollY) { 8745 if (mScroller == null) { 8746 scrollTo(hs, vs); 8747 } else { 8748 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 8749 int dx = hs - mScrollX; 8750 int dy = vs - mScrollY; 8751 8752 if (duration > ANIMATED_SCROLL_GAP) { 8753 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 8754 awakenScrollBars(mScroller.getDuration()); 8755 invalidate(); 8756 } else { 8757 if (!mScroller.isFinished()) { 8758 mScroller.abortAnimation(); 8759 } 8760 8761 scrollBy(dx, dy); 8762 } 8763 8764 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 8765 } 8766 8767 changed = true; 8768 } 8769 8770 if (isFocused()) { 8771 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 8772 // requestRectangleOnScreen() is in terms of content coordinates. 8773 8774 // The offsets here are to ensure the rectangle we are using is 8775 // within our view bounds, in case the cursor is on the far left 8776 // or right. If it isn't withing the bounds, then this request 8777 // will be ignored. 8778 if (mTempRect == null) mTempRect = new Rect(); 8779 mTempRect.set(x - 2, top, x + 2, bottom); 8780 getInterestingRect(mTempRect, line); 8781 mTempRect.offset(mScrollX, mScrollY); 8782 8783 if (requestRectangleOnScreen(mTempRect)) { 8784 changed = true; 8785 } 8786 } 8787 8788 return changed; 8789 } 8790 8791 /** 8792 * Move the cursor, if needed, so that it is at an offset that is visible 8793 * to the user. This will not move the cursor if it represents more than 8794 * one character (a selection range). This will only work if the 8795 * TextView contains spannable text; otherwise it will do nothing. 8796 * 8797 * @return True if the cursor was actually moved, false otherwise. 8798 */ 8799 public boolean moveCursorToVisibleOffset() { 8800 if (!(mText instanceof Spannable)) { 8801 return false; 8802 } 8803 int start = getSelectionStart(); 8804 int end = getSelectionEnd(); 8805 if (start != end) { 8806 return false; 8807 } 8808 8809 // First: make sure the line is visible on screen: 8810 8811 int line = mLayout.getLineForOffset(start); 8812 8813 final int top = mLayout.getLineTop(line); 8814 final int bottom = mLayout.getLineTop(line + 1); 8815 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 8816 int vslack = (bottom - top) / 2; 8817 if (vslack > vspace / 4) { 8818 vslack = vspace / 4; 8819 } 8820 final int vs = mScrollY; 8821 8822 if (top < (vs + vslack)) { 8823 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 8824 } else if (bottom > (vspace + vs - vslack)) { 8825 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 8826 } 8827 8828 // Next: make sure the character is visible on screen: 8829 8830 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 8831 final int hs = mScrollX; 8832 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 8833 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 8834 8835 // line might contain bidirectional text 8836 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 8837 final int highChar = leftChar > rightChar ? leftChar : rightChar; 8838 8839 int newStart = start; 8840 if (newStart < lowChar) { 8841 newStart = lowChar; 8842 } else if (newStart > highChar) { 8843 newStart = highChar; 8844 } 8845 8846 if (newStart != start) { 8847 Selection.setSelection((Spannable) mText, newStart); 8848 return true; 8849 } 8850 8851 return false; 8852 } 8853 8854 @Override 8855 public void computeScroll() { 8856 if (mScroller != null) { 8857 if (mScroller.computeScrollOffset()) { 8858 mScrollX = mScroller.getCurrX(); 8859 mScrollY = mScroller.getCurrY(); 8860 invalidateParentCaches(); 8861 postInvalidate(); // So we draw again 8862 } 8863 } 8864 } 8865 8866 private void getInterestingRect(Rect r, int line) { 8867 convertFromViewportToContentCoordinates(r); 8868 8869 // Rectangle can can be expanded on first and last line to take 8870 // padding into account. 8871 // TODO Take left/right padding into account too? 8872 if (line == 0) r.top -= getExtendedPaddingTop(); 8873 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 8874 } 8875 8876 private void convertFromViewportToContentCoordinates(Rect r) { 8877 final int horizontalOffset = viewportToContentHorizontalOffset(); 8878 r.left += horizontalOffset; 8879 r.right += horizontalOffset; 8880 8881 final int verticalOffset = viewportToContentVerticalOffset(); 8882 r.top += verticalOffset; 8883 r.bottom += verticalOffset; 8884 } 8885 8886 int viewportToContentHorizontalOffset() { 8887 return getCompoundPaddingLeft() - mScrollX; 8888 } 8889 8890 int viewportToContentVerticalOffset() { 8891 int offset = getExtendedPaddingTop() - mScrollY; 8892 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 8893 offset += getVerticalOffset(false); 8894 } 8895 return offset; 8896 } 8897 8898 @Override 8899 public void debug(int depth) { 8900 super.debug(depth); 8901 8902 String output = debugIndent(depth); 8903 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 8904 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 8905 + "} "; 8906 8907 if (mText != null) { 8908 8909 output += "mText=\"" + mText + "\" "; 8910 if (mLayout != null) { 8911 output += "mLayout width=" + mLayout.getWidth() 8912 + " height=" + mLayout.getHeight(); 8913 } 8914 } else { 8915 output += "mText=NULL"; 8916 } 8917 Log.d(VIEW_LOG_TAG, output); 8918 } 8919 8920 /** 8921 * Convenience for {@link Selection#getSelectionStart}. 8922 */ 8923 @ViewDebug.ExportedProperty(category = "text") 8924 public int getSelectionStart() { 8925 return Selection.getSelectionStart(getText()); 8926 } 8927 8928 /** 8929 * Convenience for {@link Selection#getSelectionEnd}. 8930 */ 8931 @ViewDebug.ExportedProperty(category = "text") 8932 public int getSelectionEnd() { 8933 return Selection.getSelectionEnd(getText()); 8934 } 8935 8936 /** 8937 * Return true iff there is a selection inside this text view. 8938 */ 8939 public boolean hasSelection() { 8940 final int selectionStart = getSelectionStart(); 8941 final int selectionEnd = getSelectionEnd(); 8942 8943 return selectionStart >= 0 && selectionStart != selectionEnd; 8944 } 8945 8946 String getSelectedText() { 8947 if (!hasSelection()) { 8948 return null; 8949 } 8950 8951 final int start = getSelectionStart(); 8952 final int end = getSelectionEnd(); 8953 return String.valueOf( 8954 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 8955 } 8956 8957 /** 8958 * Sets the properties of this field (lines, horizontally scrolling, 8959 * transformation method) to be for a single-line input. 8960 * 8961 * @attr ref android.R.styleable#TextView_singleLine 8962 */ 8963 public void setSingleLine() { 8964 setSingleLine(true); 8965 } 8966 8967 /** 8968 * Sets the properties of this field to transform input to ALL CAPS 8969 * display. This may use a "small caps" formatting if available. 8970 * This setting will be ignored if this field is editable or selectable. 8971 * 8972 * This call replaces the current transformation method. Disabling this 8973 * will not necessarily restore the previous behavior from before this 8974 * was enabled. 8975 * 8976 * @see #setTransformationMethod(TransformationMethod) 8977 * @attr ref android.R.styleable#TextView_textAllCaps 8978 */ 8979 public void setAllCaps(boolean allCaps) { 8980 if (allCaps) { 8981 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 8982 } else { 8983 setTransformationMethod(null); 8984 } 8985 } 8986 8987 /** 8988 * If true, sets the properties of this field (number of lines, horizontally scrolling, 8989 * transformation method) to be for a single-line input; if false, restores these to the default 8990 * conditions. 8991 * 8992 * Note that the default conditions are not necessarily those that were in effect prior this 8993 * method, and you may want to reset these properties to your custom values. 8994 * 8995 * @attr ref android.R.styleable#TextView_singleLine 8996 */ 8997 @android.view.RemotableViewMethod 8998 public void setSingleLine(boolean singleLine) { 8999 // Could be used, but may break backward compatibility. 9000 // if (mSingleLine == singleLine) return; 9001 setInputTypeSingleLine(singleLine); 9002 applySingleLine(singleLine, true, true); 9003 } 9004 9005 /** 9006 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 9007 * @param singleLine 9008 */ 9009 private void setInputTypeSingleLine(boolean singleLine) { 9010 if (mEditor != null 9011 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 9012 == EditorInfo.TYPE_CLASS_TEXT) { 9013 if (singleLine) { 9014 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 9015 } else { 9016 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 9017 } 9018 } 9019 } 9020 9021 private void applySingleLine(boolean singleLine, boolean applyTransformation, 9022 boolean changeMaxLines) { 9023 mSingleLine = singleLine; 9024 if (singleLine) { 9025 setLines(1); 9026 setHorizontallyScrolling(true); 9027 if (applyTransformation) { 9028 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 9029 } 9030 } else { 9031 if (changeMaxLines) { 9032 setMaxLines(Integer.MAX_VALUE); 9033 } 9034 setHorizontallyScrolling(false); 9035 if (applyTransformation) { 9036 setTransformationMethod(null); 9037 } 9038 } 9039 } 9040 9041 /** 9042 * Causes words in the text that are longer than the view's width 9043 * to be ellipsized instead of broken in the middle. You may also 9044 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 9045 * to constrain the text to a single line. Use <code>null</code> 9046 * to turn off ellipsizing. 9047 * 9048 * If {@link #setMaxLines} has been used to set two or more lines, 9049 * only {@link android.text.TextUtils.TruncateAt#END} and 9050 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 9051 * (other ellipsizing types will not do anything). 9052 * 9053 * @attr ref android.R.styleable#TextView_ellipsize 9054 */ 9055 public void setEllipsize(TextUtils.TruncateAt where) { 9056 // TruncateAt is an enum. != comparison is ok between these singleton objects. 9057 if (mEllipsize != where) { 9058 mEllipsize = where; 9059 9060 if (mLayout != null) { 9061 nullLayouts(); 9062 requestLayout(); 9063 invalidate(); 9064 } 9065 } 9066 } 9067 9068 /** 9069 * Sets how many times to repeat the marquee animation. Only applied if the 9070 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 9071 * 9072 * @see #getMarqueeRepeatLimit() 9073 * 9074 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 9075 */ 9076 public void setMarqueeRepeatLimit(int marqueeLimit) { 9077 mMarqueeRepeatLimit = marqueeLimit; 9078 } 9079 9080 /** 9081 * Gets the number of times the marquee animation is repeated. Only meaningful if the 9082 * TextView has marquee enabled. 9083 * 9084 * @return the number of times the marquee animation is repeated. -1 if the animation 9085 * repeats indefinitely 9086 * 9087 * @see #setMarqueeRepeatLimit(int) 9088 * 9089 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 9090 */ 9091 public int getMarqueeRepeatLimit() { 9092 return mMarqueeRepeatLimit; 9093 } 9094 9095 /** 9096 * Returns where, if anywhere, words that are longer than the view 9097 * is wide should be ellipsized. 9098 */ 9099 @ViewDebug.ExportedProperty 9100 public TextUtils.TruncateAt getEllipsize() { 9101 return mEllipsize; 9102 } 9103 9104 /** 9105 * Set the TextView so that when it takes focus, all the text is 9106 * selected. 9107 * 9108 * @attr ref android.R.styleable#TextView_selectAllOnFocus 9109 */ 9110 @android.view.RemotableViewMethod 9111 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 9112 createEditorIfNeeded(); 9113 mEditor.mSelectAllOnFocus = selectAllOnFocus; 9114 9115 if (selectAllOnFocus && !(mText instanceof Spannable)) { 9116 setText(mText, BufferType.SPANNABLE); 9117 } 9118 } 9119 9120 /** 9121 * Set whether the cursor is visible. The default is true. Note that this property only 9122 * makes sense for editable TextView. 9123 * 9124 * @see #isCursorVisible() 9125 * 9126 * @attr ref android.R.styleable#TextView_cursorVisible 9127 */ 9128 @android.view.RemotableViewMethod 9129 public void setCursorVisible(boolean visible) { 9130 if (visible && mEditor == null) return; // visible is the default value with no edit data 9131 createEditorIfNeeded(); 9132 if (mEditor.mCursorVisible != visible) { 9133 mEditor.mCursorVisible = visible; 9134 invalidate(); 9135 9136 mEditor.makeBlink(); 9137 9138 // InsertionPointCursorController depends on mCursorVisible 9139 mEditor.prepareCursorControllers(); 9140 } 9141 } 9142 9143 /** 9144 * @return whether or not the cursor is visible (assuming this TextView is editable) 9145 * 9146 * @see #setCursorVisible(boolean) 9147 * 9148 * @attr ref android.R.styleable#TextView_cursorVisible 9149 */ 9150 public boolean isCursorVisible() { 9151 // true is the default value 9152 return mEditor == null ? true : mEditor.mCursorVisible; 9153 } 9154 9155 private boolean canMarquee() { 9156 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 9157 return width > 0 && (mLayout.getLineWidth(0) > width 9158 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 9159 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 9160 } 9161 9162 private void startMarquee() { 9163 // Do not ellipsize EditText 9164 if (getKeyListener() != null) return; 9165 9166 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 9167 return; 9168 } 9169 9170 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) 9171 && getLineCount() == 1 && canMarquee()) { 9172 9173 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 9174 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 9175 final Layout tmp = mLayout; 9176 mLayout = mSavedMarqueeModeLayout; 9177 mSavedMarqueeModeLayout = tmp; 9178 setHorizontalFadingEdgeEnabled(true); 9179 requestLayout(); 9180 invalidate(); 9181 } 9182 9183 if (mMarquee == null) mMarquee = new Marquee(this); 9184 mMarquee.start(mMarqueeRepeatLimit); 9185 } 9186 } 9187 9188 private void stopMarquee() { 9189 if (mMarquee != null && !mMarquee.isStopped()) { 9190 mMarquee.stop(); 9191 } 9192 9193 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 9194 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 9195 final Layout tmp = mSavedMarqueeModeLayout; 9196 mSavedMarqueeModeLayout = mLayout; 9197 mLayout = tmp; 9198 setHorizontalFadingEdgeEnabled(false); 9199 requestLayout(); 9200 invalidate(); 9201 } 9202 } 9203 9204 private void startStopMarquee(boolean start) { 9205 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9206 if (start) { 9207 startMarquee(); 9208 } else { 9209 stopMarquee(); 9210 } 9211 } 9212 } 9213 9214 /** 9215 * This method is called when the text is changed, in case any subclasses 9216 * would like to know. 9217 * 9218 * Within <code>text</code>, the <code>lengthAfter</code> characters 9219 * beginning at <code>start</code> have just replaced old text that had 9220 * length <code>lengthBefore</code>. It is an error to attempt to make 9221 * changes to <code>text</code> from this callback. 9222 * 9223 * @param text The text the TextView is displaying 9224 * @param start The offset of the start of the range of the text that was 9225 * modified 9226 * @param lengthBefore The length of the former text that has been replaced 9227 * @param lengthAfter The length of the replacement modified text 9228 */ 9229 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 9230 // intentionally empty, template pattern method can be overridden by subclasses 9231 } 9232 9233 /** 9234 * This method is called when the selection has changed, in case any 9235 * subclasses would like to know. 9236 * 9237 * @param selStart The new selection start location. 9238 * @param selEnd The new selection end location. 9239 */ 9240 protected void onSelectionChanged(int selStart, int selEnd) { 9241 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 9242 } 9243 9244 /** 9245 * Adds a TextWatcher to the list of those whose methods are called 9246 * whenever this TextView's text changes. 9247 * <p> 9248 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 9249 * not called after {@link #setText} calls. Now, doing {@link #setText} 9250 * if there are any text changed listeners forces the buffer type to 9251 * Editable if it would not otherwise be and does call this method. 9252 */ 9253 public void addTextChangedListener(TextWatcher watcher) { 9254 if (mListeners == null) { 9255 mListeners = new ArrayList<TextWatcher>(); 9256 } 9257 9258 mListeners.add(watcher); 9259 } 9260 9261 /** 9262 * Removes the specified TextWatcher from the list of those whose 9263 * methods are called 9264 * whenever this TextView's text changes. 9265 */ 9266 public void removeTextChangedListener(TextWatcher watcher) { 9267 if (mListeners != null) { 9268 int i = mListeners.indexOf(watcher); 9269 9270 if (i >= 0) { 9271 mListeners.remove(i); 9272 } 9273 } 9274 } 9275 9276 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 9277 if (mListeners != null) { 9278 final ArrayList<TextWatcher> list = mListeners; 9279 final int count = list.size(); 9280 for (int i = 0; i < count; i++) { 9281 list.get(i).beforeTextChanged(text, start, before, after); 9282 } 9283 } 9284 9285 // The spans that are inside or intersect the modified region no longer make sense 9286 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 9287 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 9288 } 9289 9290 // Removes all spans that are inside or actually overlap the start..end range 9291 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 9292 if (!(mText instanceof Editable)) return; 9293 Editable text = (Editable) mText; 9294 9295 T[] spans = text.getSpans(start, end, type); 9296 final int length = spans.length; 9297 for (int i = 0; i < length; i++) { 9298 final int spanStart = text.getSpanStart(spans[i]); 9299 final int spanEnd = text.getSpanEnd(spans[i]); 9300 if (spanEnd == start || spanStart == end) break; 9301 text.removeSpan(spans[i]); 9302 } 9303 } 9304 9305 void removeAdjacentSuggestionSpans(final int pos) { 9306 if (!(mText instanceof Editable)) return; 9307 final Editable text = (Editable) mText; 9308 9309 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 9310 final int length = spans.length; 9311 for (int i = 0; i < length; i++) { 9312 final int spanStart = text.getSpanStart(spans[i]); 9313 final int spanEnd = text.getSpanEnd(spans[i]); 9314 if (spanEnd == pos || spanStart == pos) { 9315 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 9316 text.removeSpan(spans[i]); 9317 } 9318 } 9319 } 9320 } 9321 9322 /** 9323 * Not private so it can be called from an inner class without going 9324 * through a thunk. 9325 */ 9326 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 9327 if (mListeners != null) { 9328 final ArrayList<TextWatcher> list = mListeners; 9329 final int count = list.size(); 9330 for (int i = 0; i < count; i++) { 9331 list.get(i).onTextChanged(text, start, before, after); 9332 } 9333 } 9334 9335 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 9336 } 9337 9338 /** 9339 * Not private so it can be called from an inner class without going 9340 * through a thunk. 9341 */ 9342 void sendAfterTextChanged(Editable text) { 9343 if (mListeners != null) { 9344 final ArrayList<TextWatcher> list = mListeners; 9345 final int count = list.size(); 9346 for (int i = 0; i < count; i++) { 9347 list.get(i).afterTextChanged(text); 9348 } 9349 } 9350 9351 // Always notify AutoFillManager - it will return right away if autofill is disabled. 9352 notifyAutoFillManagerAfterTextChangedIfNeeded(); 9353 9354 hideErrorIfUnchanged(); 9355 } 9356 9357 private void notifyAutoFillManagerAfterTextChangedIfNeeded() { 9358 // It is important to not check whether the view is important for autofill 9359 // since the user can trigger autofill manually on not important views. 9360 if (!isAutofillable()) { 9361 return; 9362 } 9363 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 9364 if (afm != null) { 9365 if (DEBUG_AUTOFILL) { 9366 Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText); 9367 } 9368 afm.notifyValueChanged(TextView.this); 9369 } 9370 } 9371 9372 private boolean isAutofillable() { 9373 // It is important to not check whether the view is important for autofill 9374 // since the user can trigger autofill manually on not important views. 9375 return getAutofillType() != AUTOFILL_TYPE_NONE; 9376 } 9377 9378 void updateAfterEdit() { 9379 invalidate(); 9380 int curs = getSelectionStart(); 9381 9382 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 9383 registerForPreDraw(); 9384 } 9385 9386 checkForResize(); 9387 9388 if (curs >= 0) { 9389 mHighlightPathBogus = true; 9390 if (mEditor != null) mEditor.makeBlink(); 9391 bringPointIntoView(curs); 9392 } 9393 } 9394 9395 /** 9396 * Not private so it can be called from an inner class without going 9397 * through a thunk. 9398 */ 9399 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 9400 sLastCutCopyOrTextChangedTime = 0; 9401 9402 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 9403 if (ims == null || ims.mBatchEditNesting == 0) { 9404 updateAfterEdit(); 9405 } 9406 if (ims != null) { 9407 ims.mContentChanged = true; 9408 if (ims.mChangedStart < 0) { 9409 ims.mChangedStart = start; 9410 ims.mChangedEnd = start + before; 9411 } else { 9412 ims.mChangedStart = Math.min(ims.mChangedStart, start); 9413 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 9414 } 9415 ims.mChangedDelta += after - before; 9416 } 9417 resetErrorChangedFlag(); 9418 sendOnTextChanged(buffer, start, before, after); 9419 onTextChanged(buffer, start, before, after); 9420 } 9421 9422 /** 9423 * Not private so it can be called from an inner class without going 9424 * through a thunk. 9425 */ 9426 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 9427 // XXX Make the start and end move together if this ends up 9428 // spending too much time invalidating. 9429 9430 boolean selChanged = false; 9431 int newSelStart = -1, newSelEnd = -1; 9432 9433 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 9434 9435 if (what == Selection.SELECTION_END) { 9436 selChanged = true; 9437 newSelEnd = newStart; 9438 9439 if (oldStart >= 0 || newStart >= 0) { 9440 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 9441 checkForResize(); 9442 registerForPreDraw(); 9443 if (mEditor != null) mEditor.makeBlink(); 9444 } 9445 } 9446 9447 if (what == Selection.SELECTION_START) { 9448 selChanged = true; 9449 newSelStart = newStart; 9450 9451 if (oldStart >= 0 || newStart >= 0) { 9452 int end = Selection.getSelectionEnd(buf); 9453 invalidateCursor(end, oldStart, newStart); 9454 } 9455 } 9456 9457 if (selChanged) { 9458 mHighlightPathBogus = true; 9459 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 9460 9461 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 9462 if (newSelStart < 0) { 9463 newSelStart = Selection.getSelectionStart(buf); 9464 } 9465 if (newSelEnd < 0) { 9466 newSelEnd = Selection.getSelectionEnd(buf); 9467 } 9468 9469 if (mEditor != null) { 9470 mEditor.refreshTextActionMode(); 9471 if (!hasSelection() 9472 && mEditor.getTextActionMode() == null && hasTransientState()) { 9473 // User generated selection has been removed. 9474 setHasTransientState(false); 9475 } 9476 } 9477 onSelectionChanged(newSelStart, newSelEnd); 9478 } 9479 } 9480 9481 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 9482 || what instanceof CharacterStyle) { 9483 if (ims == null || ims.mBatchEditNesting == 0) { 9484 invalidate(); 9485 mHighlightPathBogus = true; 9486 checkForResize(); 9487 } else { 9488 ims.mContentChanged = true; 9489 } 9490 if (mEditor != null) { 9491 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 9492 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 9493 mEditor.invalidateHandlesAndActionMode(); 9494 } 9495 } 9496 9497 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 9498 mHighlightPathBogus = true; 9499 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 9500 ims.mSelectionModeChanged = true; 9501 } 9502 9503 if (Selection.getSelectionStart(buf) >= 0) { 9504 if (ims == null || ims.mBatchEditNesting == 0) { 9505 invalidateCursor(); 9506 } else { 9507 ims.mCursorChanged = true; 9508 } 9509 } 9510 } 9511 9512 if (what instanceof ParcelableSpan) { 9513 // If this is a span that can be sent to a remote process, 9514 // the current extract editor would be interested in it. 9515 if (ims != null && ims.mExtractedTextRequest != null) { 9516 if (ims.mBatchEditNesting != 0) { 9517 if (oldStart >= 0) { 9518 if (ims.mChangedStart > oldStart) { 9519 ims.mChangedStart = oldStart; 9520 } 9521 if (ims.mChangedStart > oldEnd) { 9522 ims.mChangedStart = oldEnd; 9523 } 9524 } 9525 if (newStart >= 0) { 9526 if (ims.mChangedStart > newStart) { 9527 ims.mChangedStart = newStart; 9528 } 9529 if (ims.mChangedStart > newEnd) { 9530 ims.mChangedStart = newEnd; 9531 } 9532 } 9533 } else { 9534 if (DEBUG_EXTRACT) { 9535 Log.v(LOG_TAG, "Span change outside of batch: " 9536 + oldStart + "-" + oldEnd + "," 9537 + newStart + "-" + newEnd + " " + what); 9538 } 9539 ims.mContentChanged = true; 9540 } 9541 } 9542 } 9543 9544 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 9545 && what instanceof SpellCheckSpan) { 9546 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 9547 } 9548 } 9549 9550 @Override 9551 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 9552 if (isTemporarilyDetached()) { 9553 // If we are temporarily in the detach state, then do nothing. 9554 super.onFocusChanged(focused, direction, previouslyFocusedRect); 9555 return; 9556 } 9557 9558 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 9559 9560 if (focused) { 9561 if (mText instanceof Spannable) { 9562 Spannable sp = (Spannable) mText; 9563 MetaKeyKeyListener.resetMetaState(sp); 9564 } 9565 } 9566 9567 startStopMarquee(focused); 9568 9569 if (mTransformation != null) { 9570 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 9571 } 9572 9573 super.onFocusChanged(focused, direction, previouslyFocusedRect); 9574 } 9575 9576 @Override 9577 public void onWindowFocusChanged(boolean hasWindowFocus) { 9578 super.onWindowFocusChanged(hasWindowFocus); 9579 9580 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 9581 9582 startStopMarquee(hasWindowFocus); 9583 } 9584 9585 @Override 9586 protected void onVisibilityChanged(View changedView, int visibility) { 9587 super.onVisibilityChanged(changedView, visibility); 9588 if (mEditor != null && visibility != VISIBLE) { 9589 mEditor.hideCursorAndSpanControllers(); 9590 stopTextActionMode(); 9591 } 9592 } 9593 9594 /** 9595 * Use {@link BaseInputConnection#removeComposingSpans 9596 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 9597 * state from this text view. 9598 */ 9599 public void clearComposingText() { 9600 if (mText instanceof Spannable) { 9601 BaseInputConnection.removeComposingSpans((Spannable) mText); 9602 } 9603 } 9604 9605 @Override 9606 public void setSelected(boolean selected) { 9607 boolean wasSelected = isSelected(); 9608 9609 super.setSelected(selected); 9610 9611 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 9612 if (selected) { 9613 startMarquee(); 9614 } else { 9615 stopMarquee(); 9616 } 9617 } 9618 } 9619 9620 @Override 9621 public boolean onTouchEvent(MotionEvent event) { 9622 final int action = event.getActionMasked(); 9623 if (mEditor != null) { 9624 mEditor.onTouchEvent(event); 9625 9626 if (mEditor.mSelectionModifierCursorController != null 9627 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 9628 return true; 9629 } 9630 } 9631 9632 final boolean superResult = super.onTouchEvent(event); 9633 9634 /* 9635 * Don't handle the release after a long press, because it will move the selection away from 9636 * whatever the menu action was trying to affect. If the long press should have triggered an 9637 * insertion action mode, we can now actually show it. 9638 */ 9639 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 9640 mEditor.mDiscardNextActionUp = false; 9641 9642 if (mEditor.mIsInsertionActionModeStartPending) { 9643 mEditor.startInsertionActionMode(); 9644 mEditor.mIsInsertionActionModeStartPending = false; 9645 } 9646 return superResult; 9647 } 9648 9649 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 9650 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 9651 9652 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 9653 && mText instanceof Spannable && mLayout != null) { 9654 boolean handled = false; 9655 9656 if (mMovement != null) { 9657 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 9658 } 9659 9660 final boolean textIsSelectable = isTextSelectable(); 9661 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 9662 // The LinkMovementMethod which should handle taps on links has not been installed 9663 // on non editable text that support text selection. 9664 // We reproduce its behavior here to open links for these. 9665 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 9666 getSelectionEnd(), ClickableSpan.class); 9667 9668 if (links.length > 0) { 9669 links[0].onClick(this); 9670 handled = true; 9671 } 9672 } 9673 9674 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 9675 // Show the IME, except when selecting in read-only text. 9676 final InputMethodManager imm = InputMethodManager.peekInstance(); 9677 viewClicked(imm); 9678 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { 9679 imm.showSoftInput(this, 0); 9680 } 9681 9682 // The above condition ensures that the mEditor is not null 9683 mEditor.onTouchUpEvent(event); 9684 9685 handled = true; 9686 } 9687 9688 if (handled) { 9689 return true; 9690 } 9691 } 9692 9693 return superResult; 9694 } 9695 9696 @Override 9697 public boolean onGenericMotionEvent(MotionEvent event) { 9698 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 9699 try { 9700 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 9701 return true; 9702 } 9703 } catch (AbstractMethodError ex) { 9704 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 9705 // Ignore its absence in case third party applications implemented the 9706 // interface directly. 9707 } 9708 } 9709 return super.onGenericMotionEvent(event); 9710 } 9711 9712 @Override 9713 protected void onCreateContextMenu(ContextMenu menu) { 9714 if (mEditor != null) { 9715 mEditor.onCreateContextMenu(menu); 9716 } 9717 } 9718 9719 @Override 9720 public boolean showContextMenu() { 9721 if (mEditor != null) { 9722 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 9723 } 9724 return super.showContextMenu(); 9725 } 9726 9727 @Override 9728 public boolean showContextMenu(float x, float y) { 9729 if (mEditor != null) { 9730 mEditor.setContextMenuAnchor(x, y); 9731 } 9732 return super.showContextMenu(x, y); 9733 } 9734 9735 /** 9736 * @return True iff this TextView contains a text that can be edited, or if this is 9737 * a selectable TextView. 9738 */ 9739 boolean isTextEditable() { 9740 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 9741 } 9742 9743 /** 9744 * Returns true, only while processing a touch gesture, if the initial 9745 * touch down event caused focus to move to the text view and as a result 9746 * its selection changed. Only valid while processing the touch gesture 9747 * of interest, in an editable text view. 9748 */ 9749 public boolean didTouchFocusSelect() { 9750 return mEditor != null && mEditor.mTouchFocusSelected; 9751 } 9752 9753 @Override 9754 public void cancelLongPress() { 9755 super.cancelLongPress(); 9756 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 9757 } 9758 9759 @Override 9760 public boolean onTrackballEvent(MotionEvent event) { 9761 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 9762 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 9763 return true; 9764 } 9765 } 9766 9767 return super.onTrackballEvent(event); 9768 } 9769 9770 /** 9771 * Sets the Scroller used for producing a scrolling animation 9772 * 9773 * @param s A Scroller instance 9774 */ 9775 public void setScroller(Scroller s) { 9776 mScroller = s; 9777 } 9778 9779 @Override 9780 protected float getLeftFadingEdgeStrength() { 9781 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 9782 final Marquee marquee = mMarquee; 9783 if (marquee.shouldDrawLeftFade()) { 9784 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 9785 } else { 9786 return 0.0f; 9787 } 9788 } else if (getLineCount() == 1) { 9789 final float lineLeft = getLayout().getLineLeft(0); 9790 if (lineLeft > mScrollX) return 0.0f; 9791 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 9792 } 9793 return super.getLeftFadingEdgeStrength(); 9794 } 9795 9796 @Override 9797 protected float getRightFadingEdgeStrength() { 9798 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 9799 final Marquee marquee = mMarquee; 9800 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 9801 } else if (getLineCount() == 1) { 9802 final float rightEdge = mScrollX + 9803 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 9804 final float lineRight = getLayout().getLineRight(0); 9805 if (lineRight < rightEdge) return 0.0f; 9806 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 9807 } 9808 return super.getRightFadingEdgeStrength(); 9809 } 9810 9811 /** 9812 * Calculates the fading edge strength as the ratio of the distance between two 9813 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 9814 * value for the distance calculation. 9815 * 9816 * @param position1 A horizontal position. 9817 * @param position2 A horizontal position. 9818 * @return Fading edge strength between [0.0f, 1.0f]. 9819 */ 9820 @FloatRange(from = 0.0, to = 1.0) 9821 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 9822 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 9823 if (horizontalFadingEdgeLength == 0) return 0.0f; 9824 final float diff = Math.abs(position1 - position2); 9825 if (diff > horizontalFadingEdgeLength) return 1.0f; 9826 return diff / horizontalFadingEdgeLength; 9827 } 9828 9829 private boolean isMarqueeFadeEnabled() { 9830 return mEllipsize == TextUtils.TruncateAt.MARQUEE 9831 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 9832 } 9833 9834 @Override 9835 protected int computeHorizontalScrollRange() { 9836 if (mLayout != null) { 9837 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 9838 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 9839 } 9840 9841 return super.computeHorizontalScrollRange(); 9842 } 9843 9844 @Override 9845 protected int computeVerticalScrollRange() { 9846 if (mLayout != null) { 9847 return mLayout.getHeight(); 9848 } 9849 return super.computeVerticalScrollRange(); 9850 } 9851 9852 @Override 9853 protected int computeVerticalScrollExtent() { 9854 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 9855 } 9856 9857 @Override 9858 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 9859 super.findViewsWithText(outViews, searched, flags); 9860 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 9861 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 9862 String searchedLowerCase = searched.toString().toLowerCase(); 9863 String textLowerCase = mText.toString().toLowerCase(); 9864 if (textLowerCase.contains(searchedLowerCase)) { 9865 outViews.add(this); 9866 } 9867 } 9868 } 9869 9870 /** 9871 * Type of the text buffer that defines the characteristics of the text such as static, 9872 * styleable, or editable. 9873 */ 9874 public enum BufferType { 9875 NORMAL, SPANNABLE, EDITABLE 9876 } 9877 9878 /** 9879 * Returns the TextView_textColor attribute from the TypedArray, if set, or 9880 * the TextAppearance_textColor from the TextView_textAppearance attribute, 9881 * if TextView_textColor was not set directly. 9882 * 9883 * @removed 9884 */ 9885 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 9886 if (attrs == null) { 9887 // Preserve behavior prior to removal of this API. 9888 throw new NullPointerException(); 9889 } 9890 9891 // It's not safe to use this method from apps. The parameter 'attrs' 9892 // must have been obtained using the TextView filter array which is not 9893 // available to the SDK. As such, we grab a default TypedArray with the 9894 // right filter instead here. 9895 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 9896 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 9897 if (colors == null) { 9898 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 9899 if (ap != 0) { 9900 final TypedArray appearance = context.obtainStyledAttributes( 9901 ap, R.styleable.TextAppearance); 9902 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 9903 appearance.recycle(); 9904 } 9905 } 9906 a.recycle(); 9907 9908 return colors; 9909 } 9910 9911 /** 9912 * Returns the default color from the TextView_textColor attribute from the 9913 * AttributeSet, if set, or the default color from the 9914 * TextAppearance_textColor from the TextView_textAppearance attribute, if 9915 * TextView_textColor was not set directly. 9916 * 9917 * @removed 9918 */ 9919 public static int getTextColor(Context context, TypedArray attrs, int def) { 9920 final ColorStateList colors = getTextColors(context, attrs); 9921 if (colors == null) { 9922 return def; 9923 } else { 9924 return colors.getDefaultColor(); 9925 } 9926 } 9927 9928 @Override 9929 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 9930 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 9931 // Handle Ctrl-only shortcuts. 9932 switch (keyCode) { 9933 case KeyEvent.KEYCODE_A: 9934 if (canSelectText()) { 9935 return onTextContextMenuItem(ID_SELECT_ALL); 9936 } 9937 break; 9938 case KeyEvent.KEYCODE_Z: 9939 if (canUndo()) { 9940 return onTextContextMenuItem(ID_UNDO); 9941 } 9942 break; 9943 case KeyEvent.KEYCODE_X: 9944 if (canCut()) { 9945 return onTextContextMenuItem(ID_CUT); 9946 } 9947 break; 9948 case KeyEvent.KEYCODE_C: 9949 if (canCopy()) { 9950 return onTextContextMenuItem(ID_COPY); 9951 } 9952 break; 9953 case KeyEvent.KEYCODE_V: 9954 if (canPaste()) { 9955 return onTextContextMenuItem(ID_PASTE); 9956 } 9957 break; 9958 } 9959 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 9960 // Handle Ctrl-Shift shortcuts. 9961 switch (keyCode) { 9962 case KeyEvent.KEYCODE_Z: 9963 if (canRedo()) { 9964 return onTextContextMenuItem(ID_REDO); 9965 } 9966 break; 9967 case KeyEvent.KEYCODE_V: 9968 if (canPaste()) { 9969 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 9970 } 9971 } 9972 } 9973 return super.onKeyShortcut(keyCode, event); 9974 } 9975 9976 /** 9977 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 9978 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 9979 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 9980 * sufficient. 9981 */ 9982 boolean canSelectText() { 9983 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 9984 } 9985 9986 /** 9987 * Test based on the <i>intrinsic</i> charateristics of the TextView. 9988 * The text must be spannable and the movement method must allow for arbitary selection. 9989 * 9990 * See also {@link #canSelectText()}. 9991 */ 9992 boolean textCanBeSelected() { 9993 // prepareCursorController() relies on this method. 9994 // If you change this condition, make sure prepareCursorController is called anywhere 9995 // the value of this condition might be changed. 9996 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 9997 return isTextEditable() 9998 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 9999 } 10000 10001 private Locale getTextServicesLocale(boolean allowNullLocale) { 10002 // Start fetching the text services locale asynchronously. 10003 updateTextServicesLocaleAsync(); 10004 // If !allowNullLocale and there is no cached text services locale, just return the default 10005 // locale. 10006 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 10007 : mCurrentSpellCheckerLocaleCache; 10008 } 10009 10010 /** 10011 * This is a temporary method. Future versions may support multi-locale text. 10012 * Caveat: This method may not return the latest text services locale, but this should be 10013 * acceptable and it's more important to make this method asynchronous. 10014 * 10015 * @return The locale that should be used for a word iterator 10016 * in this TextView, based on the current spell checker settings, 10017 * the current IME's locale, or the system default locale. 10018 * Please note that a word iterator in this TextView is different from another word iterator 10019 * used by SpellChecker.java of TextView. This method should be used for the former. 10020 * @hide 10021 */ 10022 // TODO: Support multi-locale 10023 // TODO: Update the text services locale immediately after the keyboard locale is switched 10024 // by catching intent of keyboard switch event 10025 public Locale getTextServicesLocale() { 10026 return getTextServicesLocale(false /* allowNullLocale */); 10027 } 10028 10029 /** 10030 * @return {@code true} if this TextView is specialized for showing and interacting with the 10031 * extracted text in a full-screen input method. 10032 * @hide 10033 */ 10034 public boolean isInExtractedMode() { 10035 return false; 10036 } 10037 10038 /** 10039 * @return {@code true} if this widget supports auto-sizing text and has been configured to 10040 * auto-size. 10041 */ 10042 private boolean isAutoSizeEnabled() { 10043 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 10044 } 10045 10046 /** 10047 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 10048 * @hide 10049 */ 10050 protected boolean supportsAutoSizeText() { 10051 return true; 10052 } 10053 10054 /** 10055 * This is a temporary method. Future versions may support multi-locale text. 10056 * Caveat: This method may not return the latest spell checker locale, but this should be 10057 * acceptable and it's more important to make this method asynchronous. 10058 * 10059 * @return The locale that should be used for a spell checker in this TextView, 10060 * based on the current spell checker settings, the current IME's locale, or the system default 10061 * locale. 10062 * @hide 10063 */ 10064 public Locale getSpellCheckerLocale() { 10065 return getTextServicesLocale(true /* allowNullLocale */); 10066 } 10067 10068 private void updateTextServicesLocaleAsync() { 10069 // AsyncTask.execute() uses a serial executor which means we don't have 10070 // to lock around updateTextServicesLocaleLocked() to prevent it from 10071 // being executed n times in parallel. 10072 AsyncTask.execute(new Runnable() { 10073 @Override 10074 public void run() { 10075 updateTextServicesLocaleLocked(); 10076 } 10077 }); 10078 } 10079 10080 private void updateTextServicesLocaleLocked() { 10081 final TextServicesManager textServicesManager = (TextServicesManager) 10082 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 10083 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 10084 final Locale locale; 10085 if (subtype != null) { 10086 locale = subtype.getLocaleObject(); 10087 } else { 10088 locale = null; 10089 } 10090 mCurrentSpellCheckerLocaleCache = locale; 10091 } 10092 10093 void onLocaleChanged() { 10094 mEditor.onLocaleChanged(); 10095 } 10096 10097 /** 10098 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 10099 * Made available to achieve a consistent behavior. 10100 * @hide 10101 */ 10102 public WordIterator getWordIterator() { 10103 if (mEditor != null) { 10104 return mEditor.getWordIterator(); 10105 } else { 10106 return null; 10107 } 10108 } 10109 10110 /** @hide */ 10111 @Override 10112 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 10113 super.onPopulateAccessibilityEventInternal(event); 10114 10115 final CharSequence text = getTextForAccessibility(); 10116 if (!TextUtils.isEmpty(text)) { 10117 event.getText().add(text); 10118 } 10119 } 10120 10121 @Override 10122 public CharSequence getAccessibilityClassName() { 10123 return TextView.class.getName(); 10124 } 10125 10126 @Override 10127 public void onProvideStructure(ViewStructure structure) { 10128 super.onProvideStructure(structure); 10129 onProvideAutoStructureForAssistOrAutofill(structure, false); 10130 } 10131 10132 @Override 10133 public void onProvideAutofillStructure(ViewStructure structure, int flags) { 10134 super.onProvideAutofillStructure(structure, flags); 10135 onProvideAutoStructureForAssistOrAutofill(structure, true); 10136 } 10137 10138 private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure, 10139 boolean forAutofill) { 10140 final boolean isPassword = hasPasswordTransformationMethod() 10141 || isPasswordInputType(getInputType()); 10142 if (forAutofill) { 10143 structure.setDataIsSensitive(!mTextFromResource); 10144 } 10145 10146 if (!isPassword || forAutofill) { 10147 if (mLayout == null) { 10148 assumeLayout(); 10149 } 10150 Layout layout = mLayout; 10151 final int lineCount = layout.getLineCount(); 10152 if (lineCount <= 1) { 10153 // Simple case: this is a single line. 10154 final CharSequence text = getText(); 10155 if (forAutofill) { 10156 structure.setText(text); 10157 } else { 10158 structure.setText(text, getSelectionStart(), getSelectionEnd()); 10159 } 10160 } else { 10161 // Complex case: multi-line, could be scrolled or within a scroll container 10162 // so some lines are not visible. 10163 final int[] tmpCords = new int[2]; 10164 getLocationInWindow(tmpCords); 10165 final int topWindowLocation = tmpCords[1]; 10166 View root = this; 10167 ViewParent viewParent = getParent(); 10168 while (viewParent instanceof View) { 10169 root = (View) viewParent; 10170 viewParent = root.getParent(); 10171 } 10172 final int windowHeight = root.getHeight(); 10173 final int topLine; 10174 final int bottomLine; 10175 if (topWindowLocation >= 0) { 10176 // The top of the view is fully within its window; start text at line 0. 10177 topLine = getLineAtCoordinateUnclamped(0); 10178 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 10179 } else { 10180 // The top of hte window has scrolled off the top of the window; figure out 10181 // the starting line for this. 10182 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 10183 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 10184 } 10185 // We want to return some contextual lines above/below the lines that are 10186 // actually visible. 10187 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 10188 if (expandedTopLine < 0) { 10189 expandedTopLine = 0; 10190 } 10191 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 10192 if (expandedBottomLine >= lineCount) { 10193 expandedBottomLine = lineCount - 1; 10194 } 10195 10196 // Convert lines into character offsets. 10197 int expandedTopChar = layout.getLineStart(expandedTopLine); 10198 int expandedBottomChar = layout.getLineEnd(expandedBottomLine); 10199 10200 // Take into account selection -- if there is a selection, we need to expand 10201 // the text we are returning to include that selection. 10202 final int selStart = getSelectionStart(); 10203 final int selEnd = getSelectionEnd(); 10204 if (selStart < selEnd) { 10205 if (selStart < expandedTopChar) { 10206 expandedTopChar = selStart; 10207 } 10208 if (selEnd > expandedBottomChar) { 10209 expandedBottomChar = selEnd; 10210 } 10211 } 10212 10213 // Get the text and trim it to the range we are reporting. 10214 CharSequence text = getText(); 10215 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 10216 text = text.subSequence(expandedTopChar, expandedBottomChar); 10217 } 10218 10219 if (forAutofill) { 10220 structure.setText(text); 10221 } else { 10222 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); 10223 10224 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 10225 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 10226 final int baselineOffset = getBaselineOffset(); 10227 for (int i = topLine; i <= bottomLine; i++) { 10228 lineOffsets[i - topLine] = layout.getLineStart(i); 10229 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; 10230 } 10231 structure.setTextLines(lineOffsets, lineBaselines); 10232 } 10233 } 10234 10235 if (!forAutofill) { 10236 // Extract style information that applies to the TextView as a whole. 10237 int style = 0; 10238 int typefaceStyle = getTypefaceStyle(); 10239 if ((typefaceStyle & Typeface.BOLD) != 0) { 10240 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 10241 } 10242 if ((typefaceStyle & Typeface.ITALIC) != 0) { 10243 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 10244 } 10245 10246 // Global styles can also be set via TextView.setPaintFlags(). 10247 int paintFlags = mTextPaint.getFlags(); 10248 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 10249 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 10250 } 10251 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 10252 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 10253 } 10254 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 10255 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 10256 } 10257 10258 // TextView does not have its own text background color. A background is either part 10259 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 10260 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 10261 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 10262 } 10263 } 10264 structure.setHint(getHint()); 10265 structure.setInputType(getInputType()); 10266 } 10267 10268 boolean canRequestAutofill() { 10269 if (!isAutofillable()) { 10270 return false; 10271 } 10272 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10273 if (afm != null) { 10274 return afm.isEnabled(); 10275 } 10276 return false; 10277 } 10278 10279 private void requestAutofill() { 10280 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 10281 if (afm != null) { 10282 afm.requestAutofill(this); 10283 } 10284 } 10285 10286 @Override 10287 public void autofill(AutofillValue value) { 10288 if (!value.isText() || !isTextEditable()) { 10289 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 10290 return; 10291 } 10292 10293 setText(value.getTextValue(), mBufferType, true, 0); 10294 } 10295 10296 @Override 10297 public @AutofillType int getAutofillType() { 10298 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 10299 } 10300 10301 @Override 10302 @Nullable 10303 public AutofillValue getAutofillValue() { 10304 return isTextEditable() ? AutofillValue.forText(getText()) : null; 10305 } 10306 10307 /** @hide */ 10308 @Override 10309 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 10310 super.onInitializeAccessibilityEventInternal(event); 10311 10312 final boolean isPassword = hasPasswordTransformationMethod(); 10313 event.setPassword(isPassword); 10314 10315 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 10316 event.setFromIndex(Selection.getSelectionStart(mText)); 10317 event.setToIndex(Selection.getSelectionEnd(mText)); 10318 event.setItemCount(mText.length()); 10319 } 10320 } 10321 10322 /** @hide */ 10323 @Override 10324 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 10325 super.onInitializeAccessibilityNodeInfoInternal(info); 10326 10327 final boolean isPassword = hasPasswordTransformationMethod(); 10328 info.setPassword(isPassword); 10329 info.setText(getTextForAccessibility()); 10330 info.setHintText(mHint); 10331 info.setShowingHintText(isShowingHint()); 10332 10333 if (mBufferType == BufferType.EDITABLE) { 10334 info.setEditable(true); 10335 if (isEnabled()) { 10336 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 10337 } 10338 } 10339 10340 if (mEditor != null) { 10341 info.setInputType(mEditor.mInputType); 10342 10343 if (mEditor.mError != null) { 10344 info.setContentInvalid(true); 10345 info.setError(mEditor.mError); 10346 } 10347 } 10348 10349 if (!TextUtils.isEmpty(mText)) { 10350 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 10351 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 10352 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 10353 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 10354 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 10355 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 10356 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 10357 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 10358 info.setAvailableExtraData( 10359 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); 10360 } 10361 10362 if (isFocused()) { 10363 if (canCopy()) { 10364 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 10365 } 10366 if (canPaste()) { 10367 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 10368 } 10369 if (canCut()) { 10370 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 10371 } 10372 if (canShare()) { 10373 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 10374 ACCESSIBILITY_ACTION_SHARE, 10375 getResources().getString(com.android.internal.R.string.share))); 10376 } 10377 if (canProcessText()) { // also implies mEditor is not null. 10378 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 10379 } 10380 } 10381 10382 // Check for known input filter types. 10383 final int numFilters = mFilters.length; 10384 for (int i = 0; i < numFilters; i++) { 10385 final InputFilter filter = mFilters[i]; 10386 if (filter instanceof InputFilter.LengthFilter) { 10387 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 10388 } 10389 } 10390 10391 if (!isSingleLine()) { 10392 info.setMultiLine(true); 10393 } 10394 } 10395 10396 @Override 10397 public void addExtraDataToAccessibilityNodeInfo( 10398 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 10399 // The only extra data we support requires arguments. 10400 if (arguments == null) { 10401 return; 10402 } 10403 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 10404 int positionInfoStartIndex = arguments.getInt( 10405 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 10406 int positionInfoLength = arguments.getInt( 10407 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 10408 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 10409 || (positionInfoStartIndex >= mText.length())) { 10410 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 10411 return; 10412 } 10413 RectF[] boundingRects = new RectF[positionInfoLength]; 10414 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 10415 populateCharacterBounds(builder, positionInfoStartIndex, 10416 positionInfoStartIndex + positionInfoLength, 10417 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 10418 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 10419 for (int i = 0; i < positionInfoLength; i++) { 10420 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 10421 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 10422 RectF bounds = cursorAnchorInfo 10423 .getCharacterBounds(positionInfoStartIndex + i); 10424 if (bounds != null) { 10425 mapRectFromViewToScreenCoords(bounds, true); 10426 boundingRects[i] = bounds; 10427 } 10428 } 10429 } 10430 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 10431 } 10432 } 10433 10434 /** 10435 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 10436 * 10437 * @param builder The builder to populate 10438 * @param startIndex The starting character index to populate 10439 * @param endIndex The ending character index to populate 10440 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 10441 * content 10442 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 10443 * @hide 10444 */ 10445 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 10446 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 10447 float viewportToContentVerticalOffset) { 10448 final int minLine = mLayout.getLineForOffset(startIndex); 10449 final int maxLine = mLayout.getLineForOffset(endIndex - 1); 10450 for (int line = minLine; line <= maxLine; ++line) { 10451 final int lineStart = mLayout.getLineStart(line); 10452 final int lineEnd = mLayout.getLineEnd(line); 10453 final int offsetStart = Math.max(lineStart, startIndex); 10454 final int offsetEnd = Math.min(lineEnd, endIndex); 10455 final boolean ltrLine = 10456 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; 10457 final float[] widths = new float[offsetEnd - offsetStart]; 10458 mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths); 10459 final float top = mLayout.getLineTop(line); 10460 final float bottom = mLayout.getLineBottom(line); 10461 for (int offset = offsetStart; offset < offsetEnd; ++offset) { 10462 final float charWidth = widths[offset - offsetStart]; 10463 final boolean isRtl = mLayout.isRtlCharAt(offset); 10464 final float primary = mLayout.getPrimaryHorizontal(offset); 10465 final float secondary = mLayout.getSecondaryHorizontal(offset); 10466 // TODO: This doesn't work perfectly for text with custom styles and 10467 // TAB chars. 10468 final float left; 10469 final float right; 10470 if (ltrLine) { 10471 if (isRtl) { 10472 left = secondary - charWidth; 10473 right = secondary; 10474 } else { 10475 left = primary; 10476 right = primary + charWidth; 10477 } 10478 } else { 10479 if (!isRtl) { 10480 left = secondary; 10481 right = secondary + charWidth; 10482 } else { 10483 left = primary - charWidth; 10484 right = primary; 10485 } 10486 } 10487 // TODO: Check top-right and bottom-left as well. 10488 final float localLeft = left + viewportToContentHorizontalOffset; 10489 final float localRight = right + viewportToContentHorizontalOffset; 10490 final float localTop = top + viewportToContentVerticalOffset; 10491 final float localBottom = bottom + viewportToContentVerticalOffset; 10492 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); 10493 final boolean isBottomRightVisible = 10494 isPositionVisible(localRight, localBottom); 10495 int characterBoundsFlags = 0; 10496 if (isTopLeftVisible || isBottomRightVisible) { 10497 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 10498 } 10499 if (!isTopLeftVisible || !isBottomRightVisible) { 10500 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 10501 } 10502 if (isRtl) { 10503 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 10504 } 10505 // Here offset is the index in Java chars. 10506 builder.addCharacterBounds(offset, localLeft, localTop, localRight, 10507 localBottom, characterBoundsFlags); 10508 } 10509 } 10510 } 10511 10512 /** 10513 * @hide 10514 */ 10515 public boolean isPositionVisible(final float positionX, final float positionY) { 10516 synchronized (TEMP_POSITION) { 10517 final float[] position = TEMP_POSITION; 10518 position[0] = positionX; 10519 position[1] = positionY; 10520 View view = this; 10521 10522 while (view != null) { 10523 if (view != this) { 10524 // Local scroll is already taken into account in positionX/Y 10525 position[0] -= view.getScrollX(); 10526 position[1] -= view.getScrollY(); 10527 } 10528 10529 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 10530 || position[1] > view.getHeight()) { 10531 return false; 10532 } 10533 10534 if (!view.getMatrix().isIdentity()) { 10535 view.getMatrix().mapPoints(position); 10536 } 10537 10538 position[0] += view.getLeft(); 10539 position[1] += view.getTop(); 10540 10541 final ViewParent parent = view.getParent(); 10542 if (parent instanceof View) { 10543 view = (View) parent; 10544 } else { 10545 // We've reached the ViewRoot, stop iterating 10546 view = null; 10547 } 10548 } 10549 } 10550 10551 // We've been able to walk up the view hierarchy and the position was never clipped 10552 return true; 10553 } 10554 10555 /** 10556 * Performs an accessibility action after it has been offered to the 10557 * delegate. 10558 * 10559 * @hide 10560 */ 10561 @Override 10562 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 10563 if (mEditor != null 10564 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { 10565 return true; 10566 } 10567 switch (action) { 10568 case AccessibilityNodeInfo.ACTION_CLICK: { 10569 return performAccessibilityActionClick(arguments); 10570 } 10571 case AccessibilityNodeInfo.ACTION_COPY: { 10572 if (isFocused() && canCopy()) { 10573 if (onTextContextMenuItem(ID_COPY)) { 10574 return true; 10575 } 10576 } 10577 } return false; 10578 case AccessibilityNodeInfo.ACTION_PASTE: { 10579 if (isFocused() && canPaste()) { 10580 if (onTextContextMenuItem(ID_PASTE)) { 10581 return true; 10582 } 10583 } 10584 } return false; 10585 case AccessibilityNodeInfo.ACTION_CUT: { 10586 if (isFocused() && canCut()) { 10587 if (onTextContextMenuItem(ID_CUT)) { 10588 return true; 10589 } 10590 } 10591 } return false; 10592 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 10593 ensureIterableTextForAccessibilitySelectable(); 10594 CharSequence text = getIterableTextForAccessibility(); 10595 if (text == null) { 10596 return false; 10597 } 10598 final int start = (arguments != null) ? arguments.getInt( 10599 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 10600 final int end = (arguments != null) ? arguments.getInt( 10601 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 10602 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 10603 // No arguments clears the selection. 10604 if (start == end && end == -1) { 10605 Selection.removeSelection((Spannable) text); 10606 return true; 10607 } 10608 if (start >= 0 && start <= end && end <= text.length()) { 10609 Selection.setSelection((Spannable) text, start, end); 10610 // Make sure selection mode is engaged. 10611 if (mEditor != null) { 10612 mEditor.startSelectionActionModeAsync(false); 10613 } 10614 return true; 10615 } 10616 } 10617 } return false; 10618 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 10619 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 10620 ensureIterableTextForAccessibilitySelectable(); 10621 return super.performAccessibilityActionInternal(action, arguments); 10622 } 10623 case ACCESSIBILITY_ACTION_SHARE: { 10624 if (isFocused() && canShare()) { 10625 if (onTextContextMenuItem(ID_SHARE)) { 10626 return true; 10627 } 10628 } 10629 } return false; 10630 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 10631 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 10632 return false; 10633 } 10634 CharSequence text = (arguments != null) ? arguments.getCharSequence( 10635 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 10636 setText(text); 10637 if (mText != null) { 10638 int updatedTextLength = mText.length(); 10639 if (updatedTextLength > 0) { 10640 Selection.setSelection((Spannable) mText, updatedTextLength); 10641 } 10642 } 10643 } return true; 10644 default: { 10645 return super.performAccessibilityActionInternal(action, arguments); 10646 } 10647 } 10648 } 10649 10650 private boolean performAccessibilityActionClick(Bundle arguments) { 10651 boolean handled = false; 10652 10653 if (!isEnabled()) { 10654 return false; 10655 } 10656 10657 if (isClickable() || isLongClickable()) { 10658 // Simulate View.onTouchEvent for an ACTION_UP event 10659 if (isFocusable() && !isFocused()) { 10660 requestFocus(); 10661 } 10662 10663 performClick(); 10664 handled = true; 10665 } 10666 10667 // Show the IME, except when selecting in read-only text. 10668 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 10669 && (isTextEditable() || isTextSelectable()) && isFocused()) { 10670 final InputMethodManager imm = InputMethodManager.peekInstance(); 10671 viewClicked(imm); 10672 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 10673 handled |= imm.showSoftInput(this, 0); 10674 } 10675 } 10676 10677 return handled; 10678 } 10679 10680 private boolean hasSpannableText() { 10681 return mText != null && mText instanceof Spannable; 10682 } 10683 10684 /** @hide */ 10685 @Override 10686 public void sendAccessibilityEventInternal(int eventType) { 10687 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 10688 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 10689 } 10690 10691 // Do not send scroll events since first they are not interesting for 10692 // accessibility and second such events a generated too frequently. 10693 // For details see the implementation of bringTextIntoView(). 10694 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 10695 return; 10696 } 10697 super.sendAccessibilityEventInternal(eventType); 10698 } 10699 10700 /** 10701 * Returns the text that should be exposed to accessibility services. 10702 * <p> 10703 * This approximates what is displayed visually. If the user has specified 10704 * that accessibility services should speak passwords, this method will 10705 * bypass any password transformation method and return unobscured text. 10706 * 10707 * @return the text that should be exposed to accessibility services, may 10708 * be {@code null} if no text is set 10709 */ 10710 @Nullable 10711 private CharSequence getTextForAccessibility() { 10712 // If the text is empty, we must be showing the hint text. 10713 if (TextUtils.isEmpty(mText)) { 10714 return mHint; 10715 } 10716 10717 // Otherwise, return whatever text is being displayed. 10718 return mTransformed; 10719 } 10720 10721 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 10722 int fromIndex, int removedCount, int addedCount) { 10723 AccessibilityEvent event = 10724 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 10725 event.setFromIndex(fromIndex); 10726 event.setRemovedCount(removedCount); 10727 event.setAddedCount(addedCount); 10728 event.setBeforeText(beforeText); 10729 sendAccessibilityEventUnchecked(event); 10730 } 10731 10732 /** 10733 * Returns whether this text view is a current input method target. The 10734 * default implementation just checks with {@link InputMethodManager}. 10735 * @return True if the TextView is a current input method target; false otherwise. 10736 */ 10737 public boolean isInputMethodTarget() { 10738 InputMethodManager imm = InputMethodManager.peekInstance(); 10739 return imm != null && imm.isActive(this); 10740 } 10741 10742 static final int ID_SELECT_ALL = android.R.id.selectAll; 10743 static final int ID_UNDO = android.R.id.undo; 10744 static final int ID_REDO = android.R.id.redo; 10745 static final int ID_CUT = android.R.id.cut; 10746 static final int ID_COPY = android.R.id.copy; 10747 static final int ID_PASTE = android.R.id.paste; 10748 static final int ID_SHARE = android.R.id.shareText; 10749 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 10750 static final int ID_REPLACE = android.R.id.replaceText; 10751 static final int ID_ASSIST = android.R.id.textAssist; 10752 static final int ID_AUTOFILL = android.R.id.autofill; 10753 10754 /** 10755 * Called when a context menu option for the text view is selected. Currently 10756 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 10757 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. 10758 * 10759 * @return true if the context menu item action was performed. 10760 */ 10761 public boolean onTextContextMenuItem(int id) { 10762 int min = 0; 10763 int max = mText.length(); 10764 10765 if (isFocused()) { 10766 final int selStart = getSelectionStart(); 10767 final int selEnd = getSelectionEnd(); 10768 10769 min = Math.max(0, Math.min(selStart, selEnd)); 10770 max = Math.max(0, Math.max(selStart, selEnd)); 10771 } 10772 10773 switch (id) { 10774 case ID_SELECT_ALL: 10775 final boolean hadSelection = hasSelection(); 10776 selectAllText(); 10777 if (mEditor != null && hadSelection) { 10778 mEditor.invalidateActionModeAsync(); 10779 } 10780 return true; 10781 10782 case ID_UNDO: 10783 if (mEditor != null) { 10784 mEditor.undo(); 10785 } 10786 return true; // Returns true even if nothing was undone. 10787 10788 case ID_REDO: 10789 if (mEditor != null) { 10790 mEditor.redo(); 10791 } 10792 return true; // Returns true even if nothing was undone. 10793 10794 case ID_PASTE: 10795 paste(min, max, true /* withFormatting */); 10796 return true; 10797 10798 case ID_PASTE_AS_PLAIN_TEXT: 10799 paste(min, max, false /* withFormatting */); 10800 return true; 10801 10802 case ID_CUT: 10803 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 10804 deleteText_internal(min, max); 10805 return true; 10806 10807 case ID_COPY: 10808 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 10809 stopTextActionMode(); 10810 return true; 10811 10812 case ID_REPLACE: 10813 if (mEditor != null) { 10814 mEditor.replace(); 10815 } 10816 return true; 10817 10818 case ID_SHARE: 10819 shareSelectedText(); 10820 return true; 10821 10822 case ID_AUTOFILL: 10823 requestAutofill(); 10824 stopTextActionMode(); 10825 return true; 10826 } 10827 return false; 10828 } 10829 10830 CharSequence getTransformedText(int start, int end) { 10831 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 10832 } 10833 10834 @Override 10835 public boolean performLongClick() { 10836 boolean handled = false; 10837 10838 if (mEditor != null) { 10839 mEditor.mIsBeingLongClicked = true; 10840 } 10841 10842 if (super.performLongClick()) { 10843 handled = true; 10844 } 10845 10846 if (mEditor != null) { 10847 handled |= mEditor.performLongClick(handled); 10848 mEditor.mIsBeingLongClicked = false; 10849 } 10850 10851 if (handled) { 10852 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 10853 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 10854 } else { 10855 MetricsLogger.action( 10856 mContext, 10857 MetricsEvent.TEXT_LONGPRESS, 10858 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 10859 } 10860 10861 return handled; 10862 } 10863 10864 @Override 10865 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 10866 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 10867 if (mEditor != null) { 10868 mEditor.onScrollChanged(); 10869 } 10870 } 10871 10872 /** 10873 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 10874 * by the IME or by the spell checker as the user types. This is done by adding 10875 * {@link SuggestionSpan}s to the text. 10876 * 10877 * When suggestions are enabled (default), this list of suggestions will be displayed when the 10878 * user asks for them on these parts of the text. This value depends on the inputType of this 10879 * TextView. 10880 * 10881 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 10882 * 10883 * In addition, the type variation must be one of 10884 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 10885 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 10886 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 10887 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 10888 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 10889 * 10890 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 10891 * 10892 * @return true if the suggestions popup window is enabled, based on the inputType. 10893 */ 10894 public boolean isSuggestionsEnabled() { 10895 if (mEditor == null) return false; 10896 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 10897 return false; 10898 } 10899 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 10900 10901 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 10902 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 10903 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 10904 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 10905 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 10906 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 10907 } 10908 10909 /** 10910 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 10911 * selection is initiated in this View. 10912 * 10913 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 10914 * Paste, Replace and Share actions, depending on what this View supports. 10915 * 10916 * <p>A custom implementation can add new entries in the default menu in its 10917 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 10918 * method. The default actions can also be removed from the menu using 10919 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 10920 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 10921 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 10922 * 10923 * <p>Returning false from 10924 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 10925 * will prevent the action mode from being started. 10926 * 10927 * <p>Action click events should be handled by the custom implementation of 10928 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 10929 * android.view.MenuItem)}. 10930 * 10931 * <p>Note that text selection mode is not started when a TextView receives focus and the 10932 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 10933 * that case, to allow for quick replacement. 10934 */ 10935 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 10936 createEditorIfNeeded(); 10937 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 10938 } 10939 10940 /** 10941 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 10942 * 10943 * @return The current custom selection callback. 10944 */ 10945 public ActionMode.Callback getCustomSelectionActionModeCallback() { 10946 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 10947 } 10948 10949 /** 10950 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 10951 * insertion is initiated in this View. 10952 * The standard implementation populates the menu with a subset of Select All, 10953 * Paste and Replace actions, depending on what this View supports. 10954 * 10955 * <p>A custom implementation can add new entries in the default menu in its 10956 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 10957 * android.view.Menu)} method. The default actions can also be removed from the menu using 10958 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 10959 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> 10960 * 10961 * <p>Returning false from 10962 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 10963 * android.view.Menu)} will prevent the action mode from being started.</p> 10964 * 10965 * <p>Action click events should be handled by the custom implementation of 10966 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 10967 * android.view.MenuItem)}.</p> 10968 * 10969 * <p>Note that text insertion mode is not started when a TextView receives focus and the 10970 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 10971 */ 10972 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 10973 createEditorIfNeeded(); 10974 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 10975 } 10976 10977 /** 10978 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 10979 * 10980 * @return The current custom insertion callback. 10981 */ 10982 public ActionMode.Callback getCustomInsertionActionModeCallback() { 10983 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 10984 } 10985 10986 /** 10987 * Sets the {@link TextClassifier} for this TextView. 10988 */ 10989 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 10990 mTextClassifier = textClassifier; 10991 } 10992 10993 /** 10994 * Returns the {@link TextClassifier} used by this TextView. 10995 * If no TextClassifier has been set, this TextView uses the default set by the 10996 * {@link TextClassificationManager}. 10997 */ 10998 @NonNull 10999 public TextClassifier getTextClassifier() { 11000 if (mTextClassifier == null) { 11001 TextClassificationManager tcm = 11002 mContext.getSystemService(TextClassificationManager.class); 11003 if (tcm != null) { 11004 mTextClassifier = tcm.getTextClassifier(); 11005 } else { 11006 mTextClassifier = TextClassifier.NO_OP; 11007 } 11008 } 11009 return mTextClassifier; 11010 } 11011 11012 /** 11013 * @hide 11014 */ 11015 protected void stopTextActionMode() { 11016 if (mEditor != null) { 11017 mEditor.stopTextActionMode(); 11018 } 11019 } 11020 11021 boolean canUndo() { 11022 return mEditor != null && mEditor.canUndo(); 11023 } 11024 11025 boolean canRedo() { 11026 return mEditor != null && mEditor.canRedo(); 11027 } 11028 11029 boolean canCut() { 11030 if (hasPasswordTransformationMethod()) { 11031 return false; 11032 } 11033 11034 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 11035 && mEditor.mKeyListener != null) { 11036 return true; 11037 } 11038 11039 return false; 11040 } 11041 11042 boolean canCopy() { 11043 if (hasPasswordTransformationMethod()) { 11044 return false; 11045 } 11046 11047 if (mText.length() > 0 && hasSelection() && mEditor != null) { 11048 return true; 11049 } 11050 11051 return false; 11052 } 11053 11054 boolean canShare() { 11055 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { 11056 return false; 11057 } 11058 return canCopy(); 11059 } 11060 11061 boolean isDeviceProvisioned() { 11062 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 11063 mDeviceProvisionedState = Settings.Global.getInt( 11064 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 11065 ? DEVICE_PROVISIONED_YES 11066 : DEVICE_PROVISIONED_NO; 11067 } 11068 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 11069 } 11070 11071 boolean canPaste() { 11072 return (mText instanceof Editable 11073 && mEditor != null && mEditor.mKeyListener != null 11074 && getSelectionStart() >= 0 11075 && getSelectionEnd() >= 0 11076 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) 11077 .hasPrimaryClip()); 11078 } 11079 11080 boolean canPasteAsPlainText() { 11081 if (!canPaste()) { 11082 return false; 11083 } 11084 11085 final ClipData clipData = 11086 ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) 11087 .getPrimaryClip(); 11088 final ClipDescription description = clipData.getDescription(); 11089 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 11090 final CharSequence text = clipData.getItemAt(0).getText(); 11091 if (isPlainType && (text instanceof Spanned)) { 11092 Spanned spanned = (Spanned) text; 11093 if (TextUtils.hasStyleSpan(spanned)) { 11094 return true; 11095 } 11096 } 11097 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 11098 } 11099 11100 boolean canProcessText() { 11101 if (getId() == View.NO_ID) { 11102 return false; 11103 } 11104 return canShare(); 11105 } 11106 11107 boolean canSelectAllText() { 11108 return canSelectText() && !hasPasswordTransformationMethod() 11109 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 11110 } 11111 11112 boolean selectAllText() { 11113 final int length = mText.length(); 11114 Selection.setSelection((Spannable) mText, 0, length); 11115 return length > 0; 11116 } 11117 11118 void replaceSelectionWithText(CharSequence text) { 11119 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); 11120 } 11121 11122 /** 11123 * Paste clipboard content between min and max positions. 11124 */ 11125 private void paste(int min, int max, boolean withFormatting) { 11126 ClipboardManager clipboard = 11127 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 11128 ClipData clip = clipboard.getPrimaryClip(); 11129 if (clip != null) { 11130 boolean didFirst = false; 11131 for (int i = 0; i < clip.getItemCount(); i++) { 11132 final CharSequence paste; 11133 if (withFormatting) { 11134 paste = clip.getItemAt(i).coerceToStyledText(getContext()); 11135 } else { 11136 // Get an item as text and remove all spans by toString(). 11137 final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); 11138 paste = (text instanceof Spanned) ? text.toString() : text; 11139 } 11140 if (paste != null) { 11141 if (!didFirst) { 11142 Selection.setSelection((Spannable) mText, max); 11143 ((Editable) mText).replace(min, max, paste); 11144 didFirst = true; 11145 } else { 11146 ((Editable) mText).insert(getSelectionEnd(), "\n"); 11147 ((Editable) mText).insert(getSelectionEnd(), paste); 11148 } 11149 } 11150 } 11151 sLastCutCopyOrTextChangedTime = 0; 11152 } 11153 } 11154 11155 private void shareSelectedText() { 11156 String selectedText = getSelectedText(); 11157 if (selectedText != null && !selectedText.isEmpty()) { 11158 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 11159 sharingIntent.setType("text/plain"); 11160 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 11161 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 11162 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 11163 Selection.setSelection((Spannable) mText, getSelectionEnd()); 11164 } 11165 } 11166 11167 private void setPrimaryClip(ClipData clip) { 11168 ClipboardManager clipboard = 11169 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 11170 clipboard.setPrimaryClip(clip); 11171 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 11172 } 11173 11174 /** 11175 * Get the character offset closest to the specified absolute position. A typical use case is to 11176 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 11177 * 11178 * @param x The horizontal absolute position of a point on screen 11179 * @param y The vertical absolute position of a point on screen 11180 * @return the character offset for the character whose position is closest to the specified 11181 * position. Returns -1 if there is no layout. 11182 */ 11183 public int getOffsetForPosition(float x, float y) { 11184 if (getLayout() == null) return -1; 11185 final int line = getLineAtCoordinate(y); 11186 final int offset = getOffsetAtCoordinate(line, x); 11187 return offset; 11188 } 11189 11190 float convertToLocalHorizontalCoordinate(float x) { 11191 x -= getTotalPaddingLeft(); 11192 // Clamp the position to inside of the view. 11193 x = Math.max(0.0f, x); 11194 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 11195 x += getScrollX(); 11196 return x; 11197 } 11198 11199 int getLineAtCoordinate(float y) { 11200 y -= getTotalPaddingTop(); 11201 // Clamp the position to inside of the view. 11202 y = Math.max(0.0f, y); 11203 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 11204 y += getScrollY(); 11205 return getLayout().getLineForVertical((int) y); 11206 } 11207 11208 int getLineAtCoordinateUnclamped(float y) { 11209 y -= getTotalPaddingTop(); 11210 y += getScrollY(); 11211 return getLayout().getLineForVertical((int) y); 11212 } 11213 11214 int getOffsetAtCoordinate(int line, float x) { 11215 x = convertToLocalHorizontalCoordinate(x); 11216 return getLayout().getOffsetForHorizontal(line, x); 11217 } 11218 11219 @Override 11220 public boolean onDragEvent(DragEvent event) { 11221 switch (event.getAction()) { 11222 case DragEvent.ACTION_DRAG_STARTED: 11223 return mEditor != null && mEditor.hasInsertionController(); 11224 11225 case DragEvent.ACTION_DRAG_ENTERED: 11226 TextView.this.requestFocus(); 11227 return true; 11228 11229 case DragEvent.ACTION_DRAG_LOCATION: 11230 if (mText instanceof Spannable) { 11231 final int offset = getOffsetForPosition(event.getX(), event.getY()); 11232 Selection.setSelection((Spannable) mText, offset); 11233 } 11234 return true; 11235 11236 case DragEvent.ACTION_DROP: 11237 if (mEditor != null) mEditor.onDrop(event); 11238 return true; 11239 11240 case DragEvent.ACTION_DRAG_ENDED: 11241 case DragEvent.ACTION_DRAG_EXITED: 11242 default: 11243 return true; 11244 } 11245 } 11246 11247 boolean isInBatchEditMode() { 11248 if (mEditor == null) return false; 11249 final Editor.InputMethodState ims = mEditor.mInputMethodState; 11250 if (ims != null) { 11251 return ims.mBatchEditNesting > 0; 11252 } 11253 return mEditor.mInBatchEditControllers; 11254 } 11255 11256 @Override 11257 public void onRtlPropertiesChanged(int layoutDirection) { 11258 super.onRtlPropertiesChanged(layoutDirection); 11259 11260 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 11261 if (mTextDir != newTextDir) { 11262 mTextDir = newTextDir; 11263 if (mLayout != null) { 11264 checkForRelayout(); 11265 } 11266 } 11267 } 11268 11269 /** 11270 * @hide 11271 */ 11272 protected TextDirectionHeuristic getTextDirectionHeuristic() { 11273 if (hasPasswordTransformationMethod()) { 11274 // passwords fields should be LTR 11275 return TextDirectionHeuristics.LTR; 11276 } 11277 11278 if (mEditor != null 11279 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 11280 == EditorInfo.TYPE_CLASS_PHONE) { 11281 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 11282 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 11283 // RTL digits. 11284 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 11285 final String zero = symbols.getDigitStrings()[0]; 11286 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 11287 // direction. 11288 final int firstCodepoint = zero.codePointAt(0); 11289 final byte digitDirection = Character.getDirectionality(firstCodepoint); 11290 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 11291 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 11292 return TextDirectionHeuristics.RTL; 11293 } else { 11294 return TextDirectionHeuristics.LTR; 11295 } 11296 } 11297 11298 // Always need to resolve layout direction first 11299 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 11300 11301 // Now, we can select the heuristic 11302 switch (getTextDirection()) { 11303 default: 11304 case TEXT_DIRECTION_FIRST_STRONG: 11305 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 11306 TextDirectionHeuristics.FIRSTSTRONG_LTR); 11307 case TEXT_DIRECTION_ANY_RTL: 11308 return TextDirectionHeuristics.ANYRTL_LTR; 11309 case TEXT_DIRECTION_LTR: 11310 return TextDirectionHeuristics.LTR; 11311 case TEXT_DIRECTION_RTL: 11312 return TextDirectionHeuristics.RTL; 11313 case TEXT_DIRECTION_LOCALE: 11314 return TextDirectionHeuristics.LOCALE; 11315 case TEXT_DIRECTION_FIRST_STRONG_LTR: 11316 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 11317 case TEXT_DIRECTION_FIRST_STRONG_RTL: 11318 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 11319 } 11320 } 11321 11322 /** 11323 * @hide 11324 */ 11325 @Override 11326 public void onResolveDrawables(int layoutDirection) { 11327 // No need to resolve twice 11328 if (mLastLayoutDirection == layoutDirection) { 11329 return; 11330 } 11331 mLastLayoutDirection = layoutDirection; 11332 11333 // Resolve drawables 11334 if (mDrawables != null) { 11335 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 11336 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 11337 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 11338 applyCompoundDrawableTint(); 11339 } 11340 } 11341 } 11342 11343 /** 11344 * Prepares a drawable for display by propagating layout direction and 11345 * drawable state. 11346 * 11347 * @param dr the drawable to prepare 11348 */ 11349 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 11350 if (dr == null) { 11351 return; 11352 } 11353 11354 dr.setLayoutDirection(getLayoutDirection()); 11355 11356 if (dr.isStateful()) { 11357 dr.setState(getDrawableState()); 11358 dr.jumpToCurrentState(); 11359 } 11360 } 11361 11362 /** 11363 * @hide 11364 */ 11365 protected void resetResolvedDrawables() { 11366 super.resetResolvedDrawables(); 11367 mLastLayoutDirection = -1; 11368 } 11369 11370 /** 11371 * @hide 11372 */ 11373 protected void viewClicked(InputMethodManager imm) { 11374 if (imm != null) { 11375 imm.viewClicked(this); 11376 } 11377 } 11378 11379 /** 11380 * Deletes the range of text [start, end[. 11381 * @hide 11382 */ 11383 protected void deleteText_internal(int start, int end) { 11384 ((Editable) mText).delete(start, end); 11385 } 11386 11387 /** 11388 * Replaces the range of text [start, end[ by replacement text 11389 * @hide 11390 */ 11391 protected void replaceText_internal(int start, int end, CharSequence text) { 11392 ((Editable) mText).replace(start, end, text); 11393 } 11394 11395 /** 11396 * Sets a span on the specified range of text 11397 * @hide 11398 */ 11399 protected void setSpan_internal(Object span, int start, int end, int flags) { 11400 ((Editable) mText).setSpan(span, start, end, flags); 11401 } 11402 11403 /** 11404 * Moves the cursor to the specified offset position in text 11405 * @hide 11406 */ 11407 protected void setCursorPosition_internal(int start, int end) { 11408 Selection.setSelection(((Editable) mText), start, end); 11409 } 11410 11411 /** 11412 * An Editor should be created as soon as any of the editable-specific fields (grouped 11413 * inside the Editor object) is assigned to a non-default value. 11414 * This method will create the Editor if needed. 11415 * 11416 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 11417 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 11418 * Editor for backward compatibility, as soon as one of these fields is assigned. 11419 * 11420 * Also note that for performance reasons, the mEditor is created when needed, but not 11421 * reset when no more edit-specific fields are needed. 11422 */ 11423 private void createEditorIfNeeded() { 11424 if (mEditor == null) { 11425 mEditor = new Editor(this); 11426 } 11427 } 11428 11429 /** 11430 * @hide 11431 */ 11432 @Override 11433 public CharSequence getIterableTextForAccessibility() { 11434 return mText; 11435 } 11436 11437 private void ensureIterableTextForAccessibilitySelectable() { 11438 if (!(mText instanceof Spannable)) { 11439 setText(mText, BufferType.SPANNABLE); 11440 } 11441 } 11442 11443 /** 11444 * @hide 11445 */ 11446 @Override 11447 public TextSegmentIterator getIteratorForGranularity(int granularity) { 11448 switch (granularity) { 11449 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 11450 Spannable text = (Spannable) getIterableTextForAccessibility(); 11451 if (!TextUtils.isEmpty(text) && getLayout() != null) { 11452 AccessibilityIterators.LineTextSegmentIterator iterator = 11453 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 11454 iterator.initialize(text, getLayout()); 11455 return iterator; 11456 } 11457 } break; 11458 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 11459 Spannable text = (Spannable) getIterableTextForAccessibility(); 11460 if (!TextUtils.isEmpty(text) && getLayout() != null) { 11461 AccessibilityIterators.PageTextSegmentIterator iterator = 11462 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 11463 iterator.initialize(this); 11464 return iterator; 11465 } 11466 } break; 11467 } 11468 return super.getIteratorForGranularity(granularity); 11469 } 11470 11471 /** 11472 * @hide 11473 */ 11474 @Override 11475 public int getAccessibilitySelectionStart() { 11476 return getSelectionStart(); 11477 } 11478 11479 /** 11480 * @hide 11481 */ 11482 public boolean isAccessibilitySelectionExtendable() { 11483 return true; 11484 } 11485 11486 /** 11487 * @hide 11488 */ 11489 @Override 11490 public int getAccessibilitySelectionEnd() { 11491 return getSelectionEnd(); 11492 } 11493 11494 /** 11495 * @hide 11496 */ 11497 @Override 11498 public void setAccessibilitySelection(int start, int end) { 11499 if (getAccessibilitySelectionStart() == start 11500 && getAccessibilitySelectionEnd() == end) { 11501 return; 11502 } 11503 CharSequence text = getIterableTextForAccessibility(); 11504 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 11505 Selection.setSelection((Spannable) text, start, end); 11506 } else { 11507 Selection.removeSelection((Spannable) text); 11508 } 11509 // Hide all selection controllers used for adjusting selection 11510 // since we are doing so explicitlty by other means and these 11511 // controllers interact with how selection behaves. 11512 if (mEditor != null) { 11513 mEditor.hideCursorAndSpanControllers(); 11514 mEditor.stopTextActionMode(); 11515 } 11516 } 11517 11518 /** @hide */ 11519 @Override 11520 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 11521 super.encodeProperties(stream); 11522 11523 TruncateAt ellipsize = getEllipsize(); 11524 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 11525 stream.addProperty("text:textSize", getTextSize()); 11526 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 11527 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 11528 stream.addProperty("text:selectionStart", getSelectionStart()); 11529 stream.addProperty("text:selectionEnd", getSelectionEnd()); 11530 stream.addProperty("text:curTextColor", mCurTextColor); 11531 stream.addProperty("text:text", mText == null ? null : mText.toString()); 11532 stream.addProperty("text:gravity", mGravity); 11533 } 11534 11535 /** 11536 * User interface state that is stored by TextView for implementing 11537 * {@link View#onSaveInstanceState}. 11538 */ 11539 public static class SavedState extends BaseSavedState { 11540 int selStart = -1; 11541 int selEnd = -1; 11542 CharSequence text; 11543 boolean frozenWithFocus; 11544 CharSequence error; 11545 ParcelableParcel editorState; // Optional state from Editor. 11546 11547 SavedState(Parcelable superState) { 11548 super(superState); 11549 } 11550 11551 @Override 11552 public void writeToParcel(Parcel out, int flags) { 11553 super.writeToParcel(out, flags); 11554 out.writeInt(selStart); 11555 out.writeInt(selEnd); 11556 out.writeInt(frozenWithFocus ? 1 : 0); 11557 TextUtils.writeToParcel(text, out, flags); 11558 11559 if (error == null) { 11560 out.writeInt(0); 11561 } else { 11562 out.writeInt(1); 11563 TextUtils.writeToParcel(error, out, flags); 11564 } 11565 11566 if (editorState == null) { 11567 out.writeInt(0); 11568 } else { 11569 out.writeInt(1); 11570 editorState.writeToParcel(out, flags); 11571 } 11572 } 11573 11574 @Override 11575 public String toString() { 11576 String str = "TextView.SavedState{" 11577 + Integer.toHexString(System.identityHashCode(this)) 11578 + " start=" + selStart + " end=" + selEnd; 11579 if (text != null) { 11580 str += " text=" + text; 11581 } 11582 return str + "}"; 11583 } 11584 11585 @SuppressWarnings("hiding") 11586 public static final Parcelable.Creator<SavedState> CREATOR = 11587 new Parcelable.Creator<SavedState>() { 11588 public SavedState createFromParcel(Parcel in) { 11589 return new SavedState(in); 11590 } 11591 11592 public SavedState[] newArray(int size) { 11593 return new SavedState[size]; 11594 } 11595 }; 11596 11597 private SavedState(Parcel in) { 11598 super(in); 11599 selStart = in.readInt(); 11600 selEnd = in.readInt(); 11601 frozenWithFocus = (in.readInt() != 0); 11602 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 11603 11604 if (in.readInt() != 0) { 11605 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 11606 } 11607 11608 if (in.readInt() != 0) { 11609 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 11610 } 11611 } 11612 } 11613 11614 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 11615 private char[] mChars; 11616 private int mStart, mLength; 11617 11618 public CharWrapper(char[] chars, int start, int len) { 11619 mChars = chars; 11620 mStart = start; 11621 mLength = len; 11622 } 11623 11624 /* package */ void set(char[] chars, int start, int len) { 11625 mChars = chars; 11626 mStart = start; 11627 mLength = len; 11628 } 11629 11630 public int length() { 11631 return mLength; 11632 } 11633 11634 public char charAt(int off) { 11635 return mChars[off + mStart]; 11636 } 11637 11638 @Override 11639 public String toString() { 11640 return new String(mChars, mStart, mLength); 11641 } 11642 11643 public CharSequence subSequence(int start, int end) { 11644 if (start < 0 || end < 0 || start > mLength || end > mLength) { 11645 throw new IndexOutOfBoundsException(start + ", " + end); 11646 } 11647 11648 return new String(mChars, start + mStart, end - start); 11649 } 11650 11651 public void getChars(int start, int end, char[] buf, int off) { 11652 if (start < 0 || end < 0 || start > mLength || end > mLength) { 11653 throw new IndexOutOfBoundsException(start + ", " + end); 11654 } 11655 11656 System.arraycopy(mChars, start + mStart, buf, off, end - start); 11657 } 11658 11659 @Override 11660 public void drawText(BaseCanvas c, int start, int end, 11661 float x, float y, Paint p) { 11662 c.drawText(mChars, start + mStart, end - start, x, y, p); 11663 } 11664 11665 @Override 11666 public void drawTextRun(BaseCanvas c, int start, int end, 11667 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 11668 int count = end - start; 11669 int contextCount = contextEnd - contextStart; 11670 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 11671 contextCount, x, y, isRtl, p); 11672 } 11673 11674 public float measureText(int start, int end, Paint p) { 11675 return p.measureText(mChars, start + mStart, end - start); 11676 } 11677 11678 public int getTextWidths(int start, int end, float[] widths, Paint p) { 11679 return p.getTextWidths(mChars, start + mStart, end - start, widths); 11680 } 11681 11682 public float getTextRunAdvances(int start, int end, int contextStart, 11683 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 11684 Paint p) { 11685 int count = end - start; 11686 int contextCount = contextEnd - contextStart; 11687 return p.getTextRunAdvances(mChars, start + mStart, count, 11688 contextStart + mStart, contextCount, isRtl, advances, 11689 advancesIndex); 11690 } 11691 11692 public int getTextRunCursor(int contextStart, int contextEnd, int dir, 11693 int offset, int cursorOpt, Paint p) { 11694 int contextCount = contextEnd - contextStart; 11695 return p.getTextRunCursor(mChars, contextStart + mStart, 11696 contextCount, dir, offset + mStart, cursorOpt); 11697 } 11698 } 11699 11700 private static final class Marquee { 11701 // TODO: Add an option to configure this 11702 private static final float MARQUEE_DELTA_MAX = 0.07f; 11703 private static final int MARQUEE_DELAY = 1200; 11704 private static final int MARQUEE_DP_PER_SECOND = 30; 11705 11706 private static final byte MARQUEE_STOPPED = 0x0; 11707 private static final byte MARQUEE_STARTING = 0x1; 11708 private static final byte MARQUEE_RUNNING = 0x2; 11709 11710 private final WeakReference<TextView> mView; 11711 private final Choreographer mChoreographer; 11712 11713 private byte mStatus = MARQUEE_STOPPED; 11714 private final float mPixelsPerSecond; 11715 private float mMaxScroll; 11716 private float mMaxFadeScroll; 11717 private float mGhostStart; 11718 private float mGhostOffset; 11719 private float mFadeStop; 11720 private int mRepeatLimit; 11721 11722 private float mScroll; 11723 private long mLastAnimationMs; 11724 11725 Marquee(TextView v) { 11726 final float density = v.getContext().getResources().getDisplayMetrics().density; 11727 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; 11728 mView = new WeakReference<TextView>(v); 11729 mChoreographer = Choreographer.getInstance(); 11730 } 11731 11732 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 11733 @Override 11734 public void doFrame(long frameTimeNanos) { 11735 tick(); 11736 } 11737 }; 11738 11739 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 11740 @Override 11741 public void doFrame(long frameTimeNanos) { 11742 mStatus = MARQUEE_RUNNING; 11743 mLastAnimationMs = mChoreographer.getFrameTime(); 11744 tick(); 11745 } 11746 }; 11747 11748 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 11749 @Override 11750 public void doFrame(long frameTimeNanos) { 11751 if (mStatus == MARQUEE_RUNNING) { 11752 if (mRepeatLimit >= 0) { 11753 mRepeatLimit--; 11754 } 11755 start(mRepeatLimit); 11756 } 11757 } 11758 }; 11759 11760 void tick() { 11761 if (mStatus != MARQUEE_RUNNING) { 11762 return; 11763 } 11764 11765 mChoreographer.removeFrameCallback(mTickCallback); 11766 11767 final TextView textView = mView.get(); 11768 if (textView != null && (textView.isFocused() || textView.isSelected())) { 11769 long currentMs = mChoreographer.getFrameTime(); 11770 long deltaMs = currentMs - mLastAnimationMs; 11771 mLastAnimationMs = currentMs; 11772 float deltaPx = deltaMs / 1000f * mPixelsPerSecond; 11773 mScroll += deltaPx; 11774 if (mScroll > mMaxScroll) { 11775 mScroll = mMaxScroll; 11776 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 11777 } else { 11778 mChoreographer.postFrameCallback(mTickCallback); 11779 } 11780 textView.invalidate(); 11781 } 11782 } 11783 11784 void stop() { 11785 mStatus = MARQUEE_STOPPED; 11786 mChoreographer.removeFrameCallback(mStartCallback); 11787 mChoreographer.removeFrameCallback(mRestartCallback); 11788 mChoreographer.removeFrameCallback(mTickCallback); 11789 resetScroll(); 11790 } 11791 11792 private void resetScroll() { 11793 mScroll = 0.0f; 11794 final TextView textView = mView.get(); 11795 if (textView != null) textView.invalidate(); 11796 } 11797 11798 void start(int repeatLimit) { 11799 if (repeatLimit == 0) { 11800 stop(); 11801 return; 11802 } 11803 mRepeatLimit = repeatLimit; 11804 final TextView textView = mView.get(); 11805 if (textView != null && textView.mLayout != null) { 11806 mStatus = MARQUEE_STARTING; 11807 mScroll = 0.0f; 11808 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 11809 - textView.getCompoundPaddingRight(); 11810 final float lineWidth = textView.mLayout.getLineWidth(0); 11811 final float gap = textWidth / 3.0f; 11812 mGhostStart = lineWidth - textWidth + gap; 11813 mMaxScroll = mGhostStart + textWidth; 11814 mGhostOffset = lineWidth + gap; 11815 mFadeStop = lineWidth + textWidth / 6.0f; 11816 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 11817 11818 textView.invalidate(); 11819 mChoreographer.postFrameCallback(mStartCallback); 11820 } 11821 } 11822 11823 float getGhostOffset() { 11824 return mGhostOffset; 11825 } 11826 11827 float getScroll() { 11828 return mScroll; 11829 } 11830 11831 float getMaxFadeScroll() { 11832 return mMaxFadeScroll; 11833 } 11834 11835 boolean shouldDrawLeftFade() { 11836 return mScroll <= mFadeStop; 11837 } 11838 11839 boolean shouldDrawGhost() { 11840 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 11841 } 11842 11843 boolean isRunning() { 11844 return mStatus == MARQUEE_RUNNING; 11845 } 11846 11847 boolean isStopped() { 11848 return mStatus == MARQUEE_STOPPED; 11849 } 11850 } 11851 11852 private class ChangeWatcher implements TextWatcher, SpanWatcher { 11853 11854 private CharSequence mBeforeText; 11855 11856 public void beforeTextChanged(CharSequence buffer, int start, 11857 int before, int after) { 11858 if (DEBUG_EXTRACT) { 11859 Log.v(LOG_TAG, "beforeTextChanged start=" + start 11860 + " before=" + before + " after=" + after + ": " + buffer); 11861 } 11862 11863 if (AccessibilityManager.getInstance(mContext).isEnabled() 11864 && !isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) { 11865 mBeforeText = buffer.toString(); 11866 } 11867 11868 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 11869 } 11870 11871 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 11872 if (DEBUG_EXTRACT) { 11873 Log.v(LOG_TAG, "onTextChanged start=" + start 11874 + " before=" + before + " after=" + after + ": " + buffer); 11875 } 11876 TextView.this.handleTextChanged(buffer, start, before, after); 11877 11878 if (AccessibilityManager.getInstance(mContext).isEnabled() 11879 && (isFocused() || isSelected() && isShown())) { 11880 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 11881 mBeforeText = null; 11882 } 11883 } 11884 11885 public void afterTextChanged(Editable buffer) { 11886 if (DEBUG_EXTRACT) { 11887 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 11888 } 11889 TextView.this.sendAfterTextChanged(buffer); 11890 11891 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 11892 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 11893 } 11894 } 11895 11896 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 11897 if (DEBUG_EXTRACT) { 11898 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 11899 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 11900 } 11901 TextView.this.spanChange(buf, what, s, st, e, en); 11902 } 11903 11904 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 11905 if (DEBUG_EXTRACT) { 11906 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 11907 } 11908 TextView.this.spanChange(buf, what, -1, s, -1, e); 11909 } 11910 11911 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 11912 if (DEBUG_EXTRACT) { 11913 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 11914 } 11915 TextView.this.spanChange(buf, what, s, -1, e, -1); 11916 } 11917 } 11918} 11919