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