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