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