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