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