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