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